Add DID support and improve login state handling
Introduces a 'did' field to SwarmUserProfile and related API routes to support decentralized identifiers. Refactors login flow to use soft navigation and synchronize AuthContext state, including new 'login' and enhanced 'unlockIdentity' methods for better user state management.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -63,6 +63,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
nodeDomain: remoteDomain,
|
||||
isBot: profile.isBot || false,
|
||||
chatPublicKey: profile.chatPublicKey,
|
||||
did: profile.did,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+26
-5
@@ -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<HTMLDivElement>(null);
|
||||
const turnstileWidgetId = useRef<string | null>(null);
|
||||
|
||||
// Import specific state
|
||||
const { unlockIdentity, login } = useAuth();
|
||||
|
||||
const [importFile, setImportFile] = useState<File | null>(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
|
||||
|
||||
@@ -22,7 +22,8 @@ interface AuthContextType {
|
||||
did: string | null;
|
||||
handle: string | null;
|
||||
checkAdmin: () => Promise<void>;
|
||||
unlockIdentity: (password: string) => Promise<void>;
|
||||
unlockIdentity: (password: string, explicitUser?: User) => Promise<void>;
|
||||
login: (user: User) => void;
|
||||
logout: () => Promise<void>;
|
||||
showUnlockPrompt: boolean;
|
||||
setShowUnlockPrompt: (show: boolean) => void;
|
||||
@@ -37,6 +38,7 @@ const AuthContext = createContext<AuthContextType>({
|
||||
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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user