docs: refresh project documentation against current codebase
Publish / publish (push) Successful in 26s

This commit is contained in:
Lucas Berger
2026-06-18 06:44:29 -04:00
parent 18d3ee6a4f
commit 1e2cc52659
11 changed files with 872 additions and 178 deletions
+58 -22
View File
@@ -10,7 +10,9 @@ Part of the [FamilySync monorepo](../../README.md).
- **Calendar broker** — polls Fastmail CalDAV every 5 minutes via `tsdav`; parses iCalendar payloads with `ical.js` and expands recurrence rules with `ical.js`'s `ICAL.RecurExpansion`; writes changes back to Fastmail through an outbox worker
- **Collaborative lists** — creates, reorders (fractional indexing), and syncs grocery/gift lists in MariaDB via Drizzle ORM
- **OIDC auth** — all `/api/*` routes protected by `@hono/oidc-auth` with authorization-code + PKCE flow against Authelia; `DEV_AUTH_BYPASS=true` skips OIDC for local development
- **Auth** — dual-mode: OIDC authorization-code + PKCE flow against Authelia (`@hono/oidc-auth`) for production; local username/password auth (scrypt, JWT session cookie) for no-OIDC or first-boot scenarios. `DEV_AUTH_BYPASS=true` skips both for local development
- **Setup wizard** — `/api/setup/*` surface guides first-run configuration of OIDC, VAPID keys, and member credentials before the app is locked
- **Admin** — role-gated `/api/admin/*` for member management, credential rotation, and calendar sharing designation
- **Live sync** — Server-Sent Events stream list mutations to connected PWA clients in real time
- **Push notifications** — web-push (VAPID) delivers reminders for shared timed events to subscribed browsers
@@ -22,17 +24,27 @@ src/
routes/
events.ts CalDAV event CRUD endpoints
lists.ts List and list-item CRUD endpoints
me.ts Authenticated user profile endpoint
me.ts Authenticated user profile endpoint + OIDC-link initiation
push.ts Push subscription registration
sse.ts SSE stream for live list updates
health.ts Unauthenticated health check
setup.ts First-run setup wizard surface (/api/setup/*)
admin.ts Role-gated admin API (members, credentials, calendars)
localAuth.ts Local login/logout endpoints (/api/auth/local/*)
authMode.ts Pre-auth auth-mode discovery (/api/auth/mode)
db/
schema.ts Drizzle table definitions (MariaDB/mysql2)
client.ts Drizzle client singleton
migrations/ SQL migrations generated by drizzle-kit
auth/
middleware.ts oidcAuthMiddleware + processOAuthCallback
middleware.ts oidcAuthMiddleware + processOAuthCallback + oidcConfigFallbackMiddleware
devBypass.ts DEV_AUTH_BYPASS passthrough (non-production only)
localAuthMiddleware.ts local-session cookie → c.get('user') middleware
localCredentials.ts scrypt password hashing and constant-time verification
localSession.ts HS256 JWT session-cookie issue / verify / clear helpers
linkNonceStore.ts Single-use nonce store for OIDC-link CSRF prevention
linkOidc.ts Atomic OIDC-identity binding + local credential removal
oidcConfig.ts Centralized OIDC config resolution (env OR app_config)
persistSessionCookie.ts Re-issues session cookie as persistent for PWA
user.ts User upsert on first login
broker/
@@ -40,11 +52,12 @@ src/
outboxWorker.ts 15-second drain of pending CalDAV writes to Fastmail
reminderScheduler.ts 1-minute scan for upcoming shared events → push
client.ts tsdav client factory
credentialSync.ts Shared validate→encrypt→store→initial-sync helper
sync.ts REPORT → ical.js → DB upsert logic
write.ts CalDAV PUT/DELETE helpers
expand.ts recurrence expansion via ICAL.RecurExpansion
expand.ts Recurrence expansion via ICAL.RecurExpansion
vevent.ts VEVENT ↔ DB row mapping
crypto.ts AES-256-GCM encrypt/decrypt for stored app passwords
crypto.ts AES-256-GCM encrypt/decrypt for stored app passwords (APP_PASSWORD_ENCRYPTION_KEY)
lib/
listEmitter.ts In-process EventEmitter for SSE fan-out
listChangeDispatcher.ts Publishes list mutations to listEmitter
@@ -53,6 +66,11 @@ src/
pushCoalescer.ts Debounces push for rapid successive edits
listAccess.ts List permission helpers
rank.ts Fractional indexing helpers
bootGuards.ts Boot-time env guards (blocks DEV_AUTH_BYPASS in production; enforces LOCAL_SESSION_SECRET)
setupGuard.ts isSetupLocked() — prevents re-running the wizard after completion
householdTimezone.ts Shared IANA timezone accessor with env fallback
outboxTrigger.ts In-process drain signal between routes and outboxWorker
requireAdmin.ts DB-enforced admin role middleware
```
## Running in the workspace
@@ -107,26 +125,44 @@ Migration files are written to `src/db/migrations/` and checked into source cont
## Environment variables
| Variable | Required | Description |
| --------------------------- | ------------------- | ---------------------------------------------------------------------- |
| `DB_HOST` | Yes | MariaDB host |
| `DB_USER` | Yes | MariaDB user |
| `DB_PASSWORD` | Yes | MariaDB password |
| `DB_NAME` | Yes | MariaDB database name |
| `DB_PORT` | No (default `3306`) | MariaDB port |
| `OIDC_ISSUER` | Yes (production) | Authelia issuer URL |
| `OIDC_CLIENT_ID` | Yes (production) | OIDC client ID |
| `OIDC_CLIENT_SECRET` | Yes (production) | OIDC client secret |
| `OIDC_AUTH_EXTERNAL_URL` | Yes (production) | External-facing URL for redirect_uri behind Pangolin tunnel |
| `VAPID_SUBJECT` | Yes (push) | `mailto:` or `https:` operator identifier |
| `VAPID_PUBLIC_KEY` | Yes (push) | VAPID public key |
| `VAPID_PRIVATE_KEY` | Yes (push) | VAPID private key |
| `CREDENTIAL_ENCRYPTION_KEY` | Yes | AES-256-GCM key for stored Fastmail app passwords |
| `DEV_AUTH_BYPASS` | No | Set to `true` (non-production only) to skip OIDC and inject a dev user |
| `NODE_ENV` | No | Set to `production` to enforce OIDC unconditionally |
| Variable | Required | Description |
| ----------------------------- | ------------------- | ---------------------------------------------------------------------------------- |
| `DB_HOST` | Yes | MariaDB host |
| `DB_USER` | Yes | MariaDB user |
| `DB_PASSWORD` | Yes | MariaDB password |
| `DB_NAME` | Yes | MariaDB database name |
| `DB_PORT` | No (default `3306`) | MariaDB port |
| `OIDC_ISSUER` | Yes (production) | Authelia issuer URL |
| `OIDC_CLIENT_ID` | Yes (production) | OIDC client ID |
| `OIDC_CLIENT_SECRET` | Yes (production) | OIDC client secret |
| `OIDC_AUTH_EXTERNAL_URL` | Yes (production) | External-facing URL for redirect_uri behind Pangolin tunnel |
| `OIDC_REDIRECT_URI` | No | Explicit redirect URI (overrides the `${OIDC_AUTH_EXTERNAL_URL}/callback` default) |
| `VAPID_SUBJECT` | Yes (push) | `mailto:` or `https:` operator identifier |
| `VAPID_PUBLIC_KEY` | Yes (push) | VAPID public key |
| `VAPID_PRIVATE_KEY` | Yes (push) | VAPID private key |
| `APP_PASSWORD_ENCRYPTION_KEY` | Yes | AES-256-GCM key (64-char hex) for stored Fastmail app passwords |
| `LOCAL_SESSION_SECRET` | Yes (local auth) | HS256 signing key for local-session JWT cookies (min 32 chars) |
| `LOCAL_SESSION_EXPIRES` | No (default `86400`)| Local session lifetime in seconds |
| `DEV_AUTH_BYPASS` | No | Set to `true` (non-production only) to skip OIDC and inject a dev user |
| `NODE_ENV` | No | Set to `production` to enforce OIDC unconditionally |
| `TZ` | No | IANA timezone fallback when household_timezone is not set in app_config |
> **Note:** `CREDENTIAL_ENCRYPTION_KEY` was renamed to `APP_PASSWORD_ENCRYPTION_KEY`. Update any existing `.env` files if upgrading from an earlier phase.
See [../../docs/CONFIGURATION.md](../../docs/CONFIGURATION.md) for the full reference.
## Authentication modes
The API supports two non-exclusive auth modes, determined at startup:
| Mode | When active | How it works |
| ---- | ----------- | ------------ |
| **Local** | Always (default) | `POST /api/auth/local/login` with username + password; issues an HS256 JWT `local-session` cookie. Requires `LOCAL_SESSION_SECRET`. |
| **OIDC** | When `OIDC_ISSUER` + `OIDC_CLIENT_ID` are set (env or app_config) | `@hono/oidc-auth` authorization-code + PKCE against Authelia. Local users can upgrade to OIDC via `POST /api/me/link-oidc`. |
| **Dev bypass** | `DEV_AUTH_BYPASS=true` in non-production | Skips both guards and injects a synthetic dev user. Blocked in `NODE_ENV=production` by boot guard. |
`GET /api/auth/mode` returns `{ localEnabled, oidcEnabled }` before authentication — the PWA uses this to decide which login form to show.
## Tests
Tests live in `tests/` (integration, route, broker unit) and `test/setup.ts` (global setup/teardown).