Description
Let someone who has forgotten their password get a reset link by email.
Depends on TRACK-96. That issue adds SMTP sending (SMTP2GO's free plan) and verified addresses. A reset only ever goes to a verified address.
Where things stand (checked 2026-09-28)
- Sign-in. Password sign-in takes a username and password (
POST /login→uiLogin→store.AuthenticatePassword). The form sits inside the "Log in with password" disclosure onlogin.html(details[data-password-login]). - No way back in. Someone who has lost their password and has no passkey can't get back in.
- Accounts without a password. Some accounts have no password (they signed up with a passkey). Others have password sign-in turned off. Both are tracked by
PasswordLoginState(HasPassword,Enabled,ActivePasskeys) instore/settings.go. - Pieces to reuse:
store.ValidatePassword(at least 12 characters), and bcrypt at the default cost, asChangePassworddoes.RevokeSessionAuthTokensForUser, which ends every web session. A password change already signs other browsers out (uiUpdatePassword).timingDecoyPasswordHashinstore/accounts.go. Sign-in already hides whether a user exists through response timing.- Pre-sign-in routes use
uiPreAuthCSRFMiddlewareandauthIPRateLimited. Per-account budgets useallowAuthIdentifier(auth_rate_limit.go). Referrer-Policy: no-referreris already set site-wide, so a token in the URL doesn't leak to other sites.
- SECURITY_MODEL.md accepted trade-offs:
- Usernames aren't secret, and sign-up already says "Username already exists".
- A password change and "Sign out everywhere" end web sessions only. API tokens and connected apps stay.
Flow
-
Ask.
- Add a "Forgot password?" link under the password form on
/login, shown only when email is configured. /forgot-passwordasks for a username or an email address.
- Add a "Forgot password?" link under the password form on
-
Give the same answer every time.
- The POST always shows "If that account has a verified email, we've sent a reset link." It's the same whether or not the account exists, has a verified email, uses only passkeys, or has been rate-limited.
- Don't send inline here, unlike TRACK-96. Respond first, then send in a background goroutine with its own timeout. Otherwise the response time would show whether an email was sent.
- This is still no queue. A lost send just means the person asks again.
-
The email.
- It goes to the verified address only, as plain text plus minimal HTML.
- It links to
TRACK_SLASH_PUBLIC_ORIGIN/reset-password?token=…. - It says the link expires in 30 minutes, and "If you didn't ask for this, ignore this email. Your password hasn't changed."
-
The token.
- 32 random bytes, of which only a SHA-256 hash is stored.
- Single use, expiring after 30 minutes.
- Asking again, or changing the password any other way, cancels older tokens.
-
Opening the link.
GET /reset-passwordonly shows the form (new password, entered twice) and never uses up the token.- This matters because mail scanners such as Outlook Safe Links open links before the person does.
-
Setting the password.
POST /reset-password:- checks the token;
- applies
ValidatePassword; - stores the bcrypt hash and marks the token used;
- ends every web session (
RevokeSessionAuthTokensForUser); - redirects to
/loginwith "Password updated. Sign in with your new password."
Don't sign the person in automatically.
-
The notice. Send "Your Trackslash password was reset" to the same address.
Rate limits
- Per IP: the existing
authIPRateLimited. - Per account: at most 3 reset emails an hour. Beyond that, sending is silently skipped and the page gives the same answer.
- Together these also keep us inside SMTP2GO's 200 emails a day.
Open decisions
- Passkey-only and password-off accounts. Recommended: respect the account's choice.
- The email says the account signs in with a passkey and links to
/login, with no reset link. - An inbox should never be able to add a password to an account that chose passkeys only.
- The alternative is to let the link add a password. That's friendlier, but it turns the inbox into a way into those accounts.
- The email says the account signs in with a passkey and links to
- Also revoke API tokens and connected apps on reset? Recommended: no, matching a password change and SECURITY_MODEL.md. The notice email points to Settings → Tokens instead.
- Accept an email address as well as a username on the form? Recommended: yes. The lookup matches verified addresses only, ignoring case.
Out of scope
- Account recovery for someone with no working passkey and no verified email.
- Resets started by an admin.
- API or MCP reset endpoints. Reset is browser-only, and connectors can't touch credentials (SECURITY_MODEL.md).
Done when
- With email on: a user with a verified address can reset from the login page and sign in with the new password. Every other session is signed out, and the notice arrives.
- With email off: there's no "Forgot password?" link and the routes return 404.
- No leaks: an unknown user, an unverified email, a passkey-only account and a rate-limited request all get the identical page, with similar response times.
- Scanner-safe: a GET of the link doesn't use up the token.
- Bad links: an expired, used or cancelled token shows "This link has expired. Ask for a new one." with a link to
/forgot-password. - Tests: integration tests cover all of the above using the capturing mailer, and check that tokens are stored only as hashes.
- SECURITY_MODEL.md says:
- a reset goes to a verified email only;
- it uses a single-use 30-minute token;
- it ends web sessions;
- what happens to API tokens, connected apps and passkey-only accounts, as decided above.
Linked issues
1Sub-issues
0GitHub
0No branches or pull requests linked.
Comments
0No comments.