import { create } from "zustand"; import { NDKUser } from "@nostr-dev-kit/ndk"; import * as SecureStore from "expo-secure-store"; import { AuthState, AuthActions, AuthMethod, SigningOperation } from "./types"; import { SECURE_STORE_KEYS } from "./constants"; const PRIVATE_KEY_STORAGE_KEY = SECURE_STORE_KEYS.PRIVATE_KEY; // Use the constant from constants.ts /** * Zustand store that manages the authentication state * Acts as a state machine to ensure consistent transitions */ export const useAuthStore = create((set, get) => ({ status: 'unauthenticated', /** * Sets the state to authenticating with the specified method */ setAuthenticating: (method) => { console.log(`[Auth] Setting state to authenticating with method: ${method}`); set({ status: 'authenticating', method }); }, /** * Sets the state to authenticated with the specified user and method */ setAuthenticated: (user, method) => { console.log(`[Auth] Setting state to authenticated for user: ${user.npub}`); set({ status: 'authenticated', user, method, }); }, /** * Manages transitions to and from the signing state * When inProgress is true, adds an operation to the signing state * When inProgress is false, removes an operation from the signing state */ setSigningInProgress: (inProgress, operation) => { const currentState = get(); if (inProgress) { // Handle transition to signing state if (currentState.status === 'signing') { // Already in signing state, update the operations list and count console.log(`[Auth] Adding operation to signing state (total: ${currentState.operationCount + 1})`); set({ operationCount: currentState.operationCount + 1, operations: [...currentState.operations, operation] }); } else if (currentState.status === 'authenticated') { // Transition from authenticated to signing console.log(`[Auth] Transitioning from authenticated to signing state`); set({ status: 'signing', user: currentState.user, method: currentState.method, operationCount: 1, operations: [operation] }); } else { // Invalid state transition - can only sign when authenticated console.error(`[Auth] Cannot sign: not authenticated (current state: ${currentState.status})`); set({ status: 'error', error: new Error(`Cannot sign: not in authenticated state (current: ${currentState.status})`), previousState: currentState }); } } else { // Handle transition from signing state if (currentState.status === 'signing') { // Remove the completed operation const updatedOperations = currentState.operations.filter( op => op !== operation ); if (updatedOperations.length === 0) { // No more operations, return to authenticated state console.log(`[Auth] All operations complete, returning to authenticated state`); set({ status: 'authenticated', user: currentState.user, method: currentState.method }); } else { // Still have pending operations console.log(`[Auth] Operation complete, ${updatedOperations.length} operations remain`); set({ operations: updatedOperations, operationCount: updatedOperations.length }); } } // If not in signing state, this is a no-op } }, /** * Performs a secure logout, clearing all auth state and secure storage */ logout: async () => { try { console.log(`[Auth] Logging out user`); // Cancel any pending operations const currentState = get(); if (currentState.status === 'signing') { console.log(`[Auth] Canceling ${currentState.operations.length} pending signing operations`); // Reject any pending operations with cancellation error currentState.operations.forEach(operation => { operation.reject(new Error('Authentication session terminated')); }); } // Securely clear all sensitive data from storage const keysToDelete = [ PRIVATE_KEY_STORAGE_KEY, 'nostr_privkey', // Original key name from ndk store 'nostr_external_signer' // External signer info ]; // Delete all secure keys await Promise.all( keysToDelete.map(key => SecureStore.deleteItemAsync(key)) ); // Reset state to unauthenticated set({ status: 'unauthenticated' }); // Log the logout event (without PII) console.info('[Auth] User logged out successfully'); return true; } catch (error) { console.error('[Auth] Error during logout:', error); return false; } }, /** * Sets the state to error with the specified error */ setError: (error) => { console.error(`[Auth] Error: ${error.message}`); const currentState = get(); set({ status: 'error', error, previousState: currentState }); } })); /** * Singleton for easier access to the auth store from non-React contexts */ export const AuthStateManager = { getState: useAuthStore.getState, setState: useAuthStore, setAuthenticating: useAuthStore.getState().setAuthenticating, setAuthenticated: useAuthStore.getState().setAuthenticated, setSigningInProgress: useAuthStore.getState().setSigningInProgress, logout: useAuthStore.getState().logout, setError: useAuthStore.getState().setError };