Files
familysync/apps/api/src/routes/me.ts
T
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
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.
2026-06-11 20:35:18 -04:00

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,
},
});
});