Files
Jyotisha/docs/operations/self-hosted-identity.md
T
2026-07-21 17:52:25 +08:00

81 lines
4.3 KiB
Markdown

# Self-hosted identity operations
This milestone deploys Better Auth beside the existing Supabase login. It does not authorize the final authentication cutover or removal of Supabase-backed business routes.
## Safe staging mode
Keep these two values exactly as shown while profile, consultation, credits, chat, and report routes still rely on Supabase JWT/RLS:
```dotenv
AUTH_PROVIDER=supabase
SELF_HOSTED_IDENTITY_ENABLED=true
```
This combination keeps `staging.jyotisha.chat/login` on Supabase, enables `/api/auth/**` for integration tests, and makes `admin.staging.jyotisha.chat/login` use the isolated Better Auth admin surface. The public and admin sessions have different secrets and host-only cookie prefixes. The staging validator deliberately rejects `AUTH_PROVIDER=self-hosted` in this milestone.
Use [the tracked staging identity example](../../deploy/.env.staging.identity.example) as a list of names only. Replace bracketed values directly on the server and keep `/opt/jyotisha-staging/.env.staging` owned by `deploy` with mode `0600`.
Generate separate secrets locally on the server:
```bash
openssl rand -base64 32
openssl rand -base64 32
```
Do not reuse either value as a PostgreSQL password. `IDENTITY_DATABASE_URL` uses the existing `IDENTITY_RUNTIME_PASSWORD` from `.env.staging.database`, percent-encoded only in the URL password component. It must point to the private Compose hostname `postgres:5432/jyotisha`; never publish PostgreSQL on a host port.
The Resend key must be staging-only. `RESEND_FROM_EMAIL` must use a sender/domain verified in Resend. CI never receives this key and uses an in-memory sender.
Validate without printing values:
```bash
cd /opt/jyotisha-staging
chmod 600 .env.staging
bash deploy/validate-staging-env.sh .env.staging
```
## Migration and smoke checks
Apply the reviewed PostgreSQL migrations through the existing `Migrate Staging Database` workflow before deploying the web image. The identity migration creates `identity.users`, `identity.sessions`, `identity.accounts`, `identity.verifications`, and `identity.otp_rate_limits` under least-privilege roles.
After deployment:
```bash
curl -fsS https://admin.staging.jyotisha.chat/login >/dev/null
curl -fsS https://admin.staging.jyotisha.chat/api/auth/get-session
test "$(curl -sS -o /dev/null -w '%{http_code}' https://admin.staging.jyotisha.chat/)" = 404
```
An unknown or unpromoted email cannot create an admin session. Promote an imported staging user only through a reviewed database/admin operation; the persisted `identity.users.role` value must include `admin` before the admin OTP flow can issue a cookie.
## Import rehearsal
Export Supabase Auth users to a JSON array in the supported fixture shape, then run a redacted dry-run first:
```bash
cd /opt/jyotisha-staging/frontend
node scripts/import-supabase-auth-users.mjs /secure/path/auth-users.json
```
The summary contains only counts. It preserves UUID, normalized email, verification timestamps, display metadata, and created/updated timestamps. It intentionally ignores passwords, sessions, JWTs, provider secrets, and Supabase platform fields.
Apply only after reviewing the dry-run and taking a local encrypted staging backup:
```bash
set -a
. ../.env.staging
set +a
node scripts/import-supabase-auth-users.mjs /secure/path/auth-users.json --apply
unset IDENTITY_DATABASE_URL
```
Reruns are idempotent by UUID and the whole import is transactional. Duplicate canonical emails abort before database writes.
## Rollback and rotation
To disable the new identity service without touching Supabase login, set `SELF_HOSTED_IDENTITY_ENABLED=false`, remove the identity-only smoke check for that separately reviewed rollback revision, and redeploy. Existing self-hosted sessions become unreachable; do not delete identity rows during application rollback.
Rotating either Better Auth secret invalidates only that surface's existing sessions. Rotate user and admin secrets separately, restart the web service, and verify the corresponding host. Rotate a leaked Resend key in Resend first, replace the server value, then restart. Never print the old or new values.
Final `AUTH_PROVIDER=self-hosted` cutover is blocked until all business modules authorize with the self-hosted session boundary, reconciliation passes, production backups and restore drills exist, and a separate reviewed cutover plan is approved.