@forestadmin/agent-bff 1.30.2 → 1.31.0

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
@@ -100,7 +100,7 @@ yarn start:dev # node --env-file=.env dist/cli.js
100
100
  | `AGENT_URL` | yes | The customer agent base URL the BFF calls via agent-client. |
101
101
  | `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 `configured.oauth: false` — but it stays `ok`, since the key gates OAuth and not boot; already-issued `bff_access` tokens still authenticate on `/agent/*` whenever `FOREST_AUTH_SECRET` is present. |
102
102
  | `HTTP_PORT` | no | Server port, integer 0–65535. Defaults to `3450`. `0` binds an OS-assigned ephemeral port. |
103
- | `BFF_ALLOWED_ORIGINS` | no | Comma-separated CORS allow-list of exact origins (scheme + host + port). No wildcard. Empty ⇒ no cross-origin browser access. |
103
+ | `BFF_ALLOWED_ORIGINS` | no | Comma-separated CORS allow-list of origins (scheme + host + port). An entry may carry a single `*` as the leading host label — `https://*.apps.zdusercontent.com` — which matches exactly one DNS label there, and nothing else: not two labels, not the apex, and never the scheme or the port. The host left after `*.` must be at least two non-empty labels, so `https://*.com` is refused; a two-label public suffix such as `https://*.co.uk` is not, and would allow every site under it. Any other `*` in the host is refused and warned about at boot; a `*` outside the host — in userinfo, a path or a query — is stripped along with the rest of the URL, so `https://*@example.com` is simply the exact origin `https://example.com`. Empty ⇒ no cross-origin browser access. |
104
104
  | `BFF_DEFAULT_TIMEZONE` | no | Fallback IANA timezone used when a request carries neither an `X-Forest-Timezone` header nor a body `timezone`. |
105
105
  | `BFF_PUBLIC_URL` | no | The BFF's own external base URL, published as `servers[0].url` in the OpenAPI document so a generated client resolves endpoints without being configured by hand. Absent, `servers[0].url` stays `/`, which a consumer that fetched the document over HTTP resolves against that URL — but which leaves a client generated from an offline `forest-bff openapi` export with no base URL at all. Trailing slashes are stripped. A malformed value fails the boot, and so does one carrying credentials, a query string or a fragment: credentials would be published to every reader of the document, and anything behind a `?` or `#` swallows the path a generated client appends. |
106
106
  | `BFF_AGENT_TIMEOUT_MS` | no | How long the BFF waits for the customer agent on any data or action call, in milliseconds. Defaults to `10000`. Past it the route answers `504 agent_timeout`; any outright transport failure — a refused connection, an unresolvable host, a connection reset, a TLS failure — answers `502 network_error` instead. Surrounding whitespace is trimmed, then a malformed value (non-integer, `0`, or above 2147483647) fails the boot; an unset, empty, or whitespace-only value counts as absent and takes the default. |
@@ -117,8 +117,11 @@ yarn start:dev # node --env-file=.env dist/cli.js
117
117
  fails fast at boot: the process exits with a clear error and never echoes the offending value.
118
118
  - A required var that is absent (or empty / whitespace-only) does not crash the server. It boots and
119
119
  reports the gap through `/health` (503 `degraded`).
120
- - Malformed `BFF_ALLOWED_ORIGINS` entries (including a literal `*`) are dropped and logged once at
121
- boot (`Warn`); they never enter the allow-list, so a wildcard origin can never be served.
120
+ - Malformed `BFF_ALLOWED_ORIGINS` entries are dropped and logged once at boot (`Warn`); they never
121
+ enter the allow-list. That covers a literal `*`, a `*` outside the leading host label
122
+ (`https://a.*.example.com`), several `*`, a host left with fewer than two non-empty labels
123
+ (`https://*.com`, `https://*.com.`, `https://*..com`, `https://*.localhost`), and a pattern on an
124
+ IP (`https://*.127.0.0.1`). Only the single leading-label form is served as a wildcard.
122
125
 
123
126
  ## Request edge (`/agent/*`)
124
127
 
@@ -158,8 +161,23 @@ a `POST /oauth/token` concern (`session_invalidated`, OAuth/RFC shape).
158
161
 
159
162
  ### CORS
160
163
 
161
- Two layers, both driven by exact-origin matching (case-insensitive scheme/host, default ports
162
- normalized away, no trailing slash, no wildcard, no subdomain matching):
164
+ Two layers, both driven by the same matcher (case-insensitive scheme/host, default ports normalized
165
+ away, no trailing slash). An entry with no `*` matches by strict equality. An entry whose leading
166
+ host label is `*` — `https://*.apps.zdusercontent.com` — matches exactly one DNS label in its place:
167
+ `https://1231469.apps.zdusercontent.com` passes, `https://a.b.apps.zdusercontent.com` and the apex
168
+ `https://apps.zdusercontent.com` do not. Scheme and port are never wildcarded and must match
169
+ exactly. The host left after `*.` must be at least two non-empty labels; every other `*` shape is
170
+ refused at parse time and warned about at boot.
171
+
172
+ The wildcard exists for hosts that serve an application under an opaque generated subdomain — an
173
+ installed Zendesk app is served from `https://<app-id>.apps.zdusercontent.com` — where naming the
174
+ exact origin means copying an id the operator does not choose. Understand what it grants: every app
175
+ of every publisher on that domain is inside the allow-list. That is acceptable here because CORS is
176
+ not the authentication boundary — a `bff_access` session or an API key is still required, and
177
+ another origin cannot read this app's tokens — but it is a real widening, so keep the suffix as
178
+ specific as the host allows.
179
+
180
+ The two layers:
163
181
 
164
182
  - **Layer 1 (transport)** — the only layer that sets `Access-Control-Allow-Origin`. An origin in
165
183
  `BFF_ALLOWED_ORIGINS` is echoed back exactly; any other `Origin` is refused outright with
@@ -175,6 +193,11 @@ normalized away, no trailing slash, no wildcard, no subdomain matching):
175
193
  boundary, not the origin. An opaque `Origin: null` (sandboxed iframe, cross-origin redirect) is a
176
194
  present origin and is rejected. An empty per-key list is a no-op.
177
195
 
196
+ A wildcard in a key's `allowedOrigins` (layer 2) is matched the same way. Those entries come from
197
+ the Forest SaaS and never pass through the boot-time parser, so an illegal pattern there gets no
198
+ warning of its own; it counts as an origin that can never pass, and the key-level `Warn` names the
199
+ key once when *every* one of its origins is outside `BFF_ALLOWED_ORIGINS` or illegal.
200
+
178
201
  **Local development:** browsers still enforce CORS against `localhost`, so add your dev origin(s) to
179
202
  `BFF_ALLOWED_ORIGINS` (e.g. `BFF_ALLOWED_ORIGINS=http://localhost:4200`) — there is no dev bypass.
180
203
 
@@ -11,4 +11,5 @@ export declare function parseAllowedOrigins(raw: string | undefined): {
11
11
  invalid: string[];
12
12
  };
13
13
  export declare function originAllowed(requestOrigin: string | undefined, allowList: string[]): boolean;
14
+ export declare function allowedEntriesIntersect(left: string, right: string): boolean;
14
15
  //# sourceMappingURL=origin.d.ts.map
@@ -5,6 +5,7 @@ exports.hasOrigin = hasOrigin;
5
5
  exports.normalizeOrigin = normalizeOrigin;
6
6
  exports.parseAllowedOrigins = parseAllowedOrigins;
7
7
  exports.originAllowed = originAllowed;
8
+ exports.allowedEntriesIntersect = allowedEntriesIntersect;
8
9
  const MAX_LOGGED_ORIGIN_LENGTH = 256;
9
10
  /**
10
11
  * The origin as it is safe to log: caller-controlled, so a header near Node's ~16kb limit would
@@ -34,6 +35,33 @@ function normalizeOrigin(raw) {
34
35
  return null;
35
36
  return url.origin;
36
37
  }
38
+ const WILDCARD_LABEL_PREFIX = '*.';
39
+ const MIN_WILDCARD_HOST_LABELS = 2;
40
+ function parseAllowedEntry(raw) {
41
+ const origin = normalizeOrigin(raw);
42
+ if (origin === null)
43
+ return null;
44
+ const { hostname } = new URL(origin);
45
+ const stars = hostname.split('*').length - 1;
46
+ if (stars === 0)
47
+ return { origin, wildcard: false };
48
+ if (stars > 1 || !hostname.startsWith(WILDCARD_LABEL_PREFIX))
49
+ return null;
50
+ const parentLabels = hostname.slice(WILDCARD_LABEL_PREFIX.length).split('.');
51
+ if (parentLabels.length < MIN_WILDCARD_HOST_LABELS || parentLabels.some(label => label === '')) {
52
+ return null;
53
+ }
54
+ return { origin, wildcard: true };
55
+ }
56
+ function matchesWildcard(entry, request) {
57
+ if (entry.protocol !== request.protocol || entry.port !== request.port)
58
+ return false;
59
+ const suffixWithDot = entry.hostname.slice('*'.length);
60
+ if (!request.hostname.endsWith(suffixWithDot))
61
+ return false;
62
+ const head = request.hostname.slice(0, -suffixWithDot.length);
63
+ return head !== '' && !head.includes('.');
64
+ }
37
65
  function parseAllowedOrigins(raw) {
38
66
  if (raw === undefined || raw.trim() === '')
39
67
  return { origins: [], invalid: [] };
@@ -44,11 +72,11 @@ function parseAllowedOrigins(raw) {
44
72
  .map(entry => entry.trim())
45
73
  .filter(entry => entry !== '');
46
74
  for (const entry of entries) {
47
- const normalized = normalizeOrigin(entry);
48
- if (normalized === null)
75
+ const parsed = parseAllowedEntry(entry);
76
+ if (parsed === null)
49
77
  invalid.push(entry);
50
- else if (!origins.includes(normalized))
51
- origins.push(normalized);
78
+ else if (!origins.includes(parsed.origin))
79
+ origins.push(parsed.origin);
52
80
  }
53
81
  return { origins, invalid };
54
82
  }
@@ -56,6 +84,29 @@ function originAllowed(requestOrigin, allowList) {
56
84
  const normalized = normalizeOrigin(requestOrigin);
57
85
  if (normalized === null)
58
86
  return false;
59
- return allowList.some(entry => normalizeOrigin(entry) === normalized);
87
+ const request = new URL(normalized);
88
+ if (request.hostname.includes('*'))
89
+ return false;
90
+ return allowList.some(raw => {
91
+ const entry = parseAllowedEntry(raw);
92
+ if (entry === null)
93
+ return false;
94
+ if (!entry.wildcard)
95
+ return entry.origin === normalized;
96
+ return matchesWildcard(new URL(entry.origin), request);
97
+ });
98
+ }
99
+ function allowedEntriesIntersect(left, right) {
100
+ const a = parseAllowedEntry(left);
101
+ const b = parseAllowedEntry(right);
102
+ if (a === null || b === null)
103
+ return false;
104
+ if (a.wildcard && b.wildcard)
105
+ return a.origin === b.origin;
106
+ if (a.wildcard)
107
+ return matchesWildcard(new URL(a.origin), new URL(b.origin));
108
+ if (b.wildcard)
109
+ return matchesWildcard(new URL(b.origin), new URL(a.origin));
110
+ return a.origin === b.origin;
60
111
  }
61
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JpZ2luLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvcnMvb3JpZ2luLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBT0Esd0NBRUM7QUFFRCw4QkFFQztBQUVELDBDQWlCQztBQUVELGtEQXNCQztBQUVELHNDQUtDO0FBL0RELE1BQU0sd0JBQXdCLEdBQUcsR0FBRyxDQUFDO0FBRXJDOzs7O0dBSUc7QUFDSCxTQUFnQixjQUFjLENBQUMsR0FBVztJQUN4QyxPQUFPLEdBQUcsQ0FBQyxNQUFNLEdBQUcsd0JBQXdCLENBQUMsQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsd0JBQXdCLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUM7QUFDcEcsQ0FBQztBQUVELFNBQWdCLFNBQVMsQ0FBQyxHQUE4QjtJQUN0RCxPQUFPLEdBQUcsS0FBSyxTQUFTLElBQUksR0FBRyxLQUFLLElBQUksSUFBSSxHQUFHLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDO0FBQ2hFLENBQUM7QUFFRCxTQUFnQixlQUFlLENBQUMsR0FBOEI7SUFDNUQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUVqQyxNQUFNLE9BQU8sR0FBRyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDM0IsSUFBSSxPQUFPLEtBQUssTUFBTTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXBDLElBQUksR0FBUSxDQUFDO0lBRWIsSUFBSSxDQUFDO1FBQ0gsR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3pCLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCxJQUFJLEdBQUcsQ0FBQyxNQUFNLEtBQUssTUFBTTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXZDLE9BQU8sR0FBRyxDQUFDLE1BQU0sQ0FBQztBQUNwQixDQUFDO0FBRUQsU0FBZ0IsbUJBQW1CLENBQUMsR0FBdUI7SUFJekQsSUFBSSxHQUFHLEtBQUssU0FBUyxJQUFJLEdBQUcsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFO1FBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxFQUFFLEVBQUUsT0FBTyxFQUFFLEVBQUUsRUFBRSxDQUFDO0lBRWhGLE1BQU0sT0FBTyxHQUFhLEVBQUUsQ0FBQztJQUM3QixNQUFNLE9BQU8sR0FBYSxFQUFFLENBQUM7SUFFN0IsTUFBTSxPQUFPLEdBQUcsR0FBRztTQUNoQixLQUFLLENBQUMsR0FBRyxDQUFDO1NBQ1YsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDO1NBQzFCLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssS0FBSyxFQUFFLENBQUMsQ0FBQztJQUVqQyxLQUFLLE1BQU0sS0FBSyxJQUFJLE9BQU8sRUFBRSxDQUFDO1FBQzVCLE1BQU0sVUFBVSxHQUFHLGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUUxQyxJQUFJLFVBQVUsS0FBSyxJQUFJO1lBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQzthQUN4QyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUM7WUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQ25FLENBQUM7SUFFRCxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxDQUFDO0FBQzlCLENBQUM7QUFFRCxTQUFnQixhQUFhLENBQUMsYUFBaUMsRUFBRSxTQUFtQjtJQUNsRixNQUFNLFVBQVUsR0FBRyxlQUFlLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDbEQsSUFBSSxVQUFVLEtBQUssSUFBSTtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBRXRDLE9BQU8sU0FBUyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLGVBQWUsQ0FBQyxLQUFLLENBQUMsS0FBSyxVQUFVLENBQUMsQ0FBQztBQUN4RSxDQUFDIn0=
112
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JpZ2luLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvcnMvb3JpZ2luLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBT0Esd0NBRUM7QUFFRCw4QkFFQztBQUVELDBDQWlCQztBQXdDRCxrREFzQkM7QUFFRCxzQ0FlQztBQUVELDBEQVVDO0FBM0hELE1BQU0sd0JBQXdCLEdBQUcsR0FBRyxDQUFDO0FBRXJDOzs7O0dBSUc7QUFDSCxTQUFnQixjQUFjLENBQUMsR0FBVztJQUN4QyxPQUFPLEdBQUcsQ0FBQyxNQUFNLEdBQUcsd0JBQXdCLENBQUMsQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsd0JBQXdCLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUM7QUFDcEcsQ0FBQztBQUVELFNBQWdCLFNBQVMsQ0FBQyxHQUE4QjtJQUN0RCxPQUFPLEdBQUcsS0FBSyxTQUFTLElBQUksR0FBRyxLQUFLLElBQUksSUFBSSxHQUFHLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDO0FBQ2hFLENBQUM7QUFFRCxTQUFnQixlQUFlLENBQUMsR0FBOEI7SUFDNUQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUVqQyxNQUFNLE9BQU8sR0FBRyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDM0IsSUFBSSxPQUFPLEtBQUssTUFBTTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXBDLElBQUksR0FBUSxDQUFDO0lBRWIsSUFBSSxDQUFDO1FBQ0gsR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3pCLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCxJQUFJLEdBQUcsQ0FBQyxNQUFNLEtBQUssTUFBTTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXZDLE9BQU8sR0FB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LENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxLQUFLLElBQUk7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUMzQyxJQUFJLENBQUMsQ0FBQyxRQUFRLElBQUksQ0FBQyxDQUFDLFFBQVE7UUFBRSxPQUFPLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUMzRCxJQUFJLENBQUMsQ0FBQyxRQUFRO1FBQUUsT0FBTyxlQUFlLENBQUMsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksR0FBRyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQzdFLElBQUksQ0FBQyxDQUFDLFFBQVE7UUFBRSxPQUFPLGVBQWUsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7SUFFN0UsT0FBTyxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUM7QUFDL0IsQ0FBQyJ9
@@ -13,16 +13,17 @@ const bff_http_error_1 = require("../http/bff-http-error");
13
13
  const MAX_ASSESSED_KEYS = 1000;
14
14
  function createPerKeyOriginMiddleware({ logger, serverAllowedOrigins, }) {
15
15
  const assessedKeys = new Set();
16
- function reportOriginsThatCanNeverPass(allowedOrigins, keyHash) {
16
+ function reportOriginsThatCanNeverPass(allowedOrigins, keyHash, renderingId) {
17
17
  if (serverAllowedOrigins.length === 0 || assessedKeys.has(keyHash))
18
18
  return;
19
- const canNeverPass = !allowedOrigins.some(entry => (0, origin_1.originAllowed)(entry, serverAllowedOrigins));
19
+ const canNeverPass = !allowedOrigins.some(entry => serverAllowedOrigins.some(serverOrigin => (0, origin_1.allowedEntriesIntersect)(entry, serverOrigin)));
20
20
  if (assessedKeys.size >= MAX_ASSESSED_KEYS)
21
21
  assessedKeys.clear();
22
22
  assessedKeys.add(keyHash);
23
23
  if (canNeverPass) {
24
24
  logger('Warn', 'BFF key origins are all outside BFF_ALLOWED_ORIGINS', {
25
25
  keyHash,
26
+ renderingId,
26
27
  keyOrigins: allowedOrigins.map(origin_1.loggableOrigin),
27
28
  });
28
29
  }
@@ -36,7 +37,7 @@ function createPerKeyOriginMiddleware({ logger, serverAllowedOrigins, }) {
36
37
  return;
37
38
  }
38
39
  const keyHash = (0, api_key_1.fingerprintApiKey)(ctx.get(api_key_middleware_1.BFF_KEY_HEADER));
39
- reportOriginsThatCanNeverPass(allowedOrigins, keyHash);
40
+ reportOriginsThatCanNeverPass(allowedOrigins, keyHash, identity.renderingId);
40
41
  if ((0, origin_1.hasOrigin)(origin) && !(0, origin_1.originAllowed)(origin, allowedOrigins)) {
41
42
  logger('Warn', 'BFF per-key origin rejected', {
42
43
  origin: (0, origin_1.loggableOrigin)(origin),
@@ -49,4 +50,4 @@ function createPerKeyOriginMiddleware({ logger, serverAllowedOrigins, }) {
49
50
  await next();
50
51
  };
51
52
  }
52
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVyLWtleS1vcmlnaW4uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29ycy9wZXIta2V5LW9yaWdpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQXFCQSwrQ0FnREM7QUFqRUQscUNBQW9FO0FBQ3BFLGdEQUF1RDtBQUN2RCxzRUFBK0Q7QUFDL0QsMkRBQTBEO0FBTzFELG9HQUFvRztBQUNwRyxzR0FBc0c7QUFDdEcsb0dBQW9HO0FBQ3BHLGlHQUFpRztBQUNqRywwRkFBMEY7QUFDMUYsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLENBQUM7QUFFL0IsU0FBd0IsNEJBQTRCLENBQUMsRUFDbkQsTUFBTSxFQUNOLG9CQUFvQixHQUNVO0lBQzlCLE1BQU0sWUFBWSxHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7SUFFdkMsU0FBUyw2QkFBNkIsQ0FBQyxjQUF3QixFQUFFLE9BQWU7UUFDOUUsSUFBSSxvQkFBb0IsQ0FBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLFlBQVksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDO1lBQUUsT0FBTztRQUUzRSxNQUFNLFlBQVksR0FBRyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxJQUFBLHNCQUFhLEVBQUMsS0FBSyxFQUFFLG9CQUFvQixDQUFDLENBQUMsQ0FBQztRQUMvRixJQUFJLFlBQVksQ0FBQyxJQUFJLElBQUksaUJBQWlCO1lBQUUsWUFBWSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2pFLFlBQVksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFMUIsSUFBSSxZQUFZLEVBQUUsQ0FBQztZQUNqQixNQUFNLENBQUMsTUFBTSxFQUFFLHFEQUFxRCxFQUFFO2dCQUNwRSxPQUFPO2dCQUNQLFVBQVUsRUFBRSxjQUFjLENBQUMsR0FBRyxDQUFDLHVCQUFjLENBQUM7YUFDL0MsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztJQUNILENBQUM7SUFFRCxPQUFPLEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxHQUFHLEVBQUUsSUFBSTtRQUNwRCxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLGNBQW9ELENBQUM7UUFDaEYsTUFBTSxjQUFjLEdBQUcsUUFBUSxFQUFFLGNBQWMsSUFBSSxFQUFFLENBQUM7UUFDdEQsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUVqQyxJQUFJLENBQUMsUUFBUSxJQUFJLGNBQWMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDN0MsTUFBTSxJQUFJLEVBQUUsQ0FBQztZQUViLE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxPQUFPLEdBQUcsSUFBQSwyQkFBaUIsRUFBQyxHQUFHLENBQUMsR0FBRyxDQUFDLG1DQUFjLENBQUMsQ0FBQyxDQUFDO1FBQzNELDZCQUE2QixDQUFDLGNBQWMsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUV2RCxJQUFJLElBQUEsa0JBQVMsRUFBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUEsc0JBQWEsRUFBQyxNQUFNLEVBQUUsY0FBYyxDQUFDLEVBQUUsQ0FBQztZQUNoRSxNQUFNLENBQUMsTUFBTSxFQUFFLDZCQUE2QixFQUFFO2dCQUM1QyxNQUFNLEVBQUUsSUFBQSx1QkFBYyxFQUFDLE1BQU0sQ0FBQztnQkFDOUIsSUFBSSxFQUFFLEdBQUcsQ0FBQyxJQUFJO2dCQUNkLE9BQU87Z0JBQ1AsV0FBVyxFQUFFLFFBQVEsQ0FBQyxXQUFXO2FBQ2xDLENBQUMsQ0FBQztZQUVILE1BQU0sSUFBQSxpQ0FBZ0IsR0FBRSxDQUFDO1FBQzNCLENBQUM7UUFFRCxNQUFNLElBQUksRUFBRSxDQUFDO0lBQ2YsQ0FBQyxDQUFDO0FBQ0osQ0FBQyJ9
53
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVyLWtleS1vcmlnaW4uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29ycy9wZXIta2V5LW9yaWdpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQXFCQSwrQ0F1REM7QUF4RUQscUNBQTZGO0FBQzdGLGdEQUF1RDtBQUN2RCxzRUFBK0Q7QUFDL0QsMkRBQTBEO0FBTzFELG9HQUFvRztBQUNwRyxzR0FBc0c7QUFDdEcsb0dBQW9HO0FBQ3BHLGlHQUFpRztBQUNqRywwRkFBMEY7QUFDMUYsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLENBQUM7QUFFL0IsU0FBd0IsNEJBQTRCLENBQUMsRUFDbkQsTUFBTSxFQUNOLG9CQUFvQixHQUNVO0lBQzlCLE1BQU0sWUFBWSxHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7SUFFdkMsU0FBUyw2QkFBNkIsQ0FDcEMsY0FBd0IsRUFDeEIsT0FBZSxFQUNmLFdBQW1CO1FBRW5CLElBQUksb0JBQW9CLENBQUMsTUFBTSxLQUFLLENBQUMsSUFBSSxZQUFZLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQztZQUFFLE9BQU87UUFFM0UsTUFBTSxZQUFZLEdBQUcsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQ2hELG9CQUFvQixDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDLElBQUEsZ0NBQXVCLEVBQUMsS0FBSyxFQUFFLFlBQVksQ0FBQyxDQUFDLENBQ3hGLENBQUM7UUFDRixJQUFJLFlBQVksQ0FBQyxJQUFJLElBQUksaUJBQWlCO1lBQUUsWUFBWSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2pFLFlBQVksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFMUIsSUFBSSxZQUFZLEVBQUUsQ0FBQztZQUNqQixNQUFNLENBQUMsTUFBTSxFQUFFLHFEQUFxRCxFQUFFO2dCQUNwRSxPQUFPO2dCQUNQLFdBQVc7Z0JBQ1gsVUFBVSxFQUFFLGNBQWMsQ0FBQyxHQUFHLENBQUMsdUJBQWMsQ0FBQzthQUMvQyxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU8sS0FBSyxVQUFVLHNCQUFzQixDQUFDLEdBQUcsRUFBRSxJQUFJO1FBQ3BELE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsY0FBb0QsQ0FBQztRQUNoRixNQUFNLGNBQWMsR0FBRyxRQUFRLEVBQUUsY0FBYyxJQUFJLEVBQUUsQ0FBQztRQUN0RCxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRWpDLElBQUksQ0FBQyxRQUFRLElBQUksY0FBYyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUM3QyxNQUFNLElBQUksRUFBRSxDQUFDO1lBRWIsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFBLDJCQUFpQixFQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsbUNBQWMsQ0FBQyxDQUFDLENBQUM7UUFDM0QsNkJBQTZCLENBQUMsY0FBYyxFQUFFLE9BQU8sRUFBRSxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUM7UUFFN0UsSUFBSSxJQUFBLGtCQUFTLEVBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFBLHNCQUFhLEVBQUMsTUFBTSxFQUFFLGNBQWMsQ0FBQyxFQUFFLENBQUM7WUFDaEUsTUFBTSxDQUFDLE1BQU0sRUFBRSw2QkFBNkIsRUFBRTtnQkFDNUMsTUFBTSxFQUFFLElBQUEsdUJBQWMsRUFBQyxNQUFNLENBQUM7Z0JBQzlCLElBQUksRUFBRSxHQUFHLENBQUMsSUFBSTtnQkFDZCxPQUFPO2dCQUNQLFdBQVcsRUFBRSxRQUFRLENBQUMsV0FBVzthQUNsQyxDQUFDLENBQUM7WUFFSCxNQUFNLElBQUEsaUNBQWdCLEdBQUUsQ0FBQztRQUMzQixDQUFDO1FBRUQsTUFBTSxJQUFJLEVBQUUsQ0FBQztJQUNmLENBQUMsQ0FBQztBQUNKLENBQUMifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forestadmin/agent-bff",
3
- "version": "1.30.2",
3
+ "version": "1.31.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "forest-bff": "dist/cli.js"