feat(mfa): behavior redesign - triage helper, login-time offer/skip, OAuth redirect handling#62
Merged
Merged
Conversation
…stop the auto-firing passkey prompt - AuthorizerRoot/AuthorizerContext: surface a configLoadError banner instead of silently rendering a blank login screen when the backend meta fetch fails. - AuthorizerRoot: gate AuthorizerSocialLogin behind the Login view like its siblings, instead of rendering on every view. - AuthorizerBasicAuthLogin/AuthorizerSignup: switch validation effects to functional state updates and null-guard pristine fields, so simultaneous effects can't clobber each other's error state on mount. - AuthorizerBasicAuthLogin: defer the WebAuthn conditional-mediation (passkey autofill) ceremony from mount to the email field's first focus, so it no longer fires a native passkey prompt on every page load. - example app: drop noisy console.log callbacks (onStateChangeCallback, onLogin/onMagicLinkLogin/onSignup) that spammed the console on every state dispatch.
Fixes the actual gaps hit during manual testing: login/signup/passkey handlers have zero wiring for the withheld-token contract, and AuthorizerPasskeyLogin silently treats a withheld (null-token) response as a successful login. Reuses the existing AuthorizerMFASetup picker and AuthorizerVerifyOtp screens rather than rebuilding them.
6 tasks: expose new meta fields on config, shared AuthResponse triage helper, AuthorizerMFASetup login mode with skip + real email/SMS OTP wiring, wire login/signup/passkey-login through the triage helper (fixes the passkey null-token bug), locked-account screen, OAuth mfa_required redirect handling.
…_mfa_enforced on config
Focusing the email field triggered a conditional-mediation WebAuthn ceremony (loginWithPasskeyAutofill) on every fresh mount, surfacing a 1Password/passkey popup unexpectedly on the login page. The explicit "Sign in with a passkey" button already covers this path without the surprise prompt.
lakhansamani
added a commit
that referenced
this pull request
Jul 16, 2026
Passkey is a single factor (something you have). Offering it as a standalone primary-login path when the org requires MFA would let a user complete auth without ever hitting the second factor. The server already refuses this independently, but the button shouldn't invite the attempt - and resolveAuthStep's existing 'verify' branch already surfaces the server's refusal cleanly if this guard is ever bypassed (e.g. a stale config read). Ported forward from the closed PR #61 (feat/enforce-mfa-passkey-gate, superseded by the plan-based #62) - the only piece of that branch not already covered by the merged behavior redesign.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
resolveAuthSteptriage helper replaces each component's own ad hoc should_show_/should_offer_ checksAuthorizerMFASetupgainsloginContext- same component now doubles as the login-time offer/skip screen, not just the settings-screen hubAuthorizerPasskeyLogintreated a withheld (null access_token) response as a successful loginAuthorizerRootdetects the OAuthmfa_requiredredirect param and routes into the MFA screenDesign spec and implementation plan:
docs/superpowers/plans/2026-07-15-mfa-behavior-redesign-react.mdKnown limitations (accepted, not fixed here)
OAuth-redirect MFA path only, confirmed against the live backend source:
skip_mfa_setup/otp_mfa_setuprequire email or phone_number to resolve the pending user, and the redirect (mfa_required=1&mfa_methods=...) carries neither.Fix needs a small backend change (carry email + an offer/verify signal in the redirect query params) - out of scope for this repo, tracked separately.
Test plan
npx tsc --noEmitclean across the whole packageshould_offer_mfa_setup- zero hits.superpowers/sdd/review artifacts on the branch)