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.
This commit is contained in:
@@ -24,8 +24,8 @@
|
||||
* - This file must never be removed — the pattern is referenced by Plan 02 routes.
|
||||
*/
|
||||
|
||||
import type { MiddlewareHandler } from 'hono'
|
||||
import { COLOR_PALETTE } from './user.js'
|
||||
import type { MiddlewareHandler } from 'hono';
|
||||
import { COLOR_PALETTE } from './user.js';
|
||||
|
||||
export const DEV_USER = {
|
||||
id: 1,
|
||||
@@ -33,7 +33,7 @@ export const DEV_USER = {
|
||||
oidcSub: 'dev-user',
|
||||
displayName: 'Dev User',
|
||||
color: COLOR_PALETTE[0], // '#4A90D9' — first palette slot
|
||||
} as const
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Extend Hono's ContextVariableMap so that c.get('user') / c.set('user', ...)
|
||||
@@ -43,7 +43,7 @@ export const DEV_USER = {
|
||||
*/
|
||||
declare module 'hono' {
|
||||
interface ContextVariableMap {
|
||||
user: typeof DEV_USER
|
||||
user: typeof DEV_USER;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,18 +59,18 @@ export function devAuthBypass(): MiddlewareHandler {
|
||||
// Hard production guard — FIRST check, before reading any other env var.
|
||||
// Ensures this middleware can never grant access in production regardless of config.
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return async (_c, next) => next()
|
||||
return async (_c, next) => next();
|
||||
}
|
||||
|
||||
// Bypass flag not set — passthrough; OIDC auth proceeds normally.
|
||||
if (process.env.DEV_AUTH_BYPASS !== 'true') {
|
||||
return async (_c, next) => next()
|
||||
return async (_c, next) => next();
|
||||
}
|
||||
|
||||
// Bypass active: inject fixed dev user into Hono context.
|
||||
// Routes that read c.get('user') will receive DEV_USER.
|
||||
return async (c, next) => {
|
||||
c.set('user', DEV_USER)
|
||||
await next()
|
||||
}
|
||||
c.set('user', DEV_USER);
|
||||
await next();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,4 +23,4 @@
|
||||
* Source: https://github.com/honojs/middleware/tree/main/packages/oidc-auth
|
||||
*/
|
||||
|
||||
export { oidcAuthMiddleware, processOAuthCallback, getAuth } from '@hono/oidc-auth'
|
||||
export { oidcAuthMiddleware, processOAuthCallback, getAuth } from '@hono/oidc-auth';
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
* cookie only when the library produced a valid session this request.
|
||||
*/
|
||||
|
||||
import type { MiddlewareHandler } from 'hono'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import type { MiddlewareHandler } from 'hono';
|
||||
import { setCookie } from 'hono/cookie';
|
||||
|
||||
/**
|
||||
* Returns a Hono MiddlewareHandler that upgrades the session-scoped oidc-auth cookie
|
||||
@@ -37,7 +37,7 @@ export function persistSessionCookie(): MiddlewareHandler {
|
||||
// The 'as never' cast is required because 'oidcAuthJwt' is a library-internal key
|
||||
// that is not declared in Hono's ContextVariableMap — mirrors the loose-context
|
||||
// convention used elsewhere in this codebase (e.g. resolveUserId).
|
||||
const jwt = c.get('oidcAuthJwt' as never) as string | undefined
|
||||
const jwt = c.get('oidcAuthJwt' as never) as string | undefined;
|
||||
|
||||
// CRITICAL CORRECTNESS GUARD: if no valid session JWT is on context (logged-out,
|
||||
// deleted, or never-set request), do NOT touch cookies and fall straight through.
|
||||
@@ -45,16 +45,16 @@ export function persistSessionCookie(): MiddlewareHandler {
|
||||
// logged-out user would receive a new oidc-auth Set-Cookie with no value, which
|
||||
// could re-authenticate them or produce confusing browser state.
|
||||
if (!jwt) {
|
||||
await next()
|
||||
return
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
// A valid session JWT is present — re-issue the cookie BEFORE next() so that if
|
||||
// any future downstream handler deletes the cookie, that delete Set-Cookie header
|
||||
// comes last and wins (safe ordering even without a current logout/revoke route).
|
||||
const name = process.env.OIDC_COOKIE_NAME ?? 'oidc-auth'
|
||||
const path = process.env.OIDC_COOKIE_PATH ?? '/'
|
||||
const maxAge = Number(process.env.OIDC_AUTH_EXPIRES ?? 86400)
|
||||
const name = process.env.OIDC_COOKIE_NAME ?? 'oidc-auth';
|
||||
const path = process.env.OIDC_COOKIE_PATH ?? '/';
|
||||
const maxAge = Number(process.env.OIDC_AUTH_EXPIRES ?? 86400);
|
||||
|
||||
// Build the options object. domain is included ONLY when OIDC_COOKIE_DOMAIN is set —
|
||||
// mirroring the library's own conditional-domain logic so the two Set-Cookie headers
|
||||
@@ -66,15 +66,15 @@ export function persistSessionCookie(): MiddlewareHandler {
|
||||
secure: true,
|
||||
sameSite: 'Lax',
|
||||
maxAge, // seconds — Hono's setCookie maxAge unit matches OIDC_AUTH_EXPIRES unit
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.OIDC_COOKIE_DOMAIN) {
|
||||
options.domain = process.env.OIDC_COOKIE_DOMAIN
|
||||
options.domain = process.env.OIDC_COOKIE_DOMAIN;
|
||||
}
|
||||
|
||||
// Re-issue the same JWT the library signed. Do NOT re-sign or modify the payload.
|
||||
setCookie(c, name, jwt, options)
|
||||
setCookie(c, name, jwt, options);
|
||||
|
||||
await next()
|
||||
}
|
||||
await next();
|
||||
};
|
||||
}
|
||||
|
||||
+21
-35
@@ -8,9 +8,9 @@
|
||||
* Source: RESEARCH.md § "User upsert with color assignment"
|
||||
*/
|
||||
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '../db/client.js'
|
||||
import { users } from '../db/schema.js'
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import { db } from '../db/client.js';
|
||||
import { users } from '../db/schema.js';
|
||||
|
||||
/**
|
||||
* Accessible, visually-distinct palette for per-member member-color assignment.
|
||||
@@ -29,11 +29,11 @@ export const COLOR_PALETTE: string[] = [
|
||||
'#9B6DC5', // soft purple
|
||||
'#E8A840', // warm amber (near shared rose — assigned only after cool colors)
|
||||
'#E8734A', // warm coral (closest to shared rose — assigned last)
|
||||
]
|
||||
];
|
||||
|
||||
/** Coerce an OIDC claim to a trimmed non-empty string, else undefined. */
|
||||
const claimStr = (v: unknown): string | undefined =>
|
||||
typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined
|
||||
typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined;
|
||||
|
||||
/**
|
||||
* Derive the best available display name from OIDC claims, in preference order:
|
||||
@@ -54,16 +54,13 @@ const claimStr = (v: unknown): string | undefined =>
|
||||
* so the write-path upsert agrees with /api/me.
|
||||
*/
|
||||
export function deriveDisplayName(claims: {
|
||||
name?: unknown
|
||||
preferred_username?: unknown
|
||||
email?: unknown
|
||||
name?: unknown;
|
||||
preferred_username?: unknown;
|
||||
email?: unknown;
|
||||
}): string | null {
|
||||
return (
|
||||
claimStr(claims.name) ??
|
||||
claimStr(claims.preferred_username) ??
|
||||
claimStr(claims.email) ??
|
||||
null
|
||||
)
|
||||
claimStr(claims.name) ?? claimStr(claims.preferred_username) ?? claimStr(claims.email) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,17 +73,13 @@ export function deriveDisplayName(claims: {
|
||||
* Never keys on email or displayName for identity. displayName is stored as a
|
||||
* display hint only and may change without affecting identity.
|
||||
*/
|
||||
export async function upsertUser(
|
||||
oidcIss: string,
|
||||
oidcSub: string,
|
||||
displayName?: string | null,
|
||||
) {
|
||||
export async function upsertUser(oidcIss: string, oidcSub: string, displayName?: string | null) {
|
||||
// 1. Look up by composite identity key (iss + sub) — never email
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(and(eq(users.oidcIss, oidcIss), eq(users.oidcSub, oidcSub)))
|
||||
.limit(1)
|
||||
.limit(1);
|
||||
|
||||
if (existing[0]) {
|
||||
// Track the IdP display name authoritatively: when the caller supplies a
|
||||
@@ -97,13 +90,10 @@ export async function upsertUser(
|
||||
// A null displayName (no usable claim this request) never overwrites a good
|
||||
// stored value.
|
||||
if (displayName != null && displayName !== existing[0].displayName) {
|
||||
await db
|
||||
.update(users)
|
||||
.set({ displayName })
|
||||
.where(eq(users.id, existing[0].id))
|
||||
return { ...existing[0], displayName }
|
||||
await db.update(users).set({ displayName }).where(eq(users.id, existing[0].id));
|
||||
return { ...existing[0], displayName };
|
||||
}
|
||||
return existing[0]
|
||||
return existing[0];
|
||||
}
|
||||
|
||||
// 2. Assign the first palette color NOT already in use by another member.
|
||||
@@ -113,11 +103,11 @@ export async function upsertUser(
|
||||
// guarantees distinct, stable colors for up to COLOR_PALETTE.length members
|
||||
// (AUTH-03). Falls back to round-robin by count only once the palette is
|
||||
// exhausted (more members than colors).
|
||||
const usedRows = await db.select({ color: users.color }).from(users)
|
||||
const usedColors = new Set(usedRows.map((r) => r.color))
|
||||
const usedRows = await db.select({ color: users.color }).from(users);
|
||||
const usedColors = new Set(usedRows.map((r) => r.color));
|
||||
const color =
|
||||
COLOR_PALETTE.find((c) => !usedColors.has(c)) ??
|
||||
COLOR_PALETTE[usedColors.size % COLOR_PALETTE.length]
|
||||
COLOR_PALETTE[usedColors.size % COLOR_PALETTE.length];
|
||||
|
||||
// 3. Insert new user row
|
||||
// mysql2 has no RETURNING clause — use $returningId() then re-select
|
||||
@@ -129,14 +119,10 @@ export async function upsertUser(
|
||||
displayName: displayName ?? null,
|
||||
color,
|
||||
})
|
||||
.$returningId()
|
||||
.$returningId();
|
||||
|
||||
// 4. Re-select to return the full typed row
|
||||
const [newUser] = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, inserted.id))
|
||||
.limit(1)
|
||||
const [newUser] = await db.select().from(users).where(eq(users.id, inserted.id)).limit(1);
|
||||
|
||||
return newUser
|
||||
return newUser;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user