Email domain allowlist for public registration - #102
Merged
Conversation
New email_domain_allowlist_enabled + allowed_email_domains (JSON) columns on the SiteSettings singleton, plus the migration adding them (off by default so existing behaviour is unchanged until an admin opts in). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BxJwzhdM9gEm8X7oAoWF3X
OperationalSettingsOut/Update carry the allowlist flag + domain list (normalized: lowercased, deduped, capped at 50 domains / 253 chars each, malformed entries rejected inline with a 422 rather than silently dropped). The public SiteSettingsOut only exposes a policy bit (email_required) mirroring the flag — the domain list itself stays admin-only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BxJwzhdM9gEm8X7oAoWF3X
POST /register now rejects a sign-up when the allowlist is enabled and the supplied email (mandatory once enabled) doesn't match an allowed domain or one of its subdomains — with a single generic rejection message for both the missing-email and domain-mismatch cases, so the allowlist's contents (or even its existence) are never disclosed. Scoped to public registration only, per the issue: admin-created accounts (routers/users.py) and later email edits are untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BxJwzhdM9gEm8X7oAoWF3X
email_required on the public SiteSettings type, and email_domain_allowlist_enabled/allowed_email_domains on OperationalSettings/Update, plus the matching fallback default. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BxJwzhdM9gEm8X7oAoWF3X
A toggle + tag-input domain list (matching the VocabEditor pattern used elsewhere for managed vocab lists), with a caption noting subdomains are automatically allowed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BxJwzhdM9gEm8X7oAoWF3X
Reads email_required off the public site-settings hook: when the allowlist policy is enabled, the email input becomes required and drops the "(optional)" label — no client-side domain check, the server stays the source of truth. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BxJwzhdM9gEm8X7oAoWF3X
Covers: disabled by default (unchanged behaviour), enabling requires email + exposes it publicly via email_required only, missing-email and domain-mismatch rejections share the same generic message, exact and subdomain matches succeed, malformed/over-cap/duplicate domains are rejected or normalized on save, and admin-created accounts are exempt. Also updates the two existing exact-shape assertions on the public GET /site-settings response for the new email_required field. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BxJwzhdM9gEm8X7oAoWF3X
Verifies the email field renders as optional/required based on the mocked email_required value from the public site-settings hook. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BxJwzhdM9gEm8X7oAoWF3X
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.
What & why
Adds an admin-configurable email-domain allowlist (Admin → Site settings) so public self-serve registration only accepts email addresses on approved domains, per the approved plan in #56. Subdomains match automatically; a rejected sign-up sees one generic error regardless of whether the email was missing or the domain didn't match, so the allowlist's contents (or existence) are never disclosed.
Fixes #56
Plan step → commit mapping
email_domain_allowlist_enabled+allowed_email_domainscolumns onSiteSettings1f68671Add email-domain allowlist columns to SiteSettings (#56)schemas/site_settings.py: operational fields + validation (normalize/dedupe/cap/reject malformed) + publicemail_required43e238fWire email-domain allowlist into site-settings schemas + router (#56)routers/site_settings.py:_operational_out(),PUT /operationalassignment, publicGETcomputingemail_required43e238f(same commit — schema + router changed together)routers/auth.py: enforce inPOST /register(afterregistration_open, beforeemail_taken), generic rejection, newauth/registration_policy.py::domain_allowedhelper252f835Enforce email-domain allowlist in public registration (#56)schemas/auth.pyRegisterRequest.emailstaysEmailStr | None(policy enforced in router, not the schema)252f835frontend/src/lib/types.ts:email_required+ operational fieldsbdc1a93Add email-domain allowlist fields to frontend types + hooks (#56)VocabEditorpattern), subdomain caption1d132c1Add email domain allowlist card to Admin -> Site settings (#56)email_requireddrives therequiredattribute + label, server error surfaced (no client-side domain check)0db1e9fMark register page email field required when the allowlist is on (#56)44a5499Add backend tests for the email-domain allowlist (#56)005fb36Add frontend test for register page email requirement (#56)Deviations from the plan
PATCH /api/users/{id}path exists, which the tests do cover as unaffected) — so this sub-case has nothing to test and was skipped.Checklist
cd backend && .venv/bin/pytest(428 passed)cd frontend && npm run test && npx tsc --noEmit && npx eslint .(139 passed, no type/lint errors)competition_idscoping,require_permission, one hook per domain, design tokens) — no new event needed (reusessite.settings_updated); site settings is not competition-scoped by design; gated onmanage_site_settings; changes stay within the existinguse-site-settings.tshook module2026-07-28_3e4f5a6b7c8d_email_domain_allowlist.pyGenerated by Claude Code