Repository navigation
fix(auth): accept each TOTP code once - #596
Merged
Merged
Conversation
verifyTOTP accepted counters -1..+1 with no state, so an observed code could be spent again by Login, MFADisable or recovery-code regeneration for up to 90 seconds (RFC 6238 section 5.2). Record the last accepted counter in users.mfa_last_totp_counter (migration 000104) and claim it with a single conditional UPDATE, so a replayed or racing code is rejected as an invalid code. Refs #442
Contributor
|
Warning Review limit reached
This review includes 15 billable files and costs up to $3.75.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 14 minutes for your next included review. View limit detailsLimit details: You’ve used the included review currently available. Your 64 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Review configuration: ⚙️ Run configuration
📒 Files selected for processing (15)
Comment |
cristim
pushed a commit
that referenced
this pull request
Oct 16, 2026
… per account (#1000) The two items in the #442 body landed in #596 (TOTP counter) and #598 (lockout on lifecycle failures); this closes what was left open on the issue: - MFAEnable passes the enrollment code's TOTP counter to EnableMFA, which sets users.mfa_last_totp_counter in the same conditional UPDATE, so the code typed to turn MFA on cannot be replayed at Login inside its window. The counter is replaced, not raised, because it belonged to the previous secret. - Login's TOTP / recovery-code leg gets a per-account "login_mfa" bucket (10 / 15 min, keyed by the parsed, lower-cased email, fail-closed), so rotating source IPs no longer resets the code-guessing budget. - requireMFASession validates the session before the per-IP "mfa" bucket, so requests without a valid session do not spend that IP's budget. Closes #442 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Part 1 of #442 (TOTP replay). Refs #442; the issue stays open until the MFA lockout part lands.
users.mfa_last_totp_counter.ClaimTOTPCounteris a single conditional UPDATE, so a code is accepted once even when two requests race.Behavior notes:
updated_at: theupdate_users_updated_attrigger (000001_initial_schema.up.sql) fires on any users UPDATE. That is why twoassertOnlyChanged-style assertions in the CAS tests were relaxed.recordFailedLogin, so a double-submit counts toward MaxFailedLoginAttempts. This is intended.Not in this PR: lockout on MFA lifecycle failures and the per-user login key.