mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:31:12 -05:00
Improve security by requiring higher authentication levels for sensitive actions
Update authentication flows to enforce AAL2 requirements for MFA operations and identity disconnections, and adjust TOTP verification logic. Replit-Commit-Author: Agent Replit-Commit-Session-Id: da324197-4d44-4e4b-b342-fe8ae33cf0cf Replit-Commit-Checkpoint-Type: intermediate_checkpoint
This commit is contained in:
4
.replit
4
.replit
@@ -38,10 +38,6 @@ externalPort = 80
|
||||
localPort = 5001
|
||||
externalPort = 3000
|
||||
|
||||
[[ports]]
|
||||
localPort = 34475
|
||||
externalPort = 3003
|
||||
|
||||
[[ports]]
|
||||
localPort = 37143
|
||||
externalPort = 3001
|
||||
|
||||
@@ -225,9 +225,16 @@ export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: Password
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// Verify TOTP code
|
||||
// Get the factor ID first
|
||||
const factorId = (await supabase.auth.mfa.listFactors()).data?.totp?.[0]?.id || '';
|
||||
|
||||
if (!factorId) {
|
||||
throw new Error('No MFA factor found');
|
||||
}
|
||||
|
||||
// Create challenge
|
||||
const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({
|
||||
factorId: (await supabase.auth.mfa.listFactors()).data?.totp?.[0]?.id || ''
|
||||
factorId
|
||||
});
|
||||
|
||||
if (challengeError) {
|
||||
@@ -240,8 +247,9 @@ export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: Password
|
||||
throw challengeError;
|
||||
}
|
||||
|
||||
// Verify TOTP code with correct factorId
|
||||
const { error: verifyError } = await supabase.auth.mfa.verify({
|
||||
factorId: challengeData.id,
|
||||
factorId,
|
||||
challengeId: challengeData.id,
|
||||
code: totpCode
|
||||
});
|
||||
|
||||
@@ -90,12 +90,59 @@ export async function checkDisconnectSafety(
|
||||
|
||||
/**
|
||||
* Disconnect an OAuth identity from the user's account
|
||||
* Requires AAL2 session for security
|
||||
*/
|
||||
export async function disconnectIdentity(
|
||||
provider: OAuthProvider
|
||||
): Promise<IdentityOperationResult> {
|
||||
try {
|
||||
// Safety check first
|
||||
// AAL2 check for security-critical operation (MUST fail closed)
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
// Get AAL level - fail closed on error
|
||||
const { data: aalData, error: aalError } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel();
|
||||
if (aalError) {
|
||||
logger.error('Failed to get AAL level for identity disconnect', {
|
||||
action: 'disconnect_identity_aal_check',
|
||||
error: aalError.message
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: 'Unable to verify security level. Please try again.',
|
||||
requiresAAL2: true
|
||||
};
|
||||
}
|
||||
|
||||
const currentAal = aalData?.currentLevel || 'aal1';
|
||||
|
||||
// If not at AAL2, check if MFA is enrolled - fail closed on error
|
||||
if (currentAal !== 'aal2') {
|
||||
const { data: factors, error: factorsError } = await supabase.auth.mfa.listFactors();
|
||||
|
||||
if (factorsError) {
|
||||
logger.error('Failed to list MFA factors for identity disconnect', {
|
||||
action: 'disconnect_identity_mfa_check',
|
||||
error: factorsError.message
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: 'Unable to verify MFA status. Please try again.',
|
||||
requiresAAL2: true
|
||||
};
|
||||
}
|
||||
|
||||
const hasEnrolledMFA = factors?.totp?.some(f => f.status === 'verified') || false;
|
||||
|
||||
if (hasEnrolledMFA) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Please verify your identity with MFA before disconnecting accounts',
|
||||
requiresAAL2: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Safety check
|
||||
const safetyCheck = await checkDisconnectSafety(provider);
|
||||
if (!safetyCheck.canDisconnect) {
|
||||
return {
|
||||
|
||||
@@ -33,4 +33,5 @@ export interface IdentityOperationResult {
|
||||
needsRelogin?: boolean;
|
||||
needsEmailConfirmation?: boolean;
|
||||
email?: string;
|
||||
requiresAAL2?: boolean;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,8 @@ Deno.serve(async (req) => {
|
||||
|
||||
// Phase 1: Check AAL level
|
||||
const { data: { session } } = await supabaseClient.auth.getSession();
|
||||
const aal = session?.aal || 'aal1';
|
||||
const { data: aalData } = await supabaseClient.auth.mfa.getAuthenticatorAssuranceLevel();
|
||||
const aal = aalData?.currentLevel || 'aal1';
|
||||
|
||||
if (aal !== 'aal2') {
|
||||
edgeLogger.warn('AAL2 required for MFA removal', { action: 'mfa_unenroll_aal', userId: user.id, aal });
|
||||
|
||||
Reference in New Issue
Block a user