Skip to content

feat(agent-bff): request-edge wiring (timezone + CORS + auth-mode precedence)#1731

Merged
nbouliol merged 6 commits into
mainfrom
feature/prd-666-agent-nodejs-request-edge-wiring-timezone-cors-auth-mode
Jul 2, 2026
Merged

feat(agent-bff): request-edge wiring (timezone + CORS + auth-mode precedence)#1731
nbouliol merged 6 commits into
mainfrom
feature/prd-666-agent-nodejs-request-edge-wiring-timezone-cors-auth-mode

Conversation

@nbouliol

@nbouliol nbouliol commented Jul 1, 2026

Copy link
Copy Markdown
Member

Adds a hardened /agent/* request edge to agent-bff with a structured, type-first error contract ({ error: { type, status, message, details? } }) so consumers branch on error.type instead of message text.

Scope

  • Auth-mode precedenceoauth (Bearer) vs api-key (X-Forest-Bff-Key), mutually exclusive per request: both → 400 ambiguous_credentials, neither → 401 unauthorized. bff_access validated at the edge (expired → session_expired, bad → unauthorized). Fails closed (401) for api-key mode when the resolver is unconfigured.
  • CORS — hand-rolled exact-origin allow-list (no wildcard, default ports normalized) from BFF_ALLOWED_ORIGINS, applied globally incl. POST /oauth/token; preflight short-circuits before auth. Per-key layer-2 intersection (api-key) → 403 origin_not_allowed.
  • Timezone — resolved header → body → BFF_DEFAULT_TIMEZONE, IANA-validated (mirrors the agent), injected into the (stubbed) agent query; missing → 400 missing_timezone, invalid → 400 invalid_timezone.
  • Error contract — single shared error middleware; api-key-middleware now throws; malformed /agent bodies serialized into the structured shape.

The real agent proxy is Slice 3; the edge terminates in a 501 stub that exposes the built query for tests.

Notes / decisions

  • /oauth/* keep RFC-6749 error shape; session_invalidated stays a /oauth/token refresh-reuse error.
  • A still-valid bff_access (≤15 min) remains usable at the edge after reuse-invalidation (standard short-lived-token trade-off) — documented in the README.

Test plan

  • yarn workspace @forestadmin/agent-bff test — 353 passing (auth precedence, CORS layers + preflight, timezone resolution + injection, full error-contract suite, config parsing).
  • yarn workspace @forestadmin/agent-bff lint / build — clean.

Fixes PRD-666

🤖 Generated with Claude Code

Note

Wire request edge for /agent/* with timezone, CORS, and auth-mode precedence in agent-bff

  • Adds a full request edge for /agent/* routes in cli-core.ts: CORS, structured error handling, auth-mode resolution, per-key origin enforcement, and timezone resolution are applied in order before reaching the agent stub.
  • Auth-mode middleware (createAuthModeMiddleware) resolves OAuth vs API-key from request headers, validates HS256-signed bff_access tokens, and throws structured errors (400 ambiguous_credentials, 401 unauthorized, 401 session_expired) on failure.
  • CORS is enforced via an allow-list from BFF_ALLOWED_ORIGINS; per-key origin restrictions add a second layer for API-key authenticated requests.
  • Timezone is resolved from X-Forest-Timezone header, request body, or BFF_DEFAULT_TIMEZONE fallback; invalid or missing values return structured 4xx errors.
  • Centralized error middleware serializes all BffHttpError instances to a consistent structured body; unknown errors map to 500 internal_error.
  • Risk: the agent edge is disabled entirely (with a warning) when FOREST_AUTH_SECRET is absent, and API-key requests fail closed with 401 if no resolver is configured.

Changes since #1731 opened

  • Modified JWT token verification in verifyBffAccess utility to separate signature verification from expiration checking, validating token type ('bff_access') before manually inspecting the exp claim [75b8fb1]
  • Added specific error type mappings for HTTP status codes 413 and 415 in createErrorMiddleware factory [75b8fb1]
  • Updated isValidTimezone utility to cache canonical timezone names instead of raw input values [75b8fb1]
  • Added validation in auth-mode-middleware.verifyBffAccess to verify the exp claim is a number before checking token expiry, rejecting tokens with non-numeric or missing exp claims with unauthorized status [55db5ef]

Macroscope summarized 34c3be4.

nbouliol and others added 2 commits July 1, 2026 16:37
…cedence)

Add a hardened /agent/* request edge with a structured
{ error: { type, status, message, details? } } contract:

- Auth-mode precedence: oauth (Bearer) vs api-key (X-Forest-Bff-Key);
  both -> ambiguous_credentials, neither -> unauthorized. bff_access
  validated at the edge (expired -> session_expired, bad -> unauthorized).
- CORS: hand-rolled exact-origin allow-list (no wildcard) driven by
  BFF_ALLOWED_ORIGINS, applied globally incl. POST /oauth/token; per-key
  layer-2 intersection (api-key) -> origin_not_allowed.
- Timezone: header -> body -> BFF_DEFAULT_TIMEZONE, IANA-validated and
  injected into the (stubbed) agent query; missing/invalid -> 400.
- Single shared error middleware; api-key-middleware now throws.

Fixes PRD-666

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- extractBearerToken: case-insensitive Bearer scheme (RFC 7235)
- originAllowed: normalize per-key allowedOrigins from SaaS before compare
- /agent chain: fail closed (401) for api-key mode when resolver unconfigured
- error middleware precedes bodyParser and serializes malformed-body 4xx
  into the structured error contract
- README/.env.example: document BFF_TOKEN_ENCRYPTION_KEY (gates OAuth)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jul 1, 2026

Copy link
Copy Markdown

PRD-666

@qltysh

qltysh Bot commented Jul 1, 2026

Copy link
Copy Markdown

4 new issues

Tool Category Rule Count
qlty Structure Function with many returns (count = 5): normalizeOrigin 2
qlty Structure Function with many parameters (count = 4): constructor 1
qlty Structure Complex binary expression 1

@qltysh

qltysh Bot commented Jul 1, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

⬆️ Merging this pull request will increase total coverage on main by 0.03%.

Modified Files with Diff Coverage (14)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/agent-bff/src/api-key/api-key-middleware.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/cli-core.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/config/env-config.ts100.0%
New Coverage rating: A
packages/agent-bff/src/http/bff-http-error.ts100.0%
New Coverage rating: A
packages/agent-bff/src/auth/auth-mode.ts100.0%
New Coverage rating: A
packages/agent-bff/src/agent/build-agent-query.ts100.0%
New Coverage rating: A
packages/agent-bff/src/cors/cors-middleware.ts100.0%
New Coverage rating: A
packages/agent-bff/src/agent/agent-stub.ts100.0%
New Coverage rating: A
packages/agent-bff/src/cors/origin.ts100.0%
New Coverage rating: A
packages/agent-bff/src/timezone/timezone.ts100.0%
New Coverage rating: A
packages/agent-bff/src/auth/auth-mode-middleware.ts100.0%
New Coverage rating: A
packages/agent-bff/src/timezone/timezone-middleware.ts100.0%
New Coverage rating: A
packages/agent-bff/src/http/error-middleware.ts100.0%
New Coverage rating: A
packages/agent-bff/src/cors/per-key-origin.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

Comment thread packages/agent-bff/src/api-key/api-key-middleware.ts
Comment thread packages/agent-bff/src/cors/cors-middleware.ts
Comment thread packages/agent-bff/README.md Outdated
nbouliol and others added 2 commits July 1, 2026 17:15
… contract

- README: without BFF_TOKEN_ENCRYPTION_KEY only /oauth/* issuance is disabled;
  already-issued bff_access tokens still authenticate on /agent/*
- api-key-middleware: document that it rethrows ApiKeyError and must sit behind
  an error middleware

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- cli-core: agent edge disabled without FOREST_AUTH_SECRET, malformed
  BFF_ALLOWED_ORIGINS warning, api-key guard pass-through for oauth mode,
  non-agent path skipping the agent chain
- timezone-middleware: body-field resolution incl. non-string fallback

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/agent-bff/src/timezone/timezone.ts Outdated
Comment thread packages/agent-bff/src/auth/auth-mode-middleware.ts Outdated
Comment thread packages/agent-bff/src/http/error-middleware.ts Outdated
Comment thread packages/agent-bff/src/http/bff-http-error.ts Outdated
…apping

- timezone: cache canonical IANA form (resolvedOptions().timeZone) not the raw
  value, so case variants can't grow the validation Set without bound
- auth-mode: verify with ignoreExpiration then check type before expiry, so a
  wrong-typed token is unauthorized (not session_expired)
- error middleware: map known client statuses (413, 415) to their own type
  instead of a blanket invalid_request
- bff-http-error: drop dead retryAfter field and unused BffErrorType union

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/agent-bff/src/auth/auth-mode-middleware.ts
With ignoreExpiration enabled, a token missing exp would be accepted
indefinitely; treat a missing/non-numeric exp as unauthorized.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nbouliol nbouliol merged commit 4ba9402 into main Jul 2, 2026
32 checks passed
@nbouliol nbouliol deleted the feature/prd-666-agent-nodejs-request-edge-wiring-timezone-cors-auth-mode branch July 2, 2026 09:13
forest-bot added a commit that referenced this pull request Jul 2, 2026
# @forestadmin/agent-bff [1.4.0](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/agent-bff@1.3.0...@forestadmin/agent-bff@1.4.0) (2026-07-02)

### Features

* **agent-bff:** request-edge wiring (timezone + CORS + auth-mode precedence) ([#1731](#1731)) ([4ba9402](4ba9402))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants