v0.2.4
Admin two-factor auth, account lockout, peer comparison, OAuth PKCE, configurable hasher, and password pepper rotation.
Admin two-factor auth — POST /auth/admin/login now unconditionally requires a second factor when email 2FA is available, even when RequireEmail2FA is false. Disable it with TwoFactorConfig.DisableAdminTwoFactor if you have another second factor in place. Admin 2FA is disabled outside development by default.
Account lockout — Failed login attempts are now tracked per email address. After MaxFailedLogins (default 3) consecutive failures, the account is locked for LockoutDuration (default 15 minutes). The counter is shared across user and admin login routes. All settings are tunable under SecurityConfig.
Peer comparison — WithPeerComparison checks new emails, usernames, and display names against existing accounts and rejects overlapping values. Similar-username lockout is a separate, opt-in variant under WithSimilarUsernameLockout.
OAuth PKCE — All OAuth providers now generate and verify PKCE code_verifier/code_challenge pairs. The challenge is per-session and stored in oauth_state, so two in-flight OAuth logins on the same browser don't interfere. This closes the authorization-code interception gap without changing any public API.
Configurable hasher — WithPasswordHasher lets you replace bcrypt with any implementation that satisfies a two-method Hash/Compare interface. The built-in Argon2id hasher ships at hasher/argon2id with RFC 9106 section 4 defaults (64 MiB, 3 iterations, parallelism 4). A new startup check rejects any hasher whose output cannot self-identify its algorithm.
Password pepper rotation — WithPepperRotatedAt tells the library the exact UTC moment a new secret went live, so pre-rotation low-entropy codes (2FA, verification, set-password, delete-account) fail as expired instead of invalid.
Breaking changes
- PKCE required — All OAuth providers now require PKCE. This is transparent to most callers, but a hand-rolled
port.Providerreturning a non-emptyAuthCodeURLvalue must include acode_challengequery parameter. - Argon2id memory — The default increased from 32 MiB to 64 MiB per hash/comparison. Benchmark before deploying on memory-constrained hosts.
- Mailer errors return 200 — Signup, invite, password reset, password change, delete-request, delete-account, and verification emails that fail in transit still return
200 OKwithcode: mailer_error. OnlyPOST /auth/mailer/testexposes the actual SMTP status.POST /auth/admin/delete-userreturns500 Internal Server Erroron event-hook failure.
Other changes
- Audit logs now store session
user_agentandip_addressat creation time, andGET /auth/sessionsincludesuser_agentandip_addresson every row. CSRFTokenConfig.ExposeCSRFTokenInBodymakesGET /auth/csrf-tokenreturn the token in the body instead of a bare204.CSRFTokenConfigis now created by default when leftnil— you only need to pass it when overriding individual fields.POST /auth/admin/delete-userruns an event hook and never sends email; event-hook failure returns500instead of200withmailer_error.POST /auth/admin/create-usernow emails a verification link to the created user.- Admin login now returns
admin_useralongsidesession_token, matching the shape ofGET /auth/admin/me. GET /auth/admin/sessionsnow returnsmeta.totalalongside the session list.- Duplicate organization invites now return
409 Conflictinstead of silently reusing the existing token. Validaterejects passwords over 72 bytes before hashing, regardless ofMinLength, so a too-long password gets a clear400 weak_passwordinstead of a generic500.POST /auth/2fa/resendnow enforcesVerificationResendInterval.POST /auth/refreshnow returns401instead of400on invalid/expired tokens.