Skip to content

release: v1.43.3 - #1236

Merged
MBombeck merged 6 commits into
mainfrom
fix/v1433-stepup
Oct 17, 2026
Merged

MBombeck merged 6 commits into
mainfrom
fix/v1433-stepup

Conversation

@MBombeck

Copy link
Copy Markdown
Owner

Patch release: second-factor confirmation dialog for every step-up-gated action (Refs #959).

A Guardian signed in through single sign-on with a second factor enrolled
could not hand a managed profile over once five minutes had passed since
sign-in: the handover answered 401 auth.stepup.required, and the panel
printed "Confirm your second factor, then try again" with no way to do so.

The same held for every action behind requireFreshMfa: creating, editing
and ending a managed profile, adding and removing a Guardian, disabling
the second factor, rotating recovery codes, removing a security key or a
passkey, changing the password, the data wipe, account deletion and the
key rotation. The recent-proof routes had the dialog; these had text.

- requireFreshMfa names the proofs that clear a stale stamp in
  meta.methods (the possession proofs the account holds, never a
  password, which stamps reproofAt only).
- The re-proof flow treats that refusal like auth.reproof.required:
  open the dialog, post the proof to /api/auth/reproof, send the request
  once more. The flow moves into a React-free module so it can be driven
  in tests; the hook binds it to the dialog.
- The managed-profile mutation hooks take a required proof guard, and
  every caller passes recentProof.run. The other surfaces wrap their
  requests in recentProof.run; disabling the second factor asks before
  its confirmation opens, so the code typed there is not spent twice.
- An account with nothing to re-prove in place (single sign-on only, no
  factor) is told to sign in again when adding a factor, instead of being
  offered a password it does not have.
- step-up-web-caller-guard freezes the gated route methods and fails when
  a web request to one of them runs outside the flow.

An account whose only second factor is the identity provider's is still
asked for an app factor on the managed-profile acts: the app cannot see
the provider's factor. Enrolling right after a sign-in needs no further
proof, and the confirmation stamps the session, so the act follows.

Refs #959
…lers

The re-proof dialog now opens from every fresh-factor refusal, so the hook
and the dialog are imported by more on-demand chunks, which the aggregate
counts once each. The build measured 4977 KB gz against the 4975 cap; every
watched route stays inside its allowance. Loading the dialog through
next/dynamic measured 4979 and grew every watched route, so it is not kept.

Refs #959
…e acts

An account whose only strong credential is a primary passkey was refused
auth.stepup.mfa_not_enrolled on every action behind requireFreshMfa, so it
could not create, edit, hand over or delete a managed profile or manage its
guardians, although a passkey sign-in or re-proof already stamps the
session the gate reads.

requireFreshMfa now defaults to the any-possession proof source: a passkey
makes the gate reachable, and a passkey re-proof through the dialog clears
it. The refusal offers passkey in meta.methods, never the password.

hasSecondFactorEnrolled keeps its narrow meaning on purpose. It decides
whether the conditional gates apply at all (account deletion, the data
reset, the password change, the export, offering manage access, the
recent-proof branches and the native Bearer rules), so none of those
change, and password sign-in still challenges only TOTP and security keys.
The MFA-management routes keep the second-factor source. The decision and
its limits are written beside FreshFactorProofSource.

Copy, the OpenAPI description and the sharing and key-rotation docs say
"second factor or passkey" where this gate applies.

Refs #959
…ged out

An aged-out step-up no longer ends in a refusal sentence on the card: the
refusal names the proofs it takes and the shared re-proof dialog opens above
the pending confirmation. The journey now asserts that dialog, that
cancelling it sends nothing more and leaves the codes as they were, and that
proving the factor there runs the rotation once more and issues a fresh
batch. The proof's code waits for the step after the enrolment code, which
the replay floor would refuse.

Refs #959
…creens

The decision screen is reached by a client navigation, during which the old
route's title unmounts and the new one mounts from a suspended metadata
boundary that can reveal after the body has painted. Under load the scan ran
in that stretch and reported document-title. The helper now asserts the
title first, so a screen that never gets one still fails.

Refs #959
@MBombeck
MBombeck merged commit 271e8d6 into main Oct 17, 2026
25 checks passed
@MBombeck
MBombeck deleted the fix/v1433-stepup branch October 17, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant