Adds the global re-authentication modal for guarded routes and the owner/admin MFA controls on the settings page. request() in lib/api.ts now intercepts a 403 step_up_required response, awaits re-authentication through a callback registered by StepUpModal (lib/stepup.ts), and retries the original request exactly once. The modal offers TOTP, recovery code and password, since the webauthn step-up routes (/me/step-up/webauthn/begin and /finish) are not registered server-side yet; it omits the passkey option rather than calling a route that does not exist. me.stepUp posts one factor to /api/me/step-up. The settings page gains an owner-only "Require MFA" toggle and the members table gains an MFA column and a "Reset MFA" action, both routed through the existing PUT /api/settings and DELETE /api/org/users/:id/mfa.
19 lines
700 B
TypeScript
19 lines
700 B
TypeScript
// The step-up broker. `request()` in api.ts lives outside React, so it cannot
|
|
// render a dialog itself; instead it calls back into whatever prompt the
|
|
// StepUpModal registered when it mounted inside the (app) layout.
|
|
|
|
type Prompt = (methods: string[]) => Promise<void>;
|
|
|
|
let prompt: Prompt | null = null;
|
|
|
|
// The provider registers the real prompt at mount. Before that, or outside the
|
|
// app shell, step-up simply fails rather than hanging forever.
|
|
export function registerStepUpPrompt(fn: Prompt | null) {
|
|
prompt = fn;
|
|
}
|
|
|
|
export async function requestStepUp(methods: string[]): Promise<void> {
|
|
if (!prompt) throw new Error("re-authentication is required");
|
|
return prompt(methods);
|
|
}
|