Refactor: Implement full authentication overhaul

This commit is contained in:
gpt-engineer-app[bot]
2025-10-14 14:01:17 +00:00
parent ccfa83faee
commit 23f7cbb9de
8 changed files with 525 additions and 122 deletions

View File

@@ -4,17 +4,14 @@ import { supabase } from '@/integrations/supabase/client';
import type { Profile } from '@/types/database';
import { toast } from '@/hooks/use-toast';
import { authLog, authWarn, authError } from '@/lib/authLogger';
export interface CheckAalResult {
needsStepUp: boolean;
hasMfaEnrolled: boolean;
currentLevel: 'aal1' | 'aal2' | null;
}
import type { AALLevel, CheckAalResult } from '@/types/auth';
import { getSessionAal, checkAalStepUp as checkAalStepUpService, signOutUser } from '@/lib/authService';
import { clearAllAuthFlags } from '@/lib/sessionFlags';
interface AuthContextType {
user: User | null;
session: Session | null;
aal: 'aal1' | 'aal2' | null;
aal: AALLevel | null;
loading: boolean;
pendingEmail: string | null;
sessionError: string | null;
@@ -29,7 +26,7 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
function AuthProviderComponent({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [session, setSession] = useState<Session | null>(null);
const [aal, setAal] = useState<'aal1' | 'aal2' | null>(null);
const [aal, setAal] = useState<AALLevel | null>(null);
const [loading, setLoading] = useState(true);
const [pendingEmail, setPendingEmail] = useState<string | null>(null);
const [sessionError, setSessionError] = useState<string | null>(null);
@@ -88,30 +85,13 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
// Clear any error
setSessionError(null);
// Update session and user state based on event
if (event === 'SIGNED_IN' && session) {
authLog('[Auth] SIGNED_IN - user authenticated');
setSession(session);
setUser(session.user);
const userAal = (session.user as any).aal as 'aal1' | 'aal2' | undefined;
setAal(userAal || 'aal1');
// Synchronous state updates only
setSession(session);
setUser(session?.user ?? null);
// Handle loading state
if (event === 'SIGNED_IN' || event === 'INITIAL_SESSION') {
setLoading(false);
} else if (event === 'INITIAL_SESSION') {
if (session?.user) {
authLog('[Auth] INITIAL_SESSION - user exists');
setSession(session);
setUser(session.user);
const userAal = (session.user as any).aal as 'aal1' | 'aal2' | undefined;
setAal(userAal || 'aal1');
setLoading(false);
} else {
authLog('[Auth] INITIAL_SESSION - no user');
setSession(null);
setUser(null);
setAal(null);
setLoading(false);
return;
}
} else if (event === 'SIGNED_OUT') {
authLog('[Auth] SIGNED_OUT - clearing state');
setSession(null);
@@ -119,13 +99,20 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
setAal(null);
setLoading(false);
return;
} else {
setSession(session);
setUser(session?.user ?? null);
const userAal = session?.user ? ((session.user as any).aal as 'aal1' | 'aal2' | undefined) : null;
setAal(userAal || null);
}
// Defer async operations to avoid blocking the auth state change callback
setTimeout(async () => {
// Get AAL level from Supabase API (ground truth, not cached session data)
if (session) {
const currentAal = await getSessionAal(session);
setAal(currentAal);
authLog('[Auth] Current AAL:', currentAal);
} else {
setAal(null);
}
}, 0);
// Detect confirmed email change: email changed AND no longer pending
if (
session?.user &&
@@ -217,10 +204,11 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
}, []);
const signOut = async () => {
const { error } = await supabase.auth.signOut();
if (error) {
authError('Error signing out:', error);
throw error;
authLog('[Auth] Signing out...');
const result = await signOutUser();
if (!result.success) {
authError('Error signing out:', result.error);
throw new Error(result.error);
}
};
@@ -229,26 +217,7 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
};
const checkAalStepUp = async (): Promise<CheckAalResult> => {
if (!session?.user) {
return { needsStepUp: false, hasMfaEnrolled: false, currentLevel: null };
}
try {
const { data: { currentLevel } } =
await supabase.auth.mfa.getAuthenticatorAssuranceLevel();
const { data: factors } = await supabase.auth.mfa.listFactors();
const hasMfaEnrolled = factors?.totp?.some(f => f.status === 'verified') || false;
return {
needsStepUp: hasMfaEnrolled && currentLevel === 'aal1',
hasMfaEnrolled,
currentLevel: currentLevel as 'aal1' | 'aal2' | null,
};
} catch (error) {
authError('[Auth] Failed to check AAL status:', error);
return { needsStepUp: false, hasMfaEnrolled: false, currentLevel: null };
}
return checkAalStepUpService(session);
};
const value = {