Compare commits

...

3 Commits

Author SHA1 Message Date
pac7
10daaf73f2 Enforce higher security level for sensitive user actions
Require Authentication Assurance Level 2 (AAL2) for user banning/unbanning, role assignments, content moderation, system settings changes, and database operations.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 8d6e34be-9b3f-4293-af2d-feb575094935
Replit-Commit-Checkpoint-Type: full_checkpoint
2025-10-28 00:01:58 +00:00
pac7
f2aeb2aa93 Improve navigation by adding a new sidebar menu component
Add a new sidebar navigation component, including routing and styling, to enhance user experience and provide quick access to different sections of the application.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: da324197-4d44-4e4b-b342-fe8ae33cf0cf
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7cdf4e95-3f41-4180-b8e3-8ef56d032c0e/da324197-4d44-4e4b-b342-fe8ae33cf0cf/FKxKaZw
2025-10-27 23:54:50 +00:00
pac7
d1f01d9228 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
2025-10-27 23:53:33 +00:00
5 changed files with 62 additions and 9 deletions

View File

@@ -38,10 +38,6 @@ externalPort = 80
localPort = 5001
externalPort = 3000
[[ports]]
localPort = 34475
externalPort = 3003
[[ports]]
localPort = 37143
externalPort = 3001

View File

@@ -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
});

View File

@@ -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 {

View File

@@ -33,4 +33,5 @@ export interface IdentityOperationResult {
needsRelogin?: boolean;
needsEmailConfirmation?: boolean;
email?: string;
requiresAAL2?: boolean;
}

View File

@@ -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 });