From fb2c80b0e3259ba9ed78af9831fab3970163c282 Mon Sep 17 00:00:00 2001 From: Christomatt Date: Tue, 27 Jan 2026 02:12:43 +0100 Subject: [PATCH] feat(chat): Validate and cache remote user public keys for encryption - Add validation to check if cached users have valid public keys (PEM format) - Fetch remote public keys when local cache has placeholder/invalid keys - Update existing cached users with real public keys instead of creating duplicates - Include public key in user profile API response for E2E encrypted chat support - Improve error handling to reject recipients without valid encryption keys - Ensure remote user data is properly extracted from API responses This prevents sending encrypted messages to users with placeholder keys and ensures the chat system always has access to valid public keys for encryption operations. --- src/app/api/swarm/chat/send/route.ts | 45 +++++++++++++++++++--------- src/app/api/users/[handle]/route.ts | 1 + 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/app/api/swarm/chat/send/route.ts b/src/app/api/swarm/chat/send/route.ts index bf13de4..1738139 100644 --- a/src/app/api/swarm/chat/send/route.ts +++ b/src/app/api/swarm/chat/send/route.ts @@ -82,8 +82,11 @@ export async function POST(request: NextRequest) { where: eq(users.handle, recipientHandle), }); - if (!recipientUser) { - // Fetch from remote node + // Check if we have a valid public key (not a placeholder) + const hasValidPublicKey = recipientUser?.publicKey?.startsWith('-----BEGIN'); + + if (!recipientUser || !hasValidPublicKey) { + // Fetch from remote node to get the real public key try { console.log('[Chat Send] Fetching remote user from node:', domain); const protocol = domain.includes('localhost') ? 'http' : 'https'; @@ -95,25 +98,39 @@ export async function POST(request: NextRequest) { } const remoteUserData = await response.json(); - recipientPublicKey = remoteUserData.publicKey; + const userData = remoteUserData.user || remoteUserData; + recipientPublicKey = userData.publicKey; + + if (!recipientPublicKey || !recipientPublicKey.startsWith('-----BEGIN')) { + console.error('[Chat Send] Remote user has no valid public key'); + return NextResponse.json({ error: 'Recipient does not support encrypted chat' }, { status: 400 }); + } - // Cache the remote user - const [newUser] = await db.insert(users).values({ - did: remoteUserData.did || `did:swarm:${domain}:${handle}`, - handle: recipientHandle, - displayName: remoteUserData.displayName, - avatarUrl: remoteUserData.avatarUrl, - publicKey: recipientPublicKey, - }).returning(); - recipientUser = newUser; - console.log('[Chat Send] Remote user cached'); + if (recipientUser) { + // Update existing cached user with real public key + await db.update(users) + .set({ publicKey: recipientPublicKey }) + .where(eq(users.id, recipientUser.id)); + console.log('[Chat Send] Updated cached user with real public key'); + } else { + // Cache the remote user + const [newUser] = await db.insert(users).values({ + did: userData.did || `did:swarm:${domain}:${handle}`, + handle: recipientHandle, + displayName: userData.displayName, + avatarUrl: userData.avatarUrl, + publicKey: recipientPublicKey, + }).returning(); + recipientUser = newUser; + console.log('[Chat Send] Remote user cached'); + } } catch (error) { console.error('[Chat Send] Failed to fetch remote user:', error); return NextResponse.json({ error: 'Failed to reach recipient node' }, { status: 503 }); } } else { recipientPublicKey = recipientUser.publicKey; - console.log('[Chat Send] Remote user found in cache'); + console.log('[Chat Send] Remote user found in cache with valid key'); } } else { // Local user diff --git a/src/app/api/users/[handle]/route.ts b/src/app/api/users/[handle]/route.ts index 27164a0..8aa2c64 100644 --- a/src/app/api/users/[handle]/route.ts +++ b/src/app/api/users/[handle]/route.ts @@ -94,6 +94,7 @@ export async function GET(request: Request, context: RouteContext) { website: user.website, movedTo: user.movedTo, isBot: user.isBot, + publicKey: user.publicKey, // Needed for E2E encrypted chat }; // If this is a bot, include owner info