Skip to content

fix: health readiness/liveness split, service-auth key rotation, transport-aware CSRF Secure flag, and reqId in error bodies (#562, #555, #560, #558) - #641

Merged
therealjhay merged 10 commits into
Betta-Pay:mainfrom
TheCodingChef-eth:fix/api-gateway-health-auth-csrf-reqid
Sep 2, 2026
Merged

fix: health readiness/liveness split, service-auth key rotation, transport-aware CSRF Secure flag, and reqId in error bodies (#562, #555, #560, #558)#641
therealjhay merged 10 commits into
Betta-Pay:mainfrom
TheCodingChef-eth:fix/api-gateway-health-auth-csrf-reqid

Conversation

@TheCodingChef-eth

Copy link
Copy Markdown
Contributor

Summary

This PR resolves four api-gateway issues in a single branch, with each file committed independently.
closes #562 — Health endpoint readiness vs liveness split
Problem: /api/health returned 503 for unhealthy status, but there was no dedicated liveness probe. Load balancers need a simple liveness check (always 200 when the process is up) separate from the readiness check.
Solution:

  • Added GET /api/health/live — always returns 200 { status: "alive" } when the gateway process is running. Load balancers use this for liveness.
  • GET /api/health remains the readiness probe — returns 503 when critical dependents (DB, upstream engines) are down.
  • GET /api/health/all continues to aggregate all service health.
    Readiness 503 demo:
    GET /api/health → 503 { status: "unhealthy", service: "api-gateway", ... }
    GET /api/health/live → 200 { status: "alive", service: "api-gateway" }
    closes api-gateway service-auth plugin doesn't rotate internal service keys #555 — Service-auth key rotation
    Problem: Internal service credentials were static with no rotation mechanism. Compromise required full redeploy.
    Solution:
  • createServiceAuth() and registerServiceAuth() now accept string | string[]. Pass an array of secrets to support overlapping keys during rotation.
  • During rotation, both old and new keys are accepted. Once all consumers have switched, remove the old key.
  • AppOptions.interServiceSecret added to buildApp() for testing rotation scenarios.
    Rotation demo:
    // Deploy with both keys during rotation window
    registerServiceAuth(fastify, [OLD_SECRET, NEW_SECRET]);

// After all consumers switch, deploy with only the new key
registerServiceAuth(fastify, NEW_SECRET);
closes #560 — CSRF cookie Secure flag transport-aware
Problem: buildCsrfCookieHeader set Secure only when NODE_ENV === 'production'. Staging HTTPS environments in NODE_ENV=development served cookies without Secure.
Solution:

  • Added buildCsrfCookieHeader(name, value, request) utility that inspects the actual request protocol (request.protocol or x-forwarded-proto header) instead of NODE_ENV.
  • HTTPS always gets Secure regardless of env. HTTP dev still works without Secure.
    Transport-aware demo:
    // HTTPS request → Secure flag set
    buildCsrfCookieHeader('csrf', 'token', { protocol: 'https' })
    // → "csrf=token; Path=/; SameSite=Strict; HttpOnly; Secure"

// HTTP request → no Secure flag
buildCsrfCookieHeader('csrf', 'token', { protocol: 'http' })
// → "csrf=token; Path=/; SameSite=Strict; HttpOnly"
closes #558 — reqId in error response bodies
Problem: Error responses didn't include the request id, so support couldn't correlate client complaints with server logs.
Solution:

Add a dedicated liveness probe at /api/health/live that always returns
200 when the gateway process is up. Keep /api/health as the readiness
probe that returns 503 when critical dependents (DB, upstream engines)
are down, so load balancers stop routing traffic to a degraded gateway.
Test that GET /api/health/live always returns 200 even when all
downstream services are down. Test that GET /api/health returns 503
when critical dependents fail.
…ation, CSRF cookie Secure, reqId in errors

- createServiceAuth/registerServiceAuth: accept string | string[] to
  support multiple valid keys during rotation windows. Both old and new
  keys work during overlap; remove old key once all consumers migrate.
- buildCsrfCookieHeader: set Secure based on actual request protocol
  (https) instead of NODE_ENV, fixing staging HTTPS environments.
- registerErrorHandler: include reqId (x-request-id) in all error
  response bodies so support can correlate client complaints with logs.
…RF cookie, reqId tests

- Test overlapping key rotation (two valid keys, invalid rejected).
- Test buildCsrfCookieHeader: HTTPS gets Secure, HTTP does not,
  x-forwarded-proto is respected.
- Verify reqId present in Zod 400, Fastify error, and generic 500
  error response bodies.
Allow callers of buildApp to override the inter-service secret with
a string or string[] for testing key rotation scenarios.
Test that overlapping keys work during rotation window. Test that
createServiceAuth accepts arrays and rejects empty arrays.
Extend the ErrorResponse type and createErrorResponse function in
errors.ts to support an optional reqId field, matching the main
index.ts changes.
Extend the ErrorResponse type and createErrorResponse function in
shared/validation/index.ts to support an optional reqId parameter,
enabling error response bodies to carry the request id for support
correlation.
Assert that a Zod validation error on gateway routes includes reqId
in the response body for support correlation.
…Response

Add test that reads plugins.ts source to confirm the error handler
passes the request id to createErrorResponse for body correlation.
@drips-wave

drips-wave Bot commented Sep 1, 2026

Copy link
Copy Markdown

@TheCodingChef-eth Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@therealjhay
therealjhay merged commit 8bb079a into Betta-Pay:main Sep 2, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants