feat: Implement a comprehensive two-factor authentication system including setup, verification, backup codes, and integration into the login flow.

This commit is contained in:
pradeepkumar
2026-02-06 09:28:58 +05:30
parent 719b784dfe
commit 5eb0934570
10 changed files with 946 additions and 14 deletions

View File

@@ -77,6 +77,8 @@ const publicEndpoints = [
'/auth/forgot-password',
'/auth/reset-password',
'/auth/verify-email',
'/auth/2fa/verify',
'/auth/2fa/verify-backup',
'/agent-types',
];

View File

@@ -35,3 +35,15 @@ export type { AgentProfile, AgentType } from './agents.service';
// Upload Service
export { uploadService } from './upload.service';
export type { UploadedFile, UploadProgress } from './upload.service';
// Two-Factor Authentication Service
export { twoFactorService, TwoFactorService } from './two-factor.service';
export type {
TwoFactorSetupResponse,
TwoFactorEnableResponse,
TwoFactorStatusResponse,
TwoFactorLoginRequest,
TwoFactorBackupCodeRequest,
TwoFactorLoginResponse,
TwoFactorRequiredResponse,
} from './two-factor.service';

View File

@@ -0,0 +1,148 @@
import api, { ApiResponse } from './api';
// Types
export interface TwoFactorSetupResponse {
secret: string;
qrCode: string;
manualEntry: string;
}
export interface TwoFactorEnableResponse {
backupCodes: string[];
}
export interface TwoFactorStatusResponse {
enabled: boolean;
verifiedAt: string | null;
}
export interface TwoFactorLoginRequest {
tempToken: string;
token: string;
userAgent?: string;
ipAddress?: string;
}
export interface TwoFactorBackupCodeRequest {
tempToken: string;
backupCode: string;
userAgent?: string;
ipAddress?: string;
}
export interface TwoFactorLoginResponse {
user: {
id: string;
email: string;
role: string;
firstName?: string;
lastName?: string;
avatar?: string;
};
accessToken: string;
refreshToken: string;
}
export interface TwoFactorRequiredResponse {
requiresTwoFactor: true;
tempToken: string;
user: {
id: string;
email: string;
};
}
// Two-Factor Service
class TwoFactorService {
private basePath = '/auth/2fa';
/**
* Setup 2FA - generates secret and QR code
*/
async setup(): Promise<ApiResponse<TwoFactorSetupResponse>> {
const response = await api.post<ApiResponse<TwoFactorSetupResponse>>(
`${this.basePath}/setup`
);
return response.data;
}
/**
* Enable 2FA - verify token and enable
*/
async enable(token: string): Promise<ApiResponse<TwoFactorEnableResponse>> {
const response = await api.post<ApiResponse<TwoFactorEnableResponse>>(
`${this.basePath}/enable`,
{ token }
);
return response.data;
}
/**
* Disable 2FA - requires password confirmation
*/
async disable(password: string): Promise<ApiResponse<{ message: string }>> {
const response = await api.post<ApiResponse<{ message: string }>>(
`${this.basePath}/disable`,
{ password }
);
return response.data;
}
/**
* Verify 2FA token during login
*/
async verifyLogin(data: TwoFactorLoginRequest): Promise<ApiResponse<TwoFactorLoginResponse>> {
const response = await api.post<ApiResponse<TwoFactorLoginResponse>>(
`${this.basePath}/verify`,
data
);
return response.data;
}
/**
* Verify backup code during login
*/
async verifyBackupCode(data: TwoFactorBackupCodeRequest): Promise<ApiResponse<TwoFactorLoginResponse>> {
const response = await api.post<ApiResponse<TwoFactorLoginResponse>>(
`${this.basePath}/verify-backup`,
data
);
return response.data;
}
/**
* Get 2FA status
*/
async getStatus(): Promise<ApiResponse<TwoFactorStatusResponse>> {
const response = await api.get<ApiResponse<TwoFactorStatusResponse>>(
`${this.basePath}/status`
);
return response.data;
}
/**
* Regenerate backup codes - requires password confirmation
*/
async regenerateBackupCodes(password: string): Promise<ApiResponse<TwoFactorEnableResponse>> {
const response = await api.post<ApiResponse<TwoFactorEnableResponse>>(
`${this.basePath}/backup-codes`,
{ password }
);
return response.data;
}
/**
* Check if response indicates 2FA is required
*/
static isTwoFactorRequired(response: unknown): response is TwoFactorRequiredResponse {
return (
typeof response === 'object' &&
response !== null &&
'requiresTwoFactor' in response &&
(response as TwoFactorRequiredResponse).requiresTwoFactor === true
);
}
}
export const twoFactorService = new TwoFactorService();
export { TwoFactorService };