Skip to content

refactor(api): standardize all API routes with withApi, apiFetch, Zod validation, and security hardening#4100

Open
owenwahlgren wants to merge 1 commit into
masterfrom
refactor/api-standards-wip
Open

refactor(api): standardize all API routes with withApi, apiFetch, Zod validation, and security hardening#4100
owenwahlgren wants to merge 1 commit into
masterfrom
refactor/api-standards-wip

Conversation

@owenwahlgren
Copy link
Copy Markdown
Contributor

@owenwahlgren owenwahlgren commented Apr 13, 2026

Summary

Comprehensive API standardization across the entire Builders Hub backend. Migrates ~100 API routes from ad-hoc patterns to a shared infrastructure layer, fixes 12+ security vulnerabilities, adds 264 tests, eliminates all raw fetch/axios from client code, and enforces standards via ESLint + CI.

Closes #3969, closes #3971, closes #3974, closes #3908, closes #4013
Addresses #3973, #3970, #4096, #4082, #4079


Shared infrastructure (lib/api/)

  • withApi() — composable route wrapper: auth, Zod validation, Prisma-backed rate limiting, error envelope
  • successResponse()/errorResponse() — consistent { success: true, data } / { success: false, error: { code, message } } envelope
  • apiFetch() client — auto-unwraps envelope, throws ApiClientError, handles FormData natively
  • zod-validation-error for human-readable validation messages
  • assertOwnership() — generic IDOR prevention
  • parsePagination() — clamped pagination with MAX_PAGE_SIZE=100
  • Prisma-backed rate limiter with serializable transactions (TOCTOU-safe)
  • @t3-oss/env-nextjs for type-safe env validation (80+ vars)
  • Security headers (X-Content-Type-Options, X-Frame-Options, X-Request-Id) in proxy.ts

Security fixes

Client migration — zero raw fetch/axios remaining

  • ALL client components/hooks migrated from raw fetch/axios to apiFetch
  • apiFetch handles FormData natively (auto-detects, skips JSON.stringify)
  • Zero eslint-disable exceptions needed
  • ESLint no-restricted-syntax bans fetch('/api/...') in client code
  • ESLint no-restricted-imports bans axios imports
  • scripts/check-api-standards.sh validates at CI time

Route consolidation

  • /project/*/projects/* (deprecated wrappers for backwards compat)

Tests: 264 passing across 15 suites (#3908)

  • Auth (OTP rate limit, OAuth timing-safe), Faucets (key leak prevention)
  • Chat (ownership, share token entropy), Explorer (param injection)
  • Validators (IDOR, rate limiting), Badges (role isolation)
  • Projects (mass assignment, pagination), Playground (ownership)
  • Evaluate (role boundaries, schema validation), Stats, Notifications, Profile

CI/Precommit enforcement

  • .github/workflows/api-ci.yml — type-check, ESLint, standards script, vitest
  • scripts/check-api-standards.sh — stack traces, pagination, auth, secrets, raw fetch, axios
  • .lintstagedrc.js — API routes linted on commit with zero-warning tolerance

Bugs found during test audit

Bug Severity Detail
evaluate/route.ts Zod schema unwired HIGH Schema defined but never passed to withApi — zero validation
oauth/token client enumeration HIGH client_id returned 400 vs client_secret 401 — attackable
projects/[id] null 200 MEDIUM Non-existent project returned { success: true, data: null }
validator-alerts/[id] validation ordering MEDIUM Body validated after ownership check — leaked internal state
notifications/create no validation MEDIUM Empty body crashed upstream fetch
calendar/google env before validation LOW Missing API key returned 500 for bad input

New dependencies

  • zod-validation-error — human-readable Zod error messages
  • @t3-oss/env-nextjs — type-safe env validation
  • @vitejs/plugin-react (dev) — vitest React support
  • @testing-library/jest-dom, @testing-library/react, @testing-library/dom, jsdom (dev) — test deps (were transitive, now explicit)

Verification

All pass locally:

  • tsc --noEmit — 0 errors
  • eslint app/api/ --max-warnings 0 — 0 errors
  • vitest run tests/api/ — 264/264 pass
  • scripts/check-api-standards.sh — 0 errors, 0 warnings
  • Merge conflicts with master resolved (hackathons→events rename)

Test plan

  • TypeScript compiles cleanly
  • All 264 API tests pass
  • ESLint zero errors on API routes
  • Standards script zero errors, zero warnings
  • Manual smoke test: faucet, chat, explorer, validator alerts
  • Run npx prisma migrate dev --name add_api_rate_limit_log
  • Verify Vercel preview deployment builds

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 13, 2026

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

Project Deployment Actions Updated (UTC)
builder-hub Ready Ready Preview, Comment Apr 14, 2026 0:36am

Request Review

…urity hardening

- lib/api/: withApi wrapper, successResponse/errorResponse envelope, Zod validation,
  Prisma-backed rate limiting (serializable tx), assertOwnership, parsePagination
- apiFetch client: auto-unwraps envelope, handles FormData, throws ApiClientError
- 264 tests across 15 suites, api-ci.yml workflow, check-api-standards.sh
- Security: OTP rate limit, HMAC timing-safe OAuth, faucet key sanitization,
  mass assignment guards, path traversal prevention, SSRF prevention, fetch timeouts
- All client code migrated from raw fetch/axios to apiFetch (zero remaining)
- ESLint enforces apiFetch usage, bans raw fetch('/api/') and axios
- zod-validation-error, @t3-oss/env-nextjs, security headers in proxy.ts

Closes #3969, #3971, #3974, #3908, #4013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment