ReviewOS

stacks/analyticshq

[auth] There is no password reset — the only recovery path is an operator on the prod box

#34
Closed glennmichael123 opened this 9 days ago ·
9 days ago
$ grep -rniE 'forgot|reset-password|password-reset|passwordReset' resources/ routes/ app/ | wc -l
0

No route, no view, no link. login.stx has no "Forgot your password?" affordance at all.

The backend is already there — the framework exposes POST /password/forgot and POST /password/reset, and config/auth.ts:82-88 already carries the reset-token expiry setting. Nothing renders them.

So today, a user who forgets their password cannot recover the account. The only path is an operator SSHing into production and running scripts/account.ts --rotate. That is not a viable support story for a product taking signups.

Scope

  • /forgot-password view: email field → POST /password/forgot, always render the same neutral confirmation regardless of whether the address exists.
  • /reset-password view: reads the token from the URL, posts the new password.
  • The mail template for the reset link.
  • A link on login.stx.

Note

Worth confirming the framework endpoints are actually mounted in this app before building the UI against them — config/auth.ts having the setting is not proof the routes are live.

9 days ago

The flow already exists — closing, but the issue did surface a real defect, which is fixed.

This issue's evidence was a grep returning 0. It is stale: resources/views/forgot-password.stx and reset-password.stx both exist on the auth layout, login.stx links to /forgot-password, and routes/auth.ts registers all three endpoints. Landed in 7fadd77.

What was actually broken was the endpoint's behaviour. Measured against the running API:

addressresponse
a real account500 "Failed to send password reset email."
unknown404 "No account found with this email address."

Different status and different body on an unauthenticated endpoint — anyone could walk a list of addresses and learn which had accounts. Both now return an identical 200.

The cause was not this app's logic. stacksjs/stacks#2214 fixed it upstream and the fix was sitting in the installed package, but the copy that executes is the vendored storage/framework/defaults tree, which nothing refreshed — it sat at 2026-07-23, 592 files behind. Re-vendoring alone produces the correct behaviour (30a46f1); an app-level override was tried first and reverted, because copying a framework action into app/ freezes the endpoint at the version copied.

Guarded by tests/unit/password-reset-privacy.test.ts, which asserts the drift rather than any app source.

Sign in to comment on this issue.