297 lines
9.8 KiB
Markdown
297 lines
9.8 KiB
Markdown
# 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<String> 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
|