metatron-open-seo/docs/SELF_HOSTING_TEAM_MODE.md
metatroncubeswdev acc35c055c
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled
Phase 3: team-mode owner setup, user management, shared workspace
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>
2026-09-09 08:13:01 -04:00

3.0 KiB

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):

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:

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.