Add self-service sign-up and invite links #19
Reference in New Issue
Block a user
Delete Branch "signup-invites"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
First half of #7. The login page has been telling people to "ask an admin" since before any of this work started — workable for one operator, impossible for a team.
Two modes, not three
invite_only(default) andopen, chosen by an administrator in the settings table. The domain-restricted third mode from the original plan is dropped: with no email in this server there's nothing to verify an address against, so it would only check the domain of a string somebody typed.The default is the closed door, and so is the fallback for an unrecognised value — a typo in this setting should fail shut.
Invites are links, not email
Adding SMTP to send one message would be a subsystem to run, secure and monitor. The person inviting sends the link however they already talk to the person they're inviting.
A link carries the team and the role, because an account in no team sees an empty queue and can be paged by nobody — not a state to invite somebody into. Single-use by default, seven-day expiry, revocable before that: a link that works forever is a credential nobody remembers issuing, sitting in a chat log.
The uses counter increments inside the sign-up transaction, guarded by
uses < max_uses, so two people redeeming the last use at once cannot both get in. There's a test for exactly that.Details worth the space
GET /api/signupreports the mode and whether a link is usable, so the form can say "this link has expired" before somebody picks a password. It gives one answer for expired, revoked, used up and never existed — telling a stranger which it was tells them something about links they don't hold.loginandsignupnow sharestartSession.signup_modeis a word from a fixed list, so the admin endpoint now validates everything before writing anything — a request setting two values with one wrong changes neither.Verified
make fmt lint test helm-lintgreen with-raceagainst Postgres 17, plus eight new tests: the closed default, an invite landing somebody in the right team with the right role, single-use enforcement, revocation, open mode creating a team, the mode being admin-only and taking effect at once, invites being owner-only, and sign-up validating passwords and duplicate usernames like the rest of the server.Still to come in #7
The sign-up and invite-redemption pages, the first-run checklist, and the in-app integration instructions. The schema already carries
onboarding_dismissed_atfor the checklist, andusers.invited_viarecords how an account arrived — the answer to "how did this account get here" should outlive the link that made it.https://claude.ai/code/session_01RHPj4ggeFdEjKKfm4SHbD7