7.6 KiB
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_credentialstable) - 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
- SDK/Client: tsdav 2.2.2 (
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-configurationfrom issuer - Callback:
/callbackroute in Hono app; redirects to/api/login→/on success
- SDK/Client: @hono/oidc-auth 1.8.3 (
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
mariadbwith healthcheck; data persisted tomariadb_datavolume
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.)
- Location: Built output copied to
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
redison 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
- Endpoint:
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 toapps/api/dist/publicin production image - Single container serves both layers on :3000
- API: TypeScript compiled to
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:
.envfile (local development) — not committed; pattern documented in docker-compose.yml- Docker Compose environment variables — injected at runtime from
.envor 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.tsbefore 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)
- Mounted in
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
- Endpoint:
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