feat(registration): anonymous self-signup with email-domain allow-list (#62)#106
Open
martinydeAI wants to merge 11 commits into
Open
feat(registration): anonymous self-signup with email-domain allow-list (#62)#106martinydeAI wants to merge 11 commits into
martinydeAI wants to merge 11 commits into
Conversation
Adds the public `/register` route per ADR 006: - `App\Controller\RegistrationController` — thin: validates CSRF, delegates to a service, renders the form / pending page. GET + POST on `/register`; GET on `/register/pending`. Both routes are added to `access_control` as `PUBLIC_ACCESS`. Already-authenticated visitors are bounced to `/`. - `App\Security\Registration` — orchestrates email validation, the domain allow-list check, password-match check, name + password non-emptiness, and the persistence step via the existing `UserManager`. Persists with `status = Pending`. Each failure raises a `RegistrationException` carrying a translation key. - `App\Security\AllowedEmailDomains` — value object that wraps the comma-separated env var `REGISTRATION_ALLOWED_EMAIL_DOMAINS`, normalising on construction (lowercase, trim, dedup, drop empties). Bound to services via `%env(default:…:REGISTRATION_ALLOWED_EMAIL_DOMAINS)%` with `example.test` as the default so dev / tests boot without needing the var set. - Twig templates re-use the existing `Form/*`, `Eyebrow`, and `Layout/*` components; no new component families. - Translations live in the existing `messages` domain under `register.*`. A pending user created by this route cannot sign in (covered by the checker from PR #88) — the integration test exercises that hand-off end-to-end. Sequenced as PR 4 of the User management milestone plan. Stacked on PR 3 (#88) since `UserManager::createUser(..., status: Pending)` is only useful when the checker rejects pending logins; PR 3 is itself stacked on PR 1 (#86) for the `name` + `status` fields. Labelled `do-not-merge` until both bases land. The domain-extraction helper from PR #87 (`App\Security\EmailDomain`) isn't pulled in here to keep the stack chain clean — the same one-line extraction is inlined in `Registration` with a TODO to fold back into the helper once PR #87 lands on `develop`. Closes #62. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Correct comment formatting for allowed email domains.
Removed inlined comments regarding EmailDomain dependency and clarified the self-signup rules.
Per review on PR #90 - apply the test-comment convention to the three new test files (RegistrationControllerTest, AllowedEmailDomainsTest, RegistrationTest). 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>
PHP CS Fixer flagged a trailing-space-only line that survived the rebase. Pure formatting fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
7 tasks
tuj
requested changes
Jun 23, 2026
4 tasks
Address PR #106 review feedback: - Move the `REGISTRATION_ALLOWED_EMAIL_DOMAINS` env binding from the services.yaml parameter + bind block to an `#[Autowire]` attribute on `AllowedEmailDomains::__construct`. The wiring now lives next to the consumer instead of in central config. - Default the var to empty in `.env` (with a comment explaining how to populate it) so production must opt in to allowing signup, instead of silently accepting `example.test`. - Set `REGISTRATION_ALLOWED_EMAIL_DOMAINS=example.test` in `.env.test` so the existing registration tests keep the same baseline without leaking the placeholder into other envs. - Drop the redundant `parameters` entry from `services.yaml`. 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 #62.
Description
Replaces PR #90. The original PR #90 was based on PR #88's now-orphaned
branch (PR #88 was "merged" into a stacked base that never reached
develop). This PR rebases the registration work onto PR #105 (the
replacement for PR #88's content) so it can land cleanly on develop
once PR #105 does.
Adds anonymous self-signup at
/register:GET /register— renders the form (email, name, password,password confirmation, CSRF token).
POST /register— validates the inputs, creates the user withstatus = Pending, redirects to/register/pending. On validationfailure: 422 + re-render with a localised error.
GET /register/pending— "thanks, your account is awaitingapproval" landing page.
The validation logic lives in
App\Security\Registration(service);the controller stays thin per project conventions. Each failure
raises a
RegistrationExceptioncarrying a translation key, whichthe controller renders through
|trans. CSRF-protected viacsrf_token('register').Domain allow-list: comma-separated env var
REGISTRATION_ALLOWED_EMAIL_DOMAINS=aarhus.dk,kk.dk,…, bound via%env(default:…:REGISTRATION_ALLOWED_EMAIL_DOMAINS)%withexample.testas the dev / test default so the app boots withoutthe var set. Parsing happens once in
App\Security\AllowedEmailDomains(lowercase + trim + dedup + dropempties).
A pending user created by this route cannot sign in: the integration
test covers the hand-off through PR #105's
AccountStatusChecker.Screenshot of the result
n/a — form re-uses the same
Form/*components and base layout as/login. Manual screenshot can be added on request.Checklist
Verified locally:
task coding-standards-check— green.task test-coverage— 124 tests, 406 assertions, 100% coverage.Additional comments or questions
The branch currently carries PR #105's four commits at its base
(rebased from
feature/issue-63-account-status-checker). The"net" diff this PR adds on top is the seven registration-specific
commits. Once PR #105 merges, a rebase onto develop drops the
duplicate base commits and the diff cleans up to just the
registration work.
Details - AI specificities
docs/adr/006-user-approval-and-account-state.md(already on develop). Specifically the "Registration" section.
stacked branch (
feature/issue-63-account-status-checker). PRfeat(security): reject pending and blocked users at login (#63) #88 was technically merged but into its now-orphaned base
(
feature/issue-45-user-entity-extension), so PR feat(security): reject pending and blocked users at login (#63) #88's contentnever reached develop. PR feat(security): reject non-approved users at login (#63, #103) #105 is the rebased-onto-develop
replacement for PR feat(security): reject pending and blocked users at login (#63) #88, and this PR is the rebased-onto-feat(security): reject non-approved users at login (#63, #103) #105
replacement for PR feat(registration): anonymous self-signup with email-domain allow-list (#62) #90.
registration commits. That's intentional during the
do-not-merge window. After PR feat(security): reject non-approved users at login (#63, #103) #105 lands, those four become
redundant against develop and a single rebase removes them.
(per ADR 006).
lands.
register.