mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 09:11:12 -05:00
Refactor: Implement full authentication overhaul
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user