Files
smoothschedule/frontend/src/api/mfa.ts
poduck 4cd6610f2a Fix double /api/ prefix in API endpoint calls
When VITE_API_URL=/api, axios baseURL is already set to /api. However, all endpoint calls included the /api/ prefix, creating double paths like /api/api/auth/login/.

Removed /api/ prefix from 81 API endpoint calls across 22 files:
- src/api/auth.ts - Fixed login, logout, me, refresh, hijack endpoints
- src/api/client.ts - Fixed token refresh endpoint
- src/api/profile.ts - Fixed all profile, email, password, MFA, sessions endpoints
- src/hooks/*.ts - Fixed all remaining API calls (users, appointments, resources, etc)
- src/pages/*.tsx - Fixed signup and email verification endpoints

This ensures API requests use the correct path: /api/auth/login/ instead of /api/api/auth/login/

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 15:27:57 -05:00

234 lines
6.6 KiB
TypeScript

/**
* MFA (Two-Factor Authentication) API
*/
import apiClient from './client';
// ============================================================================
// Types
// ============================================================================
export interface MFAStatus {
mfa_enabled: boolean;
mfa_method: 'NONE' | 'SMS' | 'TOTP' | 'BOTH';
methods: ('SMS' | 'TOTP' | 'BACKUP')[];
phone_last_4: string | null;
phone_verified: boolean;
totp_verified: boolean;
backup_codes_count: number;
backup_codes_generated_at: string | null;
trusted_devices_count: number;
}
export interface TOTPSetupResponse {
success: boolean;
secret: string;
qr_code: string; // Data URL for QR code image
provisioning_uri: string;
message: string;
}
export interface MFAEnableResponse {
success: boolean;
message: string;
mfa_method: string;
backup_codes?: string[];
backup_codes_message?: string;
}
export interface BackupCodesResponse {
success: boolean;
backup_codes: string[];
message: string;
warning: string;
}
export interface BackupCodesStatus {
count: number;
generated_at: string | null;
}
export interface TrustedDevice {
id: number;
name: string;
ip_address: string;
created_at: string;
last_used_at: string;
expires_at: string;
is_current: boolean;
}
export interface MFALoginResponse {
mfa_required: boolean;
user_id?: number;
mfa_methods?: string[];
phone_last_4?: string;
}
export interface MFAVerifyResponse {
success: boolean;
access: string;
refresh: string;
user: {
id: number;
email: string;
username: string;
first_name: string;
last_name: string;
full_name: string;
role: string;
business_subdomain: string | null;
mfa_enabled: boolean;
};
}
// ============================================================================
// MFA Status
// ============================================================================
/**
* Get current MFA status
*/
export const getMFAStatus = async (): Promise<MFAStatus> => {
const response = await apiClient.get<MFAStatus>('/auth/mfa/status/');
return response.data;
};
// ============================================================================
// SMS Setup
// ============================================================================
/**
* Send phone verification code
*/
export const sendPhoneVerification = async (phone: string): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.post('/auth/mfa/phone/send/', { phone });
return response.data;
};
/**
* Verify phone number with code
*/
export const verifyPhone = async (code: string): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.post('/auth/mfa/phone/verify/', { code });
return response.data;
};
/**
* Enable SMS MFA (requires verified phone)
*/
export const enableSMSMFA = async (): Promise<MFAEnableResponse> => {
const response = await apiClient.post<MFAEnableResponse>('/auth/mfa/sms/enable/');
return response.data;
};
// ============================================================================
// TOTP Setup (Authenticator App)
// ============================================================================
/**
* Initialize TOTP setup (returns QR code and secret)
*/
export const setupTOTP = async (): Promise<TOTPSetupResponse> => {
const response = await apiClient.post<TOTPSetupResponse>('/auth/mfa/totp/setup/');
return response.data;
};
/**
* Verify TOTP code to complete setup
*/
export const verifyTOTPSetup = async (code: string): Promise<MFAEnableResponse> => {
const response = await apiClient.post<MFAEnableResponse>('/auth/mfa/totp/verify/', { code });
return response.data;
};
// ============================================================================
// Backup Codes
// ============================================================================
/**
* Generate new backup codes (invalidates old ones)
*/
export const generateBackupCodes = async (): Promise<BackupCodesResponse> => {
const response = await apiClient.post<BackupCodesResponse>('/auth/mfa/backup-codes/');
return response.data;
};
/**
* Get backup codes status
*/
export const getBackupCodesStatus = async (): Promise<BackupCodesStatus> => {
const response = await apiClient.get<BackupCodesStatus>('/auth/mfa/backup-codes/status/');
return response.data;
};
// ============================================================================
// Disable MFA
// ============================================================================
/**
* Disable MFA (requires password or valid MFA code)
*/
export const disableMFA = async (credentials: { password?: string; mfa_code?: string }): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.post('/auth/mfa/disable/', credentials);
return response.data;
};
// ============================================================================
// MFA Login Challenge
// ============================================================================
/**
* Send MFA code for login (SMS only)
*/
export const sendMFALoginCode = async (userId: number, method: 'SMS' | 'TOTP' = 'SMS'): Promise<{ success: boolean; message: string; method: string }> => {
const response = await apiClient.post('/auth/mfa/login/send/', { user_id: userId, method });
return response.data;
};
/**
* Verify MFA code to complete login
*/
export const verifyMFALogin = async (
userId: number,
code: string,
method: 'SMS' | 'TOTP' | 'BACKUP',
trustDevice: boolean = false
): Promise<MFAVerifyResponse> => {
const response = await apiClient.post<MFAVerifyResponse>('/auth/mfa/login/verify/', {
user_id: userId,
code,
method,
trust_device: trustDevice,
});
return response.data;
};
// ============================================================================
// Trusted Devices
// ============================================================================
/**
* List trusted devices
*/
export const listTrustedDevices = async (): Promise<{ devices: TrustedDevice[] }> => {
const response = await apiClient.get('/auth/mfa/devices/');
return response.data;
};
/**
* Revoke a specific trusted device
*/
export const revokeTrustedDevice = async (deviceId: number): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.delete(`/auth/mfa/devices/${deviceId}/`);
return response.data;
};
/**
* Revoke all trusted devices
*/
export const revokeAllTrustedDevices = async (): Promise<{ success: boolean; message: string; count: number }> => {
const response = await apiClient.delete('/auth/mfa/devices/revoke-all/');
return response.data;
};