mirror of https://github.com/kortix-ai/suna.git
refactor(auth): remove AALChecker and AALStatusDisplay components
This commit is contained in:
parent
0765c728ae
commit
7010de6c2c
|
@ -1,144 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useGetAAL } from '@/hooks/react-query/phone-verification';
|
||||
import { useAuth } from '@/components/AuthProvider';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
interface AALCheckerProps {
|
||||
children: React.ReactNode;
|
||||
redirectTo?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* AALChecker component that validates MFA requirements after authentication.
|
||||
*
|
||||
* This component follows the standard Supabase AAL flow AND enforces phone verification
|
||||
* requirements for new users (created after cutoff date):
|
||||
*
|
||||
* For new users:
|
||||
* - If no MFA enrolled: Force phone verification enrollment
|
||||
* - If MFA enrolled but not verified: Redirect to verification
|
||||
* - If MFA verified: Allow access
|
||||
*
|
||||
* For existing users (grandfathered):
|
||||
* - Follow standard AAL flow without forcing enrollment
|
||||
* - aal1 -> aal1: Allow access (optional MFA)
|
||||
* - aal1 -> aal2: Redirect to verification
|
||||
* - aal2 -> aal2: Allow access
|
||||
* - aal2 -> aal1: Force reauthentication
|
||||
*/
|
||||
export function AALChecker({ children, redirectTo = '/auth/phone-verification' }: AALCheckerProps) {
|
||||
const { user, isLoading: authLoading } = useAuth();
|
||||
const { data: aalData, isLoading: aalLoading, error: aalError } = useGetAAL();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
// Only check AAL if user is authenticated and AAL data is available
|
||||
if (!authLoading && user && aalData && !aalLoading) {
|
||||
const { action_required, current_level, next_level, verification_required } = aalData;
|
||||
|
||||
console.log('AAL Check:', {
|
||||
action_required,
|
||||
current_level,
|
||||
next_level,
|
||||
phone_verification_required: verification_required,
|
||||
message: aalData.message
|
||||
});
|
||||
|
||||
// Handle new users who need phone verification
|
||||
if (verification_required) {
|
||||
if (current_level === "aal1" && next_level === "aal1") {
|
||||
// New user has no MFA enrolled - force enrollment
|
||||
console.log('New user without MFA enrolled, redirecting to phone verification:', redirectTo);
|
||||
router.push(redirectTo);
|
||||
return;
|
||||
}
|
||||
// If new user has MFA enrolled, follow standard AAL flow below
|
||||
}
|
||||
|
||||
// Standard AAL flow (for all users)
|
||||
switch (action_required) {
|
||||
case 'verify_mfa':
|
||||
// User has MFA enrolled but needs to verify it
|
||||
console.log('Redirecting to MFA verification:', redirectTo);
|
||||
router.push(redirectTo);
|
||||
break;
|
||||
|
||||
case 'reauthenticate':
|
||||
// User has stale JWT due to MFA changes, force reauthentication
|
||||
console.log('MFA state changed, forcing reauthentication');
|
||||
router.push('/auth?message=Please sign in again due to security changes');
|
||||
break;
|
||||
|
||||
case 'none':
|
||||
// No action required, user can proceed
|
||||
console.log('AAL check passed, no action required');
|
||||
break;
|
||||
|
||||
case 'unknown':
|
||||
default:
|
||||
// Unknown AAL state, log and allow access (fail open)
|
||||
console.warn('Unknown AAL state:', { current_level, next_level, action_required });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [user, authLoading, aalData, aalLoading, router, redirectTo]);
|
||||
|
||||
// Show loading while checking authentication or AAL status
|
||||
if (authLoading || (user && aalLoading)) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">Checking authentication...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If not authenticated, don't render children (let auth redirect handle it)
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If AAL check failed, allow access (fail open for UX)
|
||||
if (aalError) {
|
||||
console.error('AAL check failed:', aalError);
|
||||
}
|
||||
|
||||
// Check if new user needs phone verification enrollment
|
||||
if (aalData?.verification_required &&
|
||||
aalData?.current_level === "aal1" &&
|
||||
aalData?.next_level === "aal1") {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">Setting up required verification...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If AAL check indicates MFA verification is needed, don't render children
|
||||
// (the useEffect above will handle the redirect)
|
||||
if (aalData?.action_required === 'verify_mfa') {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">Redirecting to verification...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If AAL check indicates reauthentication is needed, don't render children
|
||||
if (aalData?.action_required === 'reauthenticate') {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">Redirecting to sign in...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// AAL check passed or no action required, render children
|
||||
return <>{children}</>;
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useGetAAL } from '@/hooks/react-query/phone-verification';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Loader2, Shield, ShieldAlert, ShieldCheck, ShieldX, Phone } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* AALStatusDisplay component shows the current AAL status and what action is required.
|
||||
* Useful for debugging and understanding the MFA flow.
|
||||
*/
|
||||
export function AALStatusDisplay() {
|
||||
const { data: aalData, isLoading: aalLoading, error: aalError } = useGetAAL();
|
||||
|
||||
if (aalLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Shield className="w-5 h-5" />
|
||||
MFA Status
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
<span>Checking MFA status...</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (aalError) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<ShieldX className="w-5 h-5 text-red-500" />
|
||||
MFA Status Error
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>
|
||||
Failed to check MFA status: {aalError instanceof Error ? aalError?.message : 'Unknown error'}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!aalData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getStatusIcon = () => {
|
||||
// Check if new user needs phone verification enrollment
|
||||
if (aalData?.phone_verification_required &&
|
||||
aalData.current_level === "aal1" &&
|
||||
aalData.next_level === "aal1") {
|
||||
return <Phone className="w-5 h-5 text-orange-500" />;
|
||||
}
|
||||
|
||||
switch (aalData?.action_required) {
|
||||
case 'none':
|
||||
return <ShieldCheck className="w-5 h-5 text-green-500" />;
|
||||
case 'verify_mfa':
|
||||
return <ShieldAlert className="w-5 h-5 text-yellow-500" />;
|
||||
case 'reauthenticate':
|
||||
return <ShieldX className="w-5 h-5 text-red-500" />;
|
||||
default:
|
||||
return <Shield className="w-5 h-5 text-gray-500" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = () => {
|
||||
// Check if new user needs phone verification enrollment
|
||||
if (aalData?.phone_verification_required &&
|
||||
aalData.current_level === "aal1" &&
|
||||
aalData.next_level === "aal1") {
|
||||
return 'bg-orange-100 text-orange-800';
|
||||
}
|
||||
|
||||
switch (aalData?.action_required) {
|
||||
case 'none':
|
||||
return 'bg-green-100 text-green-800';
|
||||
case 'verify_mfa':
|
||||
return 'bg-yellow-100 text-yellow-800';
|
||||
case 'reauthenticate':
|
||||
return 'bg-red-100 text-red-800';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
};
|
||||
|
||||
const getActionDescription = () => {
|
||||
const current = aalData?.current_level;
|
||||
const next = aalData?.next_level;
|
||||
const isNewUser = aalData?.phone_verification_required;
|
||||
|
||||
if (current === 'aal1' && next === 'aal1') {
|
||||
if (isNewUser) {
|
||||
return 'As a new user, you are required to set up phone verification for enhanced security.';
|
||||
} else {
|
||||
return 'MFA is not enrolled for this account. You can optionally set up MFA for enhanced security.';
|
||||
}
|
||||
} else if (current === 'aal1' && next === 'aal2') {
|
||||
return 'MFA is enrolled but not verified. Please complete MFA verification to access all features.';
|
||||
} else if (current === 'aal2' && next === 'aal2') {
|
||||
return 'MFA is fully verified and active. Your account has enhanced security.';
|
||||
} else if (current === 'aal2' && next === 'aal1') {
|
||||
return 'MFA settings have changed. Please sign in again to refresh your session.';
|
||||
} else {
|
||||
return `Unknown AAL combination: ${current} → ${next}`;
|
||||
}
|
||||
};
|
||||
|
||||
const getEffectiveAction = () => {
|
||||
// Check if new user needs phone verification enrollment
|
||||
if (aalData?.phone_verification_required &&
|
||||
aalData.current_level === "aal1" &&
|
||||
aalData.next_level === "aal1") {
|
||||
return 'ENROLL MFA';
|
||||
}
|
||||
|
||||
return aalData?.action_required?.replace('_', ' ').toUpperCase() || 'UNKNOWN';
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
{getStatusIcon()}
|
||||
Multi-Factor Authentication Status
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Current security level and required actions
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-600">Current Level</label>
|
||||
<Badge variant="outline" className="ml-2">
|
||||
{aalData.current_level?.toUpperCase() || 'Unknown'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-600">Next Level</label>
|
||||
<Badge variant="outline" className="ml-2">
|
||||
{aalData.next_level?.toUpperCase() || 'Unknown'}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-600">Action Required</label>
|
||||
<Badge className={`ml-2 ${getStatusColor()}`}>
|
||||
{getEffectiveAction()}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-600">User Type</label>
|
||||
<Badge variant="outline" className="ml-2">
|
||||
{aalData?.phone_verification_required ? 'NEW USER' : 'EXISTING USER'}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{aalData && (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-600">Phone Verification Required</label>
|
||||
<Badge
|
||||
variant={aalData.phone_verification_required ? "destructive" : "secondary"}
|
||||
className="ml-2"
|
||||
>
|
||||
{aalData.phone_verification_required ? 'YES' : 'NO'}
|
||||
</Badge>
|
||||
{aalData.user_created_at && (
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Account created: {new Date(aalData.user_created_at).toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{aalData.current_authentication_methods && aalData.current_authentication_methods.length > 0 && (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-600">Authentication Methods</label>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{aalData.current_authentication_methods.map((method, index) => (
|
||||
<Badge key={index} variant="secondary" className="text-xs">
|
||||
{method}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
{aalData.message || getActionDescription()}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div className="text-xs text-gray-500 bg-gray-50 p-3 rounded">
|
||||
<strong>AAL Flow Reference:</strong>
|
||||
<ul className="mt-1 space-y-1">
|
||||
<li>• aal1 → aal1: No MFA enrolled{aalData?.phone_verification_required ? ' (new users must enroll)' : ' (optional for existing users)'}</li>
|
||||
<li>• aal1 → aal2: MFA enrolled, verification required</li>
|
||||
<li>• aal2 → aal2: MFA verified and active</li>
|
||||
<li>• aal2 → aal1: MFA disabled, reauthentication needed</li>
|
||||
</ul>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue