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