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:
@@ -3,7 +3,7 @@ phase: 01-foundation-broker-spike
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["01-01"]
|
||||
depends_on: ['01-01']
|
||||
files_modified:
|
||||
- apps/api/src/auth/middleware.ts
|
||||
- apps/api/src/auth/user.ts
|
||||
@@ -19,33 +19,33 @@ requirements: [AUTH-01, AUTH-02, AUTH-03]
|
||||
must_haves:
|
||||
truths:
|
||||
- "An unauthenticated request to /api/* is redirected to Authelia's authorize endpoint (302)"
|
||||
- "After login, the OIDC callback upserts a users row keyed by oidc_iss + oidc_sub (never email)"
|
||||
- "Each member is auto-assigned a stable, distinct color from a curated palette on first login; re-login returns the same color"
|
||||
- "Session persists via @hono/oidc-auth refresh-token rotation — no iframe, refresh held backend-side"
|
||||
- 'After login, the OIDC callback upserts a users row keyed by oidc_iss + oidc_sub (never email)'
|
||||
- 'Each member is auto-assigned a stable, distinct color from a curated palette on first login; re-login returns the same color'
|
||||
- 'Session persists via @hono/oidc-auth refresh-token rotation — no iframe, refresh held backend-side'
|
||||
- "GET /api/me returns the authenticated user's identity + color"
|
||||
- "The PWA shell renders the logged-in member's name and color swatch"
|
||||
artifacts:
|
||||
- path: "apps/api/src/auth/user.ts"
|
||||
provides: "upsertUser(oidcIss, oidcSub, displayName) with round-robin color assignment"
|
||||
exports: ["upsertUser", "COLOR_PALETTE"]
|
||||
- path: "apps/api/src/auth/middleware.ts"
|
||||
provides: "oidcAuthMiddleware wiring + getAuth → upsertUser bridge"
|
||||
- path: "apps/api/src/routes/me.ts"
|
||||
provides: "GET /api/me → { user: { id, displayName, color } }"
|
||||
exports: ["meRouter"]
|
||||
- path: 'apps/api/src/auth/user.ts'
|
||||
provides: 'upsertUser(oidcIss, oidcSub, displayName) with round-robin color assignment'
|
||||
exports: ['upsertUser', 'COLOR_PALETTE']
|
||||
- path: 'apps/api/src/auth/middleware.ts'
|
||||
provides: 'oidcAuthMiddleware wiring + getAuth → upsertUser bridge'
|
||||
- path: 'apps/api/src/routes/me.ts'
|
||||
provides: 'GET /api/me → { user: { id, displayName, color } }'
|
||||
exports: ['meRouter']
|
||||
key_links:
|
||||
- from: "apps/api/src/routes/me.ts"
|
||||
to: "apps/api/src/auth/user.ts"
|
||||
via: "upsertUser call"
|
||||
- from: 'apps/api/src/routes/me.ts'
|
||||
to: 'apps/api/src/auth/user.ts'
|
||||
via: 'upsertUser call'
|
||||
pattern: "upsertUser\\("
|
||||
- from: "apps/api/src/index.ts"
|
||||
to: "@hono/oidc-auth"
|
||||
via: "oidcAuthMiddleware on /api/*"
|
||||
pattern: "oidcAuthMiddleware"
|
||||
- from: "apps/pwa/src/App.tsx"
|
||||
to: "/api/me"
|
||||
via: "React Query fetch"
|
||||
pattern: "api/me"
|
||||
- from: 'apps/api/src/index.ts'
|
||||
to: '@hono/oidc-auth'
|
||||
via: 'oidcAuthMiddleware on /api/*'
|
||||
pattern: 'oidcAuthMiddleware'
|
||||
- from: 'apps/pwa/src/App.tsx'
|
||||
to: '/api/me'
|
||||
via: 'React Query fetch'
|
||||
pattern: 'api/me'
|
||||
---
|
||||
|
||||
<objective>
|
||||
@@ -73,6 +73,7 @@ Output: Working Authelia OIDC login, stable identity + color, /api/me, authentic
|
||||
</context>
|
||||
|
||||
<artifacts_produced>
|
||||
|
||||
## Artifacts this phase produces (Plan 02)
|
||||
|
||||
New files: `apps/api/src/auth/middleware.ts`, `apps/api/src/auth/user.ts`, `apps/api/src/routes/me.ts`, `apps/pwa/src/api/client.ts`.
|
||||
@@ -105,6 +106,7 @@ New env vars: `OIDC_AUTH_SECRET`, `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_
|
||||
Create `src/auth/user.ts` exporting `COLOR_PALETTE` (a curated array of >=4 visually-distinct, accessible hex hues per D-06 / Claude's Discretion — e.g. calm blue, warm coral, forest green, soft purple; exact values Claude's choice) and `upsertUser(oidcIss, oidcSub, displayName?)`. Logic per RESEARCH example: SELECT existing by `and(eq(users.oidcIss, oidcIss), eq(users.oidcSub, oidcSub))`; if present return it; else COUNT existing users, assign `COLOR_PALETTE[count % length]`, INSERT, return the new row. Use `$returningId()` then re-select (mysql2 has no RETURNING). Never key on email.
|
||||
|
||||
Fill `tests/auth/user.test.ts` GREEN using the test-DB fixture (tests/helpers/db.ts): assert (a) first insert assigns palette[0]; (b) second user assigns palette[1]; (c) re-upsert of user 1 returns the identical row + color and does not create a duplicate; (d) lookup is by iss+sub.
|
||||
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd apps/api && pnpm vitest run tests/auth/user.test.ts --reporter=verbose</automated>
|
||||
@@ -136,6 +138,7 @@ New env vars: `OIDC_AUTH_SECRET`, `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_
|
||||
PWA: create `apps/pwa/src/api/client.ts` with a typed `fetchMe()` (GET /api/me, credentials: 'include'). Update `App.tsx`: React Query `useQuery(['me'], fetchMe)`; on 401/redirect the browser follows Authelia (full-page). Render the member's displayName and a color swatch using `user.color`. Keep the /health indicator from Plan 01.
|
||||
|
||||
Also record the Authelia client registration YAML (from RESEARCH Pattern 1) in the SUMMARY so the operator can paste it into Authelia's configuration.yml — this is the only human-side config (no code change in this repo).
|
||||
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd apps/api && pnpm exec tsc --noEmit && grep -q "oidcAuthMiddleware" src/index.ts && grep -q "OIDC_AUTH_EXTERNAL_URL" ../../.env.example && grep -q "upsertUser" src/routes/me.ts</automated>
|
||||
@@ -155,24 +158,26 @@ New env vars: `OIDC_AUTH_SECRET`, `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Browser → Pangolin → Hono /api/* | Untrusted client; only authenticated requests cross (OIDC session cookie) |
|
||||
| Authelia → /callback | OIDC authorization-code exchange; PKCE + state validate the callback |
|
||||
| Hono → Authelia token endpoint | Backend confidential client; client_secret + refresh token never reach the browser |
|
||||
| Boundary | Description |
|
||||
| --------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| Browser → Pangolin → Hono /api/\* | Untrusted client; only authenticated requests cross (OIDC session cookie) |
|
||||
| Authelia → /callback | OIDC authorization-code exchange; PKCE + state validate the callback |
|
||||
| Hono → Authelia token endpoint | Backend confidential client; client_secret + refresh token never reach the browser |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-02-01 | Spoofing | OIDC redirect_uri | mitigate | Authelia validates exact match; OIDC_REDIRECT_URI env must equal the registered URI; OIDC_AUTH_EXTERNAL_URL set so Pangolin Host header cannot forge the redirect (Pitfall 1) |
|
||||
| T-02-02 | Spoofing | CSRF on /callback | mitigate | @hono/oidc-auth uses PKCE (state + code_verifier); require_pkce true, S256 in Authelia client |
|
||||
| T-02-03 | Tampering | OIDC session JWT cookie | mitigate | Cookie signed with OIDC_AUTH_SECRET (32+ char), httpOnly + Secure + SameSite; verified every request |
|
||||
| T-02-04 | Information Disclosure | Refresh token / client_secret | mitigate | Backend-only (D-12); never serialized to frontend; not logged; OIDC_CLIENT_SECRET is the plain secret in env, never committed |
|
||||
| T-02-05 | Elevation of Privilege | /api/* without auth | mitigate | oidcAuthMiddleware mounted on /api/*; no guest access (ASVS V4) |
|
||||
| T-02-06 | Spoofing | Identity confusion via mutable email | mitigate | Identity keyed on oidc_iss + oidc_sub, never email (D-10) |
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
| --------- | ---------------------- | ------------------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| T-02-01 | Spoofing | OIDC redirect_uri | mitigate | Authelia validates exact match; OIDC_REDIRECT_URI env must equal the registered URI; OIDC_AUTH_EXTERNAL_URL set so Pangolin Host header cannot forge the redirect (Pitfall 1) |
|
||||
| T-02-02 | Spoofing | CSRF on /callback | mitigate | @hono/oidc-auth uses PKCE (state + code_verifier); require_pkce true, S256 in Authelia client |
|
||||
| T-02-03 | Tampering | OIDC session JWT cookie | mitigate | Cookie signed with OIDC_AUTH_SECRET (32+ char), httpOnly + Secure + SameSite; verified every request |
|
||||
| T-02-04 | Information Disclosure | Refresh token / client_secret | mitigate | Backend-only (D-12); never serialized to frontend; not logged; OIDC_CLIENT_SECRET is the plain secret in env, never committed |
|
||||
| T-02-05 | Elevation of Privilege | /api/\* without auth | mitigate | oidcAuthMiddleware mounted on /api/\*; no guest access (ASVS V4) |
|
||||
| T-02-06 | Spoofing | Identity confusion via mutable email | mitigate | Identity keyed on oidc_iss + oidc_sub, never email (D-10) |
|
||||
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
@@ -185,11 +190,12 @@ New env vars: `OIDC_AUTH_SECRET`, `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- AUTH-01: unauthenticated /api/* redirects to Authelia; login lands authenticated (verified live in Plan 04)
|
||||
|
||||
- AUTH-01: unauthenticated /api/\* redirects to Authelia; login lands authenticated (verified live in Plan 04)
|
||||
- AUTH-02: session persists via backend refresh-token rotation (no iframe)
|
||||
- AUTH-03: stable identity (iss+sub) + stable distinct per-member color, asserted by unit tests
|
||||
- /api/me returns the member; PWA shell shows name + color
|
||||
</success_criteria>
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/01-foundation-broker-spike/01-02-SUMMARY.md` when done. Include the Authelia client registration YAML for the operator.
|
||||
|
||||
Reference in New Issue
Block a user