Repository navigation
Client passwords: a closed local realm's refusal no longer counts towards the login lockout - #845
Merged
Merged
Conversation
A local realm that accepts no passwords from clients holds its provider reject-only in the clients' chain. It refused a password for one of its accounts as Rejected, which BasicAuthSource and the npm legacy login counted as a failed sign-in on the (network, username) lockout that POST /auth/login shares. A client still configured with the password locked its owner out of the browser login. The reject-only provider now answers NotAccepted, folded by the chain into ChainOutcome::NotAccepted, which both paths answer like a wrong password (401, same dummy-hash timing) but release the attempt: nothing was checked, so it is no guess. Wrong passwords on realms that check them still count. Fixes #795 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJZsSSxZHKzUeJ3TMiG
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.
Fixes #795
Problem
When a local realm accepts no passwords from clients, the clients' chain holds its provider as
reject_only. That provider refused a local account's password asAuthOutcome::Rejected.BasicAuthSourceandverify_password_login(the npm legacy login) then counted it as a failure on the same (network, username) lockout thatPOST /auth/loginchecks. A client still configured with the password (an IDE's NuGet restore, docker) locked its owner out of the browser login. That is the login the upgrade note sends them to, so they can get a token.Change
AuthOutcome::NotAccepted(new): a provider owns the name but takes no password for it here.LocalPasswordProviderreturns it instead ofRejectedfor every name it owns when it isreject_only: active, inactive, invited or service account. It still runs the dummy argon2 verification first, so the timing is the same as before.ChainOutcome::NotAccepted(new): it is terminal for a local provider in the same way asRejected, so the password is never forwarded upstream (Auth realms: a credential-forwarding realm that accepts no client passwords lets its users' passwords go to the next realm's upstream #756 is unchanged). From a non-local provider it folds likeRejected. The metrics record it asRejected.BasicAuthSourceandverify_password_loginanswer it exactly like a wrong password (SourceOutcome::Invalid/LoginRefusal::Invalid, the same status and body) but callattempt.released()instead ofattempt.failed(). Nothing was verified, so there is nothing to brute-force.docs/changes/2.2.0/795.fixed.md.No admin API or UI change.
Tests
crates/server/tests/client_passwords.rs,a_closed_local_realms_password_from_clients_does_not_lock_the_browser_login:npm login --auth-type=legacywith it, all 401 (with the old mapping this fails: npm got 429);POST /auth/loginwith it from the same address then succeeds;login_max_failureswrong browser passwords still lock the pair (429).crates/core/tests/local_password.rs:NotAccepted;a_reject_only_refusal_is_not_counted_towards_the_lockout: the chain givesNotAccepted, Basic answers 401, the throttle tracks nothing and the upstream is never called, while a wrong password through a checking provider is still counted.cargo clippy -p forge-core -p forge-server --all-targets -- -D warnings,cargo fmt,cargo xtask changelog --check, the forge-core auth unit tests,forge-corelocal_passwordandupstream_basic, andforge-serverclient_passwords,auth_realms,auth_settingsandnpm. All pass.Note
There is one remaining difference between names. When the local realm is last in the clients' chain (no forwarding realm after it), a name the local store does not own still counts towards the lockout, and a local account's name now does not. Over the npm legacy login, the lockout's 429 can therefore tell those names apart after 10 tries. With a forwarding realm after the local one, a local account was already told apart (by timing, as
forwarding_followsdocuments). The issue asks for the release, so this PR keeps it.Generated by Claude Code