Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
75 lines
2.8 KiB
TypeScript
75 lines
2.8 KiB
TypeScript
/**
|
|
* 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,
|
|
},
|
|
});
|
|
});
|