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:
Z User committed 2026-02-19 23:29:52 +00:00
commit 6a8977c4c3
14 files changed
+1494

No files matched your search

+126
View File
@@ -0,0 +1,126 @@
import 'package:objectbox/objectbox.dart';
/// User entity - works for both HOME and GUEST servers
///
/// On HOME server:
/// - isLocal = true
/// - stores password, encrypted private key
/// - this is where identity lives
///
/// On GUEST server:
/// - isLocal = false
/// - homeServer contains the URL of user's home
/// - publicKey cached for verification
@Entity()
class User {
@Id()
int id = 0;
// Global identifier: "username@homeserver.com"
@Unique()
String userId;
// Basic profile
@Index()
String username;
String? displayName;
String? avatarUrl;
String? bio;
// Identity
String publicKey; // Ed25519 public key, always stored
// HOME SERVER ONLY: Auth & key backup
String? passwordHash;
String? encryptedPrivateKey; // Encrypted with user's password
String? keyPairSalt;
// Server relationship
bool isLocal; // true = this is their home server
String? homeServer; // if guest, where is their home? (e.g., "https://my.server.com")
// Server-specific
List<String> roles; // ["admin", "moderator", "member"]
@Property(type: PropertyType.date)
DateTime createdAt;
@Property(type: PropertyType.date)
DateTime? lastSyncedAt; // When profile was last refreshed from home
User({
required this.userId,
required this.username,
required this.publicKey,
required this.isLocal,
this.displayName,
this.avatarUrl,
this.bio,
this.passwordHash,
this.encryptedPrivateKey,
this.keyPairSalt,
this.homeServer,
List<String>? roles,
DateTime? createdAt,
DateTime? lastSyncedAt,
}) : roles = roles ?? ['member'],
createdAt = createdAt ?? DateTime.now(),
lastSyncedAt = lastSyncedAt;
}
@Entity()
class Channel {
@Id()
int id = 0;
@Unique()
String name;
String? description;
@Property(type: PropertyType.date)
DateTime createdAt;
Channel({
required this.name,
this.description,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
}
@Entity()
class Message {
@Id()
int id = 0;
int channelId;
// Author info - includes enough for cross-server profile fetch
int authorLocalId;
String authorUserId; // "alice@homeserver.com"
String? authorHomeServer; // for profile fetching
// Cached for display (snapshot at message time)
String authorUsername;
String? authorAvatarUrl;
String content;
@Property(type: PropertyType.date)
DateTime createdAt;
@Property(type: PropertyType.date)
DateTime? editedAt;
Message({
required this.channelId,
required this.authorLocalId,
required this.authorUserId,
required this.authorUsername,
required this.content,
this.authorHomeServer,
this.authorAvatarUrl,
DateTime? createdAt,
this.editedAt,
}) : createdAt = createdAt ?? DateTime.now();
}