11 KiB
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 bytenantIdand 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:
- GLOBAL_ADMIN – Full access to all tenants, can create/delete tenants
- TENANT_ADMIN – Management of a single tenant (channels, agents, staff, settings)
- 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)
- Passkey shard: Derived from WebAuthn
- 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:
- startupHandle – DB migrations, housekeeping scheduler (every 12h)
- loggingHandle – Request/response logging
- i18nHandle – Locale detection and setting
- rateLimitHandle – IP-based rate limiting (20 req/2s)
- corsHandle – CORS headers
- secHeaderHandle – Security headers (CSP, HSTS, etc.)
- apiAuthHandle – JWT validation for API routes
- 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:
- PostgreSQL – Read-only filesystem, Docker secrets, SCRAM-SHA-256
- SvelteKit App – Rootless (UID 1001), read-only, healthcheck at
/api/health - 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):
- Migrate central database
- Migrate all tenant databases in parallel
- Start housekeeping scheduler (every 12 hours):
- Clean up expired invitations
- Delete expired sessions
- Remove expired PIN reset tokens per tenant