mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-22 16:51:33 +00:00

- Standardize secure storage keys across auth systems - Fix inconsistent key naming in NDK store and auth providers - Implement proper credential migration between storage systems - Enhance error handling during credential restoration - Fix private key authentication not persisting across app restarts - Add detailed logging for auth initialization sequence - Improve overall authentication stability with better state management
179 lines
5.6 KiB
TypeScript
179 lines
5.6 KiB
TypeScript
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<AuthState & AuthActions>((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
|
|
};
|