Files
familysync/.planning/codebase/INTEGRATIONS.md
T

7.6 KiB

External Integrations

Analysis Date: 2026-06-09

APIs & External Services

CalDAV (Fastmail):

  • Fastmail CalDAV endpoint - Calendar read/write for all household calendars
    • SDK/Client: tsdav 2.2.2 (apps/api/src/broker/client.ts)
    • Auth: Basic auth with Fastmail app password (per-member, stored encrypted in member_credentials table)
    • Endpoint: https://caldav.fastmail.com
    • Operations: PROPFIND (discover calendars), REPORT (fetch events), PUT (create/update), DELETE (remove events)
    • Principal URL pattern: https://caldav.fastmail.com/dav/principals/user/{email}/
    • Parse responses via ical.js; expand recurrence with rrule

OIDC (Authelia):

  • Authelia OIDC identity provider - User authentication and session management
    • SDK/Client: @hono/oidc-auth 1.8.3 (apps/api/src/auth/middleware.ts)
    • Auth method: Authorization-code flow with PKCE (S256 challenge method)
    • Token auth: client_secret_basic (plaintext secret, NOT pbkdf2 hash)
    • Required env vars: OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET
    • Session: Storage-less JWT cookies; refresh via stored refresh token every 15 min (default OIDC_AUTH_REFRESH_INTERVAL)
    • Requested scopes: openid profile email offline_access (customize via OIDC_SCOPES env var)
    • Metadata discovery: Fetches /.well-known/openid-configuration from issuer
    • Callback: /callback route in Hono app; redirects to /api/login/ on success

Data Storage

Databases:

  • MariaDB 11 - Primary relational database (required; PostgreSQL not available)
    • Connection: Environment vars (DB_HOST, DB_PORT 3306, DB_USER, DB_PASSWORD, DB_NAME)
    • Client: mysql2 3.22.4 (native driver via Drizzle ORM)
    • Schema: apps/api/src/db/schema.ts (Drizzle mysqlTable definitions)
    • Tables: users, member_credentials, calendars, calendarEvents, calendarOutbox
    • Connection pool: 10 connections max (mysql2 createPool)
    • Migrations: Generated by drizzle-kit; stored in apps/api/src/db/migrations/
    • Local dev: Docker service mariadb with healthcheck; data persisted to mariadb_data volume

File Storage:

  • Local filesystem only - PWA static assets built by Vite
    • Location: Built output copied to apps/api/dist/public (Dockerfile pwa-builder stage)
    • Served by Hono via serveStatic middleware on the same :3000 port
    • No external cloud storage (S3, GCS, etc.)

Caching:

  • Redis 7-Alpine - Declared in docker-compose.yml but unused in Phase 1
    • Reserved for Phase 4 live list sync (pub/sub for broadcasting list-change events across Node processes)
    • Local dev: Docker service redis on port 6379
    • Client: ioredis (not yet added to dependencies; planned for Phase 4)

Authentication & Identity

Auth Provider:

  • Authelia (self-hosted, pre-deployed on Unraid host)
    • Implementation: RFC-compliant OIDC provider
    • User identity: Composite key of oidc_iss + oidc_sub (never email, per D-10 in schema)
    • Session flow: Browser top-level nav to /api/login → 302 redirect to Authelia authorize → user logs in → POST to /callback → JWT session cookie set → browser redirected to /
    • Invalid XHR redirects: Browser blocks cross-origin redirects from fetch/XHR to external IdP; PWA handles via maybeRedirectToLogin() (top-level navigation)
    • Claims policy: Authelia 4.39+ required for name/email/preferred_username in ID token (otherwise defaults to "Member" display name)

Dev Bypass (non-production only):

  • DEV_AUTH_BYPASS environment variable (NODE_ENV !== 'production')
    • When enabled: Skips @hono/oidc-auth middleware; injects DEV_USER into context
    • Allows local development without live Authelia instance
    • Implementation: apps/api/src/auth/devBypass.ts

Monitoring & Observability

Error Tracking:

  • Not detected - Errors logged to console; no external service integration

Logs:

  • Console-based - Events logged to stdout/stderr
    • Backend (Hono): Startup message, CalDAV poller errors (per-credential logging, T-03-04), outbox worker status
    • Frontend: React error boundaries catch component errors

Health Check:

  • GET /health endpoint (unauthenticated)
    • Endpoint: apps/api/src/routes/health.ts
    • Used by Docker Compose healthcheck for mariadb service
    • MariaDB test: healthcheck.sh --connect --innodb_initialized

CI/CD & Deployment

Hosting:

  • Docker on Unraid host (self-hosted)
    • Container image: Single production image from Dockerfile (API + PWA on port :3000)
    • Orchestration: Docker Compose (docker-compose.yml + docker-compose.dev.yml overrides)
    • Environment: Split-DNS internal domain; private IPs internally; external access via Pangolin/Newt tunnel

CI Pipeline:

  • Not detected - No GitHub Actions, GitLab CI, or similar configured

Build Output:

  • Docker multi-stage build:
    • API: TypeScript compiled to apps/api/dist/ by tsc
    • PWA: Vite bundles to apps/pwa/dist/; copied to apps/api/dist/public in production image
    • Single container serves both layers on :3000

Environment Configuration

Required env vars (Backend):

  • Database: DB_HOST, DB_PORT (default 3306), DB_USER, DB_PASSWORD, DB_NAME, DB_ROOT_PASSWORD
  • OIDC: OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI, OIDC_AUTH_EXTERNAL_URL (mandatory for Pangolin redirects)
  • Session: OIDC_AUTH_SECRET (32+ chars for JWT cookie signing)
  • Scopes: OIDC_SCOPES (default: openid profile email offline_access)
  • Encryption: APP_PASSWORD_ENCRYPTION_KEY (AES-256-GCM key for encrypting Fastmail app passwords)
  • Environment: NODE_ENV (production/development)
  • Dev override: DEV_AUTH_BYPASS (set to 'true' to disable OIDC; dev-only, NODE_ENV !== 'production')

Secrets location:

  • .env file (local development) — not committed; pattern documented in docker-compose.yml
  • Docker Compose environment variables — injected at runtime from .env or deployment config
  • Member app passwords: Encrypted in DB (member_credentials.encryptedPassword) using APP_PASSWORD_ENCRYPTION_KEY
  • OIDC client secret: Plain text in env var (NOT the pbkdf2 hash from Authelia config)

Optional env vars:

  • OIDC_AUTH_EXTERNAL_URL - MANDATORY behind Pangolin for correct redirect_uri construction (Pitfall 1)
  • DEV_AUTH_BYPASS - Dev-only; local testing without Authelia

Webhooks & Callbacks

Incoming:

  • /callback - OIDC authorization-code exchange endpoint
    • Mounted in apps/api/src/index.ts before oidcAuthMiddleware
    • Receives POST from Authelia after user login; exchanges code for tokens
    • Sets session JWT cookie; redirects to /api/login (continues to /)
    • Critical: Must not be intercepted by service worker (navigateFallbackDenylist in vite.config.ts)

Outgoing:

  • None detected - No third-party webhooks triggered by the app
  • Fastmail CalDAV: Changes are POLLED (5-min cron poller), not webhook-driven
  • List sync (Phase 4): Will use SSE (server-sent events) for client push, not webhooks

Network & Transport

HTTPS/TLS:

  • Mandatory for OIDC flows
  • Pangolin/Newt tunnel provides HTTPS reverse proxy
  • Internal domain: Split-DNS routes internal requests directly to private IP
  • External requests: Routed through Pangolin tunnel

Server-Sent Events (SSE):

  • GET /api/sse/heartbeat - Test endpoint for Pangolin compatibility
    • Endpoint: apps/api/src/routes/sse.ts
    • Uses Hono's streamSSE helper
    • Test procedure (D-08): curl -N https://familysync.<domain>/api/sse/heartbeat
    • Phase 4 will extend this for live list sync

CORS:

  • Credentials: 'include' for all fetch calls (session cookie sent cross-origin in dev proxy)
  • redirect: 'manual' for /api/me to detect OIDC redirect (prevents fetch hang on cross-origin 302 to Authelia)

Integration audit: 2026-06-09