/** * TOTP API helpers (M4-T08). * * All calls go through the typed apiClient (openapi-fetch + generated schema). * CSRF tokens are injected automatically by the client middleware for write * endpoints; GET /api/auth/totp needs no CSRF (read-only). * * Security notes: * - The TotpSetupResponse contains secret + recovery_codes in plaintext. * These are kept only in component state; callers must NOT write them to * localStorage, sessionStorage, or log them. * - After the setup phase is over the data leaves memory when the component * re-renders (one-time display only). */ import apiClient, { ApiError } from '../api/client' import type { components } from '../api/schema.d.ts' export type TotpSetupResponse = components['schemas']['TotpSetupResponse'] export type TotpStatusResponse = components['schemas']['TotpStatusResponse'] // --------------------------------------------------------------------------- // getTotpStatus — GET /api/auth/totp // --------------------------------------------------------------------------- /** Return the current TOTP enabled state for the authenticated user. */ export async function getTotpStatus(): Promise { const res = await apiClient.GET('/api/auth/totp') if (!res.data) { throw new ApiError(res.response.status, null) } return res.data } // --------------------------------------------------------------------------- // setupTotp — POST /api/auth/totp/setup // --------------------------------------------------------------------------- /** * Generate a new pending TOTP secret + recovery codes. * Returns the one-time plaintext values; caller is responsible for displaying * them exactly once and never persisting them outside component state. */ export async function setupTotp(): Promise { const res = await apiClient.POST('/api/auth/totp/setup') if (!res.data) { throw new ApiError(res.response.status, null) } return res.data } // --------------------------------------------------------------------------- // enableTotp — POST /api/auth/totp/enable // --------------------------------------------------------------------------- /** Confirm TOTP setup with the current 6-digit code from the authenticator app. */ export async function enableTotp(code: string): Promise { // The endpoint returns 204 on success; openapi-fetch yields { data: undefined }. await apiClient.POST('/api/auth/totp/enable', { body: { code } }) } // --------------------------------------------------------------------------- // disableTotp — POST /api/auth/totp/disable // --------------------------------------------------------------------------- /** * Disable TOTP. Exactly one of password or code must be provided. * On success the backend clears the secret and all recovery codes. */ export async function disableTotp(opts: { password?: string | null code?: string | null }): Promise { await apiClient.POST('/api/auth/totp/disable', { body: { password: opts.password ?? null, code: opts.code ?? null }, }) }