Files

260 lines
9.5 KiB
TypeScript

/**
* Authentication & Authorization Test Suite
*
* Tests auth flows, MFA enforcement, role checks, and session management.
*/
import { supabase } from '@/lib/supabaseClient';
import type { TestSuite, TestResult } from '../testRunner';
export const authTestSuite: TestSuite = {
id: 'auth',
name: 'Authentication & Authorization',
description: 'Tests for auth flows, MFA, roles, and permissions',
tests: [
{
id: 'auth-001',
name: 'User Session Validation',
description: 'Validates current user session is valid with proper JWT structure',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
try {
// Get current session
const { data: { session }, error } = await supabase.auth.getSession();
if (error) throw new Error(`Session fetch failed: ${error.message}`);
if (!session) throw new Error('No active session found');
if (!session.access_token) throw new Error('No access token in session');
if (!session.user) throw new Error('No user in session');
if (!session.user.id) throw new Error('No user ID in session');
// Validate token structure (JWT has 3 parts separated by dots)
const tokenParts = session.access_token.split('.');
if (tokenParts.length !== 3) {
throw new Error(`Invalid JWT structure: expected 3 parts, got ${tokenParts.length}`);
}
// Check expiration
if (session.expires_at && session.expires_at < Date.now() / 1000) {
throw new Error('Session token is expired');
}
const duration = Date.now() - startTime;
return {
id: 'auth-001',
name: 'User Session Validation',
suite: 'Authentication & Authorization',
status: 'pass',
duration,
timestamp: new Date().toISOString(),
details: {
userId: session.user.id,
email: session.user.email,
expiresAt: session.expires_at,
aal: (session.user as any).aal || 'aal1'
}
};
} catch (error) {
const duration = Date.now() - startTime;
return {
id: 'auth-001',
name: 'User Session Validation',
suite: 'Authentication & Authorization',
status: 'fail',
duration,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
};
}
}
},
{
id: 'auth-002',
name: 'Role-Based Access Control (RBAC)',
description: 'Tests role checks are consistent across hooks and database functions',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error('No authenticated user');
// Query user_roles table
const { data: roles, error: rolesError } = await supabase
.from('user_roles')
.select('role')
.eq('user_id', user.id);
if (rolesError) throw new Error(`Failed to fetch roles: ${rolesError.message}`);
// Test is_moderator() database function
const { data: isMod, error: modError } = await supabase
.rpc('is_moderator', { _user_id: user.id });
if (modError) throw new Error(`is_moderator() failed: ${modError.message}`);
// Test is_superuser() database function
const { data: isSuper, error: superError } = await supabase
.rpc('is_superuser', { _user_id: user.id });
if (superError) throw new Error(`is_superuser() failed: ${superError.message}`);
// Validate consistency
const hasModRole = roles?.some(r => ['moderator', 'admin', 'superuser'].includes(r.role));
if (hasModRole !== isMod) {
throw new Error(`Inconsistent moderator check: has role=${hasModRole}, is_moderator()=${isMod}`);
}
const hasSuperRole = roles?.some(r => r.role === 'superuser');
if (hasSuperRole !== isSuper) {
throw new Error(`Inconsistent superuser check: has role=${hasSuperRole}, is_superuser()=${isSuper}`);
}
const duration = Date.now() - startTime;
return {
id: 'auth-002',
name: 'Role-Based Access Control (RBAC)',
suite: 'Authentication & Authorization',
status: 'pass',
duration,
timestamp: new Date().toISOString(),
details: {
roles: roles?.map(r => r.role) || [],
isModerator: isMod,
isSuperuser: isSuper,
consistent: true
}
};
} catch (error) {
const duration = Date.now() - startTime;
return {
id: 'auth-002',
name: 'Role-Based Access Control (RBAC)',
suite: 'Authentication & Authorization',
status: 'fail',
duration,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
};
}
}
},
{
id: 'auth-003',
name: 'MFA Factor Detection',
description: 'Tests MFA enrollment detection and AAL level',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error('No authenticated user');
// Get MFA factors
const { data: factors, error: factorsError } = await supabase.auth.mfa.listFactors();
if (factorsError) throw new Error(`Failed to list MFA factors: ${factorsError.message}`);
const hasVerifiedFactor = factors?.totp?.some(f => f.status === 'verified') || false;
const currentAAL = (user as any).aal || 'aal1';
const duration = Date.now() - startTime;
return {
id: 'auth-003',
name: 'MFA Factor Detection',
suite: 'Authentication & Authorization',
status: 'pass',
duration,
timestamp: new Date().toISOString(),
details: {
hasVerifiedMFA: hasVerifiedFactor,
currentAAL: currentAAL,
totpFactorCount: factors?.totp?.length || 0,
verifiedFactorCount: factors?.totp?.filter(f => f.status === 'verified').length || 0
}
};
} catch (error) {
const duration = Date.now() - startTime;
return {
id: 'auth-003',
name: 'MFA Factor Detection',
suite: 'Authentication & Authorization',
status: 'fail',
duration,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
};
}
}
},
{
id: 'auth-004',
name: 'Banned User Detection',
description: 'Tests banned user detection in profiles table',
run: async (): Promise<TestResult> => {
const startTime = Date.now();
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error('No authenticated user');
// Query profile banned status
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('banned')
.eq('user_id', user.id)
.single();
if (profileError) throw new Error(`Failed to fetch profile: ${profileError.message}`);
if (!profile) throw new Error('No profile found');
// Test is_user_banned() database function
const { data: isBanned, error: bannedError } = await supabase
.rpc('is_user_banned', { p_user_id: user.id });
if (bannedError) throw new Error(`is_user_banned() failed: ${bannedError.message}`);
// Validate consistency
if (profile.banned !== isBanned) {
throw new Error(`Inconsistent banned check: profile=${profile.banned}, is_user_banned()=${isBanned}`);
}
const duration = Date.now() - startTime;
return {
id: 'auth-004',
name: 'Banned User Detection',
suite: 'Authentication & Authorization',
status: 'pass',
duration,
timestamp: new Date().toISOString(),
details: {
isBanned: profile.banned,
consistent: true
}
};
} catch (error) {
const duration = Date.now() - startTime;
return {
id: 'auth-004',
name: 'Banned User Detection',
suite: 'Authentication & Authorization',
status: 'fail',
duration,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
};
}
}
}
]
};