# 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./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_