Prevent banned users from verifying email with a surviving session #985

Closed
opened 2026-10-19 10:14:40 -05:00 by Codex · 2 comments
Member

Banned users must be prevented from activating their accounts through an email verification link, even if they retain a valid authenticated session.

Registration automatically logs the new user in before email verification. Normal bans invalidate database sessions and block subsequent login, but Fortify's verification endpoint previously checked only authentication, the signed URL, and the matching account/email hash. A surviving session could therefore verify the email after a ban. Verification does not remove the ban, but the activation action should still be denied.

Acceptance criteria

  • Reject email verification with HTTP 403 for accounts marked with banned = Y, the banned role ID, or both, even with a valid session and signed link.
  • Leave email_verified_at unset and do not dispatch the Verified event for denied attempts.
  • Preserve normal email verification and invalid-link checks.

Implementation and verification

The fix is implemented through App\Http\Requests\VerifyEmailRequest, bound to Fortify's verification request in FortifyServiceProvider::register(). It retains Fortify's existing account and email hash checks and adds the shared User::isBanned() check. Existing authentication, signed-URL validation, and throttling remain in place.

Regression coverage reproduces a surviving authenticated session for all three ban-marker combinations. The original ban regression tests failed before the fix and pass afterward.

Acceptance re-review — October 19, 2026

  • Reviewed the request binding, inherited authorization, verification controller, route middleware, and shared ban predicate against every acceptance criterion. No outstanding implementation gaps found.
  • Verify HTTP 403, unchanged unverified email state, no Verified event, and retained ban for the ban flag, banned role, and both markers.
  • Verify normal activation succeeds and emits Verified.
  • Verify invalid email hash, mismatched account ID, expired URL, and tampered signature are rejected with HTTP 403, leave both accounts unverified, and emit no Verified event.
  • Verify a guest following a valid link is redirected to login without verification or a Verified event.
  • Run email verification, authentication, and registration tests: 43 passed, 217 assertions, using the testing environment and local SQLite database.
  • Run Pint on the three issue-related PHP files, PHPStan, and git diff --check: passed.

No frontend assets, migrations, or dependencies changed. PR #986 lint and MariaDB CI checks passed (tasks #1316 and #1317).

Release tracking

  • Commit the issue-related changes and publish a PR referencing this issue: commit 6ba1f60, non-draft PR #986, assigned to jimmyb.
  • Complete CI/review and merge: PR #986 merged by jimmyb as d2d51d017c49efa767a9d67438d25eadacfccce6.
  • Deploy and verify the released behavior: dev and production deployed at d2d51d017c49efa767a9d67438d25eadacfccce6; deployed file hashes/autoload, release manifests, external HTTP health, and guest activation-route checks passed. Ban-specific regression coverage passed without modifying live accounts.

Implementation, acceptance review, PR CI, merge, and dev/production deployment are complete. This issue is closed with status/done; see the deployment comment for verification details and rollback backups. Post-merge push workflows were still running at the final check.

Banned users must be prevented from activating their accounts through an email verification link, even if they retain a valid authenticated session. Registration automatically logs the new user in before email verification. Normal bans invalidate database sessions and block subsequent login, but Fortify's verification endpoint previously checked only authentication, the signed URL, and the matching account/email hash. A surviving session could therefore verify the email after a ban. Verification does not remove the ban, but the activation action should still be denied. ## Acceptance criteria - [x] Reject email verification with HTTP 403 for accounts marked with `banned = Y`, the banned role ID, or both, even with a valid session and signed link. - [x] Leave `email_verified_at` unset and do not dispatch the `Verified` event for denied attempts. - [x] Preserve normal email verification and invalid-link checks. ## Implementation and verification The fix is implemented through `App\Http\Requests\VerifyEmailRequest`, bound to Fortify's verification request in `FortifyServiceProvider::register()`. It retains Fortify's existing account and email hash checks and adds the shared `User::isBanned()` check. Existing authentication, signed-URL validation, and throttling remain in place. Regression coverage reproduces a surviving authenticated session for all three ban-marker combinations. The original ban regression tests failed before the fix and pass afterward. ## Acceptance re-review — October 19, 2026 - [x] Reviewed the request binding, inherited authorization, verification controller, route middleware, and shared ban predicate against every acceptance criterion. No outstanding implementation gaps found. - [x] Verify HTTP 403, unchanged unverified email state, no `Verified` event, and retained ban for the ban flag, banned role, and both markers. - [x] Verify normal activation succeeds and emits `Verified`. - [x] Verify invalid email hash, mismatched account ID, expired URL, and tampered signature are rejected with HTTP 403, leave both accounts unverified, and emit no `Verified` event. - [x] Verify a guest following a valid link is redirected to login without verification or a `Verified` event. - [x] Run email verification, authentication, and registration tests: 43 passed, 217 assertions, using the testing environment and local SQLite database. - [x] Run Pint on the three issue-related PHP files, PHPStan, and `git diff --check`: passed. No frontend assets, migrations, or dependencies changed. PR #986 lint and MariaDB CI checks passed (tasks #1316 and #1317). ## Release tracking - [x] Commit the issue-related changes and publish a PR referencing this issue: commit `6ba1f60`, non-draft PR #986, assigned to `jimmyb`. - [x] Complete CI/review and merge: PR #986 merged by jimmyb as `d2d51d017c49efa767a9d67438d25eadacfccce6`. - [x] Deploy and verify the released behavior: dev and production deployed at `d2d51d017c49efa767a9d67438d25eadacfccce6`; deployed file hashes/autoload, release manifests, external HTTP health, and guest activation-route checks passed. Ban-specific regression coverage passed without modifying live accounts. Implementation, acceptance review, PR CI, merge, and dev/production deployment are complete. This issue is closed with status/done; see the deployment comment for verification details and rollback backups. Post-merge push workflows were still running at the final check.
Codex self-assigned this 2026-10-19 10:14:51 -05:00
Author
Member

Merged via PR #986 by jimmyb as d2d51d017c49efa767a9d67438d25eadacfccce6; local master has been fast-forwarded to the merge commit.

Implementation notes:

  • Registration deliberately signs users in before email verification; an authenticated session does not imply a verified email address.
  • The application now binds its verification request into Fortify, checking the shared User::isBanned() predicate before Fortify's existing account/email-hash authorization. Both the ban flag and banned role are covered.
  • A banned account with a surviving session receives HTTP 403. Its email stays unverified, no Verified event is emitted, and the ban remains intact.
  • Authentication, signed-link expiry/integrity checks, and route throttling remain in place. No vendor code was modified.
  • Regression coverage includes all three ban-marker combinations, successful verification, invalid hashes, another account's link, expired and tampered links, and guests.

Validation: 43 focused tests / 217 assertions, Pint, PHPStan, and whitespace checks passed locally. PR lint and MariaDB CI tasks #1316 and #1317 succeeded. No migrations, dependency changes, or frontend rebuild are required.

Dev and production deployment follows this closeout. The deployment checkbox will be marked complete after both targets are verified, with rollback locations recorded in a follow-up comment.

Merged via PR #986 by jimmyb as `d2d51d017c49efa767a9d67438d25eadacfccce6`; local master has been fast-forwarded to the merge commit. Implementation notes: - Registration deliberately signs users in before email verification; an authenticated session does not imply a verified email address. - The application now binds its verification request into Fortify, checking the shared `User::isBanned()` predicate before Fortify's existing account/email-hash authorization. Both the ban flag and banned role are covered. - A banned account with a surviving session receives HTTP 403. Its email stays unverified, no `Verified` event is emitted, and the ban remains intact. - Authentication, signed-link expiry/integrity checks, and route throttling remain in place. No vendor code was modified. - Regression coverage includes all three ban-marker combinations, successful verification, invalid hashes, another account's link, expired and tampered links, and guests. Validation: 43 focused tests / 217 assertions, Pint, PHPStan, and whitespace checks passed locally. PR lint and MariaDB CI tasks #1316 and #1317 succeeded. No migrations, dependency changes, or frontend rebuild are required. Dev and production deployment follows this closeout. The deployment checkbox will be marked complete after both targets are verified, with rollback locations recorded in a follow-up comment.
Author
Member

Deployed merged master revision d2d51d017c49efa767a9d67438d25eadacfccce6 to dev, verified it, then deployed and verified production.

  • Deployed only app/Http/Requests/VerifyEmailRequest.php and app/Providers/FortifyServiceProvider.php, directly from the merged commit.
  • Verified expected baseline revisions/hashes before applying, PHP syntax and autoload compatibility, deployed SHA-256 checksums, and updated release manifests on both targets.
  • Preserved existing unrelated source files, dev overlays, and environment files. No migrations, dependency installation, asset rebuild, or account/data mutations were performed.
  • External HTTP checks returned 200 for both homepages and /up; /email/verify/0/invalid returned 302 to each environment's own login page. Requests originating on the web server redirected dev traffic to the production homepage; independent external checks confirmed dev itself responds correctly.
  • Focused verification tests were rerun on merged master: 10 passed / 37 assertions. The broader pre-merge checks passed 43 tests / 217 assertions, Pint, PHPStan, and PR MariaDB CI. Ban-specific behavior is covered by those automated tests; no real accounts were altered to exercise it on the live sites.
  • Post-merge push workflows were still running at the final status check; the PR lint and MariaDB CI workflows had already passed before merge.

Rollback backups:

  • Dev: /home/mvgldev/deploy-backups/issue985-d2d51d01-tv7zixir
  • Production: /home/mvgl/deploy-backups/issue985-d2d51d01-y3nusqdc

Each backup preserves the previous provider and release manifest. To roll back this patch, restore those and remove the newly added verification request. Deployment receipts and artifacts are under each account's releases/issue985-d2d51d01 directory.

All acceptance, review, and release checklist items are now complete. Issue #985 is closed with status/done.

Deployed merged master revision `d2d51d017c49efa767a9d67438d25eadacfccce6` to dev, verified it, then deployed and verified production. - Deployed only `app/Http/Requests/VerifyEmailRequest.php` and `app/Providers/FortifyServiceProvider.php`, directly from the merged commit. - Verified expected baseline revisions/hashes before applying, PHP syntax and autoload compatibility, deployed SHA-256 checksums, and updated release manifests on both targets. - Preserved existing unrelated source files, dev overlays, and environment files. No migrations, dependency installation, asset rebuild, or account/data mutations were performed. - External HTTP checks returned 200 for both homepages and `/up`; `/email/verify/0/invalid` returned 302 to each environment's own login page. Requests originating on the web server redirected dev traffic to the production homepage; independent external checks confirmed dev itself responds correctly. - Focused verification tests were rerun on merged master: 10 passed / 37 assertions. The broader pre-merge checks passed 43 tests / 217 assertions, Pint, PHPStan, and PR MariaDB CI. Ban-specific behavior is covered by those automated tests; no real accounts were altered to exercise it on the live sites. - Post-merge push workflows were still running at the final status check; the PR lint and MariaDB CI workflows had already passed before merge. Rollback backups: - Dev: `/home/mvgldev/deploy-backups/issue985-d2d51d01-tv7zixir` - Production: `/home/mvgl/deploy-backups/issue985-d2d51d01-y3nusqdc` Each backup preserves the previous provider and release manifest. To roll back this patch, restore those and remove the newly added verification request. Deployment receipts and artifacts are under each account's `releases/issue985-d2d51d01` directory. All acceptance, review, and release checklist items are now complete. Issue #985 is closed with `status/done`.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MyVideoGameList/myvideogamelist.com#985
No description provided.