Lucas Berger
d6d91201b1
feat(01-03): implement AES-256-GCM app-password encryption (T-03-01)
...
- encryptPassword: randomBytes(12) IV, aes-256-gcm, returns JSON {iv,authTag,ciphertext}
- decryptPassword: verifies GCM auth tag; throws on tamper
- Key from APP_PASSWORD_ENCRYPTION_KEY env (64-char hex); validated on each call
- No logging of plaintext or key
2026-06-04 10:27:40 -04:00
Lucas Berger
668ed9be0d
feat(01-02): wire OIDC middleware, /api/me route, and authenticated PWA shell
...
- src/auth/middleware.ts: re-exports oidcAuthMiddleware, processOAuthCallback,
getAuth from @hono/oidc-auth; documents required env vars and AUTH-02
refresh-token rotation (no iframe, D-12)
- src/routes/me.ts: GET / calls getAuth → upsertUser(iss, sub, email) →
returns { user: { id, displayName, color } }; identity keyed on iss+sub
- src/index.ts: /callback registered before oidcAuthMiddleware; /api/*
guarded; /health remains unauthenticated; /api/me mounted
- apps/pwa/src/api/client.ts: typed fetchMe() with credentials: 'include'
- apps/pwa/src/App.tsx: useQuery(['me'], fetchMe); renders member name and
color swatch; retains /health stack indicator from Plan 01
- tsc --noEmit: clean; all tests pass
2026-06-04 10:22:46 -04:00
Lucas Berger
baabfce9e2
feat(01-02): implement upsertUser with stable color assignment (AUTH-03)
...
- Export COLOR_PALETTE (6 accessible hex hues, round-robin assignment)
- upsertUser(oidcIss, oidcSub, displayName?) keyed on iss+sub never email
- First login: COUNT existing users → assign COLOR_PALETTE[count % len]
- Re-upsert: returns existing row unchanged (idempotent, no duplicate insert)
- Uses $returningId() + re-select pattern (mysql2 no RETURNING clause)
- All 6 tests pass (GREEN)
2026-06-04 10:21:04 -04:00
Lucas Berger
96cda58509
feat(01-01): Drizzle schema + DB client + /health slice (GREEN)
...
- src/db/schema.ts: users, memberCredentials, calendars, calendarEvents tables
- users: composite unique on oidc_iss+oidc_sub (D-10 identity)
- calendar_events: separate dtstart_utc (TIMESTAMP) and dtstart_date (DATE) + allDay boolean (D-13)
- src/db/client.ts: drizzle(mysql2 pool) singleton export `db`
- drizzle.config.ts: dialect mysql, schema → migrations, dbCredentials from env
- src/routes/health.ts: GET / with real SELECT 1 DB round-trip, 200 or 503
- src/index.ts: Hono app with /health mounted before auth, serveStatic for PWA
- tests/health.test.ts: 2 tests pass (mocked DB); TDD GREEN gate
- apps/pwa/src/App.tsx: React shell fetching /health via TanStack Query
2026-06-04 09:52:36 -04:00