Browser login without an SSH key !261

merged merged by cmc on 2026-09-05 04:20 UTC · krz/gitbay:email-login into main

Discussion

cmc

An account with no SSH key could not use the web UI at all. CreateWebSession has one caller, the /login?token= handler, and only web login over SSH could mint a token for it; web signup requires pasting a public key.

An unauthenticated POST /login now mails the same one-time token to a verified address, resolved by username or address. Bounded at five an hour per account in the store, indexed, and by the existing token bucket per source. The response does not vary with whether the account exists, whether its address is verified, whether the account is disabled or pending, whether it is over its budget, or whether the identifier was empty — including on timing, which is why the send is asynchronous.

The session cookie moves from SameSite=Strict to Lax, because a link clicked in a mail client is a cross-site navigation and Strict can drop the cookie through the redirect that follows. Every cookie-authenticated mutating route carries checkOrigin; the POST routes that do not take bearer tokens or no auth at all.

Review caught a suspension bypass that the first draft introduced. The disabled check lived in Dispatch, and every previous way to mint a login token went through it. login() now re-reads the user after consuming a token and refuses a disabled account with the same response a bad token gets, and SetUserDisabled drops unclaimed login links along with sessions and API tokens. The guard is the stronger of the two: SetUserDisabled is not transactional, so a request in flight between its UPDATE and its DELETE could otherwise still open a session.

Emailed links live fifteen minutes; the SSH-minted path keeps its five. This does not widen what a browser session can do — the web dispatches with ViaAPI: true, so no SSHOnly command is reachable from one however it was obtained.

Parity updated: gitbay.wiki 5593f9c. Session minting is no longer listed as SSH-only by design — what a browser submits is a username or an address, not a credential, and the credential comes back by mail.

Design: docs/specs/2026-09-04-email-login-design.md.

Closes #155