fix: correct Experience API endpoints for TOTP and backup codes
- TOTP secret: /verification/totp/secret (not /verification/totp) - Backup codes: generate via /verification/backup-code/generate first, then bind with the returned verificationId. Cannot bind BackupCode without generating codes first. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
verifyTotp, verifyBackupCode, submitMfa,
|
||||
startWebAuthnAuth, verifyWebAuthnAuth,
|
||||
startWebAuthnRegistration, verifyWebAuthnRegistration, bindMfaProfile,
|
||||
createTotpSecret, verifyTotpSetup,
|
||||
generateBackupCodes, createTotpSecret, verifyTotpSetup,
|
||||
skipMfaEnrollment, submitInteraction,
|
||||
MfaRequiredError, MfaEnrollmentError,
|
||||
} from './experience-api';
|
||||
@@ -318,7 +318,8 @@ export function SignInPage() {
|
||||
const credential = await startWebAuthnReg({ optionsJSON: registrationOptions as any });
|
||||
const verifiedId = await verifyWebAuthnRegistration(verificationId, credential as unknown as Record<string, unknown>);
|
||||
await bindMfaProfile('WebAuthn', verifiedId);
|
||||
await bindMfaProfile('BackupCode');
|
||||
const bc = await generateBackupCodes();
|
||||
await bindMfaProfile('BackupCode', bc.verificationId);
|
||||
const result = await submitInteraction();
|
||||
window.location.replace(result);
|
||||
} catch (err) {
|
||||
@@ -353,7 +354,8 @@ export function SignInPage() {
|
||||
try {
|
||||
const verifiedId = await verifyTotpSetup(totpCode);
|
||||
await bindMfaProfile('Totp', verifiedId);
|
||||
await bindMfaProfile('BackupCode');
|
||||
const bc = await generateBackupCodes();
|
||||
await bindMfaProfile('BackupCode', bc.verificationId);
|
||||
const result = await submitInteraction();
|
||||
window.location.replace(result);
|
||||
} catch (err) {
|
||||
|
||||
@@ -322,18 +322,25 @@ export async function verifyWebAuthnRegistration(verificationId: string, payload
|
||||
return data.verificationId;
|
||||
}
|
||||
|
||||
export async function bindMfaProfile(type: string, verificationId?: string): Promise<void> {
|
||||
const body: Record<string, string> = { type };
|
||||
if (verificationId) body.verificationId = verificationId;
|
||||
const res = await request('POST', '/profile/mfa', body);
|
||||
export async function bindMfaProfile(type: string, verificationId: string): Promise<void> {
|
||||
const res = await request('POST', '/profile/mfa', { type, verificationId });
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error(err.message || `Failed to bind MFA (${res.status})`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateBackupCodes(): Promise<{ verificationId: string; codes: string[] }> {
|
||||
const res = await request('POST', '/verification/backup-code/generate');
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error(err.message || `Failed to generate backup codes (${res.status})`);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function createTotpSecret(): Promise<{ secret: string; secretQrCode: string; verificationId: string }> {
|
||||
const res = await request('POST', '/verification/totp');
|
||||
const res = await request('POST', '/verification/totp/secret');
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error(err.message || `Failed to create TOTP secret (${res.status})`);
|
||||
|
||||
Reference in New Issue
Block a user