fix(auth): centralize JWT secret + 24h TTL (Brief 1 of fix-auth-bypass) #6

Merged
varutasu merged 1 commit from brief/fix-auth-bypass/1-jwt-secret-helper into main 2026-05-23 11:40:51 -04:00
varutasu commented 2026-05-23 10:47:32 -04:00 (Migrated from github.com)

Summary

Convoy fix-auth-bypass / Brief 1 of 5. Resolves AGENTS.md gotcha #3 (hardcoded JWT_SECRET default across 7 files). See .convoys/fix-auth-bypass.md for the full convoy plan.

  • New lib/auth-secret.js is the single source of truth for JWT_SECRET and the canonical JWT_TOKEN_TTL = '24h'. The module throws at import time if process.env.JWT_SECRET is unset or empty — no silent fallback.
  • 7 caller files now import the secret from the helper instead of computing it locally. pages/api/auth/login.js and pages/api/auth/register.js additionally route token minting through generateToken in auth-utils.js (one canonical mint site), which dropped their inline jwt.sign and jsonwebtoken import.
  • TTL drift between auth-utils.generateToken ('7d') and login.js / register.js ('24h') is reconciled to 24h via the imported JWT_TOKEN_TTL. The '7d' was the drift — no UX impact, since the call path was always login→inline-24h.

Files changed

  • lib/auth-secret.js (new, 12 lines)
  • lib/permission-middleware.js (literal → import)
  • pages/api/auth-utils.js (literal → import; '7d'JWT_TOKEN_TTL)
  • pages/api/auth/login.js (literal → import; inline jwt.sign(...)generateToken(...))
  • pages/api/auth/register.js (same as login)
  • pages/api/auth/verify.js (literal → import). The no-token admin-fetch branch is intentionally preserved here — Brief 2 removes it.
  • pages/api/favorites.js (literal → import)
  • pages/api/users/search.js (literal → import)

Test plan

  • npm run build exits 0
  • npm run lint matches baseline (128 problems / 81 errors / 47 warnings); zero new lint issues in the 8 in-scope files
  • With JWT_SECRET set: import('./lib/auth-secret.js'){ JWT_SECRET: string, JWT_TOKEN_TTL: '24h' }
  • With JWT_SECRET unset OR empty string: same import throws synchronously with descriptive error (reviewer reproduced both cases)
  • grep "process.env.JWT_SECRET" returns exactly 1 hit (the helper itself)
  • grep "your-secret-key-change-in-production" returns zero hits
  • Reviewer to verify on preview deployment: log in → decode token → confirm exp - iat ≈ 86400 (24h). Unset JWT_SECRET in a side-deploy → confirm 500 on first auth call (i.e. fail-loud worked end-to-end on Vercel runtime).

Pre-merge requirements

  • JWT_SECRET must be set in Vercel project env for both Production and Preview before this merges, or the serverless functions will crash on cold start (R3 in convoy file).
  • All currently-logged-in users will be logged out when this deploys, because their existing tokens were signed against the fallback literal (R10 in convoy file). Acceptable per architect's call — the broken auth is the bug being fixed.

⚠️ Post-merge state — convoy is NOT complete after this PR

This PR alone does not close the auth bypass. lib/permission-middleware.js::getUserFromRequest still returns a hardcoded admin user when no Bearer token is present (intentional in this PR — that removal is Brief 2's job, which depends on this PR landing first). pages/api/auth/verify.js likewise still has its no-token admin-fetch branch (WHERE email = 'admin@tcgvault.com').

Do not advertise the convoy as complete or close the AGENTS.md gotcha #2 line item until Brief 2 also merges. This PR closes gotcha #3 only.

Out of scope (intentionally — see convoy plan)

  • The synthetic admin fallback in lib/permission-middleware.js::getUserFromRequestBrief 2
  • The no-token admin-fetch branch in pages/api/auth/verify.jsBrief 2
  • Deleting public dev endpoints (/api/simple, /api/test-*, /api/setup-database) → Brief 3 (PR #7)
  • CORS tightening + rate limiting → Brief 4
  • Vitest + auth tests → Brief 5

Audit cohort

  • role-reviewer complete — Approve-with-followups (no merge blockers; the M1 nit prompted the new "Post-merge state" section above)
  • ⏭️ design-system / a11y / browser-smoke — N/A (no UI surface)

🤖 Pipeline metadata: convoy=fix-auth-bypass, brief=1, depends_on=[], audit=reviewer-only

## Summary Convoy `fix-auth-bypass` / Brief 1 of 5. Resolves AGENTS.md gotcha #3 (hardcoded `JWT_SECRET` default across 7 files). See [`.convoys/fix-auth-bypass.md`](https://github.com/varutasu/tcg-vault/blob/convoy/fix-auth-bypass/.convoys/fix-auth-bypass.md) for the full convoy plan. - New `lib/auth-secret.js` is the single source of truth for `JWT_SECRET` and the canonical `JWT_TOKEN_TTL = '24h'`. The module **throws at import time** if `process.env.JWT_SECRET` is unset or empty — no silent fallback. - 7 caller files now import the secret from the helper instead of computing it locally. `pages/api/auth/login.js` and `pages/api/auth/register.js` additionally route token minting through `generateToken` in `auth-utils.js` (one canonical mint site), which dropped their inline `jwt.sign` and `jsonwebtoken` import. - TTL drift between `auth-utils.generateToken` (`'7d'`) and `login.js` / `register.js` (`'24h'`) is reconciled to **24h** via the imported `JWT_TOKEN_TTL`. The `'7d'` was the drift — no UX impact, since the call path was always login→inline-24h. ## Files changed - `lib/auth-secret.js` (**new**, 12 lines) - `lib/permission-middleware.js` (literal → import) - `pages/api/auth-utils.js` (literal → import; `'7d'` → `JWT_TOKEN_TTL`) - `pages/api/auth/login.js` (literal → import; inline `jwt.sign(...)` → `generateToken(...)`) - `pages/api/auth/register.js` (same as login) - `pages/api/auth/verify.js` (literal → import). **The no-token admin-fetch branch is intentionally preserved here — Brief 2 removes it.** - `pages/api/favorites.js` (literal → import) - `pages/api/users/search.js` (literal → import) ## Test plan - [x] `npm run build` exits 0 - [x] `npm run lint` matches baseline (128 problems / 81 errors / 47 warnings); zero new lint issues in the 8 in-scope files - [x] With `JWT_SECRET` set: `import('./lib/auth-secret.js')` → `{ JWT_SECRET: string, JWT_TOKEN_TTL: '24h' }` - [x] With `JWT_SECRET` unset OR empty string: same import throws synchronously with descriptive error (reviewer reproduced both cases) - [x] `grep "process.env.JWT_SECRET"` returns exactly 1 hit (the helper itself) - [x] `grep "your-secret-key-change-in-production"` returns zero hits - [ ] **Reviewer to verify on preview deployment:** log in → decode token → confirm `exp - iat ≈ 86400` (24h). Unset `JWT_SECRET` in a side-deploy → confirm 500 on first auth call (i.e. fail-loud worked end-to-end on Vercel runtime). ## Pre-merge requirements - [ ] **`JWT_SECRET` must be set in Vercel project env for both Production and Preview** before this merges, or the serverless functions will crash on cold start (R3 in convoy file). - [ ] **All currently-logged-in users will be logged out** when this deploys, because their existing tokens were signed against the fallback literal (R10 in convoy file). Acceptable per architect's call — the broken auth is the bug being fixed. ## ⚠️ Post-merge state — convoy is NOT complete after this PR This PR alone does **not** close the auth bypass. `lib/permission-middleware.js::getUserFromRequest` still returns a hardcoded admin user when no `Bearer` token is present (intentional in this PR — that removal is Brief 2's job, which depends on this PR landing first). `pages/api/auth/verify.js` likewise still has its no-token admin-fetch branch (`WHERE email = 'admin@tcgvault.com'`). **Do not advertise the convoy as complete or close the AGENTS.md gotcha #2 line item until Brief 2 also merges.** This PR closes gotcha #3 only. ## Out of scope (intentionally — see convoy plan) - The synthetic admin fallback in `lib/permission-middleware.js::getUserFromRequest` → **Brief 2** - The no-token admin-fetch branch in `pages/api/auth/verify.js` → **Brief 2** - Deleting public dev endpoints (`/api/simple`, `/api/test-*`, `/api/setup-database`) → **Brief 3** (PR #7) - CORS tightening + rate limiting → **Brief 4** - Vitest + auth tests → **Brief 5** ## Audit cohort - ✅ `role-reviewer` complete — Approve-with-followups (no merge blockers; the M1 nit prompted the new "Post-merge state" section above) - ⏭️ design-system / a11y / browser-smoke — N/A (no UI surface) --- 🤖 Pipeline metadata: convoy=fix-auth-bypass, brief=1, depends_on=[], audit=reviewer-only
vercel[bot] commented 2026-05-23 10:47:34 -04:00 (Migrated from github.com)

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tcg-vault Ready Ready Preview, Comment May 23, 2026 2:47pm

Request Review

[vc]: #PJ/skUErD5A31wIlkxJKAHNdpsOQCtrTJVCpcXEFo14=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ0Y2ctdmF1bHQiLCJwcm9qZWN0SWQiOiJwcmpfRjZXOEVvRkd3Y0g3aWVGcnRvRlNlOXdVVkFhNSIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9yYW5kYWxsLXN0aWxsd2VsbHMtcHJvamVjdHMvdGNnLXZhdWx0LzlZN3lvUTZIVjJMMzVzUzQ5dEhhNlhOREZqUkMiLCJwcmV2aWV3VXJsIjoidGNnLXZhdWx0LWdpdC1icmllZi1maXgtYXV0aC02YTNkOTctcmFuZGFsbC1zdGlsbHdlbGxzLXByb2plY3RzLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoidGNnLXZhdWx0LWdpdC1icmllZi1maXgtYXV0aC02YTNkOTctcmFuZGFsbC1zdGlsbHdlbGxzLXByb2plY3RzLnZlcmNlbC5hcHAifSwicm9vdERpcmVjdG9yeSI6bnVsbH1dLCJyZXF1ZXN0UmV2aWV3VXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3ZlcmNlbC1hZ2VudC9yZXF1ZXN0LXJldmlldz9vd25lcj12YXJ1dGFzdSZyZXBvPXRjZy12YXVsdCZwcj02In0= The latest updates on your projects. Learn more about [Vercel for GitHub](https://vercel.link/github-learn-more). | Project | Deployment | Actions | Updated (UTC) | | :--- | :----- | :------ | :------ | | [tcg-vault](https://vercel.com/randall-stillwells-projects/tcg-vault) | ![Ready](https://vercel.com/static/status/ready.svg) [Ready](https://vercel.com/randall-stillwells-projects/tcg-vault/9Y7yoQ6HV2L35sS49tHa6XNDFjRC) | [Preview](https://tcg-vault-git-brief-fix-auth-6a3d97-randall-stillwells-projects.vercel.app), [Comment](https://vercel.live/open-feedback/tcg-vault-git-brief-fix-auth-6a3d97-randall-stillwells-projects.vercel.app?via=pr-comment-feedback-link) | May 23, 2026 2:47pm | <a href="https://vercel.com/vercel-agent/request-review?owner=varutasu&repo=tcg-vault&pr=6" rel="noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://agents-vade-review.vercel.sh/request-review-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://agents-vade-review.vercel.sh/request-review-light.svg"><img src="https://agents-vade-review.vercel.sh/request-review-light.svg" alt="Request Review"></picture></a>
github-actions[bot] commented 2026-05-23 10:47:58 -04:00 (Migrated from github.com)

Pipeline Health

Build + CI gates

Gate Status
Vercel build (Preview) pass
CI: Lint pass
CI: Schema map fresh skipped
Preview smoke failure
Visual diff failure

Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build).

Role reports

Role Status
Reviewer report pending
A11y audit pending
Design system audit pending

See individual comments above for details. This rollup updates automatically.

<!-- pipeline-rollup --> ## Pipeline Health ### Build + CI gates | Gate | Status | | --- | --- | | Vercel build (Preview) | ✅ pass | | CI: Lint | ✅ pass | | CI: Schema map fresh | ❌ skipped | | Preview smoke | ❌ failure | | Visual diff | ❌ failure | _Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build)._ ### Role reports | Role | Status | | --- | --- | | Reviewer report | ⏳ pending | | A11y audit | ⏳ pending | | Design system audit | ⏳ pending | See individual comments above for details. This rollup updates automatically.
Sign in to join this conversation.
No description provided.