feat(security): reject pending and blocked users at login (#63)#88
Open
martinydeAI wants to merge 3 commits into
Open
feat(security): reject pending and blocked users at login (#63)#88martinydeAI wants to merge 3 commits into
martinydeAI wants to merge 3 commits into
Conversation
Adds `App\Security\AccountStatusChecker` implementing Symfony Security's `UserCheckerInterface`. The pre-auth hook rejects any `User` whose `status` is not `Approved`, throwing a `CustomUserMessageAccountStatusException` with a localised key: `account.pending` for users still awaiting approval and `account.blocked` for users an admin has revoked. Translations land in a new `translations/security.da.yaml` (the `security` domain the login template already renders error messages from). Wired on the `main` firewall via `security.yaml`'s `user_checker:` key. Identity state stays orthogonal to authorisation per ADR 006 — the voter from PR #87 reads roles + email, the checker reads status; neither overlaps with the other. Sequenced as PR 3 of the User management milestone plan. Stacked on PR 1 (#86) since it reads `User::$status` which doesn't exist on `develop` yet — labelled `do-not-merge` until #86 lands. Closes #63. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clarify comment about 'Blocked' user roles in AccountStatusChecker.
martinyde
requested changes
Jun 19, 2026
| * {@see CustomUserMessageAccountStatusException} halts the flow and | ||
| * surfaces the (localised) translation key on the login form. | ||
| * | ||
| * A `Blocked` user retains the roles |
Contributor
There was a problem hiding this comment.
Should we strip roles from blocked users for good measure, or is that bad practice?
4 tasks
Per review on PR #88 - apply the test-comment convention to the new AccountStatusCheckerTest and the two new login-rejection methods in SecurityControllerTest. Each `test...` method now opens with a single-line "Tests ...", "Ensures ...", or "Verifies ..." comment naming what it asserts. Pure documentation change - no test logic touched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Links to issues
Closes #63.
Description
Adds
App\Security\AccountStatusCheckerimplementing SymfonySecurity's
UserCheckerInterface. The pre-auth hook rejects anyUserwhosestatusis notApproved:Pending→CustomUserMessageAccountStatusException("account.pending")Blocked→CustomUserMessageAccountStatusException("account.blocked")Approved→ no-op (sign-in proceeds normally)Wired on the
mainfirewall viasecurity.yaml'suser_checker:key. Translations live in a new
translations/security.da.yaml(the
securitydomain that the login template already renderserror messages from via
error.messageKey|trans({}, 'security')).Identity state (this PR) stays orthogonal to authorisation
(PR #87's voter) per ADR 006 — a
Blockeduser retains the rolesthey had before being blocked, they just can't sign in to exercise
them.
Screenshot of the result
n/a — pre-auth hook, no new UI surface. The login form renders the
new localised error inside the existing error block.
Checklist
Verified locally:
task coding-standards-check— all green.task test-coverage— 58 tests, 203 assertions, 100 %coverage.
Additional comments or questions
UserCheckerInterface::checkPreAuth()andcheckPostAuth()inSymfony 8 carry an optional
?TokenInterface $token = nullsecondargument; the override matches that signature. The token slot is
unused here — neither identity check needs it.
The login template (
templates/security/login.html.twig)already renders
error.messageKey|trans(error.messageData, 'security')in its error block, so the new translations slot in without any
template change. That's why the keys had to land in the
securitytranslation domain rather than the project's
messagesdomain.The two new
SecurityControllerTestmethods create their pending /blocked users inside the test via
UserManager::createUser(..., status: UserStatus::Pending|Blocked)rather than seeding viaUserFixtures. DAMA's per-test transaction rolls them back so thebaseline Alice + Bob stay untouched.
Details - AI specificities
docs/adr/006-user-approval-and-account-state.md(Draft; lands via docs: add ADR 004 — user registration, approval, and account state #61). Specifically the "Login gating"
section.
develop.do-not-mergelabel.that's part of PR 4 (feat: anonymous self-signup with email-domain allow-list #62 registration), which renders the
check-pending page after a successful signup.
SecurityControllerTestare additive (no existing assertionschanged); same approval as PR 1.
translations/*.<locale>.yaml; no cache clear needed in tests(the kernel boots fresh per integration suite via
tests/bootstrap_integration.php).