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/analytics", analyticsRoutes);
|
||||||
app.route("/api", importExportRoutes);
|
app.route("/api", importExportRoutes);
|
||||||
app.route("/api", realtimeRoutes);
|
app.route("/api", realtimeRoutes);
|
||||||
app.route("/mcp", mcpRoutes);
|
app.route("/api/mcp", mcpRoutes);
|
||||||
|
|
||||||
const port = parseInt(process.env.PORT || "3001", 10);
|
const port = parseInt(process.env.PORT || "3001", 10);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createMiddleware } from "hono/factory";
|
import { createMiddleware } from "hono/factory";
|
||||||
import type { Context, Next } from "hono";
|
import type { Context, Next } from "hono";
|
||||||
import { jwtVerify, SignJWT } from "jose";
|
import { jwtVerify, SignJWT } from "jose";
|
||||||
import { db, users } from "@project-e/db";
|
import { createHash } from "node:crypto";
|
||||||
import { eq } from "drizzle-orm";
|
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 AUTH_SECRET = new TextEncoder().encode(process.env.AUTH_SECRET || process.env.NEXTAUTH_SECRET || "fallback-secret-change-me");
|
||||||
const COOKIE_NAME = "session";
|
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) => {
|
export const authMiddleware = createMiddleware(async (c: Context, next: Next) => {
|
||||||
const cookieHeader = c.req.header("Cookie") || "";
|
const cookieHeader = c.req.header("Cookie") || "";
|
||||||
const cookies = Object.fromEntries(
|
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 ", "");
|
const token = cookies[COOKIE_NAME] || c.req.header("Authorization")?.replace("Bearer ", "");
|
||||||
if (token) {
|
if (token) {
|
||||||
|
// Try JWT first
|
||||||
const user = await verifyToken(token);
|
const user = await verifyToken(token);
|
||||||
if (user) {
|
if (user) {
|
||||||
c.set("user", user);
|
c.set("user", user);
|
||||||
return next();
|
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);
|
c.set("user", null);
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ function CalendarPage() {
|
|||||||
events={calendarEvents}
|
events={calendarEvents}
|
||||||
startAccessor="start"
|
startAccessor="start"
|
||||||
endAccessor="end"
|
endAccessor="end"
|
||||||
|
date={date}
|
||||||
view={view}
|
view={view}
|
||||||
defaultView={Views.MONTH}
|
defaultView={Views.MONTH}
|
||||||
onNavigate={handleNavigate}
|
onNavigate={handleNavigate}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ services:
|
|||||||
- PORT=3000
|
- PORT=3000
|
||||||
- DATABASE_URL=postgresql://project_e:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@db:5432/project_e
|
- DATABASE_URL=postgresql://project_e:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@db:5432/project_e
|
||||||
- AUTH_SECRET=${AUTH_SECRET:-stub-secret}
|
- AUTH_SECRET=${AUTH_SECRET:-stub-secret}
|
||||||
|
- INITIAL_ADMIN_EMAIL=${INITIAL_ADMIN_EMAIL}
|
||||||
|
- INITIAL_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD}
|
||||||
networks:
|
networks:
|
||||||
- project-e-network
|
- project-e-network
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
Reference in New Issue
Block a user