Skip to content

fix(auth): keep the typed email when the sign-in form shows an error - #77

Merged
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/auth-form-loses-typed-email
Jul 30, 2026
Merged

fix(auth): keep the typed email when the sign-in form shows an error#77
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/auth-form-loses-typed-email

Conversation

@clawedassistant26

Copy link
Copy Markdown
Contributor

The bug

authPage() in apps/pwa/src/routes/auth.mjs renders the email input with a value so a visitor does not have to type their address again. It reads that value from req.query.email.

Every path that re-renders this page with an error is a POST, so the address is in req.body, not the query string. All four error paths come back with the field blank:

what the visitor did error shown email field
signed in with the wrong password Wrong email or password. blank
signed up with a 5-character password Password must be at least 8 characters. blank
signed up with an address that already has an account That email already has an account - sign in. blank
signed up with a malformed address Enter a valid email. blank

So mistyping your password on sign-in costs you your email address too.

Repro

Run against unmodified main (df9d1e0), real authRouter + real libsql file db, no fault injection:

sign-in, wrong password
  status 200 | error: Wrong email or password.
  typed:    "taken@example.com"
  rendered: value=""  <-- LOST

sign-up, password too short
  typed:    "newbie@example.com"
  rendered: value=""  <-- LOST

sign-up, email already taken
  typed:    "taken@example.com"
  rendered: value=""  <-- LOST

sign-up, malformed email
  typed:    "not-an-email"
  rendered: value=""  <-- LOST

control GET /?email=query@example.com -> value="query@example.com"

The fix

Read the submitted body first, fall back to the query string:

const email = req.body?.email || req.query.email || "";

The /?email=... prefill keeps working, and the value still goes through esc().

Tests

New apps/pwa/test/auth-form-email.test.mjs, 3 tests, same throwaway-libsql + skip-guard shape as logout-csrf.test.mjs:

  1. all four error paths keep the typed email (and still show the right error)
  2. a hostile address cannot break out of the value attribute - now that submitted input is echoed, a\"><script>alert(1)</script><input x=\" round-trips through the field with no markup escaping it
  3. control: /?email=... still prefills, an empty form is empty (not \"undefined\"), and correct credentials still 302

Stash-verified by reverting only the source change: 2 fail / 1 pass unpatched, 3/3 patched. The control passes both ways, so it is not vacuous.

Suites: apps/pwa 30 -> 33, root npm test 204 -> 207, 0 failures before and after.

Note for test 2: the assertion decodes HTML entities before comparing, because a browser decodes the attribute before submitting it. Comparing raw markup would measure the escaping instead of what reaches the server.

The email field renders a value attribute so a visitor does not have to
retype their address, but it read req.query.email. Every path that
re-renders this page with an error is a POST, so the address is in the
body: a wrong password, a password under 8 characters, an email that
already has an account and a malformed email all came back with the
field blank.

Read the submitted body first and fall back to the query string, so an
existing /?email=... link still prefills the field.
@ralyodio
ralyodio merged commit 1416c44 into moshcoder:main Jul 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants