diff --git a/api/src/routes/auth.routes.ts b/api/src/routes/auth.routes.ts index 17dfcd9..7acfa31 100644 --- a/api/src/routes/auth.routes.ts +++ b/api/src/routes/auth.routes.ts @@ -6,12 +6,16 @@ import type { RouteContext, RouteModule } from "./types"; import { jsonResponse, errorResponse } from "./utils"; import { logger } from "@shared/lib/logger"; +import { DrizzleClient } from "@shared/db/DrizzleClient"; +import { users } from "@shared/db/schema"; +import { eq } from "drizzle-orm"; -// In-memory session store: token → { discordId, username, avatar, expiresAt } +// In-memory session store: token → { discordId, username, avatar, role, expiresAt } export interface Session { discordId: string; username: string; avatar: string | null; + role: "admin" | "player"; expiresAt: number; } @@ -144,26 +148,34 @@ async function handler(ctx: RouteContext): Promise { const user = await userRes.json() as { id: string; username: string; avatar: string | null }; - // Check allowlist - const adminIds = getAdminIds(); - if (adminIds.length > 0 && !adminIds.includes(user.id)) { - logger.warn("auth", `Unauthorized login attempt by ${user.username} (${user.id})`); + // Check enrollment — user must exist in the users table + const dbUser = await DrizzleClient.query.users.findFirst({ + where: eq(users.id, BigInt(user.id)), + }); + + if (!dbUser) { + logger.info("auth", `Non-enrolled login attempt by ${user.username} (${user.id})`); return new Response( - `

Access Denied

Your Discord account is not authorized.

`, + `

Not Enrolled

You need to use the Aurora bot in Discord before you can access this panel.

Go back`, { status: 403, headers: { "Content-Type": "text/html" } } ); } + // Determine role + const adminIds = getAdminIds(); + const role: "admin" | "player" = adminIds.includes(user.id) ? "admin" : "player"; + // Create session const token = generateToken(); sessions.set(token, { discordId: user.id, username: user.username, avatar: user.avatar, + role, expiresAt: Date.now() + SESSION_MAX_AGE, }); - logger.info("auth", `Admin login: ${user.username} (${user.id})`); + logger.info("auth", `Login: ${user.username} (${user.id}) as ${role}`); // Get return_to URL from redirect token cookie const cookies = parseCookies(ctx.req.headers.get("cookie")); @@ -213,13 +225,15 @@ async function handler(ctx: RouteContext): Promise { // GET /auth/me — return current session info if (pathname === "/auth/me" && method === "GET") { const session = getSession(ctx.req); - if (!session) return jsonResponse({ authenticated: false }, 401); + if (!session) return jsonResponse({ authenticated: false, enrolled: false }); return jsonResponse({ authenticated: true, + enrolled: true, user: { discordId: session.discordId, username: session.username, avatar: session.avatar, + role: session.role, }, }); } diff --git a/api/src/routes/index.ts b/api/src/routes/index.ts index eb408f5..3906e54 100644 --- a/api/src/routes/index.ts +++ b/api/src/routes/index.ts @@ -4,7 +4,7 @@ */ import type { RouteContext, RouteModule } from "./types"; -import { authRoutes, isAuthenticated } from "./auth.routes"; +import { authRoutes, isAuthenticated, getSession } from "./auth.routes"; import { healthRoutes } from "./health.routes"; import { statsRoutes } from "./stats.routes"; import { actionsRoutes } from "./actions.routes"; @@ -70,9 +70,21 @@ export async function handleRequest(req: Request, url: URL): Promise ctx.pathname.startsWith(p)); + + // Players can access their own user data + const isOwnUserRoute = ctx.pathname.match(/^\/api\/users\/\d+/) && session.role === "player"; + + if (session.role === "player" && !isPlayerAllowed && !isOwnUserRoute) { + return errorResponse("Admin access required", 403); + } } // Try protected routes