Files
thrilltrack-explorer/lib/services/auth/mfaService.ts

211 lines
5.6 KiB
TypeScript

/**
* MFA (Multi-Factor Authentication) Service
*
* Handles TOTP (Time-based One-Time Password) setup, verification,
* and management for two-factor authentication.
*/
import { apiClient } from '@/lib/api/client';
import {
TOTPSetupResponse,
TOTPConfirmData,
TOTPVerifyData,
MFAChallengeData,
AuthResponse,
} from '@/lib/types/auth';
import { tokenStorage } from './tokenStorage';
/**
* Enable MFA/2FA for current user
* Returns TOTP secret and QR code URL
*/
export async function setupTOTP(): Promise<TOTPSetupResponse> {
const response = await apiClient.post<TOTPSetupResponse>('/api/v1/auth/mfa/enable');
return response.data;
}
/**
* Confirm MFA setup with verification token
* Completes MFA setup after verifying the token is valid
*/
export async function confirmTOTP(data: TOTPConfirmData): Promise<void> {
await apiClient.post('/api/v1/auth/mfa/confirm', data);
}
/**
* Disable MFA/2FA for current user
* Removes all TOTP devices and disables MFA requirement
*/
export async function disableTOTP(): Promise<void> {
await apiClient.post('/api/v1/auth/mfa/disable');
}
/**
* Verify MFA token (for testing)
* Returns whether the token is valid
*/
export async function verifyTOTP(data: TOTPVerifyData): Promise<boolean> {
try {
await apiClient.post('/api/v1/auth/mfa/verify', data);
return true;
} catch (error) {
return false;
}
}
/**
* Complete MFA challenge during login
* Called when user has MFA enabled and needs to provide token
*/
export async function challengeMFA(data: MFAChallengeData): Promise<AuthResponse> {
const response = await apiClient.post<AuthResponse>('/api/v1/auth/mfa/challenge', {
token: data.code,
});
// Store tokens after successful MFA challenge
tokenStorage.setAccessToken(response.data.access);
tokenStorage.setRefreshToken(response.data.refresh);
return response.data;
}
/**
* Generate new backup codes
* Returns a list of one-time use backup codes
*/
export async function generateBackupCodes(): Promise<string[]> {
const response = await apiClient.post<{ backup_codes: string[] }>('/api/v1/auth/mfa/backup-codes');
return response.data.backup_codes;
}
/**
* Use a backup code to login when TOTP is unavailable
*/
export async function useBackupCode(code: string): Promise<AuthResponse> {
const response = await apiClient.post<AuthResponse>('/api/v1/auth/mfa/backup-code', {
code,
});
// Store tokens after successful backup code use
tokenStorage.setAccessToken(response.data.access);
tokenStorage.setRefreshToken(response.data.refresh);
return response.data;
}
/**
* Remove MFA with password confirmation
*/
export async function removeMFA(password: string): Promise<void> {
await apiClient.post('/api/v1/auth/mfa/remove', {
password,
});
}
/**
* WebAuthn/Passkey Support
* Django-allauth provides WebAuthn through its MFA module
*/
export interface WebAuthnCredential {
id: string;
name: string;
created_at: string;
last_used?: string;
}
/**
* Get list of registered WebAuthn credentials (passkeys)
*/
export async function getWebAuthnCredentials(): Promise<WebAuthnCredential[]> {
const response = await apiClient.get<{ credentials: WebAuthnCredential[] }>(
'/accounts/mfa/webauthn/list/'
);
return response.data.credentials;
}
/**
* Start WebAuthn registration process
* Returns challenge data for credential creation
*/
export async function startWebAuthnRegistration(): Promise<PublicKeyCredentialCreationOptions> {
const response = await apiClient.get<PublicKeyCredentialCreationOptions>(
'/accounts/mfa/webauthn/add/'
);
return response.data;
}
/**
* Complete WebAuthn registration
* @param credential The created PublicKeyCredential from browser
* @param name Optional friendly name for the credential
*/
export async function completeWebAuthnRegistration(
credential: PublicKeyCredential,
name?: string
): Promise<void> {
await apiClient.post('/accounts/mfa/webauthn/add/', {
credential: JSON.stringify(credential),
name,
});
}
/**
* Remove a WebAuthn credential
* @param credentialId The ID of the credential to remove
*/
export async function removeWebAuthnCredential(credentialId: string): Promise<void> {
await apiClient.post(`/accounts/mfa/webauthn/remove/${credentialId}/`);
}
/**
* Start WebAuthn authentication challenge
* Used during login when user has passkey enabled
*/
export async function startWebAuthnAuthentication(): Promise<PublicKeyCredentialRequestOptions> {
const response = await apiClient.get<PublicKeyCredentialRequestOptions>(
'/accounts/mfa/webauthn/authenticate/'
);
return response.data;
}
/**
* Complete WebAuthn authentication
* @param credential The assertion credential from browser
*/
export async function completeWebAuthnAuthentication(
credential: PublicKeyCredential
): Promise<AuthResponse> {
const response = await apiClient.post<AuthResponse>(
'/accounts/mfa/webauthn/authenticate/',
{
credential: JSON.stringify(credential),
}
);
// Store tokens after successful WebAuthn authentication
tokenStorage.setAccessToken(response.data.access);
tokenStorage.setRefreshToken(response.data.refresh);
return response.data;
}
// Export all functions as a service object for convenience
export const mfaService = {
setupTOTP,
confirmTOTP,
disableTOTP,
verifyTOTP,
challengeMFA,
generateBackupCodes,
useBackupCode,
removeMFA,
// WebAuthn/Passkey methods
getWebAuthnCredentials,
startWebAuthnRegistration,
completeWebAuthnRegistration,
removeWebAuthnCredential,
startWebAuthnAuthentication,
completeWebAuthnAuthentication,
};