Commit Graph
12 Commits
Author SHA1 Message Date
Lucas Berger 4b34b16f02 fix(02): dev-auth bypass no longer blocked by oidcAuthMiddleware
- index.ts: compute devBypassActive at startup; skip app.use(oidcAuthMiddleware)
  entirely when active so the OIDC guard never runs in local dev
- routes/me.ts: read c.get('user') first; return dev identity directly when
  devAuthBypass injected it, bypassing getAuth() and the DB upsert
- auth/devBypass.ts: add ContextVariableMap augmentation for 'user' key;
  correct stale comment that claimed getAuth/401 path was still active
2026-06-05 13:48:00 -04:00
Lucas Berger 9ee26c07a7 feat(02-02): evolve /api/events to windowed endpoint with color/owner join
- zValidator enforces YYYY-MM-DD regex on start/end (T-02b-01)
- 90-day window cap prevents DoS (T-02b-02)
- innerJoin calendarEvents→calendars→users for color + isShared + ownerUserId
- SQL pre-filter includes hasRrule=true rows regardless of dtstartUtc range
- expandOccurrences() called per row; shared calendar uses #F25C7A rose color
- events.test.ts: added @hono/oidc-auth mock; 4/4 assertions green
2026-06-05 10:30:36 -04:00
Lucas Berger 6736194a4a feat(02-02): add expandOccurrences() with VTIMEZONE + allDay split + EXDATE
- ICAL.TimezoneService.register() runs before RecurExpansion (DST correctness)
- All-day events serialized as YYYY-MM-DD strings (no time shift)
- EXDATE exclusions handled internally by ICAL.RecurExpansion
- Malformed rawVevent returns [] without throwing
- expand.test.ts DST, all-day, EXDATE assertions green (3/3)
2026-06-05 10:30:22 -04:00
Lucas Berger 8bd44b33c7 feat(02-01): dev-auth bypass middleware with production hard guard
- Create apps/api/src/auth/devBypass.ts: devAuthBypass() middleware with
  NODE_ENV=production hard guard as first conditional (T-02-01 mitigation)
- Exports DEV_USER const (id:1, color:COLOR_PALETTE[0]) for test reference
- Mount devAuthBypass() before oidcAuthMiddleware on /api/* in index.ts
- Add devBypass.test.ts: all three behavioral cases pass (production guard,
  unset-flag passthrough, active-injection)
- Add DEV_AUTH_BYPASS to .env.example with production warning comment
- Extend docs/deployment.md with dev-auth bypass section and production prohibition
2026-06-05 09:32:00 -04:00
Lucas Berger 75252eb08c feat(02-01): schema columns, PWA vitest harness, ICS fixtures, RED test stubs
- Add calendarEvents.hasRrule boolean + idx_calendar_events_has_rrule index (Phase 2 pre-filter)
- Add calendars.isShared boolean for shared-family calendar identification
- Create apps/pwa/vitest.config.ts with jsdom environment
- Add vitest, @testing-library/react, jsdom, @testing-library/jest-dom to PWA devDependencies
- Add "test": "vitest run" script to apps/pwa/package.json
- Create three ICS fixtures: weekly-dst.ics (DST spanning), allday-birthday.ics, exdate-series.ics
- Create RED test stub expand.test.ts with concrete DST wall-clock assertions (10:00 local both sides of March 2026 boundary)
- Create RED test stub events.test.ts with 400 validation and color/isShared field contracts
- Create RED test stub hydrateEvents.test.ts with Temporal type and calendarId routing contracts (shared→"shared", personal→String(ownerUserId))
- Create RED test stub calendarConfig.test.ts with firstDayOfWeek 0→7 translation contract
2026-06-05 09:29:45 -04:00
Lucas Berger 48f90ceca9 feat(01-04): wire broker + routes into bootstrap, add SSE endpoint, EventProof
- Mount /api/events, /api/sse in index.ts behind oidcAuthMiddleware; /callback + /health before guard
- Call startBrokerPoller() on boot (5-min ctag-poll background schedule)
- Add sseRouter with GET /heartbeat (streamSSE, 10s interval) for Pangolin SSE smoke test (D-08, T-04-01)
- Add CAL-08 spike script (broker/spike.ts): createFastmailClient → fetchCalendars → print calendar URLs
- Add fetchEvents() to pwa/api/client.ts with typed CalendarEvent/EventsResponse shapes
- Add EventProof.tsx: React Query ['events'], renders first event title+date or empty-state (CAL-01 broker proof)
- Update App.tsx to render MemberBadge + EventProof on landing page
- Add ical.js@2.2.1 to PWA dependencies for VEVENT summary parsing in EventProof
- All 24 API unit tests green; tsc --noEmit clean in both apps/api and apps/pwa
2026-06-04 11:16:10 -04:00
Lucas Berger 23b8e5326d feat(01-03): implement ctag poller with node-cron 5-min schedule
- startBrokerPoller schedules runPoll via node-cron */5 * * * *
- runPoll loads all member_credentials, decrypts each app password
- ctag/syncToken comparison skips syncCalendar when ctag unchanged
- errors per-credential caught and logged without crashing the poller
- runPoll exported for unit-testing with vi.mock injected dependencies
2026-06-04 11:08:46 -04:00
Lucas Berger dd02207318 feat(01-03): implement broker client, syncCalendar, and events route (CAL-01)
- client.ts: createFastmailClient(email, appPassword) → tsdav DAVClient via Basic auth
- sync.ts: syncCalendar upserts calendar row, selects ID, fetches+parses VEVENTs with ical.js
  - all-day DATE → dtstartDate (Date obj at T00:00:00Z), dtstartUtc=null, allDay=true
  - timed → dtstartUtc (JS Date), dtstartDate=null, allDay=false (D-13/Pitfall #3)
  - onDuplicateKeyUpdate on calendarId+uid composite key (idempotent)
- routes/events.ts: GET /api/events reads cache only — no tsdav import (T-03-02)
- tsc --noEmit clean; all 6 sync tests pass
2026-06-04 10:33:28 -04:00
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