# Chat Server Architecture - Complete Reference ## Project Overview Self-hosted Discord alternative with decentralized identity. Each user has a "home server" where their identity lives, and can join other "guest servers" seamlessly using cryptographic signatures. ## Core Architecture Decisions ### Home Server Model - **NO identity server** - just use one regular server as "home" - **NO federation protocol** - servers don't talk to each other - **Client does everything** - syncs, cross-server requests, profile updates ### Identity System - Current implementation: secp256k1 keypair/signatures via `crypton` EC - Target (planned): Ed25519 migration - Private key = proof of identity - Public key stored on home server at `/.well-known/users/:username` - userId format: `username@serverhost` ### Single Sign-On Flow 1. User registers/logs in ONCE on home server 2. Gets JWT token + encrypted keypair 3. To join other servers: sign a message with private key 4. Other server verifies signature against home server's public key 5. No password ever sent to guest servers --- ## What's Implemented ### ✅ Done - User model with keypair, home server reference - Channel and Message models - Crypto service (keypair generation, signing, verification) - JWT authentication middleware - Register handler (creates keypair) - Login handler (returns encrypted keypair for device sync) - Join server handler (signature-based verification with identity checks) - HTTP client for cross-server public key fetching - Public endpoints for cross-server identity verification - Channel CRUD - Message CRUD with author caching ### ❌ Not Implemented - WebSocket gateway for real-time events - Notification system (mentions/replies/unread state) - Push delivery pipeline (FCM/APNs/WebPush device tokens + dispatch) - Profile sync (push updates to all servers) - Sticker packs (future feature) - File uploads - Permissions system (roles exist but not enforced) - Message editing/deletion permissions --- ## Database Models ```dart // User - works for both HOME and GUEST servers @Entity() class User { int id; String userId; // "alice@homeserver.com" String username; String? displayName, avatarUrl, bio; String publicKey; // Ed25519, always stored // HOME SERVER ONLY: String? passwordHash; String? encryptedPrivateKey; // For device sync String? keyPairSalt; bool isLocal; // true = this is home server String? homeServer; // if guest, where is home? List roles; // Server-specific DateTime createdAt, lastSyncedAt; } @Entity() class Channel { int id; String name; String? description; DateTime createdAt; } @Entity() class Message { int id; int channelId; // Author info (includes cross-server fetch capability) int authorLocalId; String authorUserId; // "alice@home.server" String? authorHomeServer; // Cached snapshot at message time String authorUsername; String? authorAvatarUrl; String content; DateTime createdAt, editedAt; } @Entity() class Notification { int id; int recipientLocalId; String recipientUserId; // "alice@home.server" String type; // "mention", "reply", "system" String title; String body; int? channelId; int? messageId; bool isRead; DateTime createdAt; DateTime? readAt; } ``` --- ## API Endpoints ### Public (No Auth) ``` POST /auth/register → Create account on HOME server (generates keypair) POST /auth/login → Login to HOME server (returns keypair backup) POST /auth/join → Join THIS server with signature (no password) GET /.well-known/users/:username → Public profile + public key GET /.well-known/verify/:userId → Verify user exists GET /.well-known/server → Server info ``` ### Protected (JWT Required) ``` GET /api/me → Current user profile GET /api/channels → List channels POST /api/channels → Create channel GET /api/channels/:id → Get channel GET /api/messages?channelId=X → Get messages POST /api/messages → Send message PUT /api/messages/:id → Edit message DELETE /api/messages/:id → Delete message GET /api/notifications → List notifications POST /api/notifications/read → Mark notifications read POST /api/push/devices → Register device push token DELETE /api/push/devices/:id → Remove push token ``` --- ## Realtime & Notifications (Planned MVP) - Add WebSocket endpoint (e.g. `/gateway`) authenticated by JWT. - Keep REST as source of truth; WebSocket is for event fanout. - On message create/edit/delete: 1. Write to DB 2. Emit gateway event to subscribed clients 3. Generate notification records for mention/reply targets - If recipient is online, deliver notification over gateway immediately. - If recipient is offline and has device token, enqueue push dispatch. - Reconnect strategy: client backfills missed messages using `GET /api/messages?channelId=X&after=lastSeenMessageId`. - Full Discord-style resume/replay is optional and can be added later. --- ## Key Files ``` lib/ ├── server.dart # Entry point, routes, ObjectBox init ├── models.dart # User, Channel, Message entities ├── objectbox.g.dart # Generated │ ├── config/config.dart # Server host, JWT secret, port │ └── src/ ├── auth/ │ ├── crypto_service.dart # Keypair, signing, encryption │ └── auth_middleware.dart # JWT verification │ ├── handlers/ │ ├── auth_handlers.dart # register, login, join, me │ ├── public_handlers.dart # .well-known/* │ ├── channel_handlers.dart │ └── message_handlers.dart │ └── utils/extensions.dart # Request/Response helpers ``` --- ## Join Server Flow (Implemented) ``` CLIENT GUEST SERVER HOME SERVER │ │ │ │ POST /auth/join │ │ │ {userId, homeServer, timestamp, │ │ │ signature} │ │ │─────────────────────────────────────►│ │ │ │ │ │ │ GET /.well-known/users/ │ │ │ alice │ │ │────────────────────────►│ │ │ │ │ │ {publicKey, ...} │ │ │◄────────────────────────│ │ │ │ │ │ verify signature │ │ │ create shadow account │ │ │ │ │ {token, localId} │ │ │◄─────────────────────────────────────│ │ ``` The join flow now: - Fetches identity from home server over HTTP (`/.well-known/users/:username`) - Verifies claimed `userId` matches home-server response - Verifies signature before issuing token (including already-joined users) --- ## Tech Stack - **Framework**: Relic 1.0 (Dart web server) - **Database**: ObjectBox - **Auth**: JWT + secp256k1 signatures (current), Ed25519 (planned) - **Crypto**: crypton package (EC) - **Password**: bcrypt --- ## Design Decisions Rationale | Decision | Why | |----------|-----| | No identity server | Avoids hosting another service, one server = home | | No server-to-server | Eliminates federation complexity, client handles everything | | Keypair identity | Allows passwordless joins, proves identity cryptographically | | Message author caching | Messages readable even if home server is down | | userId format `user@server` | Simple, familiar, self-documenting | --- ## Constraints - User CANNOT quit home server (that's where identity lives) - User CANNOT change home server easily (migration = future feature) - Home server MUST be online to join NEW servers - Home server offline = can still use already-joined servers (cached keys) --- ## Future Features (Out of Scope for MVP) 1. **Sticker Packs** - Telegram-style, live on community servers, clients cache 2. **Home Server Migration** - Sign migration certificate, chain of trust 3. **File Uploads** - With cross-server URLs 4. **Message Forwarding** - Embed with link to original 5. **Permissions** - Role-based access to channels 6. **WebSocket** - Real-time message delivery 7. **Advanced Notification Rules** - Mute/priority/snooze/digests --- ## Config to Change ```dart // lib/config/config.dart static const String serverHost = 'your-actual-domain.com'; // CHANGE THIS static const String jwtSecret = 'use-env-variable'; // CHANGE THIS ``` --- ## Next Steps 1. Add WebSocket gateway (`/gateway`) with JWT auth + channel subscriptions 2. Add message history cursor pagination (`before` + `after`) 3. Implement notifications (DB model + generation rules + unread counters) 4. Add push token registration + push dispatch worker 5. Add profile sync (client pushes to all servers on change) 6. Add proper error handling, replay protection, and logging