sign in with an email or a username

Only an address worked, though usernames are already unique and already
validated as /^[a-zA-Z0-9._-]+$/ — which is what makes this safe: no `@`, so
the two namespaces cannot overlap and there is nothing to guess. An `@` decides
which lookup runs, and it decides it exactly.

The field is still called `email` on the wire, because every client sends that
name and renaming it would break them for nothing.

Fixed a latent bug while passing: the JWT took `email` from the REQUEST rather
than the row. Identical while an address was the only way in — and a username
in the `email` claim the moment it was not, leaving the DAV door, the managed
home path and everything else keyed on email looking up a user who does not
exist.

The login field is `type="text"`, not `type="email"`, or the browser refuses to
submit a username and validates someone out of an account they have.
`autoComplete="username"`, which is what password managers store beside a
password for a field that takes either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 11:10:39 +00:00
co-authored by Claude Opus 5
parent 1e03c600b8
commit b27410a6fc
3 changed files with 56 additions and 7 deletions
@@ -46,13 +46,17 @@ export function Login() {
<form ref={formRef} onSubmit={handleSubmit} className="grid gap-4">
<Label className="grid gap-2">
<span className="text-duck-dark/70">Email</span>
<span className="text-duck-dark/70">Email or username</span>
<Input
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark placeholder:text-duck-dark/40"
type="email"
// `text`, not `email`: the browser refuses to submit a username under type="email", so the
// field would have validated the user out of an account they have.
type="text"
name="email"
placeholder="you@example.com"
autoComplete="email"
// `username` rather than `email`, which is what password managers expect for a field that
// takes either — and it is the value they already store beside the password.
autoComplete="username"
/>
</Label>