Repository navigation
Auth realms: accept client passwords over HTTP Basic per realm - #708
Merged
Merged
Conversation
Whether a client may sign in with a password (HTTP Basic on the feeds and /v2/token, npm login --auth-type=legacy) was one install-wide setting, allow_password_basic, and an enabled credential-forwarding realm turned it on for the whole chain: every local password then worked from every client, whatever the setting said. It is now each password realm's own accept_client_passwords: on by default for credential_forwarding realms (the Nexus case), off for the local realm. The realm snapshot builds a second chain for clients from the realms that accept them. A local realm that does not stays in it reject-only (LocalPasswordProvider::reject_only): a password a client sends for a local account is refused like a wrong one and never forwarded upstream. With no realm accepting client passwords the path is off. The browser login keeps the full chain, and an API token works as before. The upgrade (0006) copies allow_password_basic onto the local realm; the setting leaves the settings document and Admin -> Settings, and a PUT that still names it is refused with where it went. The realm form gets the checkbox on the local and credential-forwarding realms. The test harnesses, the Playwright setup and cargo xtask seed set the local realm's flag instead. Fixes #647 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJXRhLYxFjzrWiwKFKc
CodeQL's cleartext-logging query took the realm flag for a secret by its name: the tuple holding the local provider's position and `accept_client_passwords` was tainted, and the position reached `Vec::insert`, whose panic message goes to stderr. The flag is a yes or no, not a password. Its Rust field is `accepts_clients` now, still `accept_client_passwords` in the realm's config. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJXRhLYxFjzrWiwKFKc
Yarn's legacy login sends the admin's local password, and the local realm now takes passwords from clients only once it accepts them. The step turns that on through the realm's config, as the Rust and web e2e harnesses do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJXRhLYxFjzrWiwKFKc
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 #647
What was wrong
Whether a client could sign in with a password was one install-wide setting,
allow_password_basic(off by default). An enabled credential-forwarding realm turned it on by itself for the whole chain (RealmSnapshot::forwards_credentials). So enabling the Nexus realm made every local password work from every client, whatever the setting said. The npm legacy login was not gated at all.The change
accept_client_passwords, on the two password realms:credential_forwarding: on by default, including the realm seeded from[auth.upstream_basic];local: off by default (newLocalRealmConfig, which refuses unknown keys like the other kinds).crates/server/src/auth/realms.rs):chain: every password realm, for the browser login (POST /auth/login, and the/cli-loginpage behind it), unchanged;client_chain: the realms whose flag is on. It serves HTTP Basic on the feeds and/v2/token(impl ChainSource for RealmAuth) and the npm legacy login (verify_password_login(…, PasswordFrom::Client)). It isNone, so the path is off, when no realm accepts client passwords.impl AuthProvider for Arc<P>), so there is one HTTP client and one verdict cache per realm.LocalPasswordProvider::reject_only()(crates/core/src/auth/local_password.rs):Upgrade
migrations/0006_realm_client_passwords.sql): the local realm takes the value ofallow_password_basicfrom the stored settings (off when absent).[auth.upstream_basic]therefore keeps matching its seed fingerprint and keeps re-applying.AuthSettings.PUT /admin/settings/authrefusesallow_password_basicwith a 400 that says where it went. A leftoverauth.allow_password_basicin the config file was already refused at startup; that message now names the realm flag.Admin API and web UI
/admin/realmstakes and shows the flag in the realm'sconfig.Harnesses and docs
cargo xtask seedset the local realm's flag instead of the setting. A newTestServer::set_local_client_passwordshelper does it in tests.docs/deployment.md: Password Basic auth becomes Passwords from clients, and the settings table loses the row;docs/docker.mdanddocs/npm.mddescribe the per-realm flag;DEVELOPMENT.mdandREADME.mdare updated to match.647.feature.mdand647.upgrade.md.Tests
crates/server/tests/client_passwords.rs(new):docker login(/v2/token) andnpm login --auth-type=legacywork. The local admin's password is refused on each, with exactly the answer of a wrong password (status and body). The upstream received no request naming the local user. The admin's API token works on the feeds,docker loginand npm. The browser login takes both users' passwords.allow_password_basictrue,false, absent, and no settings row. The forwarding realm's config is unchanged, and the settings document still loads.crates/core/tests/local_password.rs:reject_onlyrefuses an active local account's right password, and skips an unknown name, with and without forwarding after it.auth_settings.rs: the settings no longer carry the key, and a PUT naming it gets a 400;service_accounts.rsuses the new helper;realms.test.tsx: the local realm's checkbox, off and sent when checked, and a forwarding realm's, on and sent asfalsewhen unchecked;RealmForm.test.ts: round trips;settings.test.tsx: the checkbox is gone.Checks run
cargo test -p forge-core --lib --test local_password --test upstream_basic.cargo test -p forge-server --liband--testforclient_passwords,auth_settings,service_accounts,auth_realms,realm_seed,credential_kinds,invitations,release_hardening,trusted_header,npm,registry,raw,admin,admin_rbac,me_tokens,telemetry. All passed.rawandregistryneededRUST_MIN_STACK=16777216in this session:raw'sgroups_fall_through_their_members_by_priorityoverflows the default test stack here onmaintoo, with the full debug info this session builds with. CI builds with line tables only.cargo clippy -p forge-core -p forge-server -p xtask --all-targets -- -D warnings,cargo clippy -p forge-e2e --all-targets --features forge-e2e -- -D warnings,cargo test -p xtask,cargo fmt --all --check,cargo xtask changelog --check,cargo xtask docs --check.pnpm run tsc,pnpm run e2e:tsc,pnpm run test(527 passed).forge-e2e) and Playwright; CI runs them.Generated by Claude Code