diff --git a/src/app/api/swarm/users/[handle]/route.ts b/src/app/api/swarm/users/[handle]/route.ts index 171f220..139d845 100644 --- a/src/app/api/swarm/users/[handle]/route.ts +++ b/src/app/api/swarm/users/[handle]/route.ts @@ -23,6 +23,7 @@ export interface SwarmUserProfile { botOwnerHandle?: string; // Handle of the bot's owner (e.g., "user" or "user@domain") nodeDomain: string; chatPublicKey?: string; + did?: string; } export interface SwarmUserPost { @@ -93,6 +94,7 @@ export async function GET(request: NextRequest, context: RouteContext) { botOwnerHandle: user.isBot && user.botOwner ? user.botOwner.handle : undefined, nodeDomain, chatPublicKey: user.chatPublicKey || undefined, + did: user.did || undefined, }; // Get user's recent posts diff --git a/src/app/api/users/[handle]/route.ts b/src/app/api/users/[handle]/route.ts index 5d65e0c..5d9f611 100644 --- a/src/app/api/users/[handle]/route.ts +++ b/src/app/api/users/[handle]/route.ts @@ -63,6 +63,7 @@ export async function GET(request: Request, context: RouteContext) { nodeDomain: remoteDomain, isBot: profile.isBot || false, chatPublicKey: profile.chatPublicKey, + did: profile.did, } }); } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 016a62d..c3e9ea8 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -2,10 +2,12 @@ import { useState, useEffect, useRef } from 'react'; import Link from 'next/link'; +import { useRouter } from 'next/navigation'; 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'; +import { useAuth } from '@/lib/contexts/AuthContext'; declare global { interface Window { @@ -23,6 +25,7 @@ declare global { } export default function LoginPage() { + const router = useRouter(); const [mode, setMode] = useState<'login' | 'register' | 'import'>('login'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); @@ -40,7 +43,8 @@ export default function LoginPage() { const turnstileRef = useRef(null); const turnstileWidgetId = useRef(null); - // Import specific state + const { unlockIdentity, login } = useAuth(); + const [importFile, setImportFile] = useState(null); const [importPassword, setImportPassword] = useState(''); const [importHandle, setImportHandle] = useState(''); @@ -191,9 +195,9 @@ export default function LoginPage() { } setImportSuccess(data.message); - // Hard redirect to ensure cookie is picked up + // Soft navigation to preserve AuthContext/KeyStore state setTimeout(() => { - window.location.href = '/'; + router.push('/'); }, 2000); } catch (err) { @@ -281,10 +285,27 @@ export default function LoginPage() { // Don't block login/registration if decryption fails - user can unlock later // The identity unlock prompt will be shown in the app } + } else { + if (process.env.NODE_ENV === 'development') console.log('[Auth] No encrypted private key returned from server'); } - // Hard redirect to ensure cookie is picked up - window.location.href = '/'; + // Sync with global auth state if we have a key (or even if we don't, to trigger load) + // But unlockIdentity specifically needs the key. + // If data.user.privateKeyEncrypted is present, we try to unlock globally. + if (data.user?.privateKeyEncrypted) { + try { + // Update AuthContext first so it has the user and key + login(data.user); + + // Now unlock (passing user explicitly to avoid async state delay) + await unlockIdentity(password, data.user); + } catch (e) { + console.error("Failed to auto-unlock identity:", e); + } + } + + // Soft navigation to preserve AuthContext/KeyStore state + router.push('/'); } catch (err) { setError(err instanceof Error ? err.message : 'An error occurred'); // Reset Turnstile on error diff --git a/src/lib/contexts/AuthContext.tsx b/src/lib/contexts/AuthContext.tsx index abaa529..c639070 100644 --- a/src/lib/contexts/AuthContext.tsx +++ b/src/lib/contexts/AuthContext.tsx @@ -22,7 +22,8 @@ interface AuthContextType { did: string | null; handle: string | null; checkAdmin: () => Promise; - unlockIdentity: (password: string) => Promise; + unlockIdentity: (password: string, explicitUser?: User) => Promise; + login: (user: User) => void; logout: () => Promise; showUnlockPrompt: boolean; setShowUnlockPrompt: (show: boolean) => void; @@ -37,6 +38,7 @@ const AuthContext = createContext({ handle: null, checkAdmin: async () => { }, unlockIdentity: async () => { }, + login: () => { }, logout: async () => { }, showUnlockPrompt: false, setShowUnlockPrompt: () => { }, @@ -74,16 +76,18 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { /** * Unlock the user's identity with their password */ - const unlockIdentity = async (password: string) => { - if (!user?.privateKeyEncrypted) { + const unlockIdentity = async (password: string, explicitUser?: User) => { + const targetUser = explicitUser || user; + + if (!targetUser?.privateKeyEncrypted) { throw new Error('No encrypted private key available'); } - await unlockIdentityHook(user.privateKeyEncrypted, password); + await unlockIdentityHook(targetUser.privateKeyEncrypted, password); // Initialize Chat Keys (Async, don't block UI but start it) - if (user.id) { - ensureReady(password, user.id).catch(err => { + if (targetUser.id) { + ensureReady(password, targetUser.id).catch(err => { console.error('Failed to initialize chat keys:', err); }); } @@ -91,6 +95,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { setShowUnlockPrompt(false); // Close prompt on success }; + /** + * Manually set the user state (called after successful login) + */ + const login = (userData: User) => { + setUser(userData); + // We re-check admin status just in case + checkAdmin(); + }; + /** * Logout the user and clear their identity */ @@ -159,6 +172,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { handle: identity?.handle || null, checkAdmin, unlockIdentity, + login, logout, showUnlockPrompt, setShowUnlockPrompt, diff --git a/src/lib/swarm/interactions.ts b/src/lib/swarm/interactions.ts index 8d8fc23..21279ec 100644 --- a/src/lib/swarm/interactions.ts +++ b/src/lib/swarm/interactions.ts @@ -271,7 +271,7 @@ async function deliverSwarmInteraction( // SECURITY: Sign the payload with the node's private key const { signPayload, getNodePrivateKey } = await import('./signature'); const privateKey = await getNodePrivateKey(); - + const signature = signPayload(payload, privateKey); const signedPayload = { ...payload as object, signature }; @@ -331,6 +331,7 @@ export interface SwarmUserProfile { botOwnerHandle?: string; // Handle of the bot's owner (e.g., "user" or "user@domain") nodeDomain: string; chatPublicKey?: string; + did?: string; } export interface SwarmUserPost {