Files
tessera-ctl/.planning/phases/05-dashboard-calendar/05-03-SUMMARY.md
T
schalli 694bcff4a4 docs(05-03): complete calendar backend plan
- Create 05-03-SUMMARY.md with full execution documentation
- Update STATE.md: advance to plan 4/5, add decisions, record metrics
- Update ROADMAP.md: mark 05-03 complete, 3/5 plans done
- Update REQUIREMENTS.md: CAL-01/02/03 + DASH-05 backend complete
2026-06-24 15:19:27 +02:00

7.8 KiB


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=<generated-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


Phase: 05-dashboard-calendar Completed: 2026-06-24