Repository navigation
Login: remember accounts on the device and offer them as a picker - #636
Merged
Merged
Conversation
After someone has signed in, the login screen shows their account the next time instead of an empty username field, like Microsoft's "Pick an account". One click continues as that account; "Use another account" shows today's form, and each tile can be forgotten. - The SPA keeps the accounts in localStorage (forge.accounts, web/src/ lib/accounts.ts): the identifier, the IdP's display name, the method with its realm and the last use, newest first, at most five, one entry per account whatever its case. Never a password, a token or the session. Every access is wrapped; with storage blocked the picker is simply not shown. The server lists no accounts, so /auth/identify still answers from public realm configuration alone. - A password sign-in is remembered when it succeeds. An OIDC sign-in leaves a pending marker in sessionStorage before the browser leaves, and the first /auth/me after the round trip turns it into an entry; coming back to the login screen drops it. - A tile continues at the password step (identifier-first: once /auth/identify confirms the identifier still signs in with a password; combined: the form with the username filled in), or at the IdP of an OIDC realm the login options still list. An OIDC account whose realm is gone is routed by /auth/identify, so it cannot loop. - GET /auth/oidc/login takes an optional login_hint, trimmed, at most 256 bytes and without control characters (anything else is dropped, logged at debug level with its size only), and passes it to the authorize request. Tiles and the identifier-first form send it, so the IdP does not ask for the account again. - remember_accounts, a new auth setting (default true), turns the picker off for kiosks and shared computers: GET /auth/login-options reports it, and with it off the screen keeps none and forgets those kept before. It is an auth setting rather than a forge.toml key, like login_mode, with its checkbox under Admin -> Settings -> Sign-in. - /auth/me reports the IdP's display name for an OIDC session; the callback now links the session to the identity that signed in. Fixes #115 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJbsBcmhipReXByB1cF
… as a password CodeQL's js/clear-text-storage-of-sensitive-data took passwordFallbackOptions for a password by its name and followed it through options.oidc[].name into the pending OIDC sign-in that beginOidcSignIn keeps in sessionStorage. What is stored there is a realm name, which /auth/login-options publishes; the constant is the login options while those cannot be read, and is now named so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJbsBcmhipReXByB1cF
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 #115
After someone has signed in, the login screen shows their account the next time instead of an empty username field, like Microsoft's "Pick an account". One click continues as that account. Use another account shows today's form, and each tile can be forgotten. Signing out keeps the account, and so does a session-expired redirect to
/login.What changes
Remembered accounts (client-side only) —
web/src/lib/accounts.tslocalStorageunderforge.accounts. Each entry holds the identifier, the IdP's display name, the method with its realm, and the last-used time. The list is newest first, at most 5, with one entry per account whatever the case (usernames are canonical lowercase/NFC).sessionStoragebefore the browser leaves. The first/auth/meafter the round trip (syncAuthSession) turns it into an entry. If the browser comes back to the login screen instead, the marker is dropped, and a marker older than the flow's 10 minutes is ignored.Picker —
web/src/routes/Login.tsx/auth/login-optionsstill lists: goes straight to/auth/oidc/loginwithredirect_toandlogin_hint./auth/identify, like a typed identifier, so it cannot loop./auth/identifyconfirms the account still signs in with a password. A domain an OIDC realm has taken over since goes to that IdP instead.?username=skips the picker.login_hintto the IdPGET /auth/oidc/loginaccepts an optionallogin_hint, which is trimmed and must be ≤ 256 bytes with no control characters.begin_login(core) adds it to the authorize URL as an encoded parameter.remember_accountssetting (defaulttrue)forge.tomlkey (see the note below).GET /auth/login-options, set through/admin/settings/auth, and has a checkbox under Admin → Settings → Sign-in./auth/medisplay name/auth/menow returnsdisplay_name: the IdP's name for an OIDC session,nullotherwise.external_identityrow. That column existed but was never filled.external_identity_dao::display_name, with its.sqlxfile.Deviation from the issue
The issue proposed
auth.remember_accountsinconfig.rsandforge.toml.example. Since #294, though, every login-screen policy (login_modeincluded) is a database auth setting, and startup refuses those keys inforge.toml. Soremember_accountssits next tologin_modeinAuthSettings. That also gives it the admin screen AGENTS.md asks for. A stored settings row from before this change reads it astrue.Tests
login_hint, the fallback for a disabled or renamed realm, forget, use another account, expired session, invitation, setting off, options unavailable). Also the admin settings checkbox. The full web suite passes (498 tests), andtscis clean for the app and e2e.sanitize_login_hintunit test.build_authorize_urlwith a hint (encoded, cannot add parameters).oidc_routes::login_passes_a_valid_login_hint_to_the_idp: valid, encoded, blank, control character, 256 vs 257 bytes.auth_me_returns_identity_and_csrf_tokennow checksdisplay_name.auth_settingscoversremember_accountsthrough the API and/auth/login-options.forge-coreOIDC flow tests,oidc_routes,auth_settingsandmepass locally.cargo clippy --workspace --all-targets -D warnings,cargo fmt --check,cargo sqlx prepare --checkandcargo xtask changelog --checkare clean.auth.spec.tssigns in, signs out, then signs in again from the tile. The whole e2e suite passes locally (22/22).Changelog:
docs/changes/2.2.0/115.feature.md. The setting and the behaviour are documented indocs/deployment.mdunder "Access and sign-in settings".Generated by Claude Code