Add encrypted key support for chat and nodes

Introduces encrypted private key storage for nodes and users, updates chat message schema to support sender-side encryption, and adds supporting libraries and tests for cryptographic signing and identity unlock flows. Includes new database migrations, API route updates, and React components for identity unlock prompts.
This commit is contained in:
Christopher
2026-01-27 17:13:28 -08:00
parent 25f71e320b
commit 5903022f8a
46 changed files with 7457 additions and 113 deletions
+40 -10
View File
@@ -4,6 +4,8 @@ import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { TriangleAlert } from 'lucide-react';
import { decryptPrivateKey } from '@/lib/crypto/private-key-client';
import { keyStore, importPrivateKey } from '@/lib/crypto/user-signing';
declare global {
interface Window {
@@ -225,21 +227,21 @@ export default function LoginPage() {
try {
const endpoint = mode === 'login' ? '/api/auth/login' : '/api/auth/register';
// Only include turnstileToken if Turnstile is enabled (site key exists)
const body = mode === 'login'
? {
email,
password,
? {
email,
password,
...(nodeInfo.turnstileSiteKey ? { turnstileToken } : {})
}
: {
email,
password,
handle,
}
: {
email,
password,
handle,
displayName,
...(nodeInfo.turnstileSiteKey ? { turnstileToken } : {})
};
};
const res = await fetch(endpoint, {
method: 'POST',
@@ -253,6 +255,34 @@ export default function LoginPage() {
throw new Error(data.error || 'Authentication failed');
}
// Decrypt and store private key if available
if (data.user?.privateKeyEncrypted) {
try {
const privateKeyDecrypted = await decryptPrivateKey(
data.user.privateKeyEncrypted,
password
);
// Import and set in memory store
// Remove PEM headers if present and clean whitespace
let cleanKey = privateKeyDecrypted
.replace(/-----BEGIN [A-Z ]+-----/, '')
.replace(/-----END [A-Z ]+-----/, '')
.replace(/\s/g, '');
const binaryDer = Buffer.from(cleanKey, 'base64');
const cryptoKey = await importPrivateKey(binaryDer);
keyStore.setPrivateKey(cryptoKey);
console.log('[Auth] Private key decrypted and stored successfully');
} catch (decryptError) {
console.error('[Auth] Failed to decrypt private key:', decryptError);
// Don't block login/registration if decryption fails - user can unlock later
// The identity unlock prompt will be shown in the app
}
}
// Hard redirect to ensure cookie is picked up
window.location.href = '/';
} catch (err) {