Bug #2 (LOW): MCP endpoint was mounted at /mcp instead of /api/mcp, inconsistent with all other API routes. Changed app.route("/mcp", ...) to app.route("/api/mcp", ...) in apps/api/src/index.ts. Bug #3 (MEDIUM): REST API endpoints only accepted JWT cookie/session auth, not API key auth. Added authenticateApiKey() to authMiddleware in apps/api/src/middleware/auth.ts so REST endpoints now accept Authorization: Bearer <api_key> as a fallback after JWT verification.
This commit is contained in:
@@ -59,7 +59,7 @@ app.route("/api/error-log", errorLogRoutes);
|
||||
app.route("/api/analytics", analyticsRoutes);
|
||||
app.route("/api", importExportRoutes);
|
||||
app.route("/api", realtimeRoutes);
|
||||
app.route("/mcp", mcpRoutes);
|
||||
app.route("/api/mcp", mcpRoutes);
|
||||
|
||||
const port = parseInt(process.env.PORT || "3001", 10);
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createMiddleware } from "hono/factory";
|
||||
import type { Context, Next } from "hono";
|
||||
import { jwtVerify, SignJWT } from "jose";
|
||||
import { db, users } from "@project-e/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { createHash } from "node:crypto";
|
||||
import { db, users, apiKeys } from "@project-e/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
|
||||
const AUTH_SECRET = new TextEncoder().encode(process.env.AUTH_SECRET || process.env.NEXTAUTH_SECRET || "fallback-secret-change-me");
|
||||
const COOKIE_NAME = "session";
|
||||
@@ -41,6 +42,33 @@ export async function verifyToken(token: string): Promise<{ id: string; email: s
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticateApiKey(apiKey: string): Promise<{ id: string; email: string; name: string } | null> {
|
||||
const keyHash = createHash("sha256").update(apiKey).digest("hex");
|
||||
|
||||
const [keyRecord] = await db
|
||||
.select({
|
||||
userId: apiKeys.userId,
|
||||
userName: users.name,
|
||||
userEmail: users.email,
|
||||
})
|
||||
.from(apiKeys)
|
||||
.innerJoin(users, eq(apiKeys.userId, users.id))
|
||||
.where(and(eq(apiKeys.keyHash, keyHash), eq(apiKeys.active, true)))
|
||||
.limit(1);
|
||||
|
||||
if (!keyRecord) return null;
|
||||
|
||||
await db.update(apiKeys)
|
||||
.set({ lastUsedAt: new Date() })
|
||||
.where(eq(apiKeys.keyHash, keyHash));
|
||||
|
||||
return {
|
||||
id: keyRecord.userId,
|
||||
email: keyRecord.userEmail,
|
||||
name: keyRecord.userName || keyRecord.userEmail,
|
||||
};
|
||||
}
|
||||
|
||||
export const authMiddleware = createMiddleware(async (c: Context, next: Next) => {
|
||||
const cookieHeader = c.req.header("Cookie") || "";
|
||||
const cookies = Object.fromEntries(
|
||||
@@ -48,11 +76,18 @@ export const authMiddleware = createMiddleware(async (c: Context, next: Next) =>
|
||||
);
|
||||
const token = cookies[COOKIE_NAME] || c.req.header("Authorization")?.replace("Bearer ", "");
|
||||
if (token) {
|
||||
// Try JWT first
|
||||
const user = await verifyToken(token);
|
||||
if (user) {
|
||||
c.set("user", user);
|
||||
return next();
|
||||
}
|
||||
// Fall back to API key auth
|
||||
const apiUser = await authenticateApiKey(token);
|
||||
if (apiUser) {
|
||||
c.set("user", apiUser);
|
||||
return next();
|
||||
}
|
||||
}
|
||||
c.set("user", null);
|
||||
return next();
|
||||
|
||||
@@ -266,6 +266,7 @@ function CalendarPage() {
|
||||
events={calendarEvents}
|
||||
startAccessor="start"
|
||||
endAccessor="end"
|
||||
date={date}
|
||||
view={view}
|
||||
defaultView={Views.MONTH}
|
||||
onNavigate={handleNavigate}
|
||||
|
||||
@@ -31,6 +31,8 @@ services:
|
||||
- PORT=3000
|
||||
- DATABASE_URL=postgresql://project_e:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@db:5432/project_e
|
||||
- AUTH_SECRET=${AUTH_SECRET:-stub-secret}
|
||||
- INITIAL_ADMIN_EMAIL=${INITIAL_ADMIN_EMAIL}
|
||||
- INITIAL_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD}
|
||||
networks:
|
||||
- project-e-network
|
||||
depends_on:
|
||||
|
||||
Reference in New Issue
Block a user