feat(auth): harden the signup/login flow (verification, anti-abuse, OAuth, sessions)#172
feat(auth): harden the signup/login flow (verification, anti-abuse, OAuth, sessions)#172dantaspaulo wants to merge 3 commits into
Conversation
…Auth, sessions) Cohesive hardening of the public auth surface, no new dependencies or tables: - Mandatory email verification with a magic link: a global `EnsureEmailVerified` middleware gates page navigation until the address is confirmed; the signed verification link verifies AND logs the user in, so it works when opened on another device. Social logins/invites are already verified; self-hosted skips. - Fix email-fixup: an unverified user who mistyped their email can correct it and resend the link (`UpdateUnverifiedEmailController`). - 45s resend cooldown with a countdown on the verify screen. - Anti-abuse on register: throttle, an autofill-proof honeypot, disposable-email blocking (`NotDisposableEmail`, config-extensible) and a per-IP daily quota. - Close OAuth account-takeover: only link/create by email when the PROVIDER confirmed it (Google `email_verified` claim; GitHub `/user/emails`). - Password reset/change now drops active DB sessions, so an attacker with an open session can't survive it. - Uniform forgot-password response to stop email enumeration (+ throttle). - `SecurityHeaders` middleware and a secure session cookie by default in prod. - Strict email validation (`Email::defaults` strict + native) everywhere a new email enters, so a@b / @localhost / whitespace are rejected before they bounce. - Real-time password-strength meter on register and reset. Covered by RegistrationAbuse, AuthHardening and OauthLinkingSecurity tests.
…test doubles Two behaviour changes in this branch broke existing suites. Handled differently, because they are different kinds of change. Gating the app behind a verified address changes the signup funnel, which is a product decision rather than a security fix, so it is now opt-in via `trypost.security.require_email_verification` (default off). The trial middleware tests pass again untouched. Requiring the provider to have verified the email before an account is linked or created by email IS a security fix: without it, a social account carrying someone else's unconfirmed address takes over that account. That one stays on, so the affected test doubles now model what a real provider returns — Google's `email_verified` OIDC claim, and GitHub's emails API, which the callback queries.
The parity test requires every locale to carry the same keys as `en`. Adds the password-strength meter, the unverified-social-email notice, the signup abuse messages, the verify-email screen copy and the uniform password-reset status to the twelve locales that landed since this branch was cut, each following its own file's wording and formal/informal register.
c1849c3 to
c45d8f0
Compare
|
Rebased on Mandatory email verification is now opt-in ( Requiring the provider to have verified the email stays on, because that one is a genuine security fix: without it, a social account carrying someone else's unconfirmed address can take over that account. The affected test doubles now model what a real provider actually returns — Google's Also added the new Locally the affected suites are green (102 passing across |
Cohesive hardening of the public signup/login surface. No new dependencies, no new tables.
Security / robustness
EnsureEmailVerifiedmiddleware gates page navigation (non-JSON GET) until the address is confirmed. The signed verification link verifies and logs the user in, so it works even when opened on another device/browser. Social logins and invites are already verified; self-hosted skips the gate.UpdateUnverifiedEmailController) — a user who mistyped their address at signup can change it and resend the link, only while unverified.email_verifiedclaim, GitHub's/user/emailsAPI. Otherwise it returns to login without linking.throttle:5,1, an autofill-proof honeypot (hiddencontact_time, cleared on mount/autofill so it never blocks a real signup), disposable-email blocking (NotDisposableEmail, config-extensible), and a per-IP daily quota (0disables).SecurityHeadersmiddleware (nosniff, X-Frame-Options, Referrer-Policy, Permissions-Policy, HSTS in prod) andsession.securedefaulting to true in production.Email::defaults()(strict + native) soa@b,@localhostand whitespace are rejected everywhere a new email enters (register, reset, invite, email-fixup) before they bounce.Signup UX
Config
New
config/trypost.phpsecurity.*(per-IP quota, disposable blocking, extra domains) andservices.github.api(overridable GitHub API host).Tests
RegistrationAbuseTest,OauthLinkingSecurityTest,AuthHardeningTest— 17 tests / 53 assertions covering the honeypot, disposable/quota/strict-email rules, the OAuth takeover fix, the verification gate, uniform forgot-password and session invalidation.