- 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.
74 lines
4.4 KiB
Plaintext
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"
|