Skip to content

Password reset

Kilat implements a self-service password reset flow in src/server/routes/auth.routes.ts with hashed reset tokens and pluggable email delivery.

  1. GET /forgot-password — renders the email form.
  2. POST /forgot-password — if the email exists, creates a reset token and emails a link. Always renders { status: "sent" } regardless of whether the account exists — no user enumeration.
  3. GET /reset-password?email=…&token=… — renders the new-password form, pre-filled from the query string.
  4. POST /reset-password — validates the token, checks password confirmation, hashes the new password, clears all reset tokens for that email, and redirects to /login?notice=password_reset.
const token = await createPasswordReset(user.email);
const link = `${config.appUrl}/reset-password?email=${encodeURIComponent(user.email)}&token=${token}`;
await sendMail({
to: user.email,
subject: "Reset your password",
text: `Reset your password:\n${link}\n\nThis link expires in 60 minutes.`,
html: `<p><a href="${link}">Reset password</a></p>`,
});

Reset tokens are 256-bit random and hashed at rest — the password_resets table stores only SHA-256(token), never the raw token. The raw token travels only in the email link.

export const RESET_TOKEN_TTL_MS = 60 * 60 * 1000; // 1 hour
Property Value
Token length 256-bit random, hex-encoded
Stored as SHA-256 hash (token_hash PK)
Expiry 60 minutes (RESET_TOKEN_TTL_MS)
Cleanup All tokens for the email cleared after reset

verifyPasswordReset checks both the hash match and the expiry timestamp. Expired or invalid tokens return a 422 with “This reset link is invalid or has expired.”

Mail drivers live in src/server/mailer.ts — plain fetch, zero dependencies. Select one via MAIL_DRIVER in wrangler.toml [vars].

Driver Use case Required env
log Dev / tests none — prints to console, records in sentMails
resend Production (Resend.com) RESEND_API_KEY, MAIL_FROM
mailtrap Testing (Mailtrap.io) MAILTRAP_API_TOKEN, optional MAILTRAP_INBOX_ID
wrangler.toml
[vars]
MAIL_DRIVER = "log"
MAIL_FROM = "no-reply@example.com"

For production with Resend:

Terminal window
wrangler secret put RESEND_API_KEY
[vars]
MAIL_DRIVER = "resend"
MAIL_FROM = "you@yourdomain.com"

config.ts validates the driver: MAIL_DRIVER=resend requires RESEND_API_KEY; MAIL_DRIVER=mailtrap requires MAILTRAP_API_TOKEN. An invalid driver name throws at boot.

The log driver pushes every sent mail into an exported sentMails array and prints a formatted box to the console. This is what the test suite asserts against — no SMTP server needed.