unic / unic/ai-developer-hub

Standardize cron / Bearer auth across API routes

Open
#51 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:auth priority:medium tech-debt
Dominant language
HTML
Stars
0
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Problem

The codebase has two different patterns for Bearer-token-protected API routes:

  1. The shared helper requireBearerSecret(request, ENV_NAME) (and its cron-specific wrapper requireCronSecret) used by every /api/sync/* route.
  2. A custom local validateAuth(request) in /api/invoices/ingest/route.ts that re-implements the same pattern slightly differently.

Two implementations of the same primitive means a future hardening (e.g. constant-time comparison, audit logging on auth failure, rate limiting) has to be applied twice and can drift. It's also harder for a new contributor — or an AI agent — to discover the canonical way to add a new protected route.

Evidence

src/app/api/invoices/ingest/route.ts:21-26:

function validateAuth(request: NextRequest): boolean {
  const secret = process.env.INVOICE_INGEST_SECRET;
  if (!secret) return false;
  const authHeader = request.headers.get(\"authorization\");
  return authHeader === `Bearer ${secret}`;
}

vs the shared helper used by /api/sync/* routes (see src/lib/cron-auth.ts or wherever requireBearerSecret lives).

The local one:

  • Does plain string equality (timing-attack-vulnerable; the shared helper should use timingSafeEqual if it doesn't already).
  • Returns a boolean instead of returning a NextResponse | null like the shared helper, so the calling code has to write its own 401 response.

Proposed approach

  1. Read src/lib/cron-auth.ts (or the canonical location of requireBearerSecret) to confirm the contract.
  2. If the shared helper does not already use a constant-time comparison, fix that first as part of this issue (use crypto.timingSafeEqual after coercing to Buffer of equal length).
  3. Replace validateAuth in /api/invoices/ingest/route.ts with a call to the shared helper passing \"INVOICE_INGEST_SECRET\".
  4. Sweep the rest of src/app/api/**/route.ts for any other one-off Bearer checks and replace them.
  5. Add a unit test for requireBearerSecret:
    • missing header → returns 401 response
    • wrong secret → returns 401
    • correct secret → returns null (i.e. "continue")
    • missing env var → returns 500 / clear error (or whatever the helper currently does — match it)

Acceptance criteria

  • No route-local Bearer-validation helpers remain in src/app/api/.
  • requireBearerSecret uses constant-time comparison.
  • Behavior of /api/invoices/ingest is unchanged (auth still works, error responses still match).
  • Unit tests cover the helper.
  • pnpm lint && pnpm typecheck && pnpm test pass.

Verification

  1. POST /api/invoices/ingest with no Authorization header → 401.
  2. POST with wrong Bearer → 401.
  3. POST with correct Bearer + valid PDF → 200 (or expected response).
  4. Existing /api/sync/* routes still work unchanged.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading src/lib/cron-auth.ts and src/app/api/invoices/ingest/route.ts to confirm the shared helper contract, then sweep src/app/api/**/route.ts for other local Bearer checks. Add the specified helper tests for missing, wrong, and correct credentials, and verify with pnpm lint && pnpm typecheck && pnpm test plus the listed ingest and sync route checks.

Written by the indexing model from the issue text.

Assessment

Tech stack
next.js, typescript
Domain
api, authentication, security, testing
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.