fix: skip MFA binding prompt for UserControlled policy during sign-in
All checks were successful
CI / build (push) Successful in 2m1s
CI / docker (push) Successful in 59s

Logto returns 422 with an MFA recommendation when policy is
UserControlled. Call POST /profile/mfa/mfa-skipped to skip the
binding prompt, then re-submit. Users who already have MFA enrolled
still get the TOTP verification flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-26 14:43:55 +02:00
parent 469b36613b
commit 6e6e4218c9

View File

@@ -71,20 +71,42 @@ export class MfaRequiredError extends Error {
}
}
async function trySubmit(): Promise<{ ok: true; redirectTo: string } | { ok: false; status: number; code: string; message: string }> {
const res = await request('POST', '/submit');
if (res.ok) {
const data = await res.json();
return { ok: true, redirectTo: data.redirectTo };
}
const err = await res.json().catch(() => ({}));
return { ok: false, status: res.status, code: err.code ?? '', message: err.message ?? `Submit failed (${res.status})` };
}
async function skipMfaBinding(): Promise<void> {
await request('POST', '/profile/mfa/mfa-skipped');
}
export async function signIn(identifier: string, password: string): Promise<string> {
await initInteraction();
const verificationId = await verifyPassword(identifier, password);
await identifyUser(verificationId);
const res = await request('POST', '/submit');
if (!res.ok) {
const err = await res.json().catch(() => ({}));
if (err.code === 'user.missing_mfa') {
throw new MfaRequiredError();
}
throw new Error(err.message || `Submit failed (${res.status})`);
const result = await trySubmit();
if (result.ok) return result.redirectTo;
// MFA already enrolled — user must verify (show TOTP input)
if (result.code === 'user.missing_mfa' || result.code === 'session.mfa.require_mfa_verification') {
throw new MfaRequiredError();
}
const data = await res.json();
return data.redirectTo;
// MFA not enrolled, UserControlled policy — skip the binding prompt.
// Also fallback: any 422 with an MFA-related code we don't recognize — try skip before failing.
if (result.status === 422 && result.code.includes('mfa')) {
await skipMfaBinding();
const retry = await trySubmit();
if (retry.ok) return retry.redirectTo;
throw new Error(retry.message);
}
throw new Error(result.message);
}
// --- Registration ---