Skip to content

v4.5.0 — password reset, install telemetry, CSP font fix

Choose a tag to compare

@thunpisit thunpisit released this 05 Sep 07:43
· 29 commits to main since this release
60daa76

v4.5.0 — password reset, anonymous install telemetry, CSP font fix

Admins can recover their own accounts (#204)

Before this release there was no password-reset flow at all — no route, no UI, no sender. The rate-limit guard listed a /forget-password path that nothing served. An admin who lost their session had no way back in: /admin/signup closes after the first account, and sending an invite needs an admin session.

  • /admin/forgot-password → a one-hour, single-use token → /admin/reset-password, with a "Forgot password?" link on the login page
  • A durable 24h per-user send limit, held in D1 rather than KV. The repo already documents a KV limiter that "verified perfectly against local miniflare — and did nothing at all on the deployed Worker" (production KV caches reads ≥60s and is eventually consistent across colos). D1's strong consistency makes the claim one atomic upsert instead of a read-then-write race. A per-IP cap backs it up; addresses are stored only as SHA-256.
  • No account enumeration: match, no-match and throttled all return one shared opaque result, and the throttle returns inside the sender rather than at the route, because "already sent today" leaks more than mere existence.
  • A successful reset revokes every other session.

Sender identity falls back to Khao Pad (ข้าวผัด) by Codustry <no-reply@codustry.com>, but a deployment's own RESEND_FROM always wins — so each fork keeps sending from its own domain.

A bug found while building it: Better Auth 1.6.5 registers /request-password-reset, not /forget-password. The latter exists only inside the library's own rate-limit path matcher, which is why the pre-existing guard entry looked correct while guarding nothing. Posting to it 404s — and since the action swallows failures for enumeration reasons, that would have been completely silent: the form reports success and no mail is ever sent.

To enable: set RESEND_API_KEY (without it the send silently no-ops) and verify your sending domain in Resend.

Anonymous install telemetry, off by default (#199, #205, #207)

Built to the Next.js/Astro standard rather than the WordPress one. WordPress sends your site URL and IP on every update check with no opt-out — a decade-old complaint with plugins existing purely to strip it. This does the opposite:

  • A random install id — deliberately not a hash of the domain, because khaopad's domain set is small and enumerable, so a hash is reversible by dictionary attack
  • Content scale in buckets, never exact counts: an exact tuple re-identifies a site across pings even with the id stripped
  • Never sent: site URL, hostname, IP, user data, content, infrastructure ids
  • environment and a hasCustomDomain boolean separate a real deployment from someone running the dev server twice — the hostname is inspected locally to compute that boolean and immediately discarded

Four gates must all be open before a single byte leaves: the setting ticked in Settings, TELEMETRY_ENDPOINT configured, the weekly cron enabled, and KHAOPAD_TELEMETRY_DISABLED unset. A fresh install sends nothing. See docs/TELEMETRY.md.

The admin webfont was blocked by our own CSP (#199)

The admin layout links IBM Plex Sans Thai + Mono, but style-src blocked the stylesheet and font-src blocked the woff2 files — so every deployment's admin has been rendering in system fonts, including production. Nothing threw; the only evidence was a console entry. Found by opening the deployed demo and reading the console, not by any check. Both directives fixed and pinned by tests.

Upgrading

Nothing is required. Telemetry stays off unless you turn it on; password reset needs RESEND_API_KEY to send mail.

Gate: 0 lint errors · 0 type errors · 1114 tests · build · theme-contract guard · CSS-inventory guard.