feat: Implement a comprehensive two-factor authentication system including setup, verification, backup codes, and integration into the login flow.
This commit is contained in:
@@ -77,6 +77,8 @@ const publicEndpoints = [
|
||||
'/auth/forgot-password',
|
||||
'/auth/reset-password',
|
||||
'/auth/verify-email',
|
||||
'/auth/2fa/verify',
|
||||
'/auth/2fa/verify-backup',
|
||||
'/agent-types',
|
||||
];
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
148
src/services/two-factor.service.ts
Normal file
148
src/services/two-factor.service.ts
Normal 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 };
|
||||
Reference in New Issue
Block a user