Files
home-automation/frontend/src/auth/totp.ts
T

78 lines
3.0 KiB
TypeScript

/**
* 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<TotpStatusResponse> {
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<TotpSetupResponse> {
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<void> {
// 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<void> {
await apiClient.POST('/api/auth/totp/disable', {
body: { password: opts.password ?? null, code: opts.code ?? null },
})
}