Files
TREK/wiki/Password-Reset.md
Maurice 9aab9e7513 feat(auth): track sign-in sessions so a session can end before it expires
A session token was a signed JWT and nothing else. Logout only cleared
the cookie, a 30-day "remember me" token stayed valid wherever a copy of
it had ended up, and nobody could sign out one device short of changing
the password or rotating the JWT secret for everyone.

Every session token now carries an id (jti) naming a row in the new
user_sessions table (id, user_id, created_at, last_seen_at, expires_at,
revoked_at, user_agent cut to 256 characters; no address is kept). The
rows live behind UserSessionsRepository and the new sessions domain,
whose SessionsService is the one issuer: AuthService.generateToken and
the OIDC sign-in both go through it, and sliding renewal re-signs a
tracked token under the same id and moves its expiry. verifyJwtAndLoadUser
takes the repository and refuses a token whose session is revoked,
expired or gone, refreshing last_seen_at at most every five minutes.
A token without a jti was issued before this change; it passes until its
own expiry (or a password change), so the upgrade signs nobody out, and
renewal turns it into a tracked session.

Sessions end on logout (the one the request names, verified even when
expired), on a password change (all, the device gets a new one), a
password reset, an admin setting a password, reset-admin.js, an MFA
disable (all but the current one), an admin clearing a user's MFA, and
with the account. GET /api/auth/sessions lists the active sessions,
DELETE /api/auth/sessions/:id ends one and POST
/api/auth/sessions/revoke-others ends all but the current, with their
contracts in @trek/shared and two new audit actions. There is no UI for
them yet. MCP exposes no account or session tools, so there is no
parallel tool to change; an MCP call made with a session JWT as bearer
goes through the same check.

The token in the login, register, demo-login, MFA and passkey bodies
stays for API clients but is marked deprecated in the API docs and in
the client type, which never read it.

A nightly session-purge job on CronRegistrarService deletes the expired
and revoked rows. The migration is 042500. The sessions domain gets its
own coverage entry at the 80 floor until a full run measures it.
2026-10-08 18:43:06 +02:00

5.7 KiB

Password Reset

Self-service reset (Forgot password?)

TREK supports email-based self-service password reset. On the login page, click the "Forgot password?" link to go to /forgot-password. Enter your email address and submit — if the address matches a local account, a reset link is sent to that inbox. The page always shows the same confirmation message regardless of whether the email was found, to prevent account enumeration.

Reset your password card with a Back to sign in link, an email field and a Send reset link button, plus a warning that SMTP is unconfigured so the reset link is written to the server console

No SMTP configured? When the server has no SMTP credentials set up, the reset link is not emailed. Instead, it is printed to the server console inside a clearly-fenced block so a self-hoster can copy and relay it manually. The forgot-password page also shows a visible hint that SMTP is unconfigured.

Reset flow

  1. Click "Forgot password?" on the login page.
  2. Enter your email address and submit.
  3. Open the reset link from your email (or console) — valid for 60 minutes.
  4. Enter a new password. If your account has MFA enabled, you must also supply a valid TOTP code or backup code before the reset completes.
  5. After a successful reset the page shows a Password updated confirmation ("You can now sign in with your new password.") with a Sign In button back to the login page — there is no automatic redirect. All existing sessions are invalidated — every device is signed out immediately.

Security properties

Property Detail
Token entropy 256-bit cryptographically random, base64url-encoded
Storage Only the SHA-256 hash is stored in the database — never the raw token
Expiry 60 minutes, single-use; any prior unconsumed token is invalidated when a new one is issued
Enumeration safety /forgot-password always returns {ok:true} with a minimum response latency pad
Rate limiting 3 requests / 15-min per IP on /forgot-password; 5 requests / 15-min per IP on /reset-password
MFA gate If the account has 2FA enabled, a valid TOTP code or backup code is required to complete the reset — a compromised mailbox alone cannot take over a 2FA-protected account
Session invalidation Resetting the password ends every session of the account on the server and bumps the password_version on the account and the pv claim in all JWTs, so every live session is rejected immediately, including ones from before sessions were tracked
Audit log user.password_reset_request, user.password_reset_success, and user.password_reset_fail events are recorded

SMTP requirement

The email delivery uses the same SMTP settings as other notification emails. See Environment-Variables for SMTP_* configuration.

OIDC accounts

Any account linked to an identity provider cannot use the forgot-password flow — one created by SSO, but equally a local account that was auto-linked to the IdP on its first SSO login. The forgot-password page still shows the generic confirmation to avoid revealing whether the email is SSO-linked. Continue using OIDC-SSO to sign in; a local password can still be set by an admin, or by you from Settings → Account if you know your current one.

Password login disabled

If the admin has globally disabled password login, no reset link is ever issued. For enumeration safety the endpoint still answers with the same generic confirmation, so the page looks exactly as it always does — the email (or the console line) simply never arrives. The refusal is recorded in the audit log as user.password_reset_request with reason: password_login_disabled.

Admin-initiated reset

An admin can set a new password for any user directly from the admin panel (Admin → Users, edit the user). Leaving the New Password field empty keeps the current password; entering one saves it immediately — no email is required. Setting a password this way also ends every session of the account and bumps its password_version, which signs the user out everywhere; separately, it deletes all of their MCP tokens and revokes their OAuth tokens.

There is no "Force password change on next login" option in the Admin Panel. That prompt is raised only for the admin account TREK seeds on first boot and for accounts restored with the reset-admin.js recovery script (see Troubleshooting). An admin-set password neither raises the flag nor clears it, so a user who already carries it is still asked to choose their own password at the next sign-in.

See Admin-Users-and-Invites for step-by-step instructions.

Password requirements

When choosing a new password (whether via the reset flow, the forced-change prompt, or the Change Password form in Settings → Account) the password must:

  • Be at least 8 characters long
  • Contain at least one uppercase letter
  • Contain at least one lowercase letter
  • Contain at least one number
  • Contain at least one special character
  • Not be a commonly used password
  • Not consist of a single repeated character

While you type, the form lists the five character rules under the password field and ticks each one off as it is met, so you see what is still missing before you submit.

Rate limiting

Endpoint Limit
/auth/forgot-password 3 requests / 15-min per IP
/auth/reset-password 5 requests / 15-min per IP
Password change (settings) Rate-limited per IP

See also: Login-and-Registration · Admin-Users-and-Invites · Two-Factor-Authentication · OIDC-SSO