Architecture
System Overview
Chatalot is a self-hosted, end-to-end encrypted chat platform. The architecture consists of a Rust server, a Svelte 5 single-page application, and an optional Tauri desktop wrapper.
+------------------+
| Cloudflare |
| Tunnel (opt.) |
+--------+---------+
|
+--------+---------+
| Axum Server |
| (Rust, :8080) |
+--+-----+-----+--+
| | |
+----------+ +--+--+ +----------+
| REST API | | WS | | Static |
| /api/* | | /ws | | SPA |
+----+-----+ +--+--+ +----------+
| |
+----+------------+----+
| AppState |
| +------------------+ |
| | PgPool | |
| | JWT keys | |
| | ConnectionMgr | |
| | Config | |
| | HTTP client | |
| +------------------+ |
+----------+-----------+
|
+--------+--------+
| PostgreSQL |
| (port 5432) |
+-----------------+
Component Architecture
Server (crates/chatalot-server)
The server is the central hub. It handles HTTP REST requests, WebSocket connections, and serves the SPA static files. Built on Axum 0.8 with Tokio as the async runtime.
chatalot-server
+-- main.rs Entry point, background tasks, graceful shutdown
+-- config.rs Environment variable configuration
+-- app_state.rs Shared state (DB pool, JWT keys, ConnectionManager)
+-- error.rs Unified error types -> HTTP status codes
+-- routes/ HTTP route handlers (24 modules)
+-- ws/ WebSocket handling
| +-- session.rs WS upgrade + auth handshake
| +-- handler.rs Message routing + rate limiting
| +-- connection_manager.rs Session registry + broadcast channels
+-- middleware/ Request pipeline
| +-- auth.rs JWT extraction + validation
| +-- community_gate.rs Community membership gate
| +-- rate_limit.rs Token bucket rate limiter
| +-- security.rs Security response headers
+-- services/ Business logic
| +-- auth_service.rs Password hashing, token issuance, lockout
| +-- file_security.rs Magic byte validation, MIME detection
| +-- css_sanitizer.rs Custom theme CSS sanitization
+-- permissions.rs Role-based permission checks
Database (crates/chatalot-db)
Database access layer with sqlx runtime queries (not compile-time macros). Contains models, repository functions, and pool/migration management.
chatalot-db
+-- pool.rs Connection pool creation (50 max, 2 min)
+-- models/ Rust structs mapped from DB rows
+-- repos/ Query functions grouped by domain
All SQL queries use sqlx::query! or sqlx::query_as! at runtime with SQLX_OFFLINE=true for CI builds.
Cryptography (crates/chatalot-crypto)
Pure Rust implementation of the Signal protocol cryptographic primitives:
chatalot-crypto
+-- x3dh.rs X3DH key agreement (session establishment)
+-- double_ratchet.rs Double Ratchet (1:1 message encryption)
+-- sender_keys.rs Sender Keys (group message encryption)
+-- aead.rs ChaCha20-Poly1305 encrypt/decrypt
+-- identity.rs Ed25519 key generation, fingerprints
+-- types.rs SecretKey (zeroize-on-drop), Fingerprint
There is also a crates/chatalot-crypto-wasm crate that compiles the crypto library to WebAssembly for use in the web client.
Common (crates/chatalot-common)
Shared types between server and (potentially) client crates:
chatalot-common
+-- ws_messages.rs ClientMessage / ServerMessage enums
+-- api_types.rs Request/response DTOs for REST API
+-- constants.rs Token lifetimes, limits, thresholds
Request Flow
REST API Request
Client
|
v
[Security Headers] middleware/security.rs
|
v
[CORS] tower_http CorsLayer (permissive for Tauri)
|
v
[Compression] tower_http CompressionLayer (gzip)
|
v
[Rate Limit] middleware/rate_limit.rs (token bucket)
|
v
[Body Limit] 110 MB max
|
v
[Route Match] /api/* -> API routes, /ws -> WS, /* -> SPA fallback
|
v
[Auth Middleware] middleware/auth.rs (Bearer JWT -> AccessClaims)
| (skipped for public routes: /auth/*, /health, /legal)
v
[Community Gate] middleware/community_gate.rs (optional, for /communities/{cid}/*)
|
v
[Handler] routes/*.rs -> business logic -> DB query -> response
WebSocket Connection
Client
|
v
[WS Upgrade] GET /ws -> 101 Switching Protocols
|
v
[Auth Handshake] Client sends: {"type":"authenticate","token":"<JWT>"}
| Server replies: {"type":"authenticated","user_id":"..."}
| 10-second timeout, no auth = disconnect
v
[Session Setup] ConnectionManager.add_session() (max 8 per user)
|
v
[Message Loop] Client sends ClientMessage, server responds with ServerMessage
| Rate limited: 10 msg/s burst, 5/s refill
| Max WS frame: 1 MB
v
[Channel Pub/Sub] Subscribe/Unsubscribe to channels via broadcast::channel (256 buffer)
|
v
[Disconnect] ConnectionManager.remove_session(), broadcast PresenceUpdate
Concurrency Model
The server uses Tokio for async I/O. Key concurrency patterns:
- ConnectionManager uses
DashMap(concurrent hash map) for lock-free session registry and channel subscriptions. - Channel broadcast uses
tokio::sync::broadcastwith a buffer of 256 messages per channel. Subscribers that fall behind receive aLaggederror. - Per-user messaging uses
tokio::sync::mpsc::UnboundedSenderfor direct server-to-client delivery. - Typing state is tracked in a
DashMap<(channel_id, user_id), Instant>with periodic cleanup. - Account lockout uses an in-memory
DashMap(resets on server restart).
Background Tasks
The server spawns several background tasks on startup:
| Task | Interval | Purpose |
|---|---|---|
| Typing timeout | 5s | Expire stale typing indicators (>10s) |
| Channel cleanup | 5min | Remove broadcast channels with zero subscribers |
| Data cleanup | 1h | Delete expired refresh tokens (>7d), used prekeys (>30d), old audit logs (>90d), orphaned voice sessions |
| Message GC | 24h | Hard-delete messages soft-deleted >30 days ago |
| Orphan file cleanup | 24h | Remove disk files with no DB record |
| Scheduled messages | 30s | Deliver messages whose scheduled_for time has passed |
| Message expiry | 5min | Delete messages past their TTL |
| Timeout cleanup | 5min | Remove expired user timeouts |
| Cache cleanup | 10min | Evict stale GIF and link preview cache entries |
Deployment Architecture
+-------------------------------------------+
| Docker Compose |
| |
| +-------------+ +------------------+ |
| | chatalot | | postgres:17 | |
| | (Rust+SPA) |--->| (chatalot-net) | |
| | :8080 | | | |
| +------+------+ +------------------+ |
| | |
| +------+------+ |
| | cloudflared | (optional, production) |
| | tunnel | |
| +-------------+ |
+-------------------------------------------+
The Docker image uses a multi-stage build:
- Rust builder -- compiles the server binary (with dependency caching)
- WASM builder -- compiles
chatalot-crypto-wasmto WebAssembly viawasm-pack - Node builder -- builds the Svelte SPA (with WASM crypto module)
- Runtime -- Debian slim with the server binary, SPA static files, and migrations
Secrets (JWT Ed25519 key pair) are mounted via Docker secrets from ./secrets/.