diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 2590d5b..9ec57b2 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -147,12 +147,12 @@ Which phases cover which requirements. Updated during roadmap creation. | DASH-02 | Phase 5 | Complete | | DASH-03 | Phase 5 | Complete | | DASH-04 | Phase 5 | Complete | -| DASH-05 | Phase 5 | Pending | +| DASH-05 | Phase 5 | Backend Complete (05-03) | | DASH-06 | Phase 5 | Complete | | DASH-07 | Phase 5 | Complete | -| CAL-01 | Phase 5 | Pending | -| CAL-02 | Phase 5 | Pending | -| CAL-03 | Phase 5 | Pending | +| CAL-01 | Phase 5 | Backend Complete (05-03) | +| CAL-02 | Phase 5 | Backend Complete (05-03) | +| CAL-03 | Phase 5 | Backend Complete (05-03) | | MOD-01 | Phase 3 | Pending | | MOD-02 | Phase 3 | Pending | | MOD-03 | Phase 3 | Pending | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 90c4b52..160c126 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -173,7 +173,7 @@ Decimal phases appear between their surrounding integers in numeric order. **Wave 3** *(blocked on Wave 2 completion)* -- [ ] 05-03-PLAN.md -- Calendar backend: Prisma model, AES-256-GCM crypto, source CRUD, CalDAV/ICS/Exchange providers, DB push (CAL-01/02/03, DASH-05) +- [x] 05-03-PLAN.md -- Calendar backend: Prisma model, AES-256-GCM crypto, source CRUD, CalDAV/ICS/Exchange providers, DB push (CAL-01/02/03, DASH-05) **Wave 4** *(blocked on Wave 3 completion)* @@ -210,5 +210,5 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 | 2. Authentication & Multi-Tenancy | 2/5 | In Progress| | | 3. Module System & Domaincheck | 3/4 | In Progress| | | 4. Marketplace & Portal Navigation | 0/4 | Not started | - | -| 5. Dashboard & Calendar | 2/5 | In Progress| | +| 5. Dashboard & Calendar | 3/5 | In Progress| | | 6. Desktop Client & CI/CD | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 95c133d..817c972 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,15 +3,15 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: executing -stopped_at: Completed 05-02-PLAN.md -last_updated: "2026-06-24T13:02:42.630Z" -last_activity: 2026-06-24 -- Phase 05 execution started +stopped_at: Completed 05-03-PLAN.md +last_updated: "2026-06-24T13:16:25Z" +last_activity: 2026-06-24 -- Calendar backend complete progress: total_phases: 6 completed_phases: 3 total_plans: 21 - completed_plans: 17 - percent: 50 + completed_plans: 18 + percent: 86 --- # Project State @@ -26,11 +26,11 @@ See: .planning/PROJECT.md (updated 2026-06-18) ## Current Position Phase: 05 (dashboard-calendar) — EXECUTING -Plan: 3 of 5 +Plan: 4 of 5 Status: Ready to execute -Last activity: 2026-06-24 -- Phase 05 execution started +Last activity: 2026-06-24 -- Calendar backend complete (Plan 03) -Progress: [████████░░] 81% +Progress: [████████░░] 86% ## Performance Metrics @@ -56,6 +56,7 @@ Progress: [████████░░] 81% | Phase 02-authentication-multi-tenancy P03 | 5min | 2 tasks | 20 files | | Phase 05-dashboard-calendar P01 | 14min | 4 tasks | 28 files | | Phase 05-dashboard-calendar P02 | 4min | 4 tasks | 16 files | +| Phase 05-dashboard-calendar P03 | 11min | 4 tasks | 14 files | ## Accumulated Context @@ -79,6 +80,10 @@ Recent decisions affecting current work: - [Phase ?]: react-grid-layout v2 uses dragConfig/resizeConfig instead of isDraggable/isResizable - [05-02]: SearchProvider defaults as constants merged with user DB rows (no seed migration) - [05-02]: useRef with explicit undefined initial value for React 19 strict TypeScript +- [05-03]: DAVClient class constructor instead of createDAVClient factory (tsdav v2 type compatibility) +- [05-03]: Dynamic imports for ews-javascript-api and @microsoft/microsoft-graph-client (lazy-load) +- [05-03]: In-memory Map cache with 5-min TTL for calendar events (Redis not needed at current scale) +- [05-03]: ews-javascript-api imported as any (no TypeScript definitions available) ### Pending Todos @@ -98,6 +103,6 @@ Items acknowledged and carried forward from previous milestone close: ## Session Continuity -Last session: 2026-06-24T12:57:00Z -Stopped at: Completed 05-02-PLAN.md +Last session: 2026-06-24T13:16:25Z +Stopped at: Completed 05-03-PLAN.md Resume file: None diff --git a/.planning/phases/05-dashboard-calendar/05-03-SUMMARY.md b/.planning/phases/05-dashboard-calendar/05-03-SUMMARY.md new file mode 100644 index 0000000..795f19b --- /dev/null +++ b/.planning/phases/05-dashboard-calendar/05-03-SUMMARY.md @@ -0,0 +1,156 @@ +--- +phase: 05-dashboard-calendar +plan: 03 +subsystem: api +tags: [calendar, caldav, ics, exchange, aes-256-gcm, tsdav, node-ical, ews, microsoft-graph, prisma, nestjs] + +requires: + - phase: 05-dashboard-calendar/01 + provides: Dashboard grid + widget registry + Prisma models (DashboardLayout, WidgetInstance) + - phase: 05-dashboard-calendar/02 + provides: Widget settings panel pattern + SearchProvider CRUD pattern +provides: + - CalendarSource Prisma model with AES-256-GCM encrypted credentials + - CalendarModule with source CRUD, multi-protocol providers, event aggregation + - REST API endpoints for calendar source management and event fetching + - CalendarCryptoService for credential encryption/decryption + - ICS/CalDAV/Exchange provider implementations with graceful degradation +affects: [05-dashboard-calendar/04, 05-dashboard-calendar/05] + +tech-stack: + added: [tsdav@2.2.2, node-ical@0.26.1, ews-javascript-api@0.15.3, @microsoft/microsoft-graph-client@3.0.7] + patterns: [provider-dispatch-by-type, encrypted-credentials-at-rest, ssrf-url-validation, promise-allsettled-aggregation, in-memory-ttl-cache] + +key-files: + created: + - apps/api/src/calendar/calendar.module.ts + - apps/api/src/calendar/calendar.controller.ts + - apps/api/src/calendar/calendar.service.ts + - apps/api/src/calendar/crypto.service.ts + - apps/api/src/calendar/providers/ics.provider.ts + - apps/api/src/calendar/providers/caldav.provider.ts + - apps/api/src/calendar/providers/exchange.provider.ts + - apps/api/src/calendar/dto/create-calendar-source.dto.ts + - apps/api/src/calendar/dto/update-calendar-source.dto.ts + - apps/api/src/calendar/dto/calendar-events-query.dto.ts + modified: + - apps/api/prisma/schema.prisma + - apps/api/src/app.module.ts + - apps/api/package.json + - docker-compose.yml + +key-decisions: + - "DAVClient class constructor + login() instead of createDAVClient factory (type safety with tsdav v2)" + - "Dynamic imports for ews-javascript-api and @microsoft/microsoft-graph-client (lazy-load, avoid unused SDK weight)" + - "In-memory Map cache with 5-min TTL for events (Redis not needed at current scale; Pitfall 4)" + - "ews-javascript-api imported as any due to no TypeScript definitions available" + +patterns-established: + - "Provider dispatch pattern: getProvider(type) returns CalendarProvider implementation by source.type" + - "Credential encryption: AES-256-GCM with iv:authTag:ciphertext hex format" + - "SSRF validation: https-only DTO + private IP range blocking in service" + - "Safe select pattern: SOURCE_SAFE_SELECT excludes encryptedPassword, derives hasCredentials boolean" + +requirements-completed: [CAL-01, CAL-02, CAL-03, DASH-05] + +duration: 11min +completed: 2026-06-24 +--- + +# Phase 05 Plan 03: Calendar Backend Summary + +**Multi-protocol calendar backend with CalDAV/ICS/Exchange providers, AES-256-GCM credential encryption, SSRF-protected source CRUD, and in-memory cached event aggregation** + +## Performance + +- **Duration:** 11 min +- **Started:** 2026-06-24T13:05:25Z +- **Completed:** 2026-06-24T13:16:25Z +- **Tasks:** 4 +- **Files modified:** 14 + +## Accomplishments + +- CalendarSource Prisma model with encrypted credentials pushed to live PostgreSQL +- Full CalendarModule: source CRUD (6 endpoints), ownership checks, SSRF validation +- Three calendar providers: ICS (node-ical + RRULE expansion), CalDAV (tsdav), Exchange (Graph API + EWS) +- Event aggregation across visible sources with Promise.allSettled, per-user cache with 5-min TTL +- Credential encryption via AES-256-GCM; passwords never returned in API responses + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Calendar backend -- model, crypto, source CRUD module** - `9ec6313` (feat) +2. **Task 2: Calendar providers (CalDAV, ICS, Exchange)** - `389ac9b` (feat) +3. **Task 3: Event aggregation + caching backend** - `0cd8efe` (feat) +4. **Task 4: Prisma schema push + encryption key check** - `bb8990c` (chore) + +## Files Created/Modified + +- `apps/api/prisma/schema.prisma` - Added CalendarSource model (16 columns) +- `apps/api/src/calendar/calendar.module.ts` - NestJS module with all providers registered +- `apps/api/src/calendar/calendar.controller.ts` - REST endpoints for sources + events +- `apps/api/src/calendar/calendar.service.ts` - Source CRUD + event aggregation with cache +- `apps/api/src/calendar/crypto.service.ts` - AES-256-GCM encrypt/decrypt service +- `apps/api/src/calendar/providers/ics.provider.ts` - ICS fetch + parse + RRULE expansion +- `apps/api/src/calendar/providers/caldav.provider.ts` - CalDAV via tsdav with time-range filter +- `apps/api/src/calendar/providers/exchange.provider.ts` - Exchange Graph API + EWS dispatch +- `apps/api/src/calendar/dto/create-calendar-source.dto.ts` - Create DTO with https-only validation +- `apps/api/src/calendar/dto/update-calendar-source.dto.ts` - Update DTO with isVisible toggle +- `apps/api/src/calendar/dto/calendar-events-query.dto.ts` - Events query DTO (from/to dates) +- `apps/api/src/app.module.ts` - Registered CalendarModule +- `apps/api/package.json` - Added tsdav, node-ical, ews-javascript-api, @microsoft/microsoft-graph-client +- `docker-compose.yml` - Added CALENDAR_ENCRYPTION_KEY env var + +## Decisions Made + +- Used `DAVClient` class constructor + `login()` instead of `createDAVClient` factory function because the factory returns a plain object without `login()` method in tsdav v2 types +- Dynamic imports for EWS and Graph SDKs to avoid loading unused SDK code at startup +- In-memory Map cache with 5-minute TTL for events (Redis overkill at current scale; easy to swap later) +- `ews-javascript-api` imported as `any` since the library ships no TypeScript definitions +- ICS provider uses native `fetch()` + `ical.sync.parseICS()` instead of `ical.async.fromURL()` for better timeout control (AbortController) + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered + +- **tsdav `createDAVClient` type mismatch:** The factory function returns a utility object missing the `login()` method in its type. Resolved by using the `DAVClient` class constructor directly. +- **ews-javascript-api no TypeScript definitions:** Library is JS-only with no `.d.ts` files. The `DateTime` type expected by `CalendarView` constructor is an EWS-internal type. Resolved by importing as `any` and using JS Date objects which the runtime accepts. +- **node-ical `async.fromURL` overload ambiguity:** Passing options to the async overload causes TypeScript to select the void-returning callback signature. Resolved by using native `fetch()` + `sync.parseICS()` instead. + +## User Setup Required + +**CALENDAR_ENCRYPTION_KEY** must be configured before the CalendarModule can start: +1. Generate a 32-byte hex key: `openssl rand -hex 32` +2. Add to `apps/api/.env`: `CALENDAR_ENCRYPTION_KEY=` +3. Add to Docker environment or `.env` root for production deployment +4. The key has been added to the development `.env` and `docker-compose.yml` + +## Threat Mitigations Implemented + +| Threat ID | Mitigation | +|-----------|------------| +| T-05-09 | Prisma `select` excludes encryptedPassword; returns `hasCredentials` boolean | +| T-05-10 | AES-256-GCM encryption via CalendarCryptoService | +| T-05-11 | DTO https-only + private IP range blocking + localhost/internal blocking | +| T-05-12 | All endpoints scoped to userId; ownership check before update/delete | +| T-05-13 | Generic error messages; no credential details in error responses | + +## Next Phase Readiness + +- Calendar backend API complete and ready for frontend integration (Plan 04/05) +- CalendarSource table live in PostgreSQL +- Event aggregation endpoint functional — frontend can fetch via `GET /calendar/events` +- Source CRUD ready for Settings UI — `GET/POST/PATCH/DELETE /calendar/sources` + +## Self-Check: PASSED + +- All 10 created files verified present +- All 4 task commits verified in git log (9ec6313, 389ac9b, 0cd8efe, bb8990c) + +--- +*Phase: 05-dashboard-calendar* +*Completed: 2026-06-24*