mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 16:31:13 -05:00
260 lines
9.5 KiB
TypeScript
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()
|
|
};
|
|
}
|
|
}
|
|
}
|
|
]
|
|
};
|