Files
freno-dev/.env.example
Michael Freno 7287f10c9a feat: migrate Nessa auth to Clerk session tokens (task 03)
- src/server/nessa-auth.ts: replace jose HS256 sign/verify with Clerk
  session JWT verification via @clerk/backend verifyToken (RS256/JWKS).
  signNessaToken removed — frontend now supplies Clerk session tokens.
- src/server/api/utils.ts: createTRPCContext verifies Clerk JWT, resolves
  ctx.nessaUserId via SELECT id FROM users WHERE clerkUserId=? on the
  shared NessaConnectionFactory. Lookup miss throws typed UNAUTHORIZED
  (webhook has not run yet). Invalid/expired tokens are swallowed; the
  enforceNessaUser middleware rejects null nessaUserId.
- src/server/api/routers/nessa-community-authz.test.ts: add clerkUserId
  lookup tests (seeded match, missing row, mismatched id, local≠clerk).
- src/server/nessa-auth.test.ts: verifyNessaToken unit tests with mocked
  @clerk/backend (valid sub, missing sub, malformed/expired/wrong-signature
  rejection) plus static audit that signNessaToken is gone.
- src/server/clerk-user-webhook.ts + src/routes/api/clerk-webhook.ts:
  Clerk user.created/user.updated webhook handler (Svix signature
  verification, idempotent upsert by clerkUserId, lazy ALTER TABLE
  migration) with full test suite.
- src/server/api/routers/nessa.ts: remove legacy register/login/google/
  apple sign-in mutations (Clerk is now the sole identity provider).
- src/env/server.ts: add NESSA_CLERK_SECRET, NESSA_CLERK_JWT_ISSUER,
  NESSA_CLERK_WEBHOOK_SECRET; NESSA_JWT_SECRET moved to optional.
- package.json: add @clerk/backend, svix; lineage/auth.test.ts and
  nessa-ownership.test.ts: add Clerk env vars to env mocks.
- .env.example: document Clerk config vars and rotation.
- delete nessa-google-oauth.test.ts (Google auth removed).

ctx.nessaUserId remains the local users.id — router bodies are untouched.
2026-07-23 01:40:53 -04:00

74 lines
4.4 KiB
Plaintext

# ──────────────────────────────────────────────────────────────────────────
# freno-dev environment variables — example / template
# ──────────────────────────────────────────────────────────────────────────
# Copy this file to `.env` and fill in real values.
# `.env` is gitignored and MUST NEVER be committed. Real secret values must
# come from your local environment or your team's secret manager — never from
# git history. See the root `AGENTS.md` "Secret Management & Rotation" section
# and `docs/security/secret-rotation-runbook.md`.
#
# The schema in `src/env/server.ts` validates PRESENCE + min length for every
# variable below. Do not leave production values blank.
# ──────────────────────────────────────────────────────────────────────────
# ── Frontend / public (safe to expose to the browser, VITE_* is shipped) ──
VITE_DOMAIN="http://localhost:3000"
VITE_AWS_BUCKET_STRING="https://example-bucket.s3.amazonaws.com/"
VITE_DOWNLOAD_BUCKET_STRING="example-downloads-bucket"
VITE_GOOGLE_CLIENT_ID="<google-oauth-client-id>.apps.googleusercontent.com"
VITE_GOOGLE_CLIENT_ID_DEV="<google-oauth-client-id-dev>.apps.googleusercontent.com"
VITE_GOOGLE_CLIENT_ID_MAGIC_DELVE="<google-oauth-client-id-magicdelve>.apps.googleusercontent.com"
VITE_GITHUB_CLIENT_ID="<github-oauth-client-id>"
VITE_GITHUB_CLIENT_ID_DEV="<github-oauth-client-id-dev>"
VITE_INFILL_ENDPOINT="https://infill.example.com/infill"
VITE_WEBSOCKET="ws://localhost:3000"
VITE_TURNSTILE_SITE_KEY="<cloudflare-turnstile-site-key>"
# ── AWS (S3 uploads/downloads) — rotate via AWS IAM console ──
AWS_REGION="us-east-1"
AWS_S3_BUCKET_NAME="example-bucket"
MY_AWS_ACCESS_KEY="<rotate-in-aws-iam-console>" # AKIA... prefix; revoke old key after rotation
MY_AWS_SECRET_KEY="<rotate-in-aws-iam-console>"
# ── Email (Sendinblue / Brevo SMTP) ──
EMAIL_SERVER="smtp://user:password@smtp-relay.sendinblue.com:587"
EMAIL_FROM="you@example.com"
SENDINBLUE_KEY="<rotate-in-brevo-console>"
# ── Auth / signing secrets (generate with: openssl rand -base64 64) ──
JWT_SECRET_KEY="<generate-64-byte-base64>" # web JWT (HS256) signing
LINEAGE_JWT_SECRET="<generate-64-byte-base64>" # Lineage game JWT (HS256) signing — isolated from web (p8-005)
LINEAGE_OFFLINE_SERIALIZATION_SECRET="<generate-64-byte-base64>" # offline lineage blob signing
# ── OAuth client secrets — rotate in provider consoles ──
GOOGLE_CLIENT_SECRET="<rotate-in-google-cloud-console>" # GOCSPX-...
GOOGLE_CLIENT_SECRET_DEV="<rotate-in-google-cloud-console>"
GITHUB_CLIENT_SECRET="<rotate-in-github-oauth-apps>"
GITHUB_CLIENT_SECRET_DEV="<rotate-in-github-oauth-apps>"
# ── Cloudflare Turnstile ──
TURNSTILE_SECRET_KEY="<rotate-in-cloudflare-dashboard>" # 0x...
# ── Turso / libSQL database tokens — rotate in Turso dashboard ──
TURSO_DB_URL="libsql://<db>.turso.io"
TURSO_DB_TOKEN="<rotate-in-turso-dashboard>" # eyJ...
TURSO_DB_API_TOKEN="<rotate-in-turso-dashboard>" # org-level API token
TURSO_LINEAGE_URL="libsql://<lineage-db>.turso.io"
TURSO_LINEAGE_TOKEN="<rotate-in-turso-dashboard>"
NESSA_DB_URL="libsql://<nessa-db>.turso.io"
NESSA_DB_TOKEN="<rotate-in-turso-dashboard>"
# Clerk authentication — rotate via Clerk Dashboard or `clerk api .../rotate_secret_keys`
NESSA_CLERK_SECRET="sk_test_<rotate-in-clerk-dashboard>" # secret key (sk_test_... for dev, sk_live_... for prod)
NESSA_CLERK_JWT_ISSUER="https://<your-app>.clerk.accounts.dev" # JWT issuer from Clerk Dashboard
NESSA_CLERK_WEBHOOK_SECRET="whsec_<rotate-in-clerk-dashboard>" # Svix signing secret from Clerk Dashboard → Webhooks → Signing Secret
APPLE_CLIENT_ID_LINEAGE=com...
APPLE_CLIENT_ID_NESSA=com...
# ── Infra / integration tokens ──
INFILL_BEARER_TOKEN="<rotate-at-infill-service>"
GITEA_URL="https://gitea.example.com"
GITEA_TOKEN="<rotate-in-gitea>"
GITHUB_API_TOKEN="<rotate-in-github-settings>" # ghp_... / github_pat_...
REDIS_URL="redis://localhost:6379"