fix(auth): let an unfinished MFA enrolment start over (#889) - #892
Merged
Merged
Conversation
Setup refused only a verified secret, then always inserted a new row. An enrolment left unfinished (tab closed, token expired) leaves an unverified row behind, and mfa_secrets.user_id is unique, so every later setup failed with 400. For a role that requires MFA that is a permanent lockout: each sign-in lands on enrolment, and enrolment cannot start. An unverified secret is now replaced in place by one conditional UPDATE (is_verified = false, tenant-scoped). It loses cleanly against a verification landing at the same moment, which then answers 409 as a verified secret always did. The abandoned key's last_totp_step and last_used_at are reset explicitly, not through a Save, as #849 requires. Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
3 tasks
This was referenced Oct 2, 2026
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.
Closes #889
A member whose role requires MFA and whose enrolment was ever left unfinished (tab closed, or the 15-minute enrolment token expired) could never sign in again.
SetupMFAUseCaserefused only a verified secret, then always inserted a new row. The unverified row from the earlier attempt made that insert fail on the uniquemfa_secrets.user_id, so/auth/mfa/setupanswered 400 on every later attempt, and every sign-in landed on that broken enrolment.What changes
UPDATE … WHERE user_id AND tenant_id AND is_verified = false, in the newMFARepository.ReplaceUnverifiedMFASecret. If a verification lands at the same moment, the update touches nothing and setup answers 409, as it always did for a verified secret.last_totp_stepandlast_used_atbelonged to the abandoned key, so they are reset explicitly rather than through aSave, which security(auth): a TOTP code can be replayed within its validity window #849's contract forbids.Verification
Run on this branch plus #888's one-file fix, because master does not compile until #888 is merged (#886), with a real Postgres:
New tests:
TestSetupMFA_ReplacesAnUnverifiedSecretandTestSetupMFA_VerifiedSecretIsKept, with a mock that enforces the uniqueuser_id. The old mock overwrote on insert, which is why this never showed up. Before the fix, the first test failed withduplicated key not allowed.TestGormMFARepository_ReplaceUnverifiedMFASecret, on sqlite and on Postgres. It checks tenant scoping, the reset of the replay step, and that a verified secret is left untouched.Live, reproducing the issue:
POST /auth/mfa/setup→ 200).400. With it, setup answers200with a new key, the code from that key answers200, and the user lands on the dashboard.Merge order
After #888: CI cannot build the backend until it is in.