Files
2026-03-09 08:42:22 +01:00

214 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Overall Architecture Open Reception
## Project Overview
Open Reception is an end-to-end encrypted appointment booking platform, funded by the German Federal Ministry of Research, Technology and Space and the PrototypeFund. License: AGPL-3.0.
## Tech Stack
- **Framework**: SvelteKit 5 (SSR + SPA) with TypeScript
- **UI**: Tailwind CSS 4, shadcn/svelte (bits-ui), Lucide Icons
- **Database**: PostgreSQL 16 via Drizzle ORM
- **Authentication**: JWT (jose), WebAuthn/Passkeys (@simplewebauthn/server)
- **Cryptography**: ML-KEM-768/Kyber (@noble/post-quantum), AES-256-GCM, Argon2, Shamir Secret Sharing
- **Email**: Nodemailer with Svelte-rendered templates
- **i18n**: Paraglide (German, English)
- **Logging**: Winston (UniversalLogger)
- **Validation**: Zod, sveltekit-superforms + formsnap
- **Testing**: Vitest (unit), Playwright (E2E)
- **Infrastructure**: Docker Compose, Caddy (reverse proxy + HTTPS)
## System Architecture
```
┌─────────────────────────────────────────────────────┐
│ Clients │
│ (Browser Clients / Staff) │
└──────────────────────┬──────────────────────────────┘
│ HTTPS
┌──────────────────────▼──────────────────────────────┐
│ Caddy Reverse Proxy │
│ (TLS Termination, Security Headers, │
│ On-Demand TLS, Gzip Compression) │
└──────────────────────┬──────────────────────────────┘
│ HTTP :3000
┌──────────────────────▼──────────────────────────────┐
│ SvelteKit Application │
│ ┌────────────────────────────────────────────────┐ │
│ │ Server Hooks Pipeline │ │
│ │ startup → logging → i18n → rateLimit → │ │
│ │ cors → secHeaders → apiAuth → authGuard │ │
│ └────────────────────────────────────────────────┘ │
│ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ API Routes │ │ Page Routes (SSR) │ │
│ │ /api/... │ │ /(pages)/... │ │
│ └────────┬─────────┘ └───────────┬────────────┘ │
│ ┌────────▼─────────────────────────▼────────────┐ │
│ │ Service Layer │ │
│ │ AppointmentService, AgentService, │ │
│ │ ChannelService, ScheduleService, │ │
│ │ StaffCryptoService, NotificationService, │ │
│ │ SessionService, AuthorizationService, │ │
│ │ EmailService, InviteService, ... │ │
│ └────────┬──────────────────────────────────────┘ │
│ ┌────────▼──────────────────────────────────────┐ │
│ │ Database Layer (Drizzle ORM) │ │
│ └────────┬──────────────────────────────────────┘ │
└───────────┼──────────────────────────────────────────┘
┌───────────▼──────────────────────────────────────────┐
│ PostgreSQL 16 │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ Central DB │ │ Tenant DB (per tenant) │ │
│ │ - tenant │ │ - agent │ │
│ │ - user │ │ - channel │ │
│ │ - user_passkey │ │ - slot_template │ │
│ │ - user_session │ │ - appointment │ │
│ │ - user_invite │ │ - notification │ │
│ │ - tenant_config │ │ - staff_crypto │ │
│ │ - challenge_ │ │ - client_appointment_ │ │
│ │ throttle │ │ tunnel │ │
│ └──────────────────┘ │ - appointment_key_share │ │
│ │ - agent_absence │ │
│ │ - auth_challenge │ │
│ │ - client_pin_reset_ │ │
│ │ token │ │
│ └──────────────────────────┘ │
└──────────────────────────────────────────────────────┘
```
## Multi-Tenant Architecture
The system uses **database-per-tenant isolation**: each tenant receives its own PostgreSQL database. The central database manages tenant metadata, users, and sessions.
- **Central DB**: Contains `tenant`, `user`, `user_passkey`, `user_session`, `user_invite`, `tenant_config`, `challenge_throttle`
- **Tenant DB** (per tenant): Contains all business-specific data (appointments, channels, agents, cryptographic material)
- **Connection management**: `getTenantDb()` resolves the database connection at runtime by `tenantId` and caches connections in a Map
### Tenant Lifecycle
Each tenant passes through five states during onboarding:
`SETTINGS``AGENTS``CHANNELS``STAFF``READY`
## User Roles & Authorization
Three roles with hierarchical access:
1. **GLOBAL_ADMIN** Full access to all tenants, can create/delete tenants
2. **TENANT_ADMIN** Management of a single tenant (channels, agents, staff, settings)
3. **STAFF** Operational access to the calendar and appointments of their own tenant
Authorization is enforced via `AuthorizationService` and `checkPermission()`, each with tenant-scope verification.
## Authentication
### Staff/Admin Authentication
- **WebAuthn/Passkeys** as the primary method
- **Passphrase** as an alternative method
- JWT Access Tokens (15 min) + Refresh Tokens (7 days)
- Sessions stored in the central database
- Token validation against the DB on every API call
### Client Authentication (Appointment Bookers)
- **PIN-based** with challenge-response protocol
- Email hash for privacy-preserving identification
- Argon2 for PIN derivation
- Challenge throttling against brute-force attacks
## End-to-End Encryption
### Core Principle: Zero-Knowledge Server
The server never has access to unencrypted personal data at any point.
### Cryptographic Primitives
- **ML-KEM-768 (Kyber)**: Post-quantum secure key exchange
- **AES-256-GCM**: Symmetric encryption of appointment data
- **Shamir Secret Sharing (2-of-2)**: Private key splitting
- **Argon2**: PIN-based key derivation (client-side)
### Staff Key Management
- Kyber key pair is generated in the browser
- Private key is split into two shards via XOR:
- **Passkey shard**: Derived from WebAuthn `authenticatorData`
- **DB shard**: Stored in the tenant database (`staff_crypto`)
- Reconstruction requires both shards (during login)
### Client Tunnel Concept
- Each client receives an encrypted "tunnel" per tenant
- Tunnel key (AES-256) encrypts all of the client's appointments
- Tunnel key is encrypted separately for each staff member using their Kyber public key
- Client private key: Shamir split (PIN shard + server shard)
## Server Hooks Pipeline
The request processing chain (`hooks.server.ts`) in execution order:
1. **startupHandle** DB migrations, housekeeping scheduler (every 12h)
2. **loggingHandle** Request/response logging
3. **i18nHandle** Locale detection and setting
4. **rateLimitHandle** IP-based rate limiting (20 req/2s)
5. **corsHandle** CORS headers
6. **secHeaderHandle** Security headers (CSP, HSTS, etc.)
7. **apiAuthHandle** JWT validation for API routes
8. **authGuard** Page-based access control (dashboard protection)
## Frontend Structure
### Route Groups
- `/(pages)/(clients)/` Public client pages (appointment booking, client login)
- `/(pages)/dashboard/` Protected admin/staff area
- `/(pages)/login/`, `/logout/`, `/setup/`, `/confirm/` Authentication flows
- `/api/` REST API with OpenAPI documentation
### Dashboard Areas
- **Calendar** Day view with appointments and slots
- **Channels** Management of bookable resources
- **Agents** Management of persons/contacts
- **Staff** User management with roles
- **Absences** Agent absences
- **Tenants** Tenant management (GLOBAL_ADMIN only)
- **Settings** Tenant configuration
### UI Component Library
Based on shadcn/svelte with custom extensions:
- **Layouts**: `sidebar-layout`, `centered-card`, `empty-layout`, `max-page-width`
- **Templates**: `empty-state`, `form-grid`, `list`, `loading`, `language-switch`
- **UI Primitives**: button, card, dialog, form, input, calendar, combobox, etc.
## Email System
- Svelte components as email templates (server-side rendered)
- Supported email types: confirmation, invitation, appointment booking, appointment reminder, appointment rejection, appointment cancellation, PIN reset
- HTML + plaintext output
- Language-dependent templates (de/en)
## Docker Infrastructure
### Development
- Only PostgreSQL as a Docker container
- SvelteKit runs locally via `npm run dev`
### Production
Three containers in an internal network:
1. **PostgreSQL** Read-only filesystem, Docker secrets, SCRAM-SHA-256
2. **SvelteKit App** Rootless (UID 1001), read-only, healthcheck at `/api/health`
3. **Caddy** HTTPS with on-demand TLS (Let's Encrypt), security headers, gzip
### Container Security Measures
- `no-new-privileges`, `cap_drop: ALL`
- Read-only root filesystems with tmpfs
- Docker secrets instead of environment variables (production)
- Internal networks, only Caddy exposes ports
## Startup & Housekeeping
On application start (`StartupService`):
1. Migrate central database
2. Migrate all tenant databases in parallel
3. Start housekeeping scheduler (every 12 hours):
- Clean up expired invitations
- Delete expired sessions
- Remove expired PIN reset tokens per tenant