@forestadmin/agent-bff 1.31.0 → 1.33.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.
Files changed (45) hide show
  1. package/README.md +107 -1
  2. package/dist/action/action-routes-middleware.d.ts +3 -1
  3. package/dist/action/action-routes-middleware.js +48 -13
  4. package/dist/activity-log/activity-log-drainer.d.ts +24 -0
  5. package/dist/activity-log/activity-log-drainer.js +63 -0
  6. package/dist/activity-log/activity-log-writer.d.ts +26 -0
  7. package/dist/activity-log/activity-log-writer.js +23 -0
  8. package/dist/activity-log/activity-logs-creator.d.ts +42 -0
  9. package/dist/activity-log/activity-logs-creator.js +209 -0
  10. package/dist/activity-log/activity-logs-service.d.ts +18 -0
  11. package/dist/activity-log/activity-logs-service.js +19 -0
  12. package/dist/activity-log/with-activity-log.d.ts +25 -0
  13. package/dist/activity-log/with-activity-log.js +63 -0
  14. package/dist/api-key/api-key-authenticator.d.ts +11 -0
  15. package/dist/api-key/api-key-authenticator.js +12 -2
  16. package/dist/api-key/api-key-client.d.ts +6 -0
  17. package/dist/api-key/api-key-client.js +2 -1
  18. package/dist/api-key/api-key-middleware.d.ts +10 -1
  19. package/dist/api-key/api-key-middleware.js +18 -1
  20. package/dist/api-key/resolve-cache.d.ts +12 -0
  21. package/dist/api-key/resolve-cache.js +38 -6
  22. package/dist/auth/auth-mode.d.ts +1 -0
  23. package/dist/auth/auth-mode.js +10 -3
  24. package/dist/auth/forest-server-token-middleware.d.ts +25 -0
  25. package/dist/auth/forest-server-token-middleware.js +90 -0
  26. package/dist/build-bff.d.ts +8 -0
  27. package/dist/build-bff.js +29 -10
  28. package/dist/cli-core.d.ts +9 -0
  29. package/dist/cli-core.js +38 -3
  30. package/dist/data/agent-query.d.ts +12 -0
  31. package/dist/data/agent-query.js +19 -1
  32. package/dist/data/data-routes-middleware.d.ts +3 -1
  33. package/dist/data/data-routes-middleware.js +41 -4
  34. package/dist/http/bff-http-server.d.ts +22 -0
  35. package/dist/http/bff-http-server.js +50 -5
  36. package/dist/http/bff-local-errors.d.ts +15 -0
  37. package/dist/http/bff-local-errors.js +29 -3
  38. package/dist/openapi/openapi-document.js +3 -3
  39. package/dist/tracing-handle.d.ts +26 -0
  40. package/dist/tracing-handle.js +40 -0
  41. package/dist/tracing-preload.d.ts +2 -0
  42. package/dist/tracing-preload.js +27 -0
  43. package/dist/tracing.d.ts +79 -0
  44. package/dist/tracing.js +168 -0
  45. package/package.json +1 -1
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = createPendingActivityLog;
4
+ exports.markActivityLog = markActivityLog;
5
+ const forestadmin_client_1 = require("@forestadmin/forestadmin-client");
6
+ const api_key_middleware_1 = require("../api-key/api-key-middleware");
7
+ const forest_server_token_middleware_1 = require("../auth/forest-server-token-middleware");
8
+ const bff_http_error_1 = require("../http/bff-http-error");
9
+ const bff_local_errors_1 = require("../http/bff-local-errors");
10
+ /**
11
+ * Fail policy for the audit trail, keyed by action type: a write whose activity log cannot be
12
+ * created is blocked (no unaudited side effect), while a read proceeds with a warning (an audit
13
+ * store outage must not take down the read surface).
14
+ *
15
+ * One case is arbitrated by the cause instead of the action type: an authorization refusal (403)
16
+ * propagates for reads too — the read itself is not authorized either.
17
+ */
18
+ const ACTION_TO_TYPE = {
19
+ index: 'read',
20
+ search: 'read',
21
+ filter: 'read',
22
+ listRelatedData: 'read',
23
+ action: 'write',
24
+ };
25
+ const MAX_STATUS_ATTEMPTS = 5;
26
+ const STATUS_RETRY_DELAY_MS = 500;
27
+ const NO_RENDERING_MESSAGE = 'This request carries no usable rendering';
28
+ function describeCause(error) {
29
+ return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
30
+ }
31
+ const FORBIDDEN = 403;
32
+ const UNAUTHORIZED = 401;
33
+ const SERVER_ERROR = 500;
34
+ /** Not found: the document may not be visible yet. 0: unreachable. 408: timeout. 429: throttled. */
35
+ const RETRYABLE_TRANSITION_STATUSES = new Set([0, 404, 408, 429]);
36
+ const AUDIT_ENDPOINT_ABSENT_STATUSES = new Set([404, 501]);
37
+ const NO_AUDIT_ENDPOINT_MESSAGE = 'The Forest server does not expose the endpoint the activity log is written through, so the ' +
38
+ 'operation was not performed';
39
+ /**
40
+ * A 403 only. A 401 is not the caller being refused: the bearer the BFF audits with is minted by
41
+ * the Forest server and cached, so a 401 says that token expired — answering the caller with
42
+ * `audit_not_authorized` would refuse a read the fail-open policy lets through.
43
+ */
44
+ function isAuthorizationRefusal(error) {
45
+ return error instanceof forestadmin_client_1.HttpError && error.status === FORBIDDEN;
46
+ }
47
+ function isExpiredAuditCredential(error) {
48
+ return error instanceof forestadmin_client_1.HttpError && error.status === UNAUTHORIZED;
49
+ }
50
+ /**
51
+ * A Forest server that does not serve the activity-log endpoint at all, rather than one failing to
52
+ * answer it. No retry can make the route appear, so the caller must not be handed a `Retry-After`
53
+ * it would keep honouring on every write.
54
+ */
55
+ function isAuditEndpointAbsent(error) {
56
+ return error instanceof forestadmin_client_1.HttpError && AUDIT_ENDPOINT_ABSENT_STATUSES.has(error.status);
57
+ }
58
+ /**
59
+ * What locates the failure for support, and nothing else: the credential, the record ids and the
60
+ * label are the payload this must never carry.
61
+ */
62
+ function auditIdentifiers(ctx, context) {
63
+ const renderingId = (0, forest_server_token_middleware_1.resolveRenderingId)(ctx);
64
+ return {
65
+ ...(renderingId === undefined ? {} : { renderingId }),
66
+ ...(context?.collectionName === undefined ? {} : { collectionName: context.collectionName }),
67
+ };
68
+ }
69
+ /** An empty string is an answer, not a value: it locates no document, so it fails the guard. */
70
+ function isPresent(value) {
71
+ return value !== null && value !== undefined && value !== '';
72
+ }
73
+ /**
74
+ * The status transition reads both the id and the index, so an answer carrying only an id strands
75
+ * the entry `pending`: the fail-closed policy has to engage here, not asynchronously afterwards.
76
+ */
77
+ function isTransitionable(activityLog) {
78
+ return isPresent(activityLog?.id) && isPresent(activityLog?.attributes?.index);
79
+ }
80
+ /**
81
+ * A credential this deployment never mints is a degradation the Forest server declares by sending
82
+ * none (`api-key/api-key-client.ts`), so it warns; only a resolution that actually failed is an
83
+ * error. The single report for either: `with-activity-log` states nothing of its own, which used to
84
+ * double every line of the read path.
85
+ */
86
+ function reportUnresolvedCredentials({ ctx, action, context, logger, error, }) {
87
+ const identifiers = { ...auditIdentifiers(ctx, context), cause: describeCause(error) };
88
+ if ((0, bff_local_errors_1.isUnretryableAuditFailure)(error)) {
89
+ logger('Warn', `Activity log for '${action}' was not created: this deployment has no credential to write ` +
90
+ 'it with', identifiers);
91
+ return;
92
+ }
93
+ logger('Error', `Activity log for '${action}' has no credentials to be created with`, identifiers);
94
+ }
95
+ async function resolveCredentials(ctx) {
96
+ const renderingId = (0, forest_server_token_middleware_1.resolveRenderingId)(ctx);
97
+ if (renderingId === undefined)
98
+ throw (0, bff_http_error_1.sessionExpired)(NO_RENDERING_MESSAGE);
99
+ return {
100
+ forestServerToken: await (0, forest_server_token_middleware_1.resolveForestServerToken)(ctx),
101
+ renderingId: String(renderingId),
102
+ };
103
+ }
104
+ async function createPendingActivityLog({ ctx, service, action, context, logger, }) {
105
+ const type = ACTION_TO_TYPE[action];
106
+ let credentials;
107
+ try {
108
+ credentials = await resolveCredentials(ctx);
109
+ }
110
+ catch (error) {
111
+ reportUnresolvedCredentials({ ctx, action, context, logger, error });
112
+ if (type === 'write')
113
+ throw error;
114
+ return null;
115
+ }
116
+ const { forestServerToken, renderingId } = credentials;
117
+ let activityLog;
118
+ try {
119
+ activityLog = await service.createMcpActivityLog({
120
+ forestServerToken,
121
+ renderingId,
122
+ action,
123
+ type,
124
+ collectionName: context?.collectionName,
125
+ recordId: context?.recordId,
126
+ recordIds: context?.recordIds,
127
+ label: context?.label,
128
+ });
129
+ }
130
+ catch (error) {
131
+ logger('Error', `Activity log for '${action}' could not be created`, {
132
+ ...auditIdentifiers(ctx, context),
133
+ cause: describeCause(error),
134
+ });
135
+ if (isAuthorizationRefusal(error))
136
+ throw (0, bff_local_errors_1.auditNotAuthorized)();
137
+ if (isExpiredAuditCredential(error))
138
+ (0, api_key_middleware_1.invalidateApiKeyIdentity)(ctx);
139
+ if (type === 'write') {
140
+ throw isAuditEndpointAbsent(error)
141
+ ? (0, bff_local_errors_1.auditUnavailable)(undefined, NO_AUDIT_ENDPOINT_MESSAGE)
142
+ : (0, bff_local_errors_1.auditUnavailable)(bff_local_errors_1.AUDIT_RETRY_AFTER_SECONDS);
143
+ }
144
+ return null;
145
+ }
146
+ if (!isTransitionable(activityLog)) {
147
+ logger('Error', `Activity log for '${action}' could not be created: the server answered with no activity ` +
148
+ 'log id or index, so the audit store dropped the write', auditIdentifiers(ctx, context));
149
+ if (type === 'write')
150
+ throw (0, bff_local_errors_1.auditUnavailable)(bff_local_errors_1.AUDIT_RETRY_AFTER_SECONDS);
151
+ return null;
152
+ }
153
+ return { activityLog, forestServerToken };
154
+ }
155
+ /**
156
+ * The transition is fired after the response, so nothing retries it downstream: an entry whose
157
+ * transition is dropped stays `pending` for good and skews the action-failure statistics. Retried
158
+ * are the failures a later attempt can land — the document not existing yet, a timeout, a throttle,
159
+ * an unreachable server (status 0) and any 5xx — plus a transport failure the client rethrows raw,
160
+ * which carries no status at all. A refusal or a malformed request is left to fail at once.
161
+ */
162
+ function isRetryableTransitionFailure(error) {
163
+ if (!(error instanceof forestadmin_client_1.HttpError))
164
+ return true;
165
+ return error.status >= SERVER_ERROR || RETRYABLE_TRANSITION_STATUSES.has(error.status);
166
+ }
167
+ async function updateStatus(options, attempt = 1) {
168
+ const { service, pending, status, logger } = options;
169
+ try {
170
+ await service.updateActivityLogStatus({
171
+ forestServerToken: pending.forestServerToken,
172
+ activityLog: pending.activityLog,
173
+ status,
174
+ });
175
+ }
176
+ catch (error) {
177
+ if (isRetryableTransitionFailure(error) && attempt < MAX_STATUS_ATTEMPTS) {
178
+ logger('Debug', `Activity log status transition failed, retrying it`, {
179
+ attempt,
180
+ attempts: MAX_STATUS_ATTEMPTS,
181
+ cause: describeCause(error),
182
+ });
183
+ await new Promise(resolve => {
184
+ // Unreferenced: a pending retry must not outlive the shutdown grace the drainer enforces.
185
+ setTimeout(resolve, STATUS_RETRY_DELAY_MS).unref();
186
+ });
187
+ await updateStatus(options, attempt + 1);
188
+ return;
189
+ }
190
+ throw error;
191
+ }
192
+ }
193
+ /**
194
+ * Fire-and-forget on purpose: the caller's response must not wait for the audit store. The drainer
195
+ * holds the promise so a shutdown can wait for it instead.
196
+ */
197
+ function markActivityLog(options) {
198
+ const { drainer, status, logger } = options;
199
+ drainer
200
+ .track(() => updateStatus(options), `'${status}' transition of the activity log ${options.pending.activityLog.id}`)
201
+ .catch(error => {
202
+ logger('Error', `Failed to mark the activity log as '${status}'`, {
203
+ activityLogId: options.pending.activityLog.id,
204
+ index: options.pending.activityLog.attributes?.index,
205
+ cause: describeCause(error),
206
+ });
207
+ });
208
+ }
209
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aXZpdHktbG9ncy1jcmVhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGl2aXR5LWxvZy9hY3Rpdml0eS1sb2dzLWNyZWF0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF1TUEsMkNBb0VDO0FBMERELDBDQWVDO0FBMVVELHdFQUE0RDtBQUU1RCxzRUFBeUU7QUFDekUsMkZBR2dEO0FBQ2hELDJEQUF3RDtBQUN4RCwrREFLa0M7QUFRbEM7Ozs7Ozs7R0FPRztBQUNILE1BQU0sY0FBYyxHQUFrRDtJQUNwRSxLQUFLLEVBQUUsTUFBTTtJQUNiLE1BQU0sRUFBRSxNQUFNO0lBQ2QsTUFBTSxFQUFFLE1BQU07SUFDZCxlQUFlLEVBQUUsTUFBTTtJQUN2QixNQUFNLEVBQUUsT0FBTztDQUNoQixDQUFDO0FBRUYsTUFBTSxtQkFBbUIsR0FBRyxDQUFDLENBQUM7QUFDOUIsTUFBTSxxQkFBcUIsR0FBRyxHQUFHLENBQUM7QUFFbEMsTUFBTSxvQkFBb0IsR0FBRywwQ0FBMEMsQ0FBQztBQStCeEUsU0FBUyxhQUFhLENBQUMsS0FBYztJQUNuQyxPQUFPLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksS0FBSyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUNwRixDQUFDO0FBRUQsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDO0FBQ3RCLE1BQU0sWUFBWSxHQUFHLEdBQUcsQ0FBQztBQUN6QixNQUFNLFlBQVksR0FBRyxHQUFHLENBQUM7QUFDekIsb0dBQW9HO0FBQ3BHLE1BQU0sNkJBQTZCLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQ2xFLE1BQU0sOEJBQThCLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxHQUFH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
@@ -0,0 +1,18 @@
1
+ import type { ActivityLogResponse, CreateActivityLogParams, UpdateActivityLogStatusParams } from '@forestadmin/forestadmin-client';
2
+ export declare const APPLICATION_SOURCE_HEADER = "Forest-Application-Source";
3
+ export declare const BFF_APPLICATION_SOURCE = "BFF";
4
+ /**
5
+ * The slice of `ActivityLogsService` the BFF uses. Named so a fake can stand in for the two calls
6
+ * without carrying the rest of the Forest client.
7
+ */
8
+ export interface ActivityLogsWriter {
9
+ createMcpActivityLog(params: CreateActivityLogParams): Promise<ActivityLogResponse>;
10
+ updateActivityLogStatus(params: UpdateActivityLogStatusParams): Promise<void>;
11
+ }
12
+ /**
13
+ * Its own instance rather than the client `oauth/forest-server-client.ts` already holds:
14
+ * `ForestAdminClientOptions` carries no `headers`, so that one cannot tell the server which channel
15
+ * wrote the log.
16
+ */
17
+ export default function createBffActivityLogsService(forestServerUrl: string): ActivityLogsWriter;
18
+ //# sourceMappingURL=activity-logs-service.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BFF_APPLICATION_SOURCE = exports.APPLICATION_SOURCE_HEADER = void 0;
4
+ exports.default = createBffActivityLogsService;
5
+ const forestadmin_client_1 = require("@forestadmin/forestadmin-client");
6
+ exports.APPLICATION_SOURCE_HEADER = 'Forest-Application-Source';
7
+ exports.BFF_APPLICATION_SOURCE = 'BFF';
8
+ /**
9
+ * Its own instance rather than the client `oauth/forest-server-client.ts` already holds:
10
+ * `ForestAdminClientOptions` carries no `headers`, so that one cannot tell the server which channel
11
+ * wrote the log.
12
+ */
13
+ function createBffActivityLogsService(forestServerUrl) {
14
+ return new forestadmin_client_1.ActivityLogsService(new forestadmin_client_1.ForestHttpApi(), {
15
+ forestServerUrl,
16
+ headers: { [exports.APPLICATION_SOURCE_HEADER]: exports.BFF_APPLICATION_SOURCE },
17
+ });
18
+ }
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWN0aXZpdHktbG9ncy1zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FjdGl2aXR5LWxvZy9hY3Rpdml0eS1sb2dzLXNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBeUJBLCtDQUtDO0FBeEJELHdFQUFxRjtBQUV4RSxRQUFBLHlCQUF5QixHQUFHLDJCQUEyQixDQUFDO0FBQ3hELFFBQUEsc0JBQXNCLEdBQUcsS0FBSyxDQUFDO0FBVzVDOzs7O0dBSUc7QUFDSCxTQUF3Qiw0QkFBNEIsQ0FBQyxlQUF1QjtJQUMxRSxPQUFPLElBQUksd0NBQW1CLENBQUMsSUFBSSxrQ0FBYSxFQUFFLEVBQUU7UUFDbEQsZUFBZTtRQUNmLE9BQU8sRUFBRSxFQUFFLENBQUMsaUNBQXlCLENBQUMsRUFBRSw4QkFBc0IsRUFBRTtLQUNqRSxDQUFDLENBQUM7QUFDTCxDQUFDIn0=
@@ -0,0 +1,25 @@
1
+ import type ActivityLogDrainer from './activity-log-drainer';
2
+ import type { ActivityLogContext, BffActivityLogAction } from './activity-logs-creator';
3
+ import type { ActivityLogsWriter } from './activity-logs-service';
4
+ import type { Logger } from '../ports/logger-port';
5
+ import type { Context } from 'koa';
6
+ export interface WithActivityLogOptions<T> {
7
+ ctx: Context;
8
+ service: ActivityLogsWriter;
9
+ drainer: ActivityLogDrainer;
10
+ action: BffActivityLogAction;
11
+ context?: ActivityLogContext;
12
+ logger: Logger;
13
+ operation: () => Promise<T>;
14
+ /**
15
+ * Errors the log records as `completed` rather than `failed` — the operation reached a business
16
+ * outcome the BFF answers with an error status.
17
+ */
18
+ isCompletedDespite?: (error: unknown) => boolean;
19
+ }
20
+ /**
21
+ * Runs an operation under an activity log: the pending log is awaited before the operation starts,
22
+ * so nothing runs unaudited, and the status transition is fired without `await` afterwards.
23
+ */
24
+ export default function withActivityLog<T>(options: WithActivityLogOptions<T>): Promise<T>;
25
+ //# sourceMappingURL=with-activity-log.d.ts.map
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.default = withActivityLog;
37
+ const activity_logs_creator_1 = __importStar(require("./activity-logs-creator"));
38
+ const COMPLETED = 'completed';
39
+ const FAILED = 'failed';
40
+ /**
41
+ * Runs an operation under an activity log: the pending log is awaited before the operation starts,
42
+ * so nothing runs unaudited, and the status transition is fired without `await` afterwards.
43
+ */
44
+ async function withActivityLog(options) {
45
+ const { ctx, service, drainer, action, context, logger, operation, isCompletedDespite } = options;
46
+ // No log line of its own when nothing came back: the creator reports the case it hit, with the
47
+ // rendering and the collection this one could not name. A read proceeds unaudited from here.
48
+ const pending = await (0, activity_logs_creator_1.default)({ ctx, service, action, context, logger });
49
+ try {
50
+ const result = await operation();
51
+ if (pending)
52
+ (0, activity_logs_creator_1.markActivityLog)({ service, drainer, pending, status: COMPLETED, logger });
53
+ return result;
54
+ }
55
+ catch (error) {
56
+ if (pending) {
57
+ const status = isCompletedDespite?.(error) ? COMPLETED : FAILED;
58
+ (0, activity_logs_creator_1.markActivityLog)({ service, drainer, pending, status, logger });
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2l0aC1hY3Rpdml0eS1sb2cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYWN0aXZpdHktbG9nL3dpdGgtYWN0aXZpdHktbG9nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBOEJBLGtDQXFCQztBQTdDRCxpRkFBb0Y7QUFFcEYsTUFBTSxTQUFTLEdBQUcsV0FBVyxDQUFDO0FBQzlCLE1BQU0sTUFBTSxHQUFHLFFBQVEsQ0FBQztBQWlCeEI7OztHQUdHO0FBQ1ksS0FBSyxVQUFVLGVBQWUsQ0FBSSxPQUFrQztJQUNqRixNQUFNLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLGtCQUFrQixFQUFFLEdBQUcsT0FBTyxDQUFDO0lBRWxHLCtGQUErRjtJQUMvRiw2RkFBNkY7SUFDN0YsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFBLCtCQUF3QixFQUFDLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFFMUYsSUFBSSxDQUFDO1FBQ0gsTUFBTSxNQUFNLEdBQUcsTUFBTSxTQUFTLEVBQUUsQ0FBQztRQUVqQyxJQUFJLE9BQU87WUFBRSxJQUFBLHVDQUFlLEVBQUMsRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFFdkYsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQ1osTUFBTSxNQUFNLEdBQUcsa0JBQWtCLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUM7WUFDaEUsSUFBQSx1Q0FBZSxFQUFDLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUVELE1BQU0sS0FBSyxDQUFDO0lBQ2QsQ0FBQztBQUNILENBQUMifQ==
@@ -9,9 +9,20 @@ export interface ApiKeyAuthenticatorOptions {
9
9
  export interface AuthenticatedApiKey {
10
10
  agentToken: string;
11
11
  identity: ResolvedApiKeyIdentity;
12
+ /** The Forest server token the resolve response carried, cached with the identity. */
13
+ forestServerToken?: string;
12
14
  }
13
15
  export interface ApiKeyAuthenticator {
14
16
  authenticate(rawKey: string): Promise<AuthenticatedApiKey>;
17
+ /**
18
+ * Forgets what a key resolved to, so the next request resolves it against the Forest server
19
+ * again. The resolution carries a short-lived server token the BFF caches with the identity:
20
+ * once that token is refused, the whole entry has to go.
21
+ *
22
+ * `credential` is the refused token, which tells a repeat refusal of the same one from the
23
+ * refusal of the token the previous invalidation went and fetched.
24
+ */
25
+ invalidate(rawKey: string, credential?: string): void;
15
26
  }
16
27
  export default function createApiKeyAuthenticator({ client, cache, authSecret, }: ApiKeyAuthenticatorOptions): ApiKeyAuthenticator;
17
28
  //# sourceMappingURL=api-key-authenticator.d.ts.map
@@ -25,7 +25,11 @@ function mapResolveError(error) {
25
25
  }
26
26
  function createApiKeyAuthenticator({ client, cache, authSecret, }) {
27
27
  function mint(identity) {
28
- return { agentToken: (0, agent_token_1.issueAgentToken)({ identity, authSecret }), identity };
28
+ return {
29
+ agentToken: (0, agent_token_1.issueAgentToken)({ identity, authSecret }),
30
+ identity,
31
+ forestServerToken: identity.saasAccessToken,
32
+ };
29
33
  }
30
34
  return {
31
35
  async authenticate(rawKey) {
@@ -56,6 +60,12 @@ function createApiKeyAuthenticator({ client, cache, authSecret, }) {
56
60
  cache.setPositive(hash, identity);
57
61
  return authenticated;
58
62
  },
63
+ invalidate(rawKey, credential) {
64
+ const parsed = (0, api_key_1.parseApiKey)(rawKey);
65
+ if (!parsed)
66
+ return;
67
+ cache.invalidate((0, api_key_1.hashApiKey)(parsed.keyId, parsed.secret), credential);
68
+ },
59
69
  };
60
70
  }
61
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1hdXRoZW50aWNhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1hdXRoZW50aWNhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBa0RBLDRDQTRDQztBQXpGRCwrQ0FBZ0Q7QUFDaEQsdUNBQW9EO0FBQ3BELHFEQUFzRDtBQUN0RCxtREFLeUI7QUFFekIsTUFBTSwrQkFBK0IsR0FBRyxDQUFDLENBQUM7QUFDMUMsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBaUJwRCxTQUFTLGVBQWUsQ0FBQyxLQUF5QjtJQUNoRCxJQUFJLEtBQUssQ0FBQyxXQUFXO1FBQUUsT0FBTyxJQUFBLHdDQUF3QixFQUFDLCtCQUErQixDQUFDLENBQUM7SUFFeEYsUUFBUSxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDckIsS0FBSyxHQUFHO1lBQ04sT0FBTyxJQUFBLDZCQUFhLEdBQUUsQ0FBQztRQUN6QixLQUFLLEdBQUc7WUFDTixPQUFPLElBQUEsd0NBQXdCLEdBQUUsQ0FBQztRQUNwQyxLQUFLLEdBQUc7WUFDTixPQUFPLElBQUEsOEJBQWMsR0FBRSxDQUFDO1FBQzFCLEtBQUssR0FBRztZQUNOLE9BQU8sSUFBQSx3Q0FBd0IsRUFBQyxLQUFLLENBQUMsVUFBVSxJQUFJLCtCQUErQixDQUFDLENBQUM7UUFDdkY7WUFDRSxPQUFPLElBQUEsd0NBQXdCLEVBQUMsK0JBQStCLENBQUMsQ0FBQztJQUNyRSxDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQXdCLHlCQUF5QixDQUFDLEVBQ2hELE1BQU0sRUFDTixLQUFLLEVBQ0wsVUFBVSxHQUNpQjtJQUMzQixTQUFTLElBQUksQ0FBQyxRQUFnQztRQUM1QyxPQUFPLEVBQUUsVUFBVSxFQUFFLElBQUEsNkJBQWUsRUFBQyxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsQ0FBQyxFQUFFLFFBQVEsRUFBRSxDQUFDO0lBQzdFLENBQUM7SUFFRCxPQUFPO1FBQ0wsS0FBSyxDQUFDLFlBQVksQ0FBQyxNQUFNO1lBQ3ZCLE1BQU0sTUFBTSxHQUFHLElBQUEscUJBQVcsRUFBQyxNQUFNLENBQUMsQ0FBQztZQUVuQyxJQUFJLENBQUMsTUFBTTtnQkFBRSxNQUFNLElBQUEsNkJBQWEsR0FBRSxDQUFDO1lBRW5DLE1BQU0sSUFBSSxHQUFHLElBQUEsb0JBQVUsRUFBQyxNQUFNLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUVyRCxNQUFNLGNBQWMsR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQy9DLElBQUksY0FBYztnQkFBRSxPQUFPLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUVoRCxNQUFNLFdBQVcsR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzVDLElBQUksV0FBVztnQkFBRSxNQUFNLFdBQVcsQ0FBQztZQUVuQyxJQUFJLFFBQWdDLENBQUM7WUFFckMsSUFBSSxDQUFDO2dCQUNILFFBQVEsR0FBRyxNQUFNLE1BQU0sQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDaEQsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2YsSUFBSSxLQUFLLFlBQVksbUNBQWtCLEVBQUUsQ0FBQztvQkFDeEMsTUFBTSxNQUFNLEdBQUcsZUFBZSxDQUFDLEtBQUssQ0FBQyxDQUFDO29CQUN0QyxJQUFJLHVCQUF1QixDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDO3dCQUFFLEtBQUssQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDO29CQUVoRixNQUFNLE1BQU0sQ0FBQztnQkFDZixDQUFDO2dCQUVELE1BQU0sS0FBSyxDQUFDO1lBQ2QsQ0FBQztZQUVELE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUNyQyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztZQUVsQyxPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO0tBQ0YsQ0FBQztBQUNKLENBQUMifQ==
71
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1hdXRoZW50aWNhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1hdXRoZW50aWNhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBNkRBLDRDQXdEQztBQWhIRCwrQ0FBZ0Q7QUFDaEQsdUNBQW9EO0FBQ3BELHFEQUFzRDtBQUN0RCxtREFLeUI7QUFFekIsTUFBTSwrQkFBK0IsR0FBRyxDQUFDLENBQUM7QUFDMUMsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBNEJwRCxTQUFTLGVBQWUsQ0FBQyxLQUF5QjtJQUNoRCxJQUFJLEtBQUssQ0FBQyxXQUFXO1FBQUUsT0FBTyxJQUFBLHdDQUF3QixFQUFDLCtCQUErQixDQUFDLENBQUM7SUFFeEYsUUFBUSxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDckIsS0FBSyxHQUFHO1lBQ04sT0FBTyxJQUFBLDZCQUFhLEdBQUUsQ0FBQztRQUN6QixLQUFLLEdBQUc7WUFDTixPQUFPLElBQUEsd0NBQXdCLEdBQUUsQ0FBQztRQUNwQyxLQUFLLEdBQUc7WUFDTixPQUFPLElBQUEsOEJBQWMsR0FBRSxDQUFDO1FBQzFCLEtBQUssR0FBRztZQUNOLE9BQU8sSUFBQSx3Q0FBd0IsRUFBQyxLQUFLLENBQUMsVUFBVSxJQUFJLCtCQUErQixDQUFDLENBQUM7UUFDdkY7WUFDRSxPQUFPLElBQUEsd0NBQXdCLEVBQUMsK0JBQStCLENBQUMsQ0FBQztJQUNyRSxDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQXdCLHlCQUF5QixDQUFDLEVBQ2hELE1BQU0sRUFDTixLQUFLLEVBQ0wsVUFBVSxHQUNpQjtJQUMzQixTQUFTLElBQUksQ0FBQyxRQUFnQztRQUM1QyxPQUFPO1lBQ0wsVUFBVSxFQUFFLElBQUEsNkJBQWUsRUFBQyxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsQ0FBQztZQUNyRCxRQUFRO1lBQ1IsaUJBQWlCLEVBQUUsUUFBUSxDQUFDLGVBQWU7U0FDNUMsQ0FBQztJQUNKLENBQUM7SUFFRCxPQUFPO1FBQ0wsS0FBSyxDQUFDLFlBQVksQ0FBQyxNQUFNO1lBQ3ZCLE1BQU0sTUFBTSxHQUFHLElBQUEscUJBQVcsRUFBQyxNQUFNLENBQUMsQ0FBQztZQUVuQyxJQUFJLENBQUMsTUFBTTtnQkFBRSxNQUFNLElBQUEsNkJBQWEsR0FBRSxDQUFDO1lBRW5DLE1BQU0sSUFBSSxHQUFHLElBQUEsb0JBQVUsRUFBQyxNQUFNLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUVyRCxNQUFNLGNBQWMsR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQy9DLElBQUksY0FBYztnQkFBRSxPQUFPLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUVoRCxNQUFNLFdBQVcsR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzVDLElBQUksV0FBVztnQkFBRSxNQUFNLFdBQVcsQ0FBQztZQUVuQyxJQUFJLFFBQWdDLENBQUM7WUFFckMsSUFBSSxDQUFDO2dCQUNILFFBQVEsR0FBRyxNQUFNLE1BQU0sQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDaEQsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2YsSUFBSSxLQUFLLFlBQVksbUNBQWtCLEVBQUUsQ0FBQztvQkFDeEMsTUFBTSxNQUFNLEdBQUcsZUFBZSxDQUFDLEtBQUssQ0FBQyxDQUFDO29CQUN0QyxJQUFJLHVCQUF1QixDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDO3dCQUFFLEtBQUssQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDO29CQUVoRixNQUFNLE1BQU0sQ0FBQztnQkFDZixDQUFDO2dCQUVELE1BQU0sS0FBSyxDQUFDO1lBQ2QsQ0FBQztZQUVELE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUNyQyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztZQUVsQyxPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO1FBRUQsVUFBVSxDQUFDLE1BQU0sRUFBRSxVQUFVO1lBQzNCLE1BQU0sTUFBTSxHQUFHLElBQUEscUJBQVcsRUFBQyxNQUFNLENBQUMsQ0FBQztZQUVuQyxJQUFJLENBQUMsTUFBTTtnQkFBRSxPQUFPO1lBRXBCLEtBQUssQ0FBQyxVQUFVLENBQUMsSUFBQSxvQkFBVSxFQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ3hFLENBQUM7S0FDRixDQUFDO0FBQ0osQ0FBQyJ9
@@ -16,6 +16,12 @@ export interface ResolvedApiKeyIdentity {
16
16
  user: ApiKeyIdentityUser;
17
17
  renderingId: number;
18
18
  allowedOrigins: string[];
19
+ /**
20
+ * Short-lived, user-scoped Forest server token, used to write the activity log. Optional so a
21
+ * Forest server that does not send one yet still resolves keys: the audit trail then degrades on
22
+ * its own terms (a read proceeds unaudited, a write is blocked) instead of taking auth down.
23
+ */
24
+ saasAccessToken?: string;
19
25
  }
20
26
  export interface ApiKeyClientOptions {
21
27
  forestServerUrl: string;
@@ -56,6 +56,7 @@ class ApiKeyClient {
56
56
  return (typeof candidate.renderingId === 'number' &&
57
57
  Array.isArray(candidate.allowedOrigins) &&
58
58
  candidate.allowedOrigins.every(entry => typeof entry === 'string') &&
59
+ (candidate.saasAccessToken === undefined || typeof candidate.saasAccessToken === 'string') &&
59
60
  ApiKeyClient.isIdentityUser(candidate.user));
60
61
  }
61
62
  // Every field the agent-token mint reads must be present, so a partial body fails here
@@ -83,4 +84,4 @@ class ApiKeyClient {
83
84
  }
84
85
  }
85
86
  exports.default = ApiKeyClient;
86
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1jbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpLWtleS9hcGkta2V5LWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxtRUFBNkQ7QUFFN0QsaUVBQTZEO0FBQXBELDJIQUFBLGtCQUFrQixPQUFBO0FBd0IzQixNQUFNLGVBQWUsR0FBRyxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBVyxDQUFDO0FBQ3hFLE1BQU0sa0JBQWtCLEdBQUcsS0FBTSxDQUFDO0FBQ2xDLE1BQU0sWUFBWSxHQUFHLGdDQUFnQyxDQUFDO0FBTXRELE1BQXFCLFlBQVk7SUFJL0IsWUFBWSxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQXVCO1FBQzdELElBQUksQ0FBQyxlQUFlLEdBQUcsZUFBZSxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO0lBQzdCLENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLFNBR25CO1FBQ0MsSUFBSSxRQUFrQixDQUFDO1FBRXZCLElBQUksQ0FBQztZQUNILFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxFQUFFO2dCQUM3QyxNQUFNLEVBQUUsTUFBTTtnQkFDZCxPQUFPLEVBQUUsRUFBRSxHQUFHLGVBQWUsRUFBRSxtQkFBbUIsRUFBRSxJQUFJLENBQUMsU0FBUyxFQUFFO2dCQUNwRSxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxTQUFTLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQzFFLE1BQU0sRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDO2FBQ2hELENBQUMsQ0FBQztRQUNMLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxNQUFNLElBQUksMENBQWtCLENBQUMsRUFBRSxXQUFXLEVBQUUs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
87
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1jbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpLWtleS9hcGkta2V5LWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxtRUFBNkQ7QUFFN0QsaUVBQTZEO0FBQXBELDJIQUFBLGtCQUFrQixPQUFBO0FBOEIzQixNQUFNLGVBQWUsR0FBRyxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBVyxDQUFDO0FBQ3hFLE1BQU0sa0JBQWtCLEdBQUcsS0FBTSxDQUFDO0FBQ2xDLE1BQU0sWUFBWSxHQUFHLGdDQUFnQyxDQUFDO0FBTXRELE1BQXFCLFlBQVk7SUFJL0IsWUFBWSxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQXVCO1FBQzdELElBQUksQ0FBQyxlQUFlLEdBQUcsZUFBZSxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO0lBQzdCLENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLFNBR25CO1FBQ0MsSUFBSSxRQUFrQixDQUFDO1FBRXZCLElBQUksQ0FBQztZQUNILFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxFQUFFO2dCQUM3QyxNQUFNLEVBQUUsTUFBTTtnQkFDZCxPQUFPLEVBQUUsRUFBRSxHQUFHLGVBQWUsRUFBRSxtQkFBbUIsRUFBRSxJQUFJLENBQUMsU0FBUyxFQUFFO2dCQUNwRSxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxTQUFTLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQzFFLE1BQU0sRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDO2FBQ2hELENBQUMsQ0FBQztRQUNMLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxNQUFNLElBQUksMENBQWtCLENBQUMsRUFBRSxXQUFXLEVBQUUs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
@@ -1,10 +1,19 @@
1
1
  import type { ApiKeyAuthenticator } from './api-key-authenticator';
2
2
  import type { Logger } from '../ports/logger-port';
3
- import type { Middleware } from 'koa';
3
+ import type { Context, Middleware } from 'koa';
4
4
  export declare const BFF_KEY_HEADER = "X-Forest-Bff-Key";
5
+ export type ApiKeyIdentityInvalidator = () => void;
5
6
  export interface ApiKeyMiddlewareOptions {
6
7
  authenticator: ApiKeyAuthenticator;
7
8
  logger: Logger;
8
9
  }
9
10
  export default function createApiKeyMiddleware({ authenticator, logger, }: ApiKeyMiddlewareOptions): Middleware;
11
+ /**
12
+ * Forgets the identity this request was authenticated with. Called when the Forest server refuses
13
+ * the token that came with it: the token is cached with the identity, so the next request must
14
+ * resolve the key again instead of replaying the refused one for the rest of the cache window.
15
+ *
16
+ * A no-op outside api-key mode — nothing else lands an invalidator.
17
+ */
18
+ export declare function invalidateApiKeyIdentity(ctx: Context): void;
10
19
  //# sourceMappingURL=api-key-middleware.d.ts.map
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BFF_KEY_HEADER = void 0;
4
4
  exports.default = createApiKeyMiddleware;
5
+ exports.invalidateApiKeyIdentity = invalidateApiKeyIdentity;
5
6
  const api_key_1 = require("./api-key");
6
7
  const api_key_error_1 = require("./api-key-error");
7
8
  exports.BFF_KEY_HEADER = 'X-Forest-Bff-Key';
@@ -34,8 +35,13 @@ function createApiKeyMiddleware({ authenticator, logger, }) {
34
35
  }
35
36
  throw error;
36
37
  }
38
+ // Bound to the token this request was authenticated with: the cache uses it to tell a repeat
39
+ // refusal of the same credential from the refusal of the one it just went and fetched.
40
+ const invalidateIdentity = () => authenticator.invalidate(rawKey, authenticated.forestServerToken);
41
+ ctx.state.invalidateApiKeyIdentity = invalidateIdentity;
37
42
  ctx.state.agentToken = authenticated.agentToken;
38
43
  ctx.state.apiKeyIdentity = authenticated.identity;
44
+ ctx.state.forestServerToken = authenticated.forestServerToken;
39
45
  ctx.set('Cache-Control', 'no-store');
40
46
  logger('Info', 'Resolved BFF API key', {
41
47
  keyHash: (0, api_key_1.fingerprintApiKey)(rawKey),
@@ -44,4 +50,15 @@ function createApiKeyMiddleware({ authenticator, logger, }) {
44
50
  await next();
45
51
  };
46
52
  }
47
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQWlCQSx5Q0EyQ0M7QUF4REQsdUNBQThDO0FBQzlDLG1EQUE4QztBQUVqQyxRQUFBLGNBQWMsR0FBRyxrQkFBa0IsQ0FBQztBQU9qRCwrRUFBK0U7QUFDL0UsNkVBQTZFO0FBQzdFLHFFQUFxRTtBQUNyRSxTQUF3QixzQkFBc0IsQ0FBQyxFQUM3QyxhQUFhLEVBQ2IsTUFBTSxHQUNrQjtJQUN4QixPQUFPLEtBQUssVUFBVSxnQkFBZ0IsQ0FBQyxHQUFHLEVBQUUsSUFBSTtRQUM5QyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLHNCQUFjLENBQUMsQ0FBQztRQUV2QyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixNQUFNLElBQUksRUFBRSxDQUFDO1lBRWIsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLGFBQWtDLENBQUM7UUFFdkMsSUFBSSxDQUFDO1lBQ0gsYUFBYSxHQUFHLE1BQU0sYUFBYSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMzRCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksS0FBSyxZQUFZLDJCQUFXLEVBQUUsQ0FBQztnQkFDakMsTUFBTSxDQUFDLE1BQU0sRUFBRSxzQkFBc0IsRUFBRTtvQkFDckMsT0FBTyxFQUFFLElBQUEsMkJBQWlCLEVBQUMsTUFBTSxDQUFDO29CQUNsQyxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUk7aUJBQ2pCLENBQUMsQ0FBQztZQUNMLENBQUM7aUJBQU0sQ0FBQztnQkFDTixNQUFNLENBQUMsT0FBTyxFQUFFLGdDQUFnQyxFQUFFO29CQUNoRCxPQUFPLEVBQUUsSUFBQSwyQkFBaUIsRUFBQyxNQUFNLENBQUM7b0JBQ2xDLEtBQUssRUFBRSxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLEtBQUssQ0FBQyxJQUFJLEtBQUssS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDO2lCQUNsRixDQUFDLENBQUM7WUFDTCxDQUFDO1lBRUQsTUFBTSxLQUFLLENBQUM7UUFDZCxDQUFDO1FBRUQsR0FBRyxDQUFDLEtBQUssQ0FBQyxVQUFVLEdBQUcsYUFBYSxDQUFDLFVBQVUsQ0FBQztRQUNoRCxHQUFHLENBQUMsS0FBSyxDQUFDLGNBQWMsR0FBRyxhQUFhLENBQUMsUUFBUSxDQUFDO1FBQ2xELEdBQUcsQ0FBQyxHQUFHLENBQUMsZUFBZSxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ3JDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsc0JBQXNCLEVBQUU7WUFDckMsT0FBTyxFQUFFLElBQUEsMkJBQWlCLEVBQUMsTUFBTSxDQUFDO1lBQ2xDLFdBQVcsRUFBRSxhQUFhLENBQUMsUUFBUSxDQUFDLFdBQVc7U0FDaEQsQ0FBQyxDQUFDO1FBRUgsTUFBTSxJQUFJLEVBQUUsQ0FBQztJQUNmLENBQUMsQ0FBQztBQUNKLENBQUMifQ==
53
+ /**
54
+ * Forgets the identity this request was authenticated with. Called when the Forest server refuses
55
+ * the token that came with it: the token is cached with the identity, so the next request must
56
+ * resolve the key again instead of replaying the refused one for the rest of the cache window.
57
+ *
58
+ * A no-op outside api-key mode — nothing else lands an invalidator.
59
+ */
60
+ function invalidateApiKeyIdentity(ctx) {
61
+ const invalidate = ctx.state.invalidateApiKeyIdentity;
62
+ invalidate?.();
63
+ }
64
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQW1CQSx5Q0FrREM7QUFTRCw0REFJQztBQTlFRCx1Q0FBOEM7QUFDOUMsbURBQThDO0FBRWpDLFFBQUEsY0FBYyxHQUFHLGtCQUFrQixDQUFDO0FBU2pELCtFQUErRTtBQUMvRSw2RUFBNkU7QUFDN0UscUVBQXFFO0FBQ3JFLFNBQXdCLHNCQUFzQixDQUFDLEVBQzdDLGFBQWEsRUFDYixNQUFNLEdBQ2tCO0lBQ3hCLE9BQU8sS0FBSyxVQUFVLGdCQUFnQixDQUFDLEdBQUcsRUFBRSxJQUFJO1FBQzlDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsc0JBQWMsQ0FBQyxDQUFDO1FBRXZDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNaLE1BQU0sSUFBSSxFQUFFLENBQUM7WUFFYixPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksYUFBa0MsQ0FBQztRQUV2QyxJQUFJLENBQUM7WUFDSCxhQUFhLEdBQUcsTUFBTSxhQUFhLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzNELENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxLQUFLLFlBQVksMkJBQVcsRUFBRSxDQUFDO2dCQUNqQyxNQUFNLENBQUMsTUFBTSxFQUFFLHNCQUFzQixFQUFFO29CQUNyQyxPQUFPLEVBQUUsSUFBQSwyQkFBaUIsRUFBQyxNQUFNLENBQUM7b0JBQ2xDLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSTtpQkFDakIsQ0FBQyxDQUFDO1lBQ0wsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLE1BQU0sQ0FBQyxPQUFPLEVBQUUsZ0NBQWdDLEVBQUU7b0JBQ2hELE9BQU8sRUFBRSxJQUFBLDJCQUFpQixFQUFDLE1BQU0sQ0FBQztvQkFDbEMsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksS0FBSyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7aUJBQ2xGLENBQUMsQ0FBQztZQUNMLENBQUM7WUFFRCxNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7UUFFRCw2RkFBNkY7UUFDN0YsdUZBQXVGO1FBQ3ZGLE1BQU0sa0JBQWtCLEdBQThCLEdBQUcsRUFBRSxDQUN6RCxhQUFhLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxhQUFhLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUVwRSxHQUFHLENBQUMsS0FBSyxDQUFDLHdCQUF3QixHQUFHLGtCQUFrQixDQUFDO1FBQ3hELEdBQUcsQ0FBQyxLQUFLLENBQUMsVUFBVSxHQUFHLGFBQWEsQ0FBQyxVQUFVLENBQUM7UUFDaEQsR0FBRyxDQUFDLEtBQUssQ0FBQyxjQUFjLEdBQUcsYUFBYSxDQUFDLFFBQVEsQ0FBQztRQUNsRCxHQUFHLENBQUMsS0FBSyxDQUFDLGlCQUFpQixHQUFHLGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQztRQUM5RCxHQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNyQyxNQUFNLENBQUMsTUFBTSxFQUFFLHNCQUFzQixFQUFFO1lBQ3JDLE9BQU8sRUFBRSxJQUFBLDJCQUFpQixFQUFDLE1BQU0sQ0FBQztZQUNsQyxXQUFXLEVBQUUsYUFBYSxDQUFDLFFBQVEsQ0FBQyxXQUFXO1NBQ2hELENBQUMsQ0FBQztRQUVILE1BQU0sSUFBSSxFQUFFLENBQUM7SUFDZixDQUFDLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsR0FBWTtJQUNuRCxNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLHdCQUFpRSxDQUFDO0lBRS9GLFVBQVUsRUFBRSxFQUFFLENBQUM7QUFDakIsQ0FBQyJ9
@@ -5,6 +5,18 @@ export interface ResolveCache {
5
5
  getNegative(hash: string): ApiKeyError | undefined;
6
6
  setPositive(hash: string, identity: ResolvedApiKeyIdentity): void;
7
7
  setNegative(hash: string, error: ApiKeyError): void;
8
+ /**
9
+ * Forgets a key. Bounded because the caller is a refusal the Forest server may repeat on every
10
+ * request: invalidating each time would defeat the cache and cost two round trips per request
11
+ * instead of one extra per window.
12
+ *
13
+ * `credential` is the server token that was refused. A window opened by one token still lets a
14
+ * second, different one through: the re-resolution that followed the first refusal caches a
15
+ * fresh token, and suppressing its refusal too would replay a credential the server rejects for
16
+ * the rest of the window. Only the second is allowed, so the bound holds whatever the server
17
+ * hands back.
18
+ */
19
+ invalidate(hash: string, credential?: string): void;
8
20
  size(): number;
9
21
  }
10
22
  export interface ResolveCacheOptions {
@@ -6,12 +6,21 @@ const DEFAULT_NEGATIVE_TTL_SECONDS = 10;
6
6
  const DEFAULT_MAX_ENTRIES = 10000;
7
7
  function createResolveCache({ now, positiveTtlSeconds = DEFAULT_POSITIVE_TTL_SECONDS, negativeTtlSeconds = DEFAULT_NEGATIVE_TTL_SECONDS, maxEntries = DEFAULT_MAX_ENTRIES, }) {
8
8
  const entries = new Map();
9
+ /**
10
+ * Per key, the window opened by its last invalidation. Bounded by `maxEntries` like the entries
11
+ * it guards: invalidations of distinct keys would otherwise grow it without limit.
12
+ */
13
+ const invalidatedUntil = new Map();
9
14
  function purgeExpired() {
10
15
  const current = now();
11
16
  for (const [hash, entry] of entries) {
12
17
  if (current >= entry.expiresAt)
13
18
  entries.delete(hash);
14
19
  }
20
+ for (const [hash, window] of invalidatedUntil) {
21
+ if (current >= window.until)
22
+ invalidatedUntil.delete(hash);
23
+ }
15
24
  }
16
25
  function liveEntry(hash) {
17
26
  const entry = entries.get(hash);
@@ -23,13 +32,16 @@ function createResolveCache({ now, positiveTtlSeconds = DEFAULT_POSITIVE_TTL_SEC
23
32
  }
24
33
  return entry;
25
34
  }
35
+ function evictOldestIfFull(map, hash) {
36
+ if (map.has(hash) || map.size < maxEntries)
37
+ return;
38
+ const oldest = map.keys().next().value;
39
+ if (oldest !== undefined)
40
+ map.delete(oldest);
41
+ }
26
42
  function store(hash, entry) {
27
43
  purgeExpired();
28
- if (!entries.has(hash) && entries.size >= maxEntries) {
29
- const oldest = entries.keys().next().value;
30
- if (oldest !== undefined)
31
- entries.delete(oldest);
32
- }
44
+ evictOldestIfFull(entries, hash);
33
45
  entries.set(hash, entry);
34
46
  }
35
47
  return {
@@ -47,10 +59,30 @@ function createResolveCache({ now, positiveTtlSeconds = DEFAULT_POSITIVE_TTL_SEC
47
59
  setNegative(hash, error) {
48
60
  store(hash, { kind: 'negative', error, expiresAt: now() + negativeTtlSeconds * 1000 });
49
61
  },
62
+ invalidate(hash, credential) {
63
+ const open = invalidatedUntil.get(hash);
64
+ const live = open !== undefined && now() < open.until;
65
+ if (live) {
66
+ if (open.retried || open.credential === credential)
67
+ return;
68
+ // The window is kept: a second refusal buys one more resolution, not a later deadline.
69
+ invalidatedUntil.set(hash, { ...open, credential, retried: true });
70
+ entries.delete(hash);
71
+ return;
72
+ }
73
+ purgeExpired();
74
+ evictOldestIfFull(invalidatedUntil, hash);
75
+ invalidatedUntil.set(hash, {
76
+ until: now() + positiveTtlSeconds * 1000,
77
+ credential,
78
+ retried: false,
79
+ });
80
+ entries.delete(hash);
81
+ },
50
82
  size() {
51
83
  purgeExpired();
52
84
  return entries.size;
53
85
  },
54
86
  };
55
87
  }
56
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS1jYWNoZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkta2V5L3Jlc29sdmUtY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFvQ0EscUNBbUVDO0FBdkVELE1BQU0sNEJBQTRCLEdBQUcsRUFBRSxDQUFDO0FBQ3hDLE1BQU0sNEJBQTRCLEdBQUcsRUFBRSxDQUFDO0FBQ3hDLE1BQU0sbUJBQW1CLEdBQUcsS0FBTSxDQUFDO0FBRW5DLFNBQXdCLGtCQUFrQixDQUFDLEVBQ3pDLEdBQUcsRUFDSCxrQkFBa0IsR0FBRyw0QkFBNEIsRUFDakQsa0JBQWtCLEdBQUcsNEJBQTRCLEVBQ2pELFVBQVUsR0FBRyxtQkFBbUIsR0FDWjtJQUNwQixNQUFNLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBc0IsQ0FBQztJQUU5QyxTQUFTLFlBQVk7UUFDbkIsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFLENBQUM7UUFFdEIsS0FBSyxNQUFNLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQ3BDLElBQUksT0FBTyxJQUFJLEtBQUssQ0FBQyxTQUFTO2dCQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkQsQ0FBQztJQUNILENBQUM7SUFFRCxTQUFTLFNBQVMsQ0FBQyxJQUFZO1FBQzdCLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDaEMsSUFBSSxDQUFDLEtBQUs7WUFBRSxPQUFPLFNBQVMsQ0FBQztRQUU3QixJQUFJLEdBQUcsRUFBRSxJQUFJLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUM3QixPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBRXJCLE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7UUFFRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCxTQUFTLEtBQUssQ0FBQyxJQUFZLEVBQUUsS0FBaUI7UUFDNUMsWUFBWSxFQUFFLENBQUM7UUFFZixJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxPQUFPLENBQUMsSUFBSSxJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQ3JELE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUM7WUFDM0MsSUFBSSxNQUFNLEtBQUssU0FBUztnQkFBRSxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ25ELENBQUM7UUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztJQUMzQixDQUFDO0lBRUQsT0FBTztRQUNMLFdBQVcsQ0FBQyxJQUFJO1lBQ2QsTUFBTSxLQUFLLEdBQUcsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBRTlCLE9BQU8sS0FBSyxFQUFFLElBQUksS0FBSyxVQUFVLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztRQUNqRSxDQUFDO1FBRUQsV0FBVyxDQUFDLElBQUk7WUFDZCxNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7WUFFOUIsT0FBTyxLQUFLLEVBQUUsSUFBSSxLQUFLLFVBQVUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO1FBQzlELENBQUM7UUFFRCxXQUFXLENBQUMsSUFBSSxFQUFFLFFBQVE7WUFDeEIsS0FBSyxDQUFDLElBQUksRUFBRSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsR0FBRyxrQkFBa0IsR0FBRyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzVGLENBQUM7UUFFRCxXQUFXLENBQUMsSUFBSSxFQUFFLEtBQUs7WUFDckIsS0FBSyxDQUFDLElBQUksRUFBRSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsR0FBRyxrQkFBa0IsR0FBRyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ3pGLENBQUM7UUFFRCxJQUFJO1lBQ0YsWUFBWSxFQUFFLENBQUM7WUFFZixPQUFPLE9BQU8sQ0FBQyxJQUFJLENBQUM7UUFDdEIsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDIn0=
88
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS1jYWNoZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkta2V5L3Jlc29sdmUtY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF3REEscUNBdUdDO0FBM0dELE1BQU0sNEJBQTRCLEdBQUcsRUFBRSxDQUFDO0FBQ3hDLE1BQU0sNEJBQTRCLEdBQUcsRUFBRSxDQUFDO0FBQ3hDLE1BQU0sbUJBQW1CLEdBQUcsS0FBTSxDQUFDO0FBRW5DLFNBQXdCLGtCQUFrQixDQUFDLEVBQ3pDLEdBQUcsRUFDSCxrQkFBa0IsR0FBRyw0QkFBNEIsRUFDakQsa0JBQWtCLEdBQUcsNEJBQTRCLEVBQ2pELFVBQVUsR0FBRyxtQkFBbUIsR0FDWjtJQUNwQixNQUFNLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBc0IsQ0FBQztJQUM5Qzs7O09BR0c7SUFDSCxNQUFNLGdCQUFnQixHQUFHLElBQUksR0FBRyxFQUE4QixDQUFDO0lBRS9ELFNBQVMsWUFBWTtRQUNuQixNQUFNLE9BQU8sR0FBRyxHQUFHLEVBQUUsQ0FBQztRQUV0QixLQUFLLE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUksT0FBTyxFQUFFLENBQUM7WUFDcEMsSUFBSSxPQUFPLElBQUksS0FBSyxDQUFDLFNBQVM7Z0JBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN2RCxDQUFDO1FBRUQsS0FBSyxNQUFNLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLGdCQUFnQixFQUFFLENBQUM7WUFDOUMsSUFBSSxPQUFPLElBQUksTUFBTSxDQUFDLEtBQUs7Z0JBQUUsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzdELENBQUM7SUFDSCxDQUFDO0lBRUQsU0FBUyxTQUFTLENBQUMsSUFBWTtRQUM3QixNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2hD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
@@ -1,5 +1,6 @@
1
1
  import type { BffAccessTokenPayload } from '../oauth/bff-token';
2
2
  export type AuthMode = 'oauth' | 'api-key';
3
+ export declare function readRenderingId(principal: BffAccessTokenPayload): number | undefined;
3
4
  export declare function requireRenderingId(principal: BffAccessTokenPayload): number;
4
5
  export declare function extractBearerToken(authorization: string | undefined): string | undefined;
5
6
  export declare function resolveAuthMode({ hasBearer, hasApiKey, }: {