@forestadmin/agent-bff 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,9 +3,10 @@
3
3
  Standalone REST BFF (Backend-For-Frontend) that lets a trusted third-party UI call a Forest Admin
4
4
  agent from a browser without learning MCP or JSON:API.
5
5
 
6
- This package is the Slice 0 scaffold: a bootable Koa 3 server with a `/health` endpoint, a version
7
- header, and env-driven config validation. Auth, data endpoints, and OpenAPI generation land in later
8
- slices.
6
+ It is a bootable Koa 3 server with a `/health` endpoint, a version header, env-driven config
7
+ validation, OAuth (Mode 1) + API-key (Mode 2) auth, and a hardened request edge (timezone, CORS,
8
+ auth-mode precedence, structured error contract). The data-endpoint proxy and OpenAPI generation
9
+ land in later slices.
9
10
 
10
11
  ## Usage
11
12
 
@@ -34,14 +35,61 @@ yarn start:dev # node --env-file=.env dist/cli.js
34
35
  | `FOREST_SERVER_URL` | yes | Forest SaaS API base URL. |
35
36
  | `FOREST_APP_URL` | yes | Forest front base URL (OAuth front-channel, later slices). |
36
37
  | `AGENT_URL` | yes | The customer agent base URL the BFF calls via agent-client. |
38
+ | `BFF_TOKEN_ENCRYPTION_KEY`| for OAuth | Base64-encoded 32-byte AES-256 key encrypting stored refresh tokens. Until it is set, the `/oauth/*` token-issuance routes are disabled and `/health` reports `degraded`; already-issued `bff_access` tokens still authenticate on `/agent/*` whenever `FOREST_AUTH_SECRET` is present. |
37
39
  | `HTTP_PORT` | no | Server port, integer 0–65535. Defaults to `3450`. `0` binds an OS-assigned ephemeral port. |
40
+ | `BFF_ALLOWED_ORIGINS`| no | Comma-separated CORS allow-list of exact origins (scheme + host + port). No wildcard. Empty ⇒ no cross-origin browser access. |
41
+ | `BFF_DEFAULT_TIMEZONE`| no | Fallback IANA timezone used when a request carries neither an `X-Forest-Timezone` header nor a body `timezone`. |
38
42
 
39
43
  ### Config validation
40
44
 
41
- - A malformed value (a non-http(s) `*_URL`, a `HTTP_PORT` that is not a decimal integer in 0–65535)
42
- fails fast at boot: the process exits with a clear error and never echoes the offending value.
45
+ - A malformed value (a non-http(s) `*_URL`, a `HTTP_PORT` that is not a decimal integer in 0–65535,
46
+ a non-IANA `BFF_DEFAULT_TIMEZONE`) fails fast at boot: the process exits with a clear error and
47
+ never echoes the offending value.
43
48
  - A required var that is absent (or empty / whitespace-only) does not crash the server. It boots and
44
49
  reports the gap through `/health` (503 `degraded`).
50
+ - Malformed `BFF_ALLOWED_ORIGINS` entries (including a literal `*`) are dropped and logged once at
51
+ boot (`Warn`); they never enter the allow-list, so a wildcard origin can never be served.
52
+
53
+ ## Request edge (`/agent/*`)
54
+
55
+ Every agent call flows through a request edge that enforces three cross-cutting concerns before the
56
+ (Slice-3) proxy runs. Errors use a structured, type-first contract — `{ error: { type, status,
57
+ message, details? } }` — so consumers branch on `error.type`, never on message text.
58
+
59
+ ### Auth-mode precedence
60
+
61
+ | Presented credentials | Result |
62
+ | ---------------------------------------------- | ------------------------------- |
63
+ | `Authorization: Bearer <bff_access>` | Mode 1 (OAuth session) |
64
+ | `X-Forest-Bff-Key` | Mode 2 (API key) |
65
+ | both | `400 ambiguous_credentials` |
66
+ | neither | `401 unauthorized` |
67
+
68
+ A Mode 1 `bff_access` that is malformed or wrong-typed → `401 unauthorized`; one that is validly
69
+ signed but expired → `401 session_expired` (the client should refresh). Refresh-token reuse remains
70
+ a `POST /oauth/token` concern (`session_invalidated`, OAuth/RFC shape).
71
+
72
+ ### CORS
73
+
74
+ Two layers, both driven by exact-origin matching (case-insensitive scheme/host, default ports
75
+ normalized away, no trailing slash, no wildcard, no subdomain matching):
76
+
77
+ - **Layer 1 (transport)** — the only layer that sets `Access-Control-Allow-Origin`. An origin in
78
+ `BFF_ALLOWED_ORIGINS` is echoed back exactly; anything else gets no CORS headers (the browser
79
+ blocks). Applies to `POST /oauth/token` too. Preflight `OPTIONS` from an allow-listed origin gets
80
+ the allowed methods + headers; credentials are never enabled.
81
+ - **Layer 2 (per-key authorization, Mode 2 only)** — when the resolved key has a non-empty
82
+ `allowedOrigins`, the request `Origin` must also be in that list (a missing `Origin` is rejected),
83
+ else `403 origin_not_allowed`. An empty per-key list is a no-op.
84
+
85
+ **Local development:** browsers still enforce CORS against `localhost`, so add your dev origin(s) to
86
+ `BFF_ALLOWED_ORIGINS` (e.g. `BFF_ALLOWED_ORIGINS=http://localhost:4200`) — there is no dev bypass.
87
+
88
+ ### Timezone
89
+
90
+ The BFF always forwards an explicit `timezone` to the agent, resolved in order: (1) `X-Forest-Timezone`
91
+ header, (2) body `timezone` field, (3) `BFF_DEFAULT_TIMEZONE`. None → `400 missing_timezone`; a
92
+ non-IANA value → `400 invalid_timezone`.
45
93
 
46
94
  ## Sessions & token rotation
47
95
 
@@ -0,0 +1,3 @@
1
+ import type { Middleware } from 'koa';
2
+ export default function createAgentStubMiddleware(): Middleware;
3
+ //# sourceMappingURL=agent-stub.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = createAgentStubMiddleware;
4
+ const build_agent_query_1 = require("./build-agent-query");
5
+ function createAgentStubMiddleware() {
6
+ return async function agentStubMiddleware(ctx) {
7
+ const query = (0, build_agent_query_1.buildAgentQuery)({ timezone: ctx.state.timezone });
8
+ ctx.state.agentQuery = query;
9
+ ctx.status = 501;
10
+ ctx.body = {
11
+ error: {
12
+ type: 'not_implemented',
13
+ status: 501,
14
+ message: 'Agent proxy is not implemented yet',
15
+ },
16
+ };
17
+ };
18
+ }
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtc3R1Yi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hZ2VudC9hZ2VudC1zdHViLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBSUEsNENBY0M7QUFoQkQsMkRBQXNEO0FBRXRELFNBQXdCLHlCQUF5QjtJQUMvQyxPQUFPLEtBQUssVUFBVSxtQkFBbUIsQ0FBQyxHQUFHO1FBQzNDLE1BQU0sS0FBSyxHQUFHLElBQUEsbUNBQWUsRUFBQyxFQUFFLFFBQVEsRUFBRSxHQUFHLENBQUMsS0FBSyxDQUFDLFFBQWtCLEVBQUUsQ0FBQyxDQUFDO1FBQzFFLEdBQUcsQ0FBQyxLQUFLLENBQUMsVUFBVSxHQUFHLEtBQUssQ0FBQztRQUU3QixHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQztRQUNqQixHQUFHLENBQUMsSUFBSSxHQUFHO1lBQ1QsS0FBSyxFQUFFO2dCQUNMLElBQUksRUFBRSxpQkFBaUI7Z0JBQ3ZCLE1BQU0sRUFBRSxHQUFHO2dCQUNYLE9BQU8sRUFBRSxvQ0FBb0M7YUFDOUM7U0FDRixDQUFDO0lBQ0osQ0FBQyxDQUFDO0FBQ0osQ0FBQyJ9
@@ -0,0 +1,8 @@
1
+ export interface AgentQuery {
2
+ timezone: string;
3
+ }
4
+ export interface BuildAgentQueryParams {
5
+ timezone: string;
6
+ }
7
+ export declare function buildAgentQuery({ timezone }: BuildAgentQueryParams): AgentQuery;
8
+ //# sourceMappingURL=build-agent-query.d.ts.map
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAgentQuery = buildAgentQuery;
4
+ function buildAgentQuery({ timezone }) {
5
+ return { timezone };
6
+ }
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGQtYWdlbnQtcXVlcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYWdlbnQvYnVpbGQtYWdlbnQtcXVlcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFRQSwwQ0FFQztBQUZELFNBQWdCLGVBQWUsQ0FBQyxFQUFFLFFBQVEsRUFBeUI7SUFDakUsT0FBTyxFQUFFLFFBQVEsRUFBRSxDQUFDO0FBQ3RCLENBQUMifQ==
@@ -5,25 +5,9 @@ exports.default = createApiKeyMiddleware;
5
5
  const api_key_1 = require("./api-key");
6
6
  const api_key_error_1 = require("./api-key-error");
7
7
  exports.BFF_KEY_HEADER = 'X-Forest-Bff-Key';
8
- function writeError(ctx, error, rawKey, logger) {
9
- if (error instanceof api_key_error_1.ApiKeyError) {
10
- if (error.retryAfter !== undefined)
11
- ctx.set('Retry-After', String(error.retryAfter));
12
- ctx.status = error.status;
13
- ctx.body = (0, api_key_error_1.toErrorBody)(error);
14
- logger('Warn', 'BFF API key rejected', {
15
- keyHash: (0, api_key_1.fingerprintApiKey)(rawKey),
16
- type: error.type,
17
- });
18
- return;
19
- }
20
- logger('Error', 'BFF API key middleware failure', {
21
- keyHash: (0, api_key_1.fingerprintApiKey)(rawKey),
22
- cause: error instanceof Error ? `${error.name}: ${error.message}` : String(error),
23
- });
24
- ctx.status = 500;
25
- ctx.body = { error: { type: 'server_error', status: 500, message: 'API key processing failed' } };
26
- }
8
+ // On authentication failure this middleware rethrows `ApiKeyError` rather than
9
+ // writing the response itself; it must be mounted behind an error middleware
10
+ // that serializes the structured body (see `createErrorMiddleware`).
27
11
  function createApiKeyMiddleware({ authenticator, logger, }) {
28
12
  return async function apiKeyMiddleware(ctx, next) {
29
13
  const rawKey = ctx.get(exports.BFF_KEY_HEADER);
@@ -36,8 +20,19 @@ function createApiKeyMiddleware({ authenticator, logger, }) {
36
20
  authenticated = await authenticator.authenticate(rawKey);
37
21
  }
38
22
  catch (error) {
39
- writeError(ctx, error, rawKey, logger);
40
- return;
23
+ if (error instanceof api_key_error_1.ApiKeyError) {
24
+ logger('Warn', 'BFF API key rejected', {
25
+ keyHash: (0, api_key_1.fingerprintApiKey)(rawKey),
26
+ type: error.type,
27
+ });
28
+ }
29
+ else {
30
+ logger('Error', 'BFF API key middleware failure', {
31
+ keyHash: (0, api_key_1.fingerprintApiKey)(rawKey),
32
+ cause: error instanceof Error ? `${error.name}: ${error.message}` : String(error),
33
+ });
34
+ }
35
+ throw error;
41
36
  }
42
37
  ctx.state.agentToken = authenticated.agentToken;
43
38
  ctx.state.apiKeyIdentity = authenticated.identity;
@@ -49,4 +44,4 @@ function createApiKeyMiddleware({ authenticator, logger, }) {
49
44
  await next();
50
45
  };
51
46
  }
52
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQW1DQSx5Q0FpQ0M7QUFoRUQsdUNBQThDO0FBQzlDLG1EQUEyRDtBQUU5QyxRQUFBLGNBQWMsR0FBRyxrQkFBa0IsQ0FBQztBQU9qRCxTQUFTLFVBQVUsQ0FBQyxHQUFZLEVBQUUsS0FBYyxFQUFFLE1BQWMsRUFBRSxNQUFjO0lBQzlFLElBQUksS0FBSyxZQUFZLDJCQUFXLEVBQUUsQ0FBQztRQUNqQyxJQUFJLEtBQUssQ0FBQyxVQUFVLEtBQUssU0FBUztZQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsYUFBYSxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQztRQUNyRixHQUFHLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFDMUIsR0FBRyxDQUFDLElBQUksR0FBRyxJQUFBLDJCQUFXLEVBQUMsS0FBSyxDQUFDLENBQUM7UUFDOUIsTUFBTSxDQUFDLE1BQU0sRUFBRSxzQkFBc0IsRUFBRTtZQUNyQyxPQUFPLEVBQUUsSUFBQSwyQkFBaUIsRUFBQyxNQUFNLENBQUM7WUFDbEMsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO1NBQ2pCLENBQUMsQ0FBQztRQUVILE9BQU87SUFDVCxDQUFDO0lBRUQsTUFBTSxDQUFDLE9BQU8sRUFBRSxnQ0FBZ0MsRUFBRTtRQUNoRCxPQUFPLEVBQUUsSUFBQSwyQkFBaUIsRUFBQyxNQUFNLENBQUM7UUFDbEMsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksS0FBSyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7S0FDbEYsQ0FBQyxDQUFDO0lBQ0gsR0FBRyxDQUFDLE1BQU0sR0FBRyxHQUFHLENBQUM7SUFDakIsR0FBRyxDQUFDLElBQUksR0FBRyxFQUFFLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxjQUFjLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsRUFBRSxDQUFDO0FBQ3BHLENBQUM7QUFFRCxTQUF3QixzQkFBc0IsQ0FBQyxFQUM3QyxhQUFhLEVBQ2IsTUFBTSxHQUNrQjtJQUN4QixPQUFPLEtBQUssVUFBVSxnQkFBZ0IsQ0FBQyxHQUFHLEVBQUUsSUFBSTtRQUM5QyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLHNCQUFjLENBQUMsQ0FBQztRQUV2QyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixNQUFNLElBQUksRUFBRSxDQUFDO1lBRWIsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLGFBQWtDLENBQUM7UUFFdkMsSUFBSSxDQUFDO1lBQ0gsYUFBYSxHQUFHLE1BQU0sYUFBYSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMzRCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLFVBQVUsQ0FBQyxHQUFHLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUV2QyxPQUFPO1FBQ1QsQ0FBQztRQUVELEdBQUcsQ0FBQyxLQUFLLENBQUMsVUFBVSxHQUFHLGFBQWEsQ0FBQyxVQUFVLENBQUM7UUFDaEQsR0FBRyxDQUFDLEtBQUssQ0FBQyxjQUFjLEdBQUcsYUFBYSxDQUFDLFFBQVEsQ0FBQztRQUNsRCxHQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNyQyxNQUFNLENBQUMsTUFBTSxFQUFFLHNCQUFzQixFQUFFO1lBQ3JDLE9BQU8sRUFBRSxJQUFBLDJCQUFpQixFQUFDLE1BQU0sQ0FBQztZQUNsQyxXQUFXLEVBQUUsYUFBYSxDQUFDLFFBQVEsQ0FBQyxXQUFXO1NBQ2hELENBQUMsQ0FBQztRQUVILE1BQU0sSUFBSSxFQUFFLENBQUM7SUFDZixDQUFDLENBQUM7QUFDSixDQUFDIn0=
47
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQWlCQSx5Q0EyQ0M7QUF4REQsdUNBQThDO0FBQzlDLG1EQUE4QztBQUVqQyxRQUFBLGNBQWMsR0FBRyxrQkFBa0IsQ0FBQztBQU9qRCwrRUFBK0U7QUFDL0UsNkVBQTZFO0FBQzdFLHFFQUFxRTtBQUNyRSxTQUF3QixzQkFBc0IsQ0FBQyxFQUM3QyxhQUFhLEVBQ2IsTUFBTSxHQUNrQjtJQUN4QixPQUFPLEtBQUssVUFBVSxnQkFBZ0IsQ0FBQyxHQUFHLEVBQUUsSUFBSTtRQUM5QyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLHNCQUFjLENBQUMsQ0FBQztRQUV2QyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixNQUFNLElBQUksRUFBRSxDQUFDO1lBRWIsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLGFBQWtDLENBQUM7UUFFdkMsSUFBSSxDQUFDO1lBQ0gsYUFBYSxHQUFHLE1BQU0sYUFBYSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMzRCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksS0FBSyxZQUFZLDJCQUFXLEVBQUUsQ0FBQztnQkFDakMsTUFBTSxDQUFDLE1BQU0sRUFBRSxzQkFBc0IsRUFBRTtvQkFDckMsT0FBTyxFQUFFLElBQUEsMkJBQWlCLEVBQUMsTUFBTSxDQUFDO29CQUNsQyxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUk7aUJBQ2pCLENBQUMsQ0FBQztZQUNMLENBQUM7aUJBQU0sQ0FBQztnQkFDTixNQUFNLENBQUMsT0FBTyxFQUFFLGdDQUFnQyxFQUFFO29CQUNoRCxPQUFPLEVBQUUsSUFBQSwyQkFBaUIsRUFBQyxNQUFNLENBQUM7b0JBQ2xDLEtBQUssRUFBRSxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLEtBQUssQ0FBQyxJQUFJLEtBQUssS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDO2lCQUNsRixDQUFDLENBQUM7WUFDTCxDQUFDO1lBRUQsTUFBTSxLQUFLLENBQUM7UUFDZCxDQUFDO1FBRUQsR0FBRyxDQUFDLEtBQUssQ0FBQyxVQUFVLEdBQUcsYUFBYSxDQUFDLFVBQVUsQ0FBQztRQUNoRCxHQUFHLENBQUMsS0FBSyxDQUFDLGNBQWMsR0FBRyxhQUFhLENBQUMsUUFBUSxDQUFDO1FBQ2xELEdBQUcsQ0FBQyxHQUFHLENBQUMsZUFBZSxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ3JDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsc0JBQXNCLEVBQUU7WUFDckMsT0FBTyxFQUFFLElBQUEsMkJBQWlCLEVBQUMsTUFBTSxDQUFDO1lBQ2xDLFdBQVcsRUFBRSxhQUFhLENBQUMsUUFBUSxDQUFDLFdBQVc7U0FDaEQsQ0FBQyxDQUFDO1FBRUgsTUFBTSxJQUFJLEVBQUUsQ0FBQztJQUNmLENBQUMsQ0FBQztBQUNKLENBQUMifQ==
@@ -0,0 +1,7 @@
1
+ import type { Middleware } from 'koa';
2
+ export declare const BFF_KEY_HEADER = "X-Forest-Bff-Key";
3
+ export interface AuthModeMiddlewareOptions {
4
+ authSecret: string;
5
+ }
6
+ export default function createAuthModeMiddleware({ authSecret, }: AuthModeMiddlewareOptions): Middleware;
7
+ //# sourceMappingURL=auth-mode-middleware.d.ts.map
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BFF_KEY_HEADER = void 0;
7
+ exports.default = createAuthModeMiddleware;
8
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
9
+ const auth_mode_1 = require("./auth-mode");
10
+ const bff_http_error_1 = require("../http/bff-http-error");
11
+ const bff_token_1 = require("../oauth/bff-token");
12
+ exports.BFF_KEY_HEADER = 'X-Forest-Bff-Key';
13
+ function verifyBffAccess(token, authSecret) {
14
+ let decoded;
15
+ // Verify the signature first but ignore expiration, so the token type can be
16
+ // checked before expiry: a wrong-typed token must be `unauthorized`, and only
17
+ // a genuine expired `bff_access` should map to `session_expired`.
18
+ try {
19
+ decoded = jsonwebtoken_1.default.verify(token, authSecret, {
20
+ algorithms: ['HS256'],
21
+ ignoreExpiration: true,
22
+ });
23
+ }
24
+ catch {
25
+ throw (0, bff_http_error_1.unauthorized)();
26
+ }
27
+ if (typeof decoded !== 'object' ||
28
+ decoded === null ||
29
+ decoded.type !== bff_token_1.BFF_ACCESS_TOKEN_TYPE) {
30
+ throw (0, bff_http_error_1.unauthorized)();
31
+ }
32
+ const { exp } = decoded;
33
+ if (typeof exp !== 'number') {
34
+ throw (0, bff_http_error_1.unauthorized)();
35
+ }
36
+ if (exp * 1000 <= Date.now()) {
37
+ throw (0, bff_http_error_1.sessionExpired)();
38
+ }
39
+ return decoded;
40
+ }
41
+ function createAuthModeMiddleware({ authSecret, }) {
42
+ return async function authModeMiddleware(ctx, next) {
43
+ const bearer = (0, auth_mode_1.extractBearerToken)(ctx.get('Authorization'));
44
+ const apiKey = ctx.get(exports.BFF_KEY_HEADER);
45
+ const mode = (0, auth_mode_1.resolveAuthMode)({ hasBearer: bearer !== undefined, hasApiKey: apiKey !== '' });
46
+ ctx.state.authMode = mode;
47
+ if (mode === 'oauth') {
48
+ ctx.state.principal = verifyBffAccess(bearer, authSecret);
49
+ }
50
+ await next();
51
+ };
52
+ }
53
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC1tb2RlLW1pZGRsZXdhcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXV0aC9hdXRoLW1vZGUtbWlkZGxld2FyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFtREEsMkNBaUJDO0FBakVELGdFQUF3QztBQUV4QywyQ0FBa0U7QUFDbEUsMkRBQXNFO0FBQ3RFLGtEQUEyRDtBQUU5QyxRQUFBLGNBQWMsR0FBRyxrQkFBa0IsQ0FBQztBQU1qRCxTQUFTLGVBQWUsQ0FBQyxLQUFhLEVBQUUsVUFBa0I7SUFDeEQsSUFBSSxPQUFnQixDQUFDO0lBRXJCLDZFQUE2RTtJQUM3RSw4RUFBOEU7SUFDOUUsa0VBQWtFO0lBQ2xFLElBQUksQ0FBQztRQUNILE9BQU8sR0FBRyxzQkFBWSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsVUFBVSxFQUFFO1lBQy9DLFVBQVUsRUFBRSxDQUFDLE9BQU8sQ0FBQztZQUNyQixnQkFBZ0IsRUFBRSxJQUFJO1NBQ3ZCLENBQUMsQ0FBQztJQUNMLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxNQUFNLElBQUEsNkJBQVksR0FBRSxDQUFDO0lBQ3ZCLENBQUM7SUFFRCxJQUNFLE9BQU8sT0FBTyxLQUFLLFFBQVE7UUFDM0IsT0FBTyxLQUFLLElBQUk7UUFDZixPQUE4QixDQUFDLElBQUksS0FBSyxpQ0FBcUIsRUFDOUQsQ0FBQztRQUNELE1BQU0sSUFBQSw2QkFBWSxHQUFFLENBQUM7SUFDdkIsQ0FBQztJQUVELE1BQU0sRUFBRSxHQUFHLEVBQUUsR0FBRyxPQUE0QixDQUFDO0lBRTdDLElBQUksT0FBTyxHQUFHLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDNUIsTUFBTSxJQUFBLDZCQUFZLEdBQUUsQ0FBQztJQUN2QixDQUFDO0lBRUQsSUFBSSxHQUFHLEdBQUcsSUFBSSxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDO1FBQzdCLE1BQU0sSUFBQSwrQkFBYyxHQUFFLENBQUM7SUFDekIsQ0FBQztJQUVELE9BQU8sT0FBZ0MsQ0FBQztBQUMxQyxDQUFDO0FBRUQsU0FBd0Isd0JBQXdCLENBQUMsRUFDL0MsVUFBVSxHQUNnQjtJQUMxQixPQUFPLEtBQUssVUFBVSxrQkFBa0IsQ0FBQyxHQUFHLEVBQUUsSUFBSTtRQUNoRCxNQUFNLE1BQU0sR0FBRyxJQUFBLDhCQUFrQixFQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQztRQUM1RCxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLHNCQUFjLENBQUMsQ0FBQztRQUV2QyxNQUFNLElBQUksR0FBRyxJQUFBLDJCQUFlLEVBQUMsRUFBRSxTQUFTLEVBQUUsTUFBTSxLQUFLLFNBQVMsRUFBRSxTQUFTLEVBQUUsTUFBTSxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFNUYsR0FBRyxDQUFDLEtBQUssQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDO1FBRTFCLElBQUksSUFBSSxLQUFLLE9BQU8sRUFBRSxDQUFDO1lBQ3JCLEdBQUcsQ0FBQyxLQUFLLENBQUMsU0FBUyxHQUFHLGVBQWUsQ0FBQyxNQUFnQixFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ3RFLENBQUM7UUFFRCxNQUFNLElBQUksRUFBRSxDQUFDO0lBQ2YsQ0FBQyxDQUFDO0FBQ0osQ0FBQyJ9
@@ -0,0 +1,7 @@
1
+ export type AuthMode = 'oauth' | 'api-key';
2
+ export declare function extractBearerToken(authorization: string | undefined): string | undefined;
3
+ export declare function resolveAuthMode({ hasBearer, hasApiKey, }: {
4
+ hasBearer: boolean;
5
+ hasApiKey: boolean;
6
+ }): AuthMode;
7
+ //# sourceMappingURL=auth-mode.d.ts.map
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractBearerToken = extractBearerToken;
4
+ exports.resolveAuthMode = resolveAuthMode;
5
+ const bff_http_error_1 = require("../http/bff-http-error");
6
+ const BEARER_PATTERN = /^Bearer[ \t]+(.+)$/i;
7
+ function extractBearerToken(authorization) {
8
+ if (!authorization)
9
+ return undefined;
10
+ const match = BEARER_PATTERN.exec(authorization.trim());
11
+ if (!match)
12
+ return undefined;
13
+ const token = match[1].trim();
14
+ return token === '' ? undefined : token;
15
+ }
16
+ function resolveAuthMode({ hasBearer, hasApiKey, }) {
17
+ if (hasBearer && hasApiKey)
18
+ throw (0, bff_http_error_1.ambiguousCredentials)();
19
+ if (hasApiKey)
20
+ return 'api-key';
21
+ if (hasBearer)
22
+ return 'oauth';
23
+ throw (0, bff_http_error_1.unauthorized)();
24
+ }
25
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC1tb2RlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2F1dGgvYXV0aC1tb2RlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBTUEsZ0RBU0M7QUFFRCwwQ0FZQztBQTdCRCwyREFBNEU7QUFJNUUsTUFBTSxjQUFjLEdBQUcscUJBQXFCLENBQUM7QUFFN0MsU0FBZ0Isa0JBQWtCLENBQUMsYUFBaUM7SUFDbEUsSUFBSSxDQUFDLGFBQWE7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUVyQyxNQUFNLEtBQUssR0FBRyxjQUFjLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQ3hELElBQUksQ0FBQyxLQUFLO1FBQUUsT0FBTyxTQUFTLENBQUM7SUFFN0IsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0lBRTlCLE9BQU8sS0FBSyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7QUFDMUMsQ0FBQztBQUVELFNBQWdCLGVBQWUsQ0FBQyxFQUM5QixTQUFTLEVBQ1QsU0FBUyxHQUlWO0lBQ0MsSUFBSSxTQUFTLElBQUksU0FBUztRQUFFLE1BQU0sSUFBQSxxQ0FBb0IsR0FBRSxDQUFDO0lBQ3pELElBQUksU0FBUztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ2hDLElBQUksU0FBUztRQUFFLE9BQU8sT0FBTyxDQUFDO0lBRTlCLE1BQU0sSUFBQSw2QkFBWSxHQUFFLENBQUM7QUFDdkIsQ0FBQyJ9
package/dist/cli-core.js CHANGED
@@ -7,20 +7,48 @@ exports.default = runCli;
7
7
  exports.reportFatalError = reportFatalError;
8
8
  const bodyparser_1 = require("@koa/bodyparser");
9
9
  const console_logger_1 = __importDefault(require("./adapters/console-logger"));
10
+ const agent_stub_1 = __importDefault(require("./agent/agent-stub"));
10
11
  const api_key_authenticator_1 = __importDefault(require("./api-key/api-key-authenticator"));
11
12
  const api_key_client_1 = __importDefault(require("./api-key/api-key-client"));
12
13
  const api_key_middleware_1 = __importDefault(require("./api-key/api-key-middleware"));
13
14
  const resolve_cache_1 = __importDefault(require("./api-key/resolve-cache"));
15
+ const auth_mode_middleware_1 = __importDefault(require("./auth/auth-mode-middleware"));
14
16
  const env_config_1 = require("./config/env-config");
17
+ const cors_middleware_1 = __importDefault(require("./cors/cors-middleware"));
18
+ const per_key_origin_1 = __importDefault(require("./cors/per-key-origin"));
15
19
  const errors_1 = require("./errors");
20
+ const bff_http_error_1 = require("./http/bff-http-error");
16
21
  const bff_http_server_1 = __importDefault(require("./http/bff-http-server"));
22
+ const error_middleware_1 = __importDefault(require("./http/error-middleware"));
17
23
  const forest_server_client_1 = __importDefault(require("./oauth/forest-server-client"));
18
24
  const oauth_routes_1 = __importDefault(require("./oauth/oauth-routes"));
19
25
  const session_store_1 = __importDefault(require("./oauth/session-store"));
20
26
  const token_cipher_1 = __importDefault(require("./oauth/token-cipher"));
27
+ const timezone_middleware_1 = __importDefault(require("./timezone/timezone-middleware"));
21
28
  const version_1 = __importDefault(require("./version"));
22
29
  const BODY_LIMIT = '16kb';
23
30
  const SESSION_TTL_SECONDS = 24 * 60 * 60;
31
+ function isAgentPath(path) {
32
+ return path === '/agent' || path.startsWith('/agent/');
33
+ }
34
+ function agentScoped(middleware) {
35
+ return async function scoped(ctx, next) {
36
+ if (!isAgentPath(ctx.path)) {
37
+ await next();
38
+ return;
39
+ }
40
+ await middleware(ctx, next);
41
+ };
42
+ }
43
+ function createApiKeyUnavailableGuard(logger) {
44
+ return async function apiKeyUnavailableGuard(ctx, next) {
45
+ if (ctx.state.authMode === 'api-key') {
46
+ logger('Error', 'API key auth requested but the resolver is not configured');
47
+ throw (0, bff_http_error_1.unauthorized)('Credentials could not be validated');
48
+ }
49
+ await next();
50
+ };
51
+ }
24
52
  function resolveOAuthConfig(config) {
25
53
  const { forestServerUrl, forestEnvSecret, forestAppUrl, forestAuthSecret, tokenEncryptionKey } = config;
26
54
  if (forestServerUrl &&
@@ -54,7 +82,7 @@ async function buildOAuthMiddlewares(config, logger) {
54
82
  environmentId,
55
83
  logger,
56
84
  });
57
- return [(0, bodyparser_1.bodyParser)({ jsonLimit: BODY_LIMIT }), oauthRoutes];
85
+ return [oauthRoutes];
58
86
  }
59
87
  function resolveApiKeyConfig(config) {
60
88
  const { forestServerUrl, forestEnvSecret, forestAuthSecret } = config;
@@ -81,11 +109,39 @@ function buildApiKeyMiddleware(config, logger) {
81
109
  });
82
110
  return (0, api_key_middleware_1.default)({ authenticator, logger });
83
111
  }
112
+ function buildAgentMiddlewares(config, logger) {
113
+ const { forestAuthSecret, defaultTimezone } = config;
114
+ if (!forestAuthSecret) {
115
+ logger('Warn', 'Agent edge disabled: FOREST_AUTH_SECRET is missing');
116
+ return [];
117
+ }
118
+ const apiKeyStep = buildApiKeyMiddleware(config, logger) ?? createApiKeyUnavailableGuard(logger);
119
+ const chain = [
120
+ (0, auth_mode_middleware_1.default)({ authSecret: forestAuthSecret }),
121
+ apiKeyStep,
122
+ (0, per_key_origin_1.default)(),
123
+ (0, timezone_middleware_1.default)({ defaultTimezone }),
124
+ (0, agent_stub_1.default)(),
125
+ ];
126
+ return chain.map(agentScoped);
127
+ }
84
128
  async function runCli(env, logger = (0, console_logger_1.default)()) {
85
129
  const config = (0, env_config_1.parseConfig)(env);
130
+ if (config.invalidAllowedOrigins.length > 0) {
131
+ logger('Warn', 'Ignoring malformed BFF_ALLOWED_ORIGINS entries', {
132
+ entries: config.invalidAllowedOrigins,
133
+ });
134
+ }
86
135
  const oauthMiddlewares = await buildOAuthMiddlewares(config, logger);
87
- const apiKeyMiddleware = buildApiKeyMiddleware(config, logger);
88
- const middlewares = [...oauthMiddlewares, ...(apiKeyMiddleware ? [apiKeyMiddleware] : [])];
136
+ const agentMiddlewares = buildAgentMiddlewares(config, logger);
137
+ const agentErrorMiddleware = agentMiddlewares.length > 0 ? [agentScoped((0, error_middleware_1.default)({ logger }))] : [];
138
+ const middlewares = [
139
+ (0, cors_middleware_1.default)({ allowedOrigins: config.allowedOrigins }),
140
+ ...agentErrorMiddleware,
141
+ (0, bodyparser_1.bodyParser)({ jsonLimit: BODY_LIMIT }),
142
+ ...oauthMiddlewares,
143
+ ...agentMiddlewares,
144
+ ];
89
145
  const server = new bff_http_server_1.default({
90
146
  port: config.httpPort,
91
147
  version: version_1.default,
@@ -100,4 +156,4 @@ function reportFatalError(err) {
100
156
  process.stderr.write(`Error: ${(0, errors_1.extractErrorMessage)(err)}\n`);
101
157
  process.exitCode = 1;
102
158
  }
103
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLWNvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2xpLWNvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUF3SEEseUJBbUJDO0FBRUQsNENBR0M7QUE1SUQsZ0RBQTZDO0FBRTdDLCtFQUE0RDtBQUM1RCw0RkFBd0U7QUFDeEUsOEVBQW9EO0FBQ3BELHNGQUFrRTtBQUNsRSw0RUFBeUQ7QUFDekQsb0RBQWtEO0FBQ2xELHFDQUErQztBQUMvQyw2RUFBbUQ7QUFDbkQsd0ZBQThEO0FBQzlELHdFQUFxRDtBQUNyRCwwRUFBK0Q7QUFDL0Qsd0VBQXFEO0FBQ3JELHdEQUFnQztBQUVoQyxNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUM7QUFDMUIsTUFBTSxtQkFBbUIsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsQ0FBQztBQVV6QyxTQUFTLGtCQUFrQixDQUFDLE1BQWlCO0lBQzNDLE1BQU0sRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFBRSxHQUM1RixNQUFNLENBQUM7SUFFVCxJQUNFLGVBQWU7UUFDZixlQUFlO1FBQ2YsWUFBWTtRQUNaLGdCQUFnQjtRQUNoQixrQkFBa0IsRUFDbEIsQ0FBQztRQUNELE9BQU8sRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFBRSxDQUFDO0lBQ2xHLENBQUM7SUFFRCxPQUFPLFNBQVMsQ0FBQztBQUNuQixDQUFDO0FBRUQsS0FBSyxVQUFVLHFCQUFxQixDQUFDLE1BQWlCLEVBQUUsTUFBYztJQUNwRSxNQUFNLFdBQVcsR0FBRyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUUvQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDakIsTUFBTSxDQUFDLE1BQU0sRUFBRSwwREFBMEQsQ0FBQyxDQUFDO1FBRTNFLE9BQU8sRUFBRSxDQUFDO0lBQ1osQ0FBQztJQUVELE1BQU0sRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFBRSxHQUM1RixXQUFXLENBQUM7SUFFZCxNQUFNLFlBQVksR0FBRyxJQUFJLDhCQUFrQixDQUFDLEVBQUUsZUFBZSxFQUFFLFNBQVMsRUFBRSxlQUFlLEVBQUUsQ0FBQyxDQUFDO0lBQzdGLE1BQU0sYUFBYSxHQUFHLE1BQU0sWUFBWSxDQUFDLGtCQUFrQixFQUFFLENBQUM7SUFFOUQsTUFBTSxZQUFZLEdBQUcsSUFBQSx1QkFBMEIsRUFBQztRQUM5QyxNQUFNLEVBQUUsSUFBQSxzQkFBaUIsRUFBQyxrQkFBa0IsQ0FBQztRQUM3QyxHQUFHLEVBQUUsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRTtRQUNyQixpQkFBaUIsRUFBRSxtQkFBbUI7S0FDdkMsQ0FBQyxDQUFDO0lBRUgsTUFBTSxXQUFXLEdBQUcsSUFBQSxzQkFBaUIsRUFBQztRQUNwQyxZQUFZO1FBQ1osWUFBWTtRQUNaLFlBQVk7UUFDWixVQUFVLEVBQUUsZ0JBQWdCO1FBQzVCLGFBQWE7UUFDYixNQUFNO0tBQ1AsQ0FBQyxDQUFDO0lBRUgsT0FBTyxDQUFDLElBQUEsdUJBQVUsRUFBQyxFQUFFLFNBQVMsRUFBRSxVQUFVLEVBQUUsQ0FBQyxFQUFFLFdBQVcsQ0FBQyxDQUFDO0FBQzlELENBQUM7QUFRRCxTQUFTLG1CQUFtQixDQUFDLE1BQWlCO0lBQzVDLE1BQU0sRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLGdCQUFnQixFQUFFLEdBQUcsTUFBTSxDQUFDO0lBRXRFLElBQUksZUFBZSxJQUFJLGVBQWUsSUFBSSxnQkFBZ0IsRUFBRSxDQUFDO1FBQzNELE9BQU8sRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLGdCQUFnQixFQUFFLENBQUM7SUFDaEUsQ0FBQztJQUVELE9BQU8sU0FBUyxDQUFDO0FBQ25CLENBQUM7QUFFRCxTQUFTLHFCQUFxQixDQUFDLE1BQWlCLEVBQUUsTUFBYztJQUM5RCxNQUFNLFlBQVksR0FBRyxtQkFBbUIsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUVqRCxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDbEIsTUFBTSxDQUFDLE1BQU0sRUFBRSwwREFBMEQsQ0FBQyxDQUFDO1FBRTNFLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFFRCxNQUFNLE1BQU0sR0FBRyxJQUFJLHdCQUFZLENBQUM7UUFDOUIsZUFBZSxFQUFFLFlBQVksQ0FBQyxlQUFlO1FBQzdDLFNBQVMsRUFBRSxZQUFZLENBQUMsZUFBZTtLQUN4QyxDQUFDLENBQUM7SUFDSCxNQUFNLEtBQUssR0FBRyxJQUFBLHVCQUFrQixFQUFDLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDNUQsTUFBTSxhQUFhLEdBQUcsSUFBQSwrQkFBeUIsRUFBQztRQUM5QyxNQUFNO1FBQ04sS0FBSztRQUNMLFVBQVUsRUFBRSxZQUFZLENBQUMsZ0JBQWdCO0tBQzFDLENBQUMsQ0FBQztJQUVILE9BQU8sSUFBQSw0QkFBc0IsRUFBQyxFQUFFLGFBQWEsRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDO0FBQzNELENBQUM7QUFFYyxLQUFLLFVBQVUsTUFBTSxDQUNsQyxHQUFzQixFQUN0QixTQUFpQixJQUFBLHdCQUFtQixHQUFFO0lBRXRDLE1BQU0sTUFBTSxHQUFHLElBQUEsd0JBQVcsRUFBQyxHQUFHLENBQUMsQ0FBQztJQUNoQyxNQUFNLGdCQUFnQixHQUFHLE1BQU0scUJBQXFCLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQ3JFLE1BQU0sZ0JBQWdCLEdBQUcscUJBQXFCLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQy9ELE1BQU0sV0FBVyxHQUFHLENBQUMsR0FBRyxnQkFBZ0IsRUFBRSxHQUFHLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUMzRixNQUFNLE1BQU0sR0FBRyxJQUFJLHlCQUFhLENBQUM7UUFDL0IsSUFBSSxFQUFFLE1BQU0sQ0FBQyxRQUFRO1FBQ3JCLE9BQU8sRUFBUCxpQkFBTztRQUNQLE1BQU07UUFDTixNQUFNO1FBQ04sV0FBVztLQUNaLENBQUMsQ0FBQztJQUVILE1BQU0sTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO0lBRXJCLE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUM7QUFFRCxTQUFnQixnQkFBZ0IsQ0FBQyxHQUFZO0lBQzNDLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLFVBQVUsSUFBQSw0QkFBbUIsRUFBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDN0QsT0FBTyxDQUFDLFFBQVEsR0FBRyxDQUFDLENBQUM7QUFDdkIsQ0FBQyJ9
159
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLWNvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2xpLWNvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFnTEEseUJBa0NDO0FBRUQsNENBR0M7QUFuTkQsZ0RBQTZDO0FBRTdDLCtFQUE0RDtBQUM1RCxvRUFBMkQ7QUFDM0QsNEZBQXdFO0FBQ3hFLDhFQUFvRDtBQUNwRCxzRkFBa0U7QUFDbEUsNEVBQXlEO0FBQ3pELHVGQUFtRTtBQUNuRSxvREFBa0Q7QUFDbEQsNkVBQTBEO0FBQzFELDJFQUFpRTtBQUNqRSxxQ0FBK0M7QUFDL0MsMERBQXFEO0FBQ3JELDZFQUFtRDtBQUNuRCwrRUFBNEQ7QUFDNUQsd0ZBQThEO0FBQzlELHdFQUFxRDtBQUNyRCwwRUFBK0Q7QUFDL0Qsd0VBQXFEO0FBQ3JELHlGQUFzRTtBQUN0RSx3REFBZ0M7QUFFaEMsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDO0FBQzFCLE1BQU0sbUJBQW1CLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLENBQUM7QUFFekMsU0FBUyxXQUFXLENBQUMsSUFBWTtJQUMvQixPQUFPLElBQUksS0FBSyxRQUFRLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUN6RCxDQUFDO0FBRUQsU0FBUyxXQUFXLENBQUMsVUFBc0I7SUFDekMsT0FBTyxLQUFLLFVBQVUsTUFBTSxDQUFDLEdBQUcsRUFBRSxJQUFJO1FBQ3BDLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDM0IsTUFBTSxJQUFJLEVBQUUsQ0FBQztZQUViLE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxVQUFVLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQzlCLENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFTLDRCQUE0QixDQUFDLE1BQWM7SUFDbEQsT0FBTyxLQUFLLFVBQVUsc0JBQXNCLENBQUMs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
@@ -8,6 +8,9 @@ export interface BFFConfig {
8
8
  forestAppUrl?: string;
9
9
  agentUrl?: string;
10
10
  tokenEncryptionKey?: string;
11
+ allowedOrigins: string[];
12
+ invalidAllowedOrigins: string[];
13
+ defaultTimezone?: string;
11
14
  httpPort: number;
12
15
  presence: PresenceMap;
13
16
  hasAllRequired: boolean;
@@ -6,8 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.REQUIRED_KEYS = void 0;
7
7
  exports.parseConfig = parseConfig;
8
8
  const zod_1 = require("zod");
9
+ const origin_1 = require("../cors/origin");
9
10
  const defaults_1 = __importDefault(require("../defaults"));
10
11
  const errors_1 = require("../errors");
12
+ const timezone_1 = require("../timezone/timezone");
11
13
  exports.REQUIRED_KEYS = [
12
14
  'FOREST_AUTH_SECRET',
13
15
  'FOREST_ENV_SECRET',
@@ -47,6 +49,13 @@ function parseEncryptionKey(raw) {
47
49
  }
48
50
  return value;
49
51
  }
52
+ function parseDefaultTimezone(raw) {
53
+ const value = normalize(raw);
54
+ if (value !== undefined && !(0, timezone_1.isValidTimezone)(value)) {
55
+ throw new errors_1.ConfigurationError(`Invalid configuration: BFF_DEFAULT_TIMEZONE must be a valid IANA timezone.`);
56
+ }
57
+ return value;
58
+ }
50
59
  function parseConfig(env) {
51
60
  const normalized = Object.fromEntries(exports.REQUIRED_KEYS.map(key => [key, normalize(env[key])]));
52
61
  for (const key of URL_KEYS) {
@@ -57,6 +66,8 @@ function parseConfig(env) {
57
66
  }
58
67
  const presence = Object.fromEntries(exports.REQUIRED_KEYS.map(key => [key, normalized[key] !== undefined]));
59
68
  const tokenEncryptionKey = parseEncryptionKey(env.BFF_TOKEN_ENCRYPTION_KEY);
69
+ const { origins: allowedOrigins, invalid: invalidAllowedOrigins } = (0, origin_1.parseAllowedOrigins)(env.BFF_ALLOWED_ORIGINS);
70
+ const defaultTimezone = parseDefaultTimezone(env.BFF_DEFAULT_TIMEZONE);
60
71
  return {
61
72
  forestAuthSecret: normalized.FOREST_AUTH_SECRET,
62
73
  forestEnvSecret: normalized.FOREST_ENV_SECRET,
@@ -64,9 +75,12 @@ function parseConfig(env) {
64
75
  forestAppUrl: normalized.FOREST_APP_URL,
65
76
  agentUrl: normalized.AGENT_URL,
66
77
  tokenEncryptionKey,
78
+ allowedOrigins,
79
+ invalidAllowedOrigins,
80
+ defaultTimezone,
67
81
  httpPort: parsePort(env.HTTP_PORT),
68
82
  presence,
69
83
  hasAllRequired: exports.REQUIRED_KEYS.every(key => presence[key]) && tokenEncryptionKey !== undefined,
70
84
  };
71
85
  }
72
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52LWNvbmZpZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb25maWcvZW52LWNvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUE0RUEsa0NBOEJDO0FBMUdELDZCQUF3QjtBQUV4QiwyREFBMkM7QUFDM0Msc0NBQStDO0FBRWxDLFFBQUEsYUFBYSxHQUFHO0lBQzNCLG9CQUFvQjtJQUNwQixtQkFBbUI7SUFDbkIsbUJBQW1CO0lBQ25CLGdCQUFnQjtJQUNoQixXQUFXO0NBQ0gsQ0FBQztBQUlYLE1BQU0sUUFBUSxHQUFHLENBQUMsbUJBQW1CLEVBQUUsZ0JBQWdCLEVBQUUsV0FBVyxDQUFVLENBQUM7QUFnQi9FLE1BQU0sZUFBZSxHQUFHLE9BQU8sQ0FBQztBQUNoQyxNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUM7QUFDdkIsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLENBQUM7QUFDaEMsTUFBTSxjQUFjLEdBQUcsd0JBQXdCLENBQUM7QUFDaEQsTUFBTSxlQUFlLEdBQUcsT0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsQ0FBQyxDQUFDO0FBRXhELFNBQVMsU0FBUyxDQUFDLEtBQXlCO0lBQzFDLE9BQU8sS0FBSyxLQUFLLFNBQVMsSUFBSSxLQUFLLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztBQUN4RSxDQUFDO0FBRUQsU0FBUyxTQUFTLENBQUMsR0FBdUI7SUFDeEMsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFLElBQUksRUFBRSxDQUFDO0lBQzVCLElBQUksT0FBTyxLQUFLLFNBQVMsSUFBSSxPQUFPLEtBQUssRUFBRTtRQUFFLE9BQU8sa0JBQWdCLENBQUM7SUFFckUsTUFBTSxJQUFJLEdBQUcsZUFBZSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFD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
86
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52LWNvbmZpZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb25maWcvZW52LWNvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUE2RkEsa0NBcUNDO0FBbElELDZCQUF3QjtBQUV4QiwyQ0FBcUQ7QUFDckQsMkRBQTJDO0FBQzNDLHNDQUErQztBQUMvQyxtREFBdUQ7QUFFMUMsUUFBQSxhQUFhLEdBQUc7SUFDM0Isb0JBQW9CO0lBQ3BCLG1CQUFtQjtJQUNuQixtQkFBbUI7SUFDbkIsZ0JBQWdCO0lBQ2hCLFdBQVc7Q0FDSCxDQUFDO0FBSVgsTUFBTSxRQUFRLEdBQUcsQ0FBQyxtQkFBbUIsRUFBRSxnQkFBZ0IsRUFBRSxXQUFXLENBQVUsQ0FBQztBQW1CL0UsTUFBTSxlQUFlLEdBQUcsT0FBTyxDQUFDO0FBQ2hDLE1BQU0sUUFBUSxHQUFHLEtBQUssQ0FBQztBQUN2QixNQUFNLG9CQUFvQixHQUFHLEVBQUUsQ0FBQztBQUNoQyxNQUFNLGNBQWMsR0FBRyx3QkFBd0IsQ0FBQztBQUNoRCxNQUFNLGVBQWUsR0FBRyxPQUFDLENBQUMsR0FBRyxDQUFDLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUM7QUFFeEQsU0FBUyxTQUFTLENBQUMsS0FBeUI7SUFDMUMsT0FBTyxLQUFLLEtBQUssU0FBUyxJQUFJLEtBQUssQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDO0FBQ3hFLENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxHQUF1QjtJQUN4QyxNQUFNLE9BQU8sR0FBRyxHQUFHLEVBQUUsSUFBSSxFQUFFLENBQUM7SUFDNUIsSUFBSSxPQUFPLEtBQUssU0FBUyxJQUFJLE9BQU8sS0FBSyxFQUFFO1FBQUUsT0FBTyxrQkFBZ0IsQ0FBQztJQUVyRSxNQUFNLElBQUksR0FBRyxlQUFlLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FB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
@@ -0,0 +1,9 @@
1
+ import type { Middleware } from 'koa';
2
+ export declare const ALLOWED_METHODS = "GET, POST, PUT, PATCH, DELETE, OPTIONS";
3
+ export declare const ALLOWED_HEADERS = "Authorization, Content-Type, X-Forest-Timezone, X-Forest-Bff-Key, X-Request-Id";
4
+ export declare const PREFLIGHT_MAX_AGE_SECONDS = 600;
5
+ export interface CorsMiddlewareOptions {
6
+ allowedOrigins: string[];
7
+ }
8
+ export default function createCorsMiddleware({ allowedOrigins, }: CorsMiddlewareOptions): Middleware;
9
+ //# sourceMappingURL=cors-middleware.d.ts.map
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PREFLIGHT_MAX_AGE_SECONDS = exports.ALLOWED_HEADERS = exports.ALLOWED_METHODS = void 0;
4
+ exports.default = createCorsMiddleware;
5
+ const origin_1 = require("./origin");
6
+ exports.ALLOWED_METHODS = 'GET, POST, PUT, PATCH, DELETE, OPTIONS';
7
+ exports.ALLOWED_HEADERS = 'Authorization, Content-Type, X-Forest-Timezone, X-Forest-Bff-Key, X-Request-Id';
8
+ exports.PREFLIGHT_MAX_AGE_SECONDS = 600;
9
+ function createCorsMiddleware({ allowedOrigins, }) {
10
+ return async function corsMiddleware(ctx, next) {
11
+ const origin = ctx.get('Origin');
12
+ if (origin)
13
+ ctx.vary('Origin');
14
+ const allowed = origin ? (0, origin_1.originAllowed)(origin, allowedOrigins) : false;
15
+ if (allowed)
16
+ ctx.set('Access-Control-Allow-Origin', origin);
17
+ if (ctx.method === 'OPTIONS') {
18
+ if (allowed) {
19
+ ctx.set('Access-Control-Allow-Methods', exports.ALLOWED_METHODS);
20
+ ctx.set('Access-Control-Allow-Headers', exports.ALLOWED_HEADERS);
21
+ ctx.set('Access-Control-Max-Age', String(exports.PREFLIGHT_MAX_AGE_SECONDS));
22
+ }
23
+ ctx.status = 204;
24
+ return;
25
+ }
26
+ await next();
27
+ };
28
+ }
29
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29ycy1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvcnMvY29ycy1taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQWFBLHVDQTBCQztBQXJDRCxxQ0FBeUM7QUFFNUIsUUFBQSxlQUFlLEdBQUcsd0NBQXdDLENBQUM7QUFDM0QsUUFBQSxlQUFlLEdBQzFCLGdGQUFnRixDQUFDO0FBQ3RFLFFBQUEseUJBQXlCLEdBQUcsR0FBRyxDQUFDO0FBTTdDLFNBQXdCLG9CQUFvQixDQUFDLEVBQzNDLGNBQWMsR0FDUTtJQUN0QixPQUFPLEtBQUssVUFBVSxjQUFjLENBQUMsR0FBRyxFQUFFLElBQUk7UUFDNUMsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUVqQyxJQUFJLE1BQU07WUFBRSxHQUFHLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRS9CLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBQSxzQkFBYSxFQUFDLE1BQU0sRUFBRSxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDO1FBRXZFLElBQUksT0FBTztZQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsNkJBQTZCLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFNUQsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQzdCLElBQUksT0FBTyxFQUFFLENBQUM7Z0JBQ1osR0FBRyxDQUFDLEdBQUcsQ0FBQyw4QkFBOEIsRUFBRSx1QkFBZSxDQUFDLENBQUM7Z0JBQ3pELEdBQUcsQ0FBQyxHQUFHLENBQUMsOEJBQThCLEVBQUUsdUJBQWUsQ0FBQyxDQUFDO2dCQUN6RCxHQUFHLENBQUMsR0FBRyxDQUFDLHdCQUF3QixFQUFFLE1BQU0sQ0FBQyxpQ0FBeUIsQ0FBQyxDQUFDLENBQUM7WUFDdkUsQ0FBQztZQUVELEdBQUcsQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDO1lBRWpCLE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxJQUFJLEVBQUUsQ0FBQztJQUNmLENBQUMsQ0FBQztBQUNKLENBQUMifQ==
@@ -0,0 +1,7 @@
1
+ export declare function normalizeOrigin(raw: string | undefined | null): string | null;
2
+ export declare function parseAllowedOrigins(raw: string | undefined): {
3
+ origins: string[];
4
+ invalid: string[];
5
+ };
6
+ export declare function originAllowed(requestOrigin: string | undefined, allowList: string[]): boolean;
7
+ //# sourceMappingURL=origin.d.ts.map
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeOrigin = normalizeOrigin;
4
+ exports.parseAllowedOrigins = parseAllowedOrigins;
5
+ exports.originAllowed = originAllowed;
6
+ function normalizeOrigin(raw) {
7
+ if (raw === undefined || raw === null)
8
+ return null;
9
+ const trimmed = raw.trim();
10
+ if (trimmed === '' || trimmed === 'null')
11
+ return null;
12
+ let url;
13
+ try {
14
+ url = new URL(trimmed);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ if (url.origin === 'null')
20
+ return null;
21
+ return url.origin;
22
+ }
23
+ function parseAllowedOrigins(raw) {
24
+ if (raw === undefined || raw.trim() === '')
25
+ return { origins: [], invalid: [] };
26
+ const origins = [];
27
+ const invalid = [];
28
+ const entries = raw
29
+ .split(',')
30
+ .map(entry => entry.trim())
31
+ .filter(entry => entry !== '');
32
+ for (const entry of entries) {
33
+ const normalized = normalizeOrigin(entry);
34
+ if (normalized === null)
35
+ invalid.push(entry);
36
+ else if (!origins.includes(normalized))
37
+ origins.push(normalized);
38
+ }
39
+ return { origins, invalid };
40
+ }
41
+ function originAllowed(requestOrigin, allowList) {
42
+ const normalized = normalizeOrigin(requestOrigin);
43
+ if (normalized === null)
44
+ return false;
45
+ return allowList.some(entry => normalizeOrigin(entry) === normalized);
46
+ }
47
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JpZ2luLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvcnMvb3JpZ2luLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsMENBaUJDO0FBRUQsa0RBc0JDO0FBRUQsc0NBS0M7QUFoREQsU0FBZ0IsZUFBZSxDQUFDLEdBQThCO0lBQzVELElBQUksR0FBRyxLQUFLLFNBQVMsSUFBSSxHQUFHLEtBQUssSUFBSTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRW5ELE1BQU0sT0FBTyxHQUFHLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUMzQixJQUFJLE9BQU8sS0FBSyxFQUFFLElBQUksT0FBTyxLQUFLLE1BQU07UUFBRSxPQUFPLElBQUksQ0FBQztJQUV0RCxJQUFJLEdBQVEsQ0FBQztJQUViLElBQUksQ0FBQztRQUNILEdBQUcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUN6QixDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLE1BQU07UUFBRSxPQUFPLElBQUksQ0FBQztJQUV2QyxPQUFPLEdBQUcsQ0FBQyxNQUFNLENBQUM7QUFDcEIsQ0FBQztBQUVELFNBQWdCLG1CQUFtQixDQUFDLEdBQXVCO0lBSXpELElBQUksR0FBRyxLQUFLLFNBQVMsSUFBSSxHQUFHLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRTtRQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsRUFBRSxFQUFFLE9BQU8sRUFBRSxFQUFFLEVBQUUsQ0FBQztJQUVoRixNQUFNLE9BQU8sR0FBYSxFQUFFLENBQUM7SUFDN0IsTUFBTSxPQUFPLEdBQWEsRUFBRSxDQUFDO0lBRTdCLE1BQU0sT0FBTyxHQUFHLEdBQUc7U0FDaEIsS0FBSyxDQUFDLEdBQUcsQ0FBQztTQUNWLEdBQUcsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztTQUMxQixNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEtBQUssRUFBRSxDQUFDLENBQUM7SUFFakMsS0FBSyxNQUFNLEtBQUssSUFBSSxPQUFPLEVBQUUsQ0FBQztRQUM1QixNQUFNLFVBQVUsR0FBRyxlQUFlLENBQUMsS0FBSyxDQUFDLENBQUM7UUFFMUMsSUFBSSxVQUFVLEtBQUssSUFBSTtZQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7YUFDeEMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDO1lBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUNuRSxDQUFDO0lBRUQsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQztBQUM5QixDQUFDO0FBRUQsU0FBZ0IsYUFBYSxDQUFDLGFBQWlDLEVBQUUsU0FBbUI7SUFDbEYsTUFBTSxVQUFVLEdBQUcsZUFBZSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQ2xELElBQUksVUFBVSxLQUFLLElBQUk7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUV0QyxPQUFPLFNBQVMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxlQUFlLENBQUMsS0FBSyxDQUFDLEtBQUssVUFBVSxDQUFDLENBQUM7QUFDeEUsQ0FBQyJ9
@@ -0,0 +1,3 @@
1
+ import type { Middleware } from 'koa';
2
+ export default function createPerKeyOriginMiddleware(): Middleware;
3
+ //# sourceMappingURL=per-key-origin.d.ts.map
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = createPerKeyOriginMiddleware;
4
+ const origin_1 = require("./origin");
5
+ const bff_http_error_1 = require("../http/bff-http-error");
6
+ function createPerKeyOriginMiddleware() {
7
+ return async function perKeyOriginMiddleware(ctx, next) {
8
+ const identity = ctx.state.apiKeyIdentity;
9
+ const allowedOrigins = identity?.allowedOrigins ?? [];
10
+ if (allowedOrigins.length > 0 && !(0, origin_1.originAllowed)(ctx.get('Origin'), allowedOrigins)) {
11
+ throw (0, bff_http_error_1.originNotAllowed)();
12
+ }
13
+ await next();
14
+ };
15
+ }
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVyLWtleS1vcmlnaW4uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29ycy9wZXIta2V5LW9yaWdpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUtBLCtDQVdDO0FBZEQscUNBQXlDO0FBQ3pDLDJEQUEwRDtBQUUxRCxTQUF3Qiw0QkFBNEI7SUFDbEQsT0FBTyxLQUFLLFVBQVUsc0JBQXNCLENBQUMsR0FBRyxFQUFFLElBQUk7UUFDcEQsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxjQUEyRCxDQUFDO1FBQ3ZGLE1BQU0sY0FBYyxHQUFHLFFBQVEsRUFBRSxjQUFjLElBQUksRUFBRSxDQUFDO1FBRXRELElBQUksY0FBYyxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFBLHNCQUFhLEVBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsRUFBRSxjQUFjLENBQUMsRUFBRSxDQUFDO1lBQ25GLE1BQU0sSUFBQSxpQ0FBZ0IsR0FBRSxDQUFDO1FBQzNCLENBQUM7UUFFRCxNQUFNLElBQUksRUFBRSxDQUFDO0lBQ2YsQ0FBQyxDQUFDO0FBQ0osQ0FBQyJ9
@@ -0,0 +1,34 @@
1
+ export declare class BffHttpError extends Error {
2
+ readonly status: number;
3
+ readonly type: string;
4
+ readonly details?: unknown;
5
+ constructor(status: number, type: string, message: string, details?: unknown);
6
+ }
7
+ export interface BffErrorBody {
8
+ error: {
9
+ type: string;
10
+ status: number;
11
+ message: string;
12
+ details?: unknown;
13
+ };
14
+ }
15
+ export declare function isSerializableError(error: unknown): error is {
16
+ status: number;
17
+ type: string;
18
+ message: string;
19
+ details?: unknown;
20
+ retryAfter?: number;
21
+ };
22
+ export declare function toErrorBody(error: {
23
+ status: number;
24
+ type: string;
25
+ message: string;
26
+ details?: unknown;
27
+ }): BffErrorBody;
28
+ export declare function unauthorized(message?: string): BffHttpError;
29
+ export declare function ambiguousCredentials(message?: string): BffHttpError;
30
+ export declare function sessionExpired(message?: string): BffHttpError;
31
+ export declare function originNotAllowed(message?: string): BffHttpError;
32
+ export declare function missingTimezone(message?: string): BffHttpError;
33
+ export declare function invalidTimezone(value: string): BffHttpError;
34
+ //# sourceMappingURL=bff-http-error.d.ts.map
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BffHttpError = void 0;
4
+ exports.isSerializableError = isSerializableError;
5
+ exports.toErrorBody = toErrorBody;
6
+ exports.unauthorized = unauthorized;
7
+ exports.ambiguousCredentials = ambiguousCredentials;
8
+ exports.sessionExpired = sessionExpired;
9
+ exports.originNotAllowed = originNotAllowed;
10
+ exports.missingTimezone = missingTimezone;
11
+ exports.invalidTimezone = invalidTimezone;
12
+ class BffHttpError extends Error {
13
+ constructor(status, type, message, details) {
14
+ super(message);
15
+ this.name = 'BffHttpError';
16
+ this.status = status;
17
+ this.type = type;
18
+ this.details = details;
19
+ }
20
+ }
21
+ exports.BffHttpError = BffHttpError;
22
+ function isSerializableError(error) {
23
+ return (typeof error === 'object' &&
24
+ error !== null &&
25
+ typeof error.status === 'number' &&
26
+ typeof error.type === 'string' &&
27
+ typeof error.message === 'string');
28
+ }
29
+ function toErrorBody(error) {
30
+ const body = {
31
+ error: { type: error.type, status: error.status, message: error.message },
32
+ };
33
+ if (error.details !== undefined)
34
+ body.error.details = error.details;
35
+ return body;
36
+ }
37
+ function unauthorized(message = 'Missing or invalid credentials') {
38
+ return new BffHttpError(401, 'unauthorized', message);
39
+ }
40
+ function ambiguousCredentials(message = 'Both Authorization and X-Forest-Bff-Key were provided') {
41
+ return new BffHttpError(400, 'ambiguous_credentials', message);
42
+ }
43
+ function sessionExpired(message = 'The BFF session has expired') {
44
+ return new BffHttpError(401, 'session_expired', message);
45
+ }
46
+ function originNotAllowed(message = 'Origin is not allowed for this key') {
47
+ return new BffHttpError(403, 'origin_not_allowed', message);
48
+ }
49
+ function missingTimezone(message = 'A timezone is required but none was provided') {
50
+ return new BffHttpError(400, 'missing_timezone', message);
51
+ }
52
+ function invalidTimezone(value) {
53
+ return new BffHttpError(400, 'invalid_timezone', `Invalid timezone: "${value}"`);
54
+ }
55
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmZmLWh0dHAtZXJyb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaHR0cC9iZmYtaHR0cC1lcnJvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUF1QkEsa0RBY0M7QUFFRCxrQ0FhQztBQUVELG9DQUVDO0FBRUQsb0RBSUM7QUFFRCx3Q0FFQztBQUVELDRDQUVDO0FBRUQsMENBSUM7QUFFRCwwQ0FFQztBQWhGRCxNQUFhLFlBQWEsU0FBUSxLQUFLO0lBS3JDLFlBQVksTUFBYyxFQUFFLElBQVksRUFBRSxPQUFlLEVBQUUsT0FBaUI7UUFDMUUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2YsSUFBSSxDQUFDLElBQUksR0FBRyxjQUFjLENBQUM7UUFDM0IsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDakIsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7SUFDekIsQ0FBQztDQUNGO0FBWkQsb0NBWUM7QUFXRCxTQUFnQixtQkFBbUIsQ0FBQyxLQUFjO0lBT2hELE9BQU8sQ0FDTCxPQUFPLEtBQUssS0FBSyxRQUFRO1FBQ3pCLEtBQUssS0FBSyxJQUFJO1FBQ2QsT0FBUSxLQUE4QixDQUFDLE1BQU0sS0FBSyxRQUFRO1FBQzFELE9BQVEsS0FBNEIsQ0FBQyxJQUFJLEtBQUssUUFBUTtRQUN0RCxPQUFRLEtBQStCLENBQUMsT0FBTyxLQUFLLFFBQVEsQ0FDN0QsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFnQixXQUFXLENBQUMsS0FLM0I7SUFDQyxNQUFNLElBQUksR0FBaUI7UUFDekIsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxPQUFPLEVBQUU7S0FDMUUsQ0FBQztJQUVGLElBQUksS0FBSyxDQUFDLE9BQU8sS0FBSyxTQUFTO1FBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQztJQUVwRSxPQUFPLElBQUksQ0FBQztBQUNkLENBQUM7QUFFRCxTQUFnQixZQUFZLENBQUMsT0FBTyxHQUFHLGdDQUFnQztJQUNyRSxPQUFPLElBQUksWUFBWSxDQUFDLEdBQUcsRUFBRSxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDeEQsQ0FBQztBQUVELFNBQWdCLG9CQUFvQixDQUNsQyxPQUFPLEdBQUcsdURBQXVEO0lBRWpFLE9BQU8sSUFBSSxZQUFZLENBQUMsR0FBRyxFQUFFLHVCQUF1QixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ2pFLENBQUM7QUFFRCxTQUFnQixjQUFjLENBQUMsT0FBTyxHQUFHLDZCQUE2QjtJQUNwRSxPQUFPLElBQUksWUFBWSxDQUFDLEdBQUcsRUFBRSxpQkFBaUIsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUMzRCxDQUFDO0FBRUQsU0FBZ0IsZ0JBQWdCLENBQUMsT0FBTyxHQUFHLG9DQUFvQztJQUM3RSxPQUFPLElBQUksWUFBWSxDQUFDLEdBQUcsRUFBRSxvQkFBb0IsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUM5RCxDQUFDO0FBRUQsU0FBZ0IsZUFBZSxDQUM3QixPQUFPLEdBQUcsOENBQThDO0lBRXhELE9BQU8sSUFBSSxZQUFZLENBQUMsR0FBRyxFQUFFLGtCQUFrQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzVELENBQUM7QUFFRCxTQUFnQixlQUFlLENBQUMsS0FBYTtJQUMzQyxPQUFPLElBQUksWUFBWSxDQUFDLEdBQUcsRUFBRSxrQkFBa0IsRUFBRSxzQkFBc0IsS0FBSyxHQUFHLENBQUMsQ0FBQztBQUNuRixDQUFDIn0=
@@ -0,0 +1,7 @@
1
+ import type { Logger } from '../ports/logger-port';
2
+ import type { Middleware } from 'koa';
3
+ export interface ErrorMiddlewareOptions {
4
+ logger: Logger;
5
+ }
6
+ export default function createErrorMiddleware({ logger }: ErrorMiddlewareOptions): Middleware;
7
+ //# sourceMappingURL=error-middleware.d.ts.map
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = createErrorMiddleware;
4
+ const bff_http_error_1 = require("./bff-http-error");
5
+ const CLIENT_ERROR_TYPES = {
6
+ 413: 'payload_too_large',
7
+ 415: 'unsupported_media_type',
8
+ };
9
+ function clientErrorStatus(error) {
10
+ if (typeof error !== 'object' || error === null)
11
+ return undefined;
12
+ const { status, statusCode } = error;
13
+ const value = typeof status === 'number' ? status : statusCode;
14
+ return typeof value === 'number' && value >= 400 && value < 500 ? value : undefined;
15
+ }
16
+ function createErrorMiddleware({ logger }) {
17
+ return async function errorMiddleware(ctx, next) {
18
+ try {
19
+ await next();
20
+ }
21
+ catch (error) {
22
+ if ((0, bff_http_error_1.isSerializableError)(error)) {
23
+ if (error.retryAfter !== undefined)
24
+ ctx.set('Retry-After', String(error.retryAfter));
25
+ ctx.status = error.status;
26
+ ctx.body = (0, bff_http_error_1.toErrorBody)(error);
27
+ return;
28
+ }
29
+ const clientStatus = clientErrorStatus(error);
30
+ if (clientStatus !== undefined) {
31
+ ctx.status = clientStatus;
32
+ ctx.body = (0, bff_http_error_1.toErrorBody)({
33
+ status: clientStatus,
34
+ type: CLIENT_ERROR_TYPES[clientStatus] ?? 'invalid_request',
35
+ message: 'Invalid request',
36
+ });
37
+ return;
38
+ }
39
+ logger('Error', 'Unhandled BFF edge error', {
40
+ cause: error instanceof Error ? `${error.name}: ${error.message}` : String(error),
41
+ });
42
+ ctx.status = 500;
43
+ ctx.body = {
44
+ error: { type: 'internal_error', status: 500, message: 'Internal server error' },
45
+ };
46
+ }
47
+ };
48
+ }
49
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3ItbWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9odHRwL2Vycm9yLW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF1QkEsd0NBbUNDO0FBdkRELHFEQUFvRTtBQU1wRSxNQUFNLGtCQUFrQixHQUEyQjtJQUNqRCxHQUFHLEVBQUUsbUJBQW1CO0lBQ3hCLEdBQUcsRUFBRSx3QkFBd0I7Q0FDOUIsQ0FBQztBQUVGLFNBQVMsaUJBQWlCLENBQUMsS0FBYztJQUN2QyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLEtBQUssSUFBSTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBRWxFLE1BQU0sRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLEdBQUcsS0FBbUQsQ0FBQztJQUNuRixNQUFNLEtBQUssR0FBRyxPQUFPLE1BQU0sS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDO0lBRS9ELE9BQU8sT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssSUFBSSxHQUFHLElBQUksS0FBSyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7QUFDdEYsQ0FBQztBQUVELFNBQXdCLHFCQUFxQixDQUFDLEVBQUUsTUFBTSxFQUEwQjtJQUM5RSxPQUFPLEtBQUssVUFBVSxlQUFlLENBQUMsR0FBRyxFQUFFLElBQUk7UUFDN0MsSUFBSSxDQUFDO1lBQ0gsTUFBTSxJQUFJLEVBQUUsQ0FBQztRQUNmLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxJQUFBLG9DQUFtQixFQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQy9CLElBQUksS0FBSyxDQUFDLFVBQVUsS0FBSyxTQUFTO29CQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsYUFBYSxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQztnQkFDckYsR0FBRyxDQUFDLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDO2dCQUMxQixHQUFHLENBQUMsSUFBSSxHQUFHLElBQUEsNEJBQVcsRUFBQyxLQUFLLENBQUMsQ0FBQztnQkFFOUIsT0FBTztZQUNULENBQUM7WUFFRCxNQUFNLFlBQVksR0FBRyxpQkFBaUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUU5QyxJQUFJLFlBQVksS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDL0IsR0FBRyxDQUFDLE1BQU0sR0FBRyxZQUFZLENBQUM7Z0JBQzFCLEdBQUcsQ0FBQyxJQUFJLEdBQUcsSUFBQSw0QkFBVyxFQUFDO29CQUNyQixNQUFNLEVBQUUsWUFBWTtvQkFDcEIsSUFBSSxFQUFFLGtCQUFrQixDQUFDLFlBQVksQ0FBQyxJQUFJLGlCQUFpQjtvQkFDM0QsT0FBTyxFQUFFLGlCQUFpQjtpQkFDM0IsQ0FBQyxDQUFDO2dCQUVILE9BQU87WUFDVCxDQUFDO1lBRUQsTUFBTSxDQUFDLE9BQU8sRUFBRSwwQkFBMEIsRUFBRTtnQkFDMUMsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksS0FBSyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7YUFDbEYsQ0FBQyxDQUFDO1lBQ0gsR0FBRyxDQUFDLE1BQU0sR0FBRyxHQUFHLENBQUM7WUFDakIsR0FBRyxDQUFDLElBQUksR0FBRztnQkFDVCxLQUFLLEVBQUUsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsdUJBQXVCLEVBQUU7YUFDakYsQ0FBQztRQUNKLENBQUM7SUFDSCxDQUFDLENBQUM7QUFDSixDQUFDIn0=
@@ -0,0 +1,7 @@
1
+ import type { Middleware } from 'koa';
2
+ export declare const TIMEZONE_HEADER = "X-Forest-Timezone";
3
+ export interface TimezoneMiddlewareOptions {
4
+ defaultTimezone?: string;
5
+ }
6
+ export default function createTimezoneMiddleware({ defaultTimezone, }: TimezoneMiddlewareOptions): Middleware;
7
+ //# sourceMappingURL=timezone-middleware.d.ts.map
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TIMEZONE_HEADER = void 0;
4
+ exports.default = createTimezoneMiddleware;
5
+ const timezone_1 = require("./timezone");
6
+ exports.TIMEZONE_HEADER = 'X-Forest-Timezone';
7
+ function bodyTimezone(ctx) {
8
+ const { body } = ctx.request;
9
+ if (typeof body !== 'object' || body === null)
10
+ return undefined;
11
+ const value = body.timezone;
12
+ return typeof value === 'string' ? value : undefined;
13
+ }
14
+ function createTimezoneMiddleware({ defaultTimezone, }) {
15
+ return async function timezoneMiddleware(ctx, next) {
16
+ ctx.state.timezone = (0, timezone_1.resolveTimezone)({
17
+ header: ctx.get(exports.TIMEZONE_HEADER),
18
+ body: bodyTimezone(ctx),
19
+ fallback: defaultTimezone,
20
+ });
21
+ await next();
22
+ };
23
+ }
24
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZXpvbmUtbWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90aW1lem9uZS90aW1lem9uZS1taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQW9CQSwyQ0FZQztBQTlCRCx5Q0FBNkM7QUFFaEMsUUFBQSxlQUFlLEdBQUcsbUJBQW1CLENBQUM7QUFNbkQsU0FBUyxZQUFZLENBQUMsR0FBOEI7SUFDbEQsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLEdBQUcsQ0FBQyxPQUE2QixDQUFDO0lBRW5ELElBQUksT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksS0FBSyxJQUFJO1FBQUUsT0FBTyxTQUFTLENBQUM7SUFFaEUsTUFBTSxLQUFLLEdBQUksSUFBK0IsQ0FBQyxRQUFRLENBQUM7SUFFeEQsT0FBTyxPQUFPLEtBQUssS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQ3ZELENBQUM7QUFFRCxTQUF3Qix3QkFBd0IsQ0FBQyxFQUMvQyxlQUFlLEdBQ1c7SUFDMUIsT0FBTyxLQUFLLFVBQVUsa0JBQWtCLENBQUMsR0FBRyxFQUFFLElBQUk7UUFDaEQsR0FBRyxDQUFDLEtBQUssQ0FBQyxRQUFRLEdBQUcsSUFBQSwwQkFBZSxFQUFDO1lBQ25DLE1BQU0sRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLHVCQUFlLENBQUM7WUFDaEMsSUFBSSxFQUFFLFlBQVksQ0FBQyxHQUFHLENBQUM7WUFDdkIsUUFBUSxFQUFFLGVBQWU7U0FDMUIsQ0FBQyxDQUFDO1FBRUgsTUFBTSxJQUFJLEVBQUUsQ0FBQztJQUNmLENBQUMsQ0FBQztBQUNKLENBQUMifQ==
@@ -0,0 +1,8 @@
1
+ export interface TimezoneSources {
2
+ header?: string;
3
+ body?: string;
4
+ fallback?: string;
5
+ }
6
+ export declare function isValidTimezone(value: string): boolean;
7
+ export declare function resolveTimezone({ header, body, fallback }: TimezoneSources): string;
8
+ //# sourceMappingURL=timezone.d.ts.map
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isValidTimezone = isValidTimezone;
4
+ exports.resolveTimezone = resolveTimezone;
5
+ const bff_http_error_1 = require("../http/bff-http-error");
6
+ const VALID_TIMEZONES = new Set();
7
+ function isValidTimezone(value) {
8
+ if (value === '')
9
+ return false;
10
+ if (VALID_TIMEZONES.has(value))
11
+ return true;
12
+ let canonical;
13
+ try {
14
+ canonical = Intl.DateTimeFormat('en-US', { timeZone: value }).resolvedOptions().timeZone;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ // Cache the canonical form, not the raw input: Intl matches IANA names
20
+ // case-insensitively, so caching raw casings would grow this Set without
21
+ // bound on attacker-controlled input.
22
+ VALID_TIMEZONES.add(canonical);
23
+ return true;
24
+ }
25
+ function resolveTimezone({ header, body, fallback }) {
26
+ const candidate = [header, body, fallback]
27
+ .map(value => value?.trim())
28
+ .find(value => value !== undefined && value !== '');
29
+ if (candidate === undefined)
30
+ throw (0, bff_http_error_1.missingTimezone)();
31
+ if (!isValidTimezone(candidate))
32
+ throw (0, bff_http_error_1.invalidTimezone)(candidate);
33
+ return candidate;
34
+ }
35
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZXpvbmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdGltZXpvbmUvdGltZXpvbmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFVQSwwQ0FrQkM7QUFFRCwwQ0FTQztBQXZDRCwyREFBMEU7QUFFMUUsTUFBTSxlQUFlLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztBQVExQyxTQUFnQixlQUFlLENBQUMsS0FBYTtJQUMzQyxJQUFJLEtBQUssS0FBSyxFQUFFO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDL0IsSUFBSSxlQUFlLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRTVDLElBQUksU0FBaUIsQ0FBQztJQUV0QixJQUFJLENBQUM7UUFDSCxTQUFTLEdBQUcsSUFBSSxDQUFDLGNBQWMsQ0FBQyxPQUFPLEVBQUUsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQyxlQUFlLEVBQUUsQ0FBQyxRQUFRLENBQUM7SUFDM0YsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUVELHVFQUF1RTtJQUN2RSx5RUFBeUU7SUFDekUsc0NBQXNDO0lBQ3RDLGVBQWUsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUM7SUFFL0IsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDO0FBRUQsU0FBZ0IsZUFBZSxDQUFDLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQW1CO0lBQ3pFLE1BQU0sU0FBUyxHQUFHLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxRQUFRLENBQUM7U0FDdkMsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDO1NBQzNCLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssS0FBSyxTQUFTLElBQUksS0FBSyxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBRXRELElBQUksU0FBUyxLQUFLLFNBQVM7UUFBRSxNQUFNLElBQUEsZ0NBQWUsR0FBRSxDQUFDO0lBQ3JELElBQUksQ0FBQyxlQUFlLENBQUMsU0FBUyxDQUFDO1FBQUUsTUFBTSxJQUFBLGdDQUFlLEVBQUMsU0FBUyxDQUFDLENBQUM7SUFFbEUsT0FBTyxTQUFTLENBQUM7QUFDbkIsQ0FBQyJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forestadmin/agent-bff",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "forest-bff": "dist/cli.js"
@@ -31,7 +31,7 @@
31
31
  "test": "jest"
32
32
  },
33
33
  "dependencies": {
34
- "@forestadmin/forestadmin-client": "1.40.3",
34
+ "@forestadmin/forestadmin-client": "1.40.4",
35
35
  "@koa/bodyparser": "^6.1.0",
36
36
  "jsonwebtoken": "^9.0.3",
37
37
  "koa": "^3.0.1",