Repository navigation
Conversation
Anyone who can reach a Hopp server can create an account, and an account without a team invite gets a team of its own. That is right for Hopp Cloud but not for a self-hosted server on the public internet. Add an instance setting, off by default so existing installs behave as before, that rejects sign-ups without a valid team invite (403). It covers email/password sign-up and the new-user branch of social login, and an unknown invite UUID is refused rather than falling through to a new team. The first account on an empty instance is exempt so a fresh install can be bootstrapped. is_admin is scoped to a user's own team, so every stranger who signs up is an admin and could otherwise reopen sign-up. The setting can therefore only be changed by the instance owner, defined as the earliest account that still exists. GET/PUT /api/auth/instance-settings expose it, and Settings shows a toggle to the owner only. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
✅ Deploy Preview for hoppdocs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
|
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueNo actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
- Cover the social-login gate with tests (refused without invite, refused with a stale invite, accepted with a valid invite, existing users can still sign in) using the existing MockSocialAuthProvider. Mutation checks confirm they fail if the gate, the 403 unwrap or the redirect is removed. - Send a refused social sign-up back to /login?error=invite_required and show a toast, instead of rendering raw JSON from the OAuth callback. - Seed the settings row in the migration and read it without writing. - Reword the bootstrap exemption: it only applies while the instance has no accounts, and is not atomic with the insert. - Validate the PUT body: a missing or non-boolean field is a 400 and never silently turns the setting off. Drop an unused validate tag. - Tests: give the test owner a real team, assert the 403 message and PUT body, and cover the 400 cases. - Docs: describe the owner accurately (earliest account that still exists), how to find them and recover, and that invite links do not expire, are visible to every team member, and work from any team. - Web: tie the toggle's description to it with aria-describedby. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Problem
Anyone who can reach a Hopp server can create an account, and a sign-up without a team invite creates a new team with that user as its admin. That is the right behaviour for Hopp Cloud, but a self-hosted server on the public internet has no way to say "only people we invite".
Change
An instance setting, off by default, so existing installs behave exactly as today. When on, an account can only be created with a valid team invite; otherwise sign-up returns
403with a readable message (the web sign-up form already displays the server message)./login?error=invite_requiredand the login page shows a toast, rather than rendering raw JSON from the OAuth callback.instance_settingstable, seeded by the migration (AutoMigrate adds it). No env var, per the request to manage it from the web interface.GET/PUT /api/auth/instance-settings(OpenAPI + regenerated types forweb-appandtauri).PUTrejects a missing or non-boolean field with400rather than defaulting tofalse.Who can change it
is_adminis scoped to a user's own team, and every non-invited sign-up becomes an admin of its own team. If any admin could flip this, a stranger could reopen sign-up on a locked server. So only the instance owner, defined as the earliest account that still exists, can change it; everyone else gets403. Hopp has no instance-wide role today, so ownership is positional rather than adding a column. It cannot be transferred and passes to the next-earliest account if the owner is deleted. Happy to move to an explicitis_instance_adminflag if you would rather have one.Things worth a maintainer's attention
STRIPE_SECRET_KEYbeing unset, which I did not want to build on), and I do not know how the hosted deployment differs. If you want the toggle limited to self-hosted installs, I would add an explicit env var (for exampleSELF_HOSTED=true, set inselfhost/compose.yml) rather than detect it from other features. Say the word and I will add it.Testing
backend/test/integration/signup_invite_test.go), including the social-login path via the existingMockSocialAuthProvider: first-account bootstrap; refused without an invite, with an unknown invite, and on the social path (no user or team left behind); accepted with a valid invite and joins the right team; open by default; existing accounts can still sign in; owner can toggle and it takes effect immediately; a non-owner team admin cannot; unauthenticated, missing-field and wrong-type requests are rejected.go test -tags integration -shuffle=on ./test/integration/ ./internal/...,gofmt,go vetpass.tsc --noEmitandeslinton the changed pages pass; changed files pass Prettier (Login.tsxalready had unrelated Prettier differences atmain, which I left alone).Not covered
golangci-lintwas not available locally.🤖 Generated with Claude Code