Refresh account pages, speed up /account, and fix 2FA sign-in from OAuth - #175
Merged
Merged
Conversation
… from OAuth Account page - Redesigned account, sign-in, consent, and two-factor pages with a single light/dark stylesheet (drops Pico). - Sessions are grouped by app, most recently used first, with per-session and per-app sign out. Revoking uses the session's row ID instead of putting the access token in the page, and requires a same-origin POST. - "Add another account" works while signed in. Account page speed - Only live sessions are loaded: the query filtered on created_at < now+2y (every token ever issued), and the page then showed all of them anyway. - App names are looked up once per app, in parallel, with a 2s cap and a cache, using metadata only (no JWKS fetch). - Tokens past every lifetime or from an old session version are pruned hourly. - The client JWKS cache was never written; it now is. Sign-in and OAuth - A second factor is now its own step. After the password checks out, the pending account and OAuth return are kept in the session, so the code page asks only for the code and always returns to /oauth/authorize. - Viewing the consent page keeps the OAuth request alive during slow 2FA. - Signed-out consent POSTs return to sign-in with the request. - Reject redirects to the client with error=access_denied. - Accept only succeeds once; expired/used requests get a readable page. - Scopes are described in plain language on the consent page.
haileyok
marked this pull request as ready for review
September 28, 2026 23:55
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.
Summary
/accountfast for accounts with many sessions: it was loading every OAuth token ever issued and looking up client metadata once per token.Changes
created_at < now + 2ymatched every token), and the page then rendered all tokens.liveOauthTokensnow reads only sessions that can still be refreshed (current session version, within the session and refresh lifetimes).ClientManager.GetMetadata, no JWKS fetch). Apps that don't answer are shown by hostname.oauthTokenCleanupRoutinedeletes tokens past every lifetime or from an old session version./account/signin/verifyasks only for the code, then redirects to/oauth/authorize?<query>. A password reset between the steps cancels the pending sign-in.client_id/request_uriare treated as an OAuth return (fixes "Sign in another account" from the dashboard, which previously bounced back to/account).redirect_uriwitherror=access_denied(previouslyclient_uri). The redirect uses&when the URI already has a query.sub IS NULL AND code IS NULL), so it only succeeds once. Expired or used requests render a readable page instead of JSON.describeScopes), with raw scopes behind a toggle. It also pre-fillslogin_hintand names the app on the sign-in page.id(orclient_id) instead of the access token, which used to appear in the page HTML, and requires a same-origin POST.partials.html(head, header, flashes, icons) and a new stylesheet.pico.cssand the unusedalert.htmlare removed.initialtemplate func andHostnameare injected by the renderer.Validation
gofmt -l .: cleango vet ./...: cleanCGO_ENABLED=1 go test -race ./...: all packages passserver/signin_oauth_redirect_test.go: two-step 2FA from OAuth returns to/oauth/authorize, no-pending and session-version-change restarts, pending expiry,oauthReturnQuery, add-account from dashboard, live-token filtering, grouping by app, pruning, revoke scoping (other accounts' sessions and cross-site requests), reject →access_denied, signed-out POST keeps the request, accept-once,describeScopes.TestSigninPageTOTPfor the new code-step redirect andTestAccountPageEscapesClientNamefor the new data shape (also asserts nojavascript:links).Review notes
Origin/Refererto match the host (same check as/account/switch).username+password+tokentogether still work (single-POST path kept).