feat: Initial MVP with home server identity model
- User model with Ed25519 keypair identity - Register/login on home server (creates keypair) - Join guest servers with signature verification - Public endpoints for cross-server identity - Channel and message CRUD - JWT authentication middleware Architecture: Home server model, no federation, client-driven sync
This commit is contained in:
commit
6a8977c4c3
14 files changed
+1494
No files matched your search
+248
@@ -0,0 +1,248 @@
|
||||
# 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<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;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
Reference in new issue
Block a user