feat(accounts): add SIGNUP_MODE to close or restrict registration - #141
Open
johannes-steidle wants to merge 1 commit into
Open
feat(accounts): add SIGNUP_MODE to close or restrict registration#141johannes-steidle wants to merge 1 commit into
johannes-steidle wants to merge 1 commit into
Conversation
A self-hosted install has to be reachable from the internet — that is where the platforms deliver OAuth callbacks and webhooks — so an open signup form is open to everyone who finds the domain. There was no way to turn it off short of putting a rule in the reverse proxy, which also blocks invited users. SIGNUP_MODE takes one of three values and defaults to "open", so nothing changes for existing deployments: open anyone can sign up invite only visitors who followed a valid invitation link closed nobody; accounts are created in the Django admin Both doors are covered. Guarding the email form alone would achieve nothing: SOCIALACCOUNT_AUTO_SIGNUP creates the account during the provider callback, so a new Google user never visits the signup page at all. The decision therefore lives in one helper used by both a new AccountAdapter and the existing SocialAccountAdapter. Invite mode reuses the invitation the visitor already carries in the session — apps.members.views puts the token there, apps.accounts.signals consumes it after signup — and requires it to be neither accepted nor expired, so a single link cannot mint accounts indefinitely. An unknown value raises ImproperlyConfigured at startup instead of falling back to a default: a typo that silently leaves signup open is the failure mode worth being loud about. The login page hides its "Sign up" link when the visitor could not sign up anyway, and keeps showing it in invite mode for visitors who followed an invitation. signup_closed.html matches the other entrance pages. Signing in is never affected. An existing user reaching the app through a social login is connected by email rather than signed up, so the gate is not consulted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Author
|
Friendly ping on this one — no rush, I know the queue is busy. Status: still merges cleanly on current Happy to rebase, split it up, or change the approach if the design doesn't fit where you want signup handling to live. And if it's simply not something you want in core, that's a fine answer too — I'll keep it in my own fork. For what it's worth, someone else has already picked the patch up for their self-hosted install, so there seems to be some demand for closing signups on a public-facing deployment. |
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 does this PR do?
Adds a
SIGNUP_MODEsetting that decides who may create an account. It takes one of three values and defaults toopen, so nothing changes for existing deployments:open(default)inviteclosedBoth doors are covered. Guarding the email form alone would achieve nothing:
SOCIALACCOUNT_AUTO_SIGNUPcreates the account during the provider callback, so a new Google user never visits the signup page at all. The decision therefore lives in one helper,signup_allowed(), used by both a newAccountAdapterand the existingSocialAccountAdapter.Invite mode reuses the invitation the visitor already carries in the session —
apps.members.viewsputs the token there,apps.accounts.signalsconsumes it after signup — and requires it to be neither accepted nor expired, so a single link cannot mint accounts indefinitely.An unknown value raises
ImproperlyConfiguredat startup instead of falling back to a default: a typo that silently leaves signup open is the failure mode worth being loud about.The login page hides its "Sign up" link when the visitor could not sign up anyway, and keeps showing it in invite mode for visitors who followed an invitation.
signup_closed.htmlmatches the other entrance pages.Signing in is never affected. An existing user reaching the app through a social login is connected by email rather than signed up, so the gate is not consulted.
Why?
A self-hosted install has to be reachable from the internet — that is where the platforms deliver OAuth callbacks and webhooks — so an open signup form is open to everyone who finds the domain. There was no way to turn it off short of putting a rule in the reverse proxy, which also blocks invited users.
How to test
Automated:
Covers all three modes across the email form and the social callback, the invite-mode rejections (expired, already accepted, unknown token), that an existing user can still log in with
closed, and that the login page shows or hides its "Sign up" link accordingly.Manually, with
SIGNUP_MODE=closedin.env:/accounts/signup/renders the closed notice instead of the form, and the "Sign up" link is gone from/accounts/login/.Then set
SIGNUP_MODE=inviteand restart:/accounts/signup/is still closed for a plain visitor.Finally,
SIGNUP_MODE=nonsensemust abort at startup withImproperlyConfiguredrather than boot.Checklist
pytest) — 1393 passedruff check .andruff format --check .).env.example