43 lines
2.2 KiB
Markdown
43 lines
2.2 KiB
Markdown
# Self-Hosted Single-Org + Invite-Only Signup
|
|||
|
|
|
||
|
|
## Purpose
|
||
|
|
Ensure self-hosted instances support only one organization, auto-enroll new users as students, and restrict signups to invite-only when configured by org admins.
|
||
|
|
|
||
|
|
## Goals
|
||
|
|
- Enforce a single organization per self-hosted instance.
|
||
|
|
- Automatically add any non-member user as a student in the self-hosted org.
|
||
|
|
- Use a single domain for dashboard + LMS in self-hosted mode.
|
||
|
|
- Allow admins to disable public signups and enforce invite-only access.
|
||
|
|
|
||
|
|
## Constraints
|
||
|
|
- First signup can create the only org; subsequent org creation is blocked.
|
||
|
|
- Users without membership are treated as students and routed to `/lms`.
|
||
|
|
- Students cannot access `/org/[slug]` routes.
|
||
|
|
- Org switching UI is hidden in self-hosted mode.
|
||
|
|
|
||
|
|
## Backend Changes
|
||
|
|
- Add `PUBLIC_IS_SELFHOSTED` to API env schema.
|
||
|
|
- Add an org count/first-org query to detect the singleton org.
|
||
|
|
- In `createOrganizationWithOwner` and `createOrg`, block org creation when self-hosted and an org exists.
|
||
|
|
- In the account bootstrap (`GET /account`), auto-add user as `ROLE.STUDENT` to the single org if they are not a member.
|
||
|
|
|
||
|
|
## Frontend Changes
|
||
|
|
- In `getOrgSiteInfo`, when self-hosted, treat the root domain as the org site by returning the single org even without subdomain.
|
||
|
|
- In app init routing, if self-hosted and user has no orgs, route to `/lms`.
|
||
|
|
- Hide org switching and create-org UI in self-hosted mode.
|
||
|
|
|
||
|
|
## Invite-Only Signup Setting
|
||
|
|
- Add `settings.signup.inviteOnly` to `organization.settings` and allow updates via `ZUpdateOrganization`.
|
||
|
|
- Add a toggle in `org/[slug]/settings/org` labeled “Allow public signups” with a description.
|
||
|
|
- Hide signup link/buttons in Auth UI and navigation when invite-only is enabled.
|
||
|
|
- Only allow signups when invite context exists:
|
||
|
|
- `invite_token` query param (course invite)
|
||
|
|
- `redirect` contains `/invite/t/` (org invite)
|
||
|
|
- `redirect` contains `invite_token` (course invite redirect)
|
||
|
|
- If invite-only and no invite context, show a message and hide the signup form.
|
||
|
|
- Enforce invite-only on the backend to block non-invite signup attempts.
|
||
|
|
|
||
|
|
## Open UX Copy
|
||
|
|
- Signup message when invite-only and no invite context.
|
||
|
|
- Description text for “Allow public signups” toggle.
|