diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 75cbd70..2a53cf8 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -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); diff --git a/apps/api/src/middleware/auth.ts b/apps/api/src/middleware/auth.ts index 779aa37..4616564 100644 --- a/apps/api/src/middleware/auth.ts +++ b/apps/api/src/middleware/auth.ts @@ -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(); diff --git a/apps/web/src/routes/_app/calendar.tsx b/apps/web/src/routes/_app/calendar.tsx index c30fbd1..005f0d7 100644 --- a/apps/web/src/routes/_app/calendar.tsx +++ b/apps/web/src/routes/_app/calendar.tsx @@ -266,6 +266,7 @@ function CalendarPage() { events={calendarEvents} startAccessor="start" endAccessor="end" + date={date} view={view} defaultView={Views.MONTH} onNavigate={handleNavigate} diff --git a/docker-compose.yml b/docker-compose.yml index d9d7c16..52440f9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: