Config keeps working.
Login throttle
Config.LoginThrottle (nil selects defaults, Disabled: true opts out).
The check runs before the user lookup and the password hash, so unknown and
known emails cost the same. The in-memory store sweeps expired entries on
writes and is capped (
MaxEntries, default 100000). The limiter serializes
read-modify-write inside one process; a shared store across processes is
last-writer-wins, which is acceptable for throttling.
Client IP comes from the connection address. Behind a reverse proxy every
client shares the proxy address for the (IP, email) key, so the per-user
lockout is the control that still bites; terminate or forward accordingly.
Admin override: a.UnlockUser(ctx, email) clears the email lockout and the
user’s MFA lockout. a.ResetPasswordAdmin does the same after a reset.
MFA
TOTP codes are accepted once. The last used time-step is stored via the optionalTOTPReplayStorage capability (AdvanceTOTPStep); storage/memory
implements it. Without the capability the step is tracked in process memory.
Attempt limits are keyed by user, so opening new pending sessions does not
reset them.
First-run bootstrap
POST /auth/email-password/signup needs the token in the
X-Setup-Token header or a setupToken body field. A generated token is
logged once at startup (SuppressSetupTokenLog hides it; read it with
a.SetupToken()). After the first user exists signup returns 403
signup_closed unless OpenSignupAfterFirstUser is set. Magic-link account
creation follows the same gate and cannot present a token. OnFirstUser is the
place to grant an admin role. GET /auth/bootstrap/status returns
{"needsSetup":bool}. Token guesses are throttled per client IP.
Recover-admin helper: a.ResetPasswordAdmin(ctx, email, newPassword).
Email canonicalization
Trim and lowercase everywhere;Config.EmailNFKC adds Unicode NFKC folding.
Enable NFKC before data exists: existing mixed-form rows are not rewritten.
Password policy
Config.PasswordPolicy: MinLength (12), MaxBytes (72), BreachChecker
(nil). &theauth.HIBPBreachChecker{} sends only a 5 character SHA-1 prefix and
fails open on network errors.
Error bodies
Auth handlers answer errors as{"code","message"} JSON. See CHANGELOG.md
for the code list.