/** * GET /api/me — returns the authenticated user's identity and assigned color. * * Flow (normal — OIDC active): * 1. getAuth(c) reads iss + sub from the OIDC session JWT cookie * (validated and refreshed by oidcAuthMiddleware — never reaches here unauthenticated) * 2. Derives displayName from OIDC claims (name → preferred_username → email → sub fallback) * then calls upsertUser(iss, sub, displayName) — writes on first visit, corrects a * previously blank displayName on subsequent visits (idempotent, keyed on iss+sub, D-10) * 3. Returns { user: { id, displayName, color } } * * Flow (dev bypass — DEV_AUTH_BYPASS=true, non-production): * devAuthBypass() injects DEV_USER via c.set('user', DEV_USER). oidcAuthMiddleware * is NOT mounted in index.ts when the bypass is active, so getAuth(c) is never called. * This handler reads c.get('user') first and short-circuits to return the dev identity * directly, skipping the DB upsert. * * The OIDC session cookie is httpOnly + Secure + SameSite (T-02-03). * No credential or refresh-token data is included in the response (T-02-04). */ import { Hono } from 'hono'; import { getAuth } from '../auth/middleware.js'; import { upsertUser, deriveDisplayName } from '../auth/user.js'; // Side-effect import: brings in the ContextVariableMap augmentation for c.get('user') import '../auth/devBypass.js'; export const meRouter = new Hono(); meRouter.get('/', async (c) => { // Dev-auth bypass path: devAuthBypass() sets c.get('user') to DEV_USER when active. // Return the injected dev identity directly — no DB round-trip, no OIDC session needed. const devUser = c.get('user'); if (devUser) { return c.json({ user: { id: devUser.id, displayName: devUser.displayName, color: devUser.color, }, }); } // Normal OIDC path: getAuth returns null only if the session is invalid. // oidcAuthMiddleware on /api/* redirects unauthenticated requests before this handler // is reached, so null here indicates a genuine session error. const auth = await getAuth(c); if (!auth) { return c.json({ error: 'Unauthorized' }, 401); } // iss and sub are the stable identity fields — identity is always keyed on iss+sub (D-10). const iss = (auth.iss as string | undefined) ?? ''; const sub = auth.sub ?? ''; // Derive the best available display name from OIDC claims (name → // preferred_username → email → sub fallback). Shared helper keeps every // upsert call site in agreement (see deriveDisplayName). const displayName = deriveDisplayName(auth); const user = await upsertUser(iss, sub, displayName); if (!user) { return c.json({ error: 'Could not resolve user' }, 500); } return c.json({ user: { id: user.id, displayName: user.displayName, color: user.color, }, }); });