Refactor chat system to remove E2EE and simplify messaging

Removed all E2EE chat endpoints, crypto logic, and related API routes, transitioning chat to plain text storage and transport. Updated chat send/receive endpoints to use signed actions and enforce DM privacy settings. Cleaned up Swarm chat inbox, key management, and related debug endpoints. Updated user profile to support DM privacy, improved search for handle queries, and refactored conversation/message logic to support the new model. Migrated bot settings and privacy settings to new locations.
This commit is contained in:
Christopher
2026-01-28 13:05:34 -08:00
parent b52b3f9804
commit ceae76d58f
44 changed files with 1945 additions and 3058 deletions
+3 -110
View File
@@ -43,9 +43,6 @@ export const users = pgTable('users', {
headerUrl: text('header_url'),
privateKeyEncrypted: text('private_key_encrypted'), // For cryptographic signing
publicKey: text('public_key').notNull(),
// E2E Chat keys (ECDH) - separate from signing keys
chatPublicKey: text('chat_public_key'), // ECDH public key for chat encryption
chatPrivateKeyEncrypted: text('chat_private_key_encrypted'), // Encrypted with user's password
nodeId: uuid('node_id').references(() => nodes.id),
// Bot-related fields
isBot: boolean('is_bot').default(false).notNull(),
@@ -69,6 +66,7 @@ export const users = pgTable('users', {
followingCount: integer('following_count').default(0).notNull(),
postsCount: integer('posts_count').default(0).notNull(),
website: text('website'),
dmPrivacy: text('dm_privacy').default('everyone').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (table) => [
@@ -927,12 +925,8 @@ export const chatMessages = pgTable('chat_messages', {
senderNodeDomain: text('sender_node_domain'), // null if local
senderDid: text('sender_did'), // DID for Signal Protocol
// Message content (encrypted for recipient with their public key)
encryptedContent: text('encrypted_content').notNull(),
// Sender's copy (encrypted with sender's public key so they can read their own messages)
senderEncryptedContent: text('sender_encrypted_content'),
// Sender's ECDH public key (for E2E decryption by recipient)
senderChatPublicKey: text('sender_chat_public_key'),
// Message content (plain text for verified chat)
content: text('content'),
// Swarm sync info
swarmMessageId: text('swarm_message_id').unique(), // Format: swarm:domain:uuid
@@ -956,108 +950,7 @@ export const chatMessagesRelations = relations(chatMessages, ({ one }) => ({
}),
}));
/**
* V2.1 E2EE: Device Bundles
* Stores the identity and signed prekeys for each user device.
* Verified by the root ECDSA identity key (signature).
*/
export const chatDeviceBundles = pgTable('chat_device_bundles', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
did: text('did').notNull(),
// The device identifier (UUID generated by client)
deviceId: text('device_id').notNull(),
// Signal Protocol fields
registrationId: integer('registration_id'),
// X25519 Identity Key (Base64)
identityKey: text('identity_key').notNull(),
// Signed PreKey (JSON: { id, key, sig })
signedPreKey: text('signed_pre_key').notNull(),
// Kyber PreKey for post-quantum security (JSON: { id, key, sig })
kyberPreKey: text('kyber_pre_key'),
// One-Time Keys (JSON array of { id, key }) - cached/uploaded batch
// Note: Individual keys are usually stored in a separate table for atomic consumption,
// but initial upload can be here or we strictly use chat_one_time_keys.
// We will use the separate table for consumption tracking.
// The ECDSA signature covering (did, deviceId, identityKey, signedPreKey)
// Signed by the user's Root Identity Key (P-256)
signature: text('signature').notNull(),
lastSeenAt: timestamp('last_seen_at').defaultNow().notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
}, (table) => [
index('chat_bundles_user_idx').on(table.userId),
index('chat_bundles_did_idx').on(table.did),
// compound index for fast lookup of a specific device
uniqueIndex('chat_bundles_device_unique').on(table.userId, table.deviceId),
]);
export const chatDeviceBundlesRelations = relations(chatDeviceBundles, ({ one, many }) => ({
user: one(users, {
fields: [chatDeviceBundles.userId],
references: [users.id],
}),
oneTimeKeys: many(chatOneTimeKeys),
}));
/**
* V2.1 E2EE: One-Time Prekeys
* Pool of disposable keys for X3DH.
*/
export const chatOneTimeKeys = pgTable('chat_one_time_keys', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
bundleId: uuid('bundle_id').notNull().references(() => chatDeviceBundles.id, { onDelete: 'cascade' }),
keyId: integer('key_id').notNull(),
publicKey: text('public_key').notNull(), // X25519 Base64
createdAt: timestamp('created_at').defaultNow().notNull(),
}, (table) => [
index('chat_otk_bundle_idx').on(table.bundleId),
// Ensure (bundleId, keyId) is unique
uniqueIndex('chat_otk_unique').on(table.bundleId, table.keyId),
]);
export const chatOneTimeKeysRelations = relations(chatOneTimeKeys, ({ one }) => ({
bundle: one(chatDeviceBundles, {
fields: [chatOneTimeKeys.bundleId],
references: [chatDeviceBundles.id],
}),
}));
/**
* V2.1 E2EE: Chat Inbox
* Stores encrypted envelopes waiting for delivery to a specific device.
*/
export const chatInbox = pgTable('chat_inbox', {
id: uuid('id').primaryKey().defaultRandom(),
// Recipient info
recipientDid: text('recipient_did').notNull(),
recipientDeviceId: text('recipient_device_id'), // Null means "all devices" or "not yet routed"
// Sender info
senderDid: text('sender_did').notNull(),
// The Envelope (SignedAction format)
// Contains: { action: "chat.deliver", did, handle, ts, nonce, data: { ciphertext, header... }, sig }
envelope: text('envelope_json').notNull(),
isRead: boolean('is_read').default(false).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
expiresAt: timestamp('expires_at').notNull(), // TTL (e.g. 30 days)
}, (table) => [
index('chat_inbox_recipient_idx').on(table.recipientDid, table.recipientDeviceId),
index('chat_inbox_created_idx').on(table.createdAt),
]);
/**
* Typing indicators for real-time chat UX.