Makes AUTH_MODE=team usable end to end. - resolveTeamContext (middleware/ensure-user/team.ts): a session resolves to a membership in the single shared workspace. No per-user fallback org — a signed-in user with no membership is treated as signed out, so the owner can actually remove people. - teamProvisioning.ts: one path that writes user + credential account + member together (hashPassword from better-auth/crypto). Shared by both entry points. - /api/team-setup (raw route, outside auth middleware): GET reports whether an owner is needed; POST creates the first owner + the shared org, then self-disables once any user exists. - /setup route + sign-in redirect: first run sends you to create the owner. - teamUsers server functions (owner/admin-gated): list / create (with temp password) / reset password / remove. Removal drops membership + sessions, keeps the user row for historical attribution. - Settings gains a "Users" tab in team mode (TeamUsers.tsx). - docs/SELF_HOSTING_TEAM_MODE.md: activation runbook (.env, build, first owner). No DB migration — all rows are existing better-auth tables. tsc / oxlint / knip clean. New teamProvisioning.test.ts (4 cases) passes; suite otherwise unchanged (pre-existing samSkills.test.ts CRLF failure only). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
80 lines
3.0 KiB
Markdown
80 lines
3.0 KiB
Markdown
# Self-hosting with team logins (`AUTH_MODE=team`)
|
|
|
|
`team` mode turns the app into a single shared workspace with individual
|
|
email/password logins. There is no billing, no Google login, no email
|
|
verification, and no self-serve signup — the owner provisions every account.
|
|
|
|
Use it when you want your team on one internal instance and need to see who did
|
|
what. For a public multi-tenant product, use `hosted` instead.
|
|
|
|
## What you get
|
|
|
|
- Email/password sign-in for each teammate.
|
|
- One shared workspace (organization). Everyone works in the same projects.
|
|
- Roles: **owner** (you), **admin** (full access except billing/owner actions),
|
|
**member** (research + view).
|
|
- Two ways to add people:
|
|
- **Settings → Users** — create an account with a temporary password directly.
|
|
- **Settings → Organization** — send an email invite link (needs no email
|
|
provider config to create the invite; the link is shown in the UI).
|
|
|
|
## 1. Set environment variables
|
|
|
|
Edit `.env` in the deployment directory (for the pm2 setup that is
|
|
`/home/dev/DOCKER/OPEN-SEO/open-seo/.env`):
|
|
|
|
```sh
|
|
AUTH_MODE=team
|
|
BETTER_AUTH_URL=https://seo.thedomainnest.com # the exact public origin, https
|
|
BETTER_AUTH_SECRET=<64 hex chars> # openssl rand -hex 32
|
|
```
|
|
|
|
`AUTH_MODE` is compiled into the browser bundle, so it must be present when
|
|
`vite build` runs — not only at runtime. Vite reads this same `.env` at build
|
|
time, so one edit covers both as long as the build runs in this directory.
|
|
|
|
Keep the existing `DATABASE_URL`, `DATAFORSEO_API_KEY`, etc.
|
|
|
|
## 2. Rebuild and restart
|
|
|
|
If your pipeline builds on push, commit/push and let it run. To do it by hand:
|
|
|
|
```sh
|
|
cd /home/dev/DOCKER/OPEN-SEO/open-seo
|
|
pnpm install --frozen-lockfile
|
|
pnpm build
|
|
pm2 restart OPEN-SEO --update-env
|
|
```
|
|
|
|
Confirm config: `curl -s http://127.0.0.1:3001/api/health | jq .checks.auth`
|
|
should report `team`.
|
|
|
|
## 3. Create the owner account
|
|
|
|
Open the site. With `team` mode on and no users yet, `/sign-in` redirects to
|
|
`/setup`. Fill in name, email, password → **Create workspace**. You are signed
|
|
in as the owner.
|
|
|
|
`/setup` disables itself the moment the owner exists. If you ever need to reset,
|
|
delete all rows from `user` (and `member`, `account`, `session`) in Postgres and
|
|
reload.
|
|
|
|
## 4. Add your team
|
|
|
|
**Settings → Users → Add user.** Enter their email, name, a temporary password,
|
|
and a role. Share the password out-of-band; they change it after signing in
|
|
(Settings → Personal, once that lands) or you reset it from the same screen.
|
|
|
|
Removing a user drops their membership and signs them out everywhere. Their
|
|
`user` row is kept so past activity still attributes correctly; re-adding them
|
|
issues a fresh password.
|
|
|
|
## Notes
|
|
|
|
- Password reset by email is not available in `team` mode. The owner/admins
|
|
reset passwords from Settings → Users.
|
|
- The MCP server and its OAuth flow are hosted-only for now; `team` deployments
|
|
serve the app UI only.
|
|
- Rolling back: set `AUTH_MODE=local_noauth`, rebuild, restart. Existing users
|
|
and data stay in the database, just unused.
|