# 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 - Ed25519 keypair generated on registration - 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 - stub) - Public endpoints for cross-server identity verification - Channel CRUD - Message CRUD with author caching ### ❌ Not Implemented - HTTP client for cross-server public key fetching - WebSocket for real-time messages - 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; } ``` --- ## 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 ``` --- ## 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 (Needs Implementation) ``` 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 `fetchPublicKeyFromHomeServer()` function in `auth_handlers.dart` needs actual HTTP implementation. --- ## Tech Stack - **Framework**: Relic 1.0 (Dart web server) - **Database**: ObjectBox - **Auth**: JWT + Ed25519 signatures - **Crypto**: crypton package for Ed25519 - **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 --- ## 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. Implement HTTP client in `fetchPublicKeyFromHomeServer()` 2. Add WebSocket for real-time 3. Add profile sync (client pushes to all servers on change) 4. Test cross-server join flow 5. Add proper error handling and logging