feat: add MFA types, hooks, and APP_MFA_REQUIRED interceptor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,14 @@ async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T>
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
const errorHeader = response.headers.get('X-Cameleer-Error');
|
||||
if (errorHeader === 'APP_MFA_REQUIRED') {
|
||||
window.location.href = '/platform/tenant/settings?mfa=required';
|
||||
throw new Error('MFA enrollment required');
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`API error ${response.status}: ${text}`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { api } from './client';
|
||||
import type { DashboardData, TenantLicenseData, TenantSettings, AuditLogPage, AuditLogFilters, SsoConnector, CreateSsoConnectorRequest, SsoTestResult } from '../types/api';
|
||||
import type { DashboardData, TenantLicenseData, TenantSettings, AuditLogPage, AuditLogFilters, SsoConnector, CreateSsoConnectorRequest, SsoTestResult, MfaStatus, MfaSetupResponse, BackupCodesResponse } from '../types/api';
|
||||
|
||||
export function useTenantDashboard() {
|
||||
return useQuery<DashboardData>({
|
||||
@@ -143,3 +143,57 @@ export function useTenantAuditLog(filters: Omit<AuditLogFilters, 'tenantId'>) {
|
||||
queryFn: () => api.get(`/tenant/audit?${params.toString()}`),
|
||||
});
|
||||
}
|
||||
|
||||
// MFA hooks
|
||||
export function useMfaStatus() {
|
||||
return useQuery<MfaStatus>({
|
||||
queryKey: ['tenant', 'mfa', 'status'],
|
||||
queryFn: () => api.get('/tenant/mfa/status'),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMfaSetup() {
|
||||
return useMutation<MfaSetupResponse, Error, void>({
|
||||
mutationFn: () => api.post('/tenant/mfa/totp/setup'),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMfaVerify() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<{ verified: boolean }, Error, { secret: string; code: string }>({
|
||||
mutationFn: (body) => api.post('/tenant/mfa/totp/verify', body),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['tenant', 'mfa'] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMfaBackupCodes() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<BackupCodesResponse, Error, void>({
|
||||
mutationFn: () => api.post('/tenant/mfa/backup-codes'),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['tenant', 'mfa'] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMfaRemove() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<void, Error, void>({
|
||||
mutationFn: () => api.delete('/tenant/mfa/totp'),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['tenant', 'mfa'] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useResetTeamMemberMfa() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<void, Error, string>({
|
||||
mutationFn: (userId) => api.delete(`/tenant/users/${userId}/mfa`),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['tenant', 'team'] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateTenantSettings() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<void, Error, Record<string, unknown>>({
|
||||
mutationFn: (updates) => api.patch('/tenant/settings', updates),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['tenant', 'settings'] }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ export interface TenantSettings {
|
||||
status: string;
|
||||
serverEndpoint: string | null;
|
||||
createdAt: string;
|
||||
mfaRequired?: boolean;
|
||||
}
|
||||
|
||||
// SSO connector types
|
||||
@@ -200,3 +201,18 @@ export interface TenantMetricsEntry {
|
||||
serverState: string;
|
||||
metrics: MetricsSummary | null;
|
||||
}
|
||||
|
||||
// MFA types
|
||||
export interface MfaStatus {
|
||||
enrolled: boolean;
|
||||
hasBackupCodes: boolean;
|
||||
}
|
||||
|
||||
export interface MfaSetupResponse {
|
||||
secret: string;
|
||||
secretQrCode: string;
|
||||
}
|
||||
|
||||
export interface BackupCodesResponse {
|
||||
codes: string[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user