@forestadmin/agent-bff 1.1.0 → 1.3.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
@@ -43,6 +43,25 @@ yarn start:dev # node --env-file=.env dist/cli.js
43
43
  - A required var that is absent (or empty / whitespace-only) does not crash the server. It boots and
44
44
  reports the gap through `/health` (503 `degraded`).
45
45
 
46
+ ## Sessions & token rotation
47
+
48
+ OAuth sessions, opaque refresh tokens, and rotated-out-token reuse detection are held in an
49
+ **in-memory, single-process** store. Reuse detection has a one-step grace window: re-presenting the
50
+ **most recently** rotated-out token (a benign retry of a refresh whose response was lost) replays
51
+ the last issued pair instead of killing the session — the stored refresh token is returned with a
52
+ freshly minted access JWT. Re-presenting any **older** rotated-out token still invalidates the whole
53
+ session, so a stolen earlier token stays detectable. The grace window is bounded by the rotation
54
+ chain, not a timer: once the client advances one more rotation, the previous token is no longer the
55
+ latest and falls back to reuse. These guarantees are only correct within one process. Two layers
56
+ are process-local: the session/rotation maps in the store, and the in-flight refresh dedup maps
57
+ (`inFlightRefreshByPresentedHash` in `oauth-routes`, `inFlightRefreshesBySid` in
58
+ `session-lifecycle`) that collapse concurrent refreshes of the same token into a single rotation.
59
+ Running the BFF behind a load balancer without sticky sessions breaks reuse detection and the
60
+ single-rotation guarantee — and a shared session store alone does not fix it: two concurrent
61
+ refreshes landing on different nodes would each try to rotate, one committing and the other failing
62
+ with `session_expired`. Horizontal scaling requires both a shared session store **and** a shared
63
+ (or node-pinned) refresh-coordination mechanism.
64
+
46
65
  ## `/health`
47
66
 
48
67
  ```jsonc
@@ -0,0 +1,8 @@
1
+ import type { ResolvedApiKeyIdentity } from './api-key-client';
2
+ export declare const AGENT_TOKEN_EXPIRES_IN = "5m";
3
+ export interface IssueAgentTokenParams {
4
+ identity: ResolvedApiKeyIdentity;
5
+ authSecret: string;
6
+ }
7
+ export declare function issueAgentToken({ identity, authSecret }: IssueAgentTokenParams): string;
8
+ //# sourceMappingURL=agent-token.d.ts.map
@@ -0,0 +1,34 @@
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.AGENT_TOKEN_EXPIRES_IN = void 0;
7
+ exports.issueAgentToken = issueAgentToken;
8
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
9
+ exports.AGENT_TOKEN_EXPIRES_IN = '5m';
10
+ function tagsToRecord(tags) {
11
+ return tags.reduce((memo, { key, value }) => ({ ...memo, [key]: value }), {});
12
+ }
13
+ function issueAgentToken({ identity, authSecret }) {
14
+ const { user, renderingId } = identity;
15
+ const firstName = user.firstName ?? '';
16
+ const lastName = user.lastName ?? '';
17
+ const tags = tagsToRecord(user.tags);
18
+ // snake_case aliases: Ruby/Python agents splat JWT claims into Caller (snake_case kwargs).
19
+ return jsonwebtoken_1.default.sign({
20
+ id: user.id,
21
+ email: user.email,
22
+ firstName,
23
+ lastName,
24
+ team: user.team,
25
+ renderingId,
26
+ tags,
27
+ permissionLevel: user.permissionLevel,
28
+ first_name: firstName,
29
+ last_name: lastName,
30
+ rendering_id: renderingId,
31
+ permission_level: user.permissionLevel,
32
+ }, authSecret, { algorithm: 'HS256', expiresIn: exports.AGENT_TOKEN_EXPIRES_IN });
33
+ }
34
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQtdG9rZW4uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpLWtleS9hZ2VudC10b2tlbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFlQSwwQ0F5QkM7QUF0Q0QsZ0VBQXdDO0FBRTNCLFFBQUEsc0JBQXNCLEdBQUcsSUFBSSxDQUFDO0FBTzNDLFNBQVMsWUFBWSxDQUFDLElBQXNDO0lBQzFELE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxFQUFFLEdBQUcsSUFBSSxFQUFFLENBQUMsR0FBRyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUNoRixDQUFDO0FBRUQsU0FBZ0IsZUFBZSxDQUFDLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBeUI7SUFDN0UsTUFBTSxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsR0FBRyxRQUFRLENBQUM7SUFDdkMsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLFNBQVMsSUFBSSxFQUFFLENBQUM7SUFDdkMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFFBQVEsSUFBSSxFQUFFLENBQUM7SUFDckMsTUFBTSxJQUFJLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUVyQywyRkFBMkY7SUFDM0YsT0FBTyxzQkFBWSxDQUFDLElBQUksQ0FDdEI7UUFDRSxFQUFFLEVBQUUsSUFBSSxDQUFDLEVBQUU7UUFDWCxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUs7UUFDakIsU0FBUztRQUNULFFBQVE7UUFDUixJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUk7UUFDZixXQUFXO1FBQ1gsSUFBSTtRQUNKLGVBQWUsRUFBRSxJQUFJLENBQUMsZUFBZTtRQUNyQyxVQUFVLEVBQUUsU0FBUztRQUNyQixTQUFTLEVBQUUsUUFBUTtRQUNuQixZQUFZLEVBQUUsV0FBVztRQUN6QixnQkFBZ0IsRUFBRSxJQUFJLENBQUMsZUFBZTtLQUN2QyxFQUNELFVBQVUsRUFDVixFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLDhCQUFzQixFQUFFLENBQzFELENBQUM7QUFDSixDQUFDIn0=
@@ -0,0 +1,17 @@
1
+ import type ApiKeyClient from './api-key-client';
2
+ import type { ResolvedApiKeyIdentity } from './api-key-client';
3
+ import type { ResolveCache } from './resolve-cache';
4
+ export interface ApiKeyAuthenticatorOptions {
5
+ client: ApiKeyClient;
6
+ cache: ResolveCache;
7
+ authSecret: string;
8
+ }
9
+ export interface AuthenticatedApiKey {
10
+ agentToken: string;
11
+ identity: ResolvedApiKeyIdentity;
12
+ }
13
+ export interface ApiKeyAuthenticator {
14
+ authenticate(rawKey: string): Promise<AuthenticatedApiKey>;
15
+ }
16
+ export default function createApiKeyAuthenticator({ client, cache, authSecret, }: ApiKeyAuthenticatorOptions): ApiKeyAuthenticator;
17
+ //# sourceMappingURL=api-key-authenticator.d.ts.map
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = createApiKeyAuthenticator;
4
+ const agent_token_1 = require("./agent-token");
5
+ const api_key_1 = require("./api-key");
6
+ const api_key_client_1 = require("./api-key-client");
7
+ const api_key_error_1 = require("./api-key-error");
8
+ const UNAVAILABLE_RETRY_AFTER_SECONDS = 5;
9
+ const NEGATIVE_CACHE_STATUSES = new Set([401, 403]);
10
+ function mapResolveError(error) {
11
+ if (error.unreachable)
12
+ return (0, api_key_error_1.keyResolutionUnavailable)(UNAVAILABLE_RETRY_AFTER_SECONDS);
13
+ switch (error.status) {
14
+ case 401:
15
+ return (0, api_key_error_1.invalidApiKey)();
16
+ case 403:
17
+ return (0, api_key_error_1.forestIdentityNotAllowed)();
18
+ case 400:
19
+ return (0, api_key_error_1.invalidRequest)();
20
+ case 429:
21
+ return (0, api_key_error_1.keyResolutionUnavailable)(error.retryAfter ?? UNAVAILABLE_RETRY_AFTER_SECONDS);
22
+ default:
23
+ return (0, api_key_error_1.keyResolutionUnavailable)(UNAVAILABLE_RETRY_AFTER_SECONDS);
24
+ }
25
+ }
26
+ function createApiKeyAuthenticator({ client, cache, authSecret, }) {
27
+ function mint(identity) {
28
+ return { agentToken: (0, agent_token_1.issueAgentToken)({ identity, authSecret }), identity };
29
+ }
30
+ return {
31
+ async authenticate(rawKey) {
32
+ const parsed = (0, api_key_1.parseApiKey)(rawKey);
33
+ if (!parsed)
34
+ throw (0, api_key_error_1.invalidApiKey)();
35
+ const hash = (0, api_key_1.hashApiKey)(parsed.keyId, parsed.secret);
36
+ const cachedIdentity = cache.getPositive(hash);
37
+ if (cachedIdentity)
38
+ return mint(cachedIdentity);
39
+ const cachedError = cache.getNegative(hash);
40
+ if (cachedError)
41
+ throw cachedError;
42
+ let identity;
43
+ try {
44
+ identity = await client.resolveApiKey(parsed);
45
+ }
46
+ catch (error) {
47
+ if (error instanceof api_key_client_1.ApiKeyResolveError) {
48
+ const mapped = mapResolveError(error);
49
+ if (NEGATIVE_CACHE_STATUSES.has(mapped.status))
50
+ cache.setNegative(hash, mapped);
51
+ throw mapped;
52
+ }
53
+ throw error;
54
+ }
55
+ const authenticated = mint(identity);
56
+ cache.setPositive(hash, identity);
57
+ return authenticated;
58
+ },
59
+ };
60
+ }
61
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1hdXRoZW50aWNhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1hdXRoZW50aWNhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBa0RBLDRDQTRDQztBQXpGRCwrQ0FBZ0Q7QUFDaEQsdUNBQW9EO0FBQ3BELHFEQUFzRDtBQUN0RCxtREFLeUI7QUFFekIsTUFBTSwrQkFBK0IsR0FBRyxDQUFDLENBQUM7QUFDMUMsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBaUJwRCxTQUFTLGVBQWUsQ0FBQyxLQUF5QjtJQUNoRCxJQUFJLEtBQUssQ0FBQyxXQUFXO1FBQUUsT0FBTyxJQUFBLHdDQUF3QixFQUFDLCtCQUErQixDQUFDLENBQUM7SUFFeEYsUUFBUSxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDckIsS0FBSyxHQUFHO1lBQ04sT0FBTyxJQUFBLDZCQUFhLEdBQUUsQ0FBQztRQUN6QixLQUFLLEdBQUc7WUFDTixPQUFPLElBQUEsd0NBQXdCLEdBQUUsQ0FBQztRQUNwQyxLQUFLLEdBQUc7WUFDTixPQUFPLElBQUEsOEJBQWMsR0FBRSxDQUFDO1FBQzFCLEtBQUssR0FBRztZQUNOLE9BQU8sSUFBQSx3Q0FBd0IsRUFBQyxLQUFLLENBQUMsVUFBVSxJQUFJLCtCQUErQixDQUFDLENBQUM7UUFDdkY7WUFDRSxPQUFPLElBQUEsd0NBQXdCLEVBQUMsK0JBQStCLENBQUMsQ0FBQztJQUNyRSxDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQXdCLHlCQUF5QixDQUFDLEVBQ2hELE1BQU0sRUFDTixLQUFLLEVBQ0wsVUFBVSxHQUNpQjtJQUMzQixTQUFTLElBQUksQ0FBQyxRQUFnQztRQUM1QyxPQUFPLEVBQUUsVUFBVSxFQUFFLElBQUEsNkJBQWUsRUFBQyxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsQ0FBQyxFQUFFLFFBQVEsRUFBRSxDQUFDO0lBQzdFLENBQUM7SUFFRCxPQUFPO1FBQ0wsS0FBSyxDQUFDLFlBQVksQ0FBQyxNQUFNO1lBQ3ZCLE1BQU0sTUFBTSxHQUFHLElBQUEscUJBQVcsRUFBQyxNQUFNLENBQUMsQ0FBQztZQUVuQyxJQUFJLENBQUMsTUFBTTtnQkFBRSxNQUFNLElBQUEsNkJBQWEsR0FBRSxDQUFDO1lBRW5DLE1BQU0sSUFBSSxHQUFHLElBQUEsb0JBQVUsRUFBQyxNQUFNLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUVyRCxNQUFNLGNBQWMsR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQy9DLElBQUksY0FBYztnQkFBRSxPQUFPLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUVoRCxNQUFNLFdBQVcsR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzVDLElBQUksV0FBVztnQkFBRSxNQUFNLFdBQVcsQ0FBQztZQUVuQyxJQUFJLFFBQWdDLENBQUM7WUFFckMsSUFBSSxDQUFDO2dCQUNILFFBQVEsR0FBRyxNQUFNLE1BQU0sQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDaEQsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2YsSUFBSSxLQUFLLFlBQVksbUNBQWtCLEVBQUUsQ0FBQztvQkFDeEMsTUFBTSxNQUFNLEdBQUcsZUFBZSxDQUFDLEtBQUssQ0FBQyxDQUFDO29CQUN0QyxJQUFJLHVCQUF1QixDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDO3dCQUFFLEtBQUssQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDO29CQUVoRixNQUFNLE1BQU0sQ0FBQztnQkFDZixDQUFDO2dCQUVELE1BQU0sS0FBSyxDQUFDO1lBQ2QsQ0FBQztZQUVELE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUNyQyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztZQUVsQyxPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO0tBQ0YsQ0FBQztBQUNKLENBQUMifQ==
@@ -0,0 +1,37 @@
1
+ export { ApiKeyResolveError } from './api-key-resolve-error';
2
+ export type { ApiKeyResolveErrorParams } from './api-key-resolve-error';
3
+ export interface ApiKeyIdentityUser {
4
+ id: number;
5
+ email: string;
6
+ firstName: string | null;
7
+ lastName: string | null;
8
+ team: string;
9
+ tags: {
10
+ key: string;
11
+ value: string;
12
+ }[];
13
+ permissionLevel: string;
14
+ }
15
+ export interface ResolvedApiKeyIdentity {
16
+ user: ApiKeyIdentityUser;
17
+ renderingId: number;
18
+ allowedOrigins: string[];
19
+ }
20
+ export interface ApiKeyClientOptions {
21
+ forestServerUrl: string;
22
+ envSecret: string;
23
+ }
24
+ export default class ApiKeyClient {
25
+ private readonly forestServerUrl;
26
+ private readonly envSecret;
27
+ constructor({ forestServerUrl, envSecret }: ApiKeyClientOptions);
28
+ resolveApiKey(parsedKey: {
29
+ keyId: string;
30
+ secret: string;
31
+ }): Promise<ResolvedApiKeyIdentity>;
32
+ private static isResolvedIdentity;
33
+ private static isIdentityUser;
34
+ private static parseRetryAfter;
35
+ private url;
36
+ }
37
+ //# sourceMappingURL=api-key-client.d.ts.map
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiKeyResolveError = void 0;
4
+ const api_key_resolve_error_1 = require("./api-key-resolve-error");
5
+ var api_key_resolve_error_2 = require("./api-key-resolve-error");
6
+ Object.defineProperty(exports, "ApiKeyResolveError", { enumerable: true, get: function () { return api_key_resolve_error_2.ApiKeyResolveError; } });
7
+ const DEFAULT_HEADERS = { 'Content-Type': 'application/json' };
8
+ const REQUEST_TIMEOUT_MS = 10000;
9
+ const RESOLVE_PATH = '/liana/v1/bff-api-keys/resolve';
10
+ class ApiKeyClient {
11
+ constructor({ forestServerUrl, envSecret }) {
12
+ this.forestServerUrl = forestServerUrl;
13
+ this.envSecret = envSecret;
14
+ }
15
+ async resolveApiKey(parsedKey) {
16
+ let response;
17
+ try {
18
+ response = await fetch(this.url(RESOLVE_PATH), {
19
+ method: 'POST',
20
+ headers: { ...DEFAULT_HEADERS, 'forest-secret-key': this.envSecret },
21
+ body: JSON.stringify({ keyId: parsedKey.keyId, secret: parsedKey.secret }),
22
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
23
+ });
24
+ }
25
+ catch {
26
+ throw new api_key_resolve_error_1.ApiKeyResolveError({ unreachable: true });
27
+ }
28
+ if (!response.ok) {
29
+ const body = (await response.json().catch(() => ({})));
30
+ const firstError = body.errors?.[0];
31
+ throw new api_key_resolve_error_1.ApiKeyResolveError({
32
+ status: response.status,
33
+ code: firstError?.meta?.code,
34
+ name: firstError?.name,
35
+ retryAfter: ApiKeyClient.parseRetryAfter(response.headers.get('retry-after')),
36
+ });
37
+ }
38
+ let body;
39
+ try {
40
+ body = await response.json();
41
+ }
42
+ catch {
43
+ throw new api_key_resolve_error_1.ApiKeyResolveError({ unreachable: true });
44
+ }
45
+ // A well-formed HTTP 200 with an incomplete body is an unusable resolution, not a caller
46
+ // error: surface it as unavailable rather than letting a later `user` deref throw a 500.
47
+ if (!ApiKeyClient.isResolvedIdentity(body)) {
48
+ throw new api_key_resolve_error_1.ApiKeyResolveError({ unreachable: true });
49
+ }
50
+ return body;
51
+ }
52
+ static isResolvedIdentity(body) {
53
+ if (typeof body !== 'object' || body === null)
54
+ return false;
55
+ const candidate = body;
56
+ return (typeof candidate.renderingId === 'number' &&
57
+ Array.isArray(candidate.allowedOrigins) &&
58
+ ApiKeyClient.isIdentityUser(candidate.user));
59
+ }
60
+ // Every field the agent-token mint reads must be present, so a partial body fails here
61
+ // (mapped to unavailable) instead of minting a token with undefined claims later.
62
+ static isIdentityUser(user) {
63
+ if (typeof user !== 'object' || user === null)
64
+ return false;
65
+ const candidate = user;
66
+ return (typeof candidate.id === 'number' &&
67
+ typeof candidate.email === 'string' &&
68
+ typeof candidate.team === 'string' &&
69
+ typeof candidate.permissionLevel === 'string' &&
70
+ Array.isArray(candidate.tags));
71
+ }
72
+ // Retry-After may be an HTTP-date rather than delay-seconds; keep only a positive integer so a
73
+ // date/negative/zero value falls back to the caller's default instead of a useless backoff hint.
74
+ static parseRetryAfter(header) {
75
+ if (header === null)
76
+ return undefined;
77
+ const seconds = Number(header);
78
+ return Number.isInteger(seconds) && seconds > 0 ? seconds : undefined;
79
+ }
80
+ url(path) {
81
+ return new URL(path, this.forestServerUrl).toString();
82
+ }
83
+ }
84
+ exports.default = ApiKeyClient;
85
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1jbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpLWtleS9hcGkta2V5LWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxtRUFBNkQ7QUFFN0QsaUVBQTZEO0FBQXBELDJIQUFBLGtCQUFrQixPQUFBO0FBd0IzQixNQUFNLGVBQWUsR0FBRyxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBVyxDQUFDO0FBQ3hFLE1BQU0sa0JBQWtCLEdBQUcsS0FBTSxDQUFDO0FBQ2xDLE1BQU0sWUFBWSxHQUFHLGdDQUFnQyxDQUFDO0FBTXRELE1BQXFCLFlBQVk7SUFJL0IsWUFBWSxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQXVCO1FBQzdELElBQUksQ0FBQyxlQUFlLEdBQUcsZUFBZSxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO0lBQzdCLENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLFNBR25CO1FBQ0MsSUFBSSxRQUFrQixDQUFDO1FBRXZCLElBQUksQ0FBQztZQUNILFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxFQUFFO2dCQUM3QyxNQUFNLEVBQUUsTUFBTTtnQkFDZCxPQUFPLEVBQUUsRUFBRSxHQUFHLGVBQWUsRUFBRSxtQkFBbUIsRUFBRSxJQUFJLENBQUMsU0FBUyxFQUFFO2dCQUNwRSxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxTQUFTLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQzFFLE1BQU0sRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDO2FBQ2hELENBQUMsQ0FBQztRQUNMLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxNQUFNLElBQUksMENBQWtCLENBQUMsRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN0RCxDQUFDO1FBRUQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUNqQixNQUFNLElBQUksR0FBRyxDQUFDLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQWtCLENBQUM7WUFDeEUsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBRXBDLE1BQU0sSUFBSSwwQ0FBa0IsQ0FBQztnQkFDM0IsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNO2dCQUN2QixJQUFJLEVBQUUsVUFBVSxFQUFFLElBQUksRUFBRSxJQUFJO2dCQUM1QixJQUFJLEVBQUUsVUFBVSxFQUFFLElBQUk7Z0JBQ3RCLFVBQVUsRUFBRSxZQUFZLENBQUMsZUFBZSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxDQUFDO2FBQzlFLENBQUMsQ0FBQztRQUNMLENBQUM7UUFFRCxJQUFJLElBQWEsQ0FBQztRQUVsQixJQUFJLENBQUM7WUFDSCxJQUFJLEdBQUcsTUFBTSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDL0IsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLE1BQU0sSUFBSSwwQ0FBa0IsQ0FBQyxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ3RELENBQUM7UUFFRCx5RkFBeUY7UUFDekYseUZBQXlGO1FBQ3pGLElBQUksQ0FBQyxZQUFZLENBQUMsa0JBQWtCLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUMzQyxNQUFNLElBQUksMENBQWtCLENBQUMsRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN0RCxDQUFDO1FBRUQsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRU8sTUFBTSxDQUFDLGtCQUFrQixDQUFDLElBQWE7UUFDN0MsSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksSUFBSSxLQUFLLElBQUk7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUU1RCxNQUFNLFNBQVMsR0FBRyxJQUEyRSxDQUFDO1FBRTlGLE9BQU8sQ0FDTCxPQUFPLFNBQVMsQ0FBQyxXQUFXLEtBQUssUUFBUTtZQUN6QyxLQUFLLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUM7WUFDdkMsWUFBWSxDQUFDLGNBQWMsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQzVDLENBQUM7SUFDSixDQUFDO0lBRUQsdUZBQXVGO0lBQ3ZGLGtGQUFrRjtJQUMxRSxNQUFNLENBQUMsY0FBYyxDQUFDLElBQWE7UUFDekMsSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksSUFBSSxLQUFLLElBQUk7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUU1RCxNQUFNLFNBQVMsR0FBRyxJQUErQixDQUFDO1FBRWxELE9BQU8sQ0FDTCxPQUFPLFNBQVMsQ0FBQyxFQUFFLEtBQUssUUFBUTtZQUNoQyxPQUFPLFNBQVMsQ0FBQyxLQUFLLEtBQUssUUFBUTtZQUNuQyxPQUFPLFNBQVMsQ0FBQyxJQUFJLEtBQUssUUFBUTtZQUNsQyxPQUFPLFNBQVMsQ0FBQyxlQUFlLEtBQUssUUFBUTtZQUM3QyxLQUFLLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FDOUIsQ0FBQztJQUNKLENBQUM7SUFFRCwrRkFBK0Y7SUFDL0YsaUdBQWlHO0lBQ3pGLE1BQU0sQ0FBQyxlQUFlLENBQUMsTUFBcUI7UUFDbEQsSUFBSSxNQUFNLEtBQUssSUFBSTtZQUFFLE9BQU8sU0FBUyxDQUFDO1FBRXRDLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUUvQixPQUFPLE1BQU0sQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLElBQUksT0FBTyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDeEUsQ0FBQztJQUVPLEdBQUcsQ0FBQyxJQUFZO1FBQ3RCLE9BQU8sSUFBSSxHQUFHLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztJQUN4RCxDQUFDO0NBQ0Y7QUFoR0QsK0JBZ0dDIn0=
@@ -0,0 +1,20 @@
1
+ export type ApiKeyErrorType = 'invalid_api_key' | 'forest_identity_not_allowed' | 'invalid_request' | 'key_resolution_unavailable';
2
+ export declare class ApiKeyError extends Error {
3
+ readonly status: number;
4
+ readonly type: ApiKeyErrorType;
5
+ readonly retryAfter?: number;
6
+ constructor(status: number, type: ApiKeyErrorType, message: string, retryAfter?: number);
7
+ }
8
+ export interface ApiKeyErrorBody {
9
+ error: {
10
+ type: ApiKeyErrorType;
11
+ status: number;
12
+ message: string;
13
+ };
14
+ }
15
+ export declare function toErrorBody(error: ApiKeyError): ApiKeyErrorBody;
16
+ export declare function invalidApiKey(message?: string): ApiKeyError;
17
+ export declare function forestIdentityNotAllowed(message?: string): ApiKeyError;
18
+ export declare function invalidRequest(message?: string): ApiKeyError;
19
+ export declare function keyResolutionUnavailable(retryAfter: number, message?: string): ApiKeyError;
20
+ //# sourceMappingURL=api-key-error.d.ts.map
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiKeyError = void 0;
4
+ exports.toErrorBody = toErrorBody;
5
+ exports.invalidApiKey = invalidApiKey;
6
+ exports.forestIdentityNotAllowed = forestIdentityNotAllowed;
7
+ exports.invalidRequest = invalidRequest;
8
+ exports.keyResolutionUnavailable = keyResolutionUnavailable;
9
+ class ApiKeyError extends Error {
10
+ constructor(status, type, message, retryAfter) {
11
+ super(message);
12
+ this.name = 'ApiKeyError';
13
+ this.status = status;
14
+ this.type = type;
15
+ this.retryAfter = retryAfter;
16
+ }
17
+ }
18
+ exports.ApiKeyError = ApiKeyError;
19
+ function toErrorBody(error) {
20
+ return { error: { type: error.type, status: error.status, message: error.message } };
21
+ }
22
+ function invalidApiKey(message = 'Invalid API key') {
23
+ return new ApiKeyError(401, 'invalid_api_key', message);
24
+ }
25
+ function forestIdentityNotAllowed(message = 'Forest identity not allowed') {
26
+ return new ApiKeyError(403, 'forest_identity_not_allowed', message);
27
+ }
28
+ function invalidRequest(message = 'Invalid request') {
29
+ return new ApiKeyError(400, 'invalid_request', message);
30
+ }
31
+ function keyResolutionUnavailable(retryAfter, message = 'Key resolution unavailable') {
32
+ return new ApiKeyError(503, 'key_resolution_unavailable', message, retryAfter);
33
+ }
34
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1lcnJvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkta2V5L2FwaS1rZXktZXJyb3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBNEJBLGtDQUVDO0FBRUQsc0NBRUM7QUFFRCw0REFFQztBQUVELHdDQUVDO0FBRUQsNERBS0M7QUEzQ0QsTUFBYSxXQUFZLFNBQVEsS0FBSztJQUtwQyxZQUFZLE1BQWMsRUFBRSxJQUFxQixFQUFFLE9BQWUsRUFBRSxVQUFtQjtRQUNyRixLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDZixJQUFJLENBQUMsSUFBSSxHQUFHLGFBQWEsQ0FBQztRQUMxQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNyQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztRQUNqQixJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsQ0FBQztJQUMvQixDQUFDO0NBQ0Y7QUFaRCxrQ0FZQztBQVVELFNBQWdCLFdBQVcsQ0FBQyxLQUFrQjtJQUM1QyxPQUFPLEVBQUUsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDO0FBQ3ZGLENBQUM7QUFFRCxTQUFnQixhQUFhLENBQUMsT0FBTyxHQUFHLGlCQUFpQjtJQUN2RCxPQUFPLElBQUksV0FBVyxDQUFDLEdBQUcsRUFBRSxpQkFBaUIsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUMxRCxDQUFDO0FBRUQsU0FBZ0Isd0JBQXdCLENBQUMsT0FBTyxHQUFHLDZCQUE2QjtJQUM5RSxPQUFPLElBQUksV0FBVyxDQUFDLEdBQUcsRUFBRSw2QkFBNkIsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUN0RSxDQUFDO0FBRUQsU0FBZ0IsY0FBYyxDQUFDLE9BQU8sR0FBRyxpQkFBaUI7SUFDeEQsT0FBTyxJQUFJLFdBQVcsQ0FBQyxHQUFHLEVBQUUsaUJBQWlCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDMUQsQ0FBQztBQUVELFNBQWdCLHdCQUF3QixDQUN0QyxVQUFrQixFQUNsQixPQUFPLEdBQUcsNEJBQTRCO0lBRXRDLE9BQU8sSUFBSSxXQUFXLENBQUMsR0FBRyxFQUFFLDRCQUE0QixFQUFFLE9BQU8sRUFBRSxVQUFVLENBQUMsQ0FBQztBQUNqRixDQUFDIn0=
@@ -0,0 +1,10 @@
1
+ import type { ApiKeyAuthenticator } from './api-key-authenticator';
2
+ import type { Logger } from '../ports/logger-port';
3
+ import type { Middleware } from 'koa';
4
+ export declare const BFF_KEY_HEADER = "X-Forest-Bff-Key";
5
+ export interface ApiKeyMiddlewareOptions {
6
+ authenticator: ApiKeyAuthenticator;
7
+ logger: Logger;
8
+ }
9
+ export default function createApiKeyMiddleware({ authenticator, logger, }: ApiKeyMiddlewareOptions): Middleware;
10
+ //# sourceMappingURL=api-key-middleware.d.ts.map
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BFF_KEY_HEADER = void 0;
4
+ exports.default = createApiKeyMiddleware;
5
+ const api_key_1 = require("./api-key");
6
+ const api_key_error_1 = require("./api-key-error");
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
+ }
27
+ function createApiKeyMiddleware({ authenticator, logger, }) {
28
+ return async function apiKeyMiddleware(ctx, next) {
29
+ const rawKey = ctx.get(exports.BFF_KEY_HEADER);
30
+ if (!rawKey) {
31
+ await next();
32
+ return;
33
+ }
34
+ let authenticated;
35
+ try {
36
+ authenticated = await authenticator.authenticate(rawKey);
37
+ }
38
+ catch (error) {
39
+ writeError(ctx, error, rawKey, logger);
40
+ return;
41
+ }
42
+ ctx.state.agentToken = authenticated.agentToken;
43
+ ctx.state.apiKeyIdentity = authenticated.identity;
44
+ ctx.set('Cache-Control', 'no-store');
45
+ logger('Info', 'Resolved BFF API key', {
46
+ keyHash: (0, api_key_1.fingerprintApiKey)(rawKey),
47
+ renderingId: authenticated.identity.renderingId,
48
+ });
49
+ await next();
50
+ };
51
+ }
52
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQW1DQSx5Q0FpQ0M7QUFoRUQsdUNBQThDO0FBQzlDLG1EQUEyRDtBQUU5QyxRQUFBLGNBQWMsR0FBRyxrQkFBa0IsQ0FBQztBQU9qRCxTQUFTLFVBQVUsQ0FBQyxHQUFZLEVBQUUsS0FBYyxFQUFFLE1BQWMsRUFBRSxNQUFjO0lBQzlFLElBQUksS0FBSyxZQUFZLDJCQUFXLEVBQUUsQ0FBQztRQUNqQyxJQUFJLEtBQUssQ0FBQyxVQUFVLEtBQUssU0FBUztZQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsYUFBYSxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQztRQUNyRixHQUFHLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFDMUIsR0FBRyxDQUFDLElBQUksR0FBRyxJQUFBLDJCQUFXLEVBQUMsS0FBSyxDQUFDLENBQUM7UUFDOUIsTUFBTSxDQUFDLE1BQU0sRUFBRSxzQkFBc0IsRUFBRTtZQUNyQyxPQUFPLEVBQUUsSUFBQSwyQkFBaUIsRUFBQyxNQUFNLENBQUM7WUFDbEMsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO1NBQ2pCLENBQUMsQ0FBQztRQUVILE9BQU87SUFDVCxDQUFDO0lBRUQsTUFBTSxDQUFDLE9BQU8sRUFBRSxnQ0FBZ0MsRUFBRTtRQUNoRCxPQUFPLEVBQUUsSUFBQSwyQkFBaUIsRUFBQyxNQUFNLENBQUM7UUFDbEMsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksS0FBSyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7S0FDbEYsQ0FBQyxDQUFDO0lBQ0gsR0FBRyxDQUFDLE1BQU0sR0FBRyxHQUFHLENBQUM7SUFDakIsR0FBRyxDQUFDLElBQUksR0FBRyxFQUFFLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxjQUFjLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsRUFBRSxDQUFDO0FBQ3BHLENBQUM7QUFFRCxTQUF3QixzQkFBc0IsQ0FBQyxFQUM3QyxhQUFhLEVBQ2IsTUFBTSxHQUNrQjtJQUN4QixPQUFPLEtBQUssVUFBVSxnQkFBZ0IsQ0FBQyxHQUFHLEVBQUUsSUFBSTtRQUM5QyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLHNCQUFjLENBQUMsQ0FBQztRQUV2QyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixNQUFNLElBQUksRUFBRSxDQUFDO1lBRWIsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLGFBQWtDLENBQUM7UUFFdkMsSUFBSSxDQUFDO1lBQ0gsYUFBYSxHQUFHLE1BQU0sYUFBYSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMzRCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLFVBQVUsQ0FBQyxHQUFHLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUV2QyxPQUFPO1FBQ1QsQ0FBQztRQUVELEdBQUcsQ0FBQyxLQUFLLENBQUMsVUFBVSxHQUFHLGFBQWEsQ0FBQyxVQUFVLENBQUM7UUFDaEQsR0FBRyxDQUFDLEtBQUssQ0FBQyxjQUFjLEdBQUcsYUFBYSxDQUFDLFFBQVEsQ0FBQztRQUNsRCxHQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNyQyxNQUFNLENBQUMsTUFBTSxFQUFFLHNCQUFzQixFQUFFO1lBQ3JDLE9BQU8sRUFBRSxJQUFBLDJCQUFpQixFQUFDLE1BQU0sQ0FBQztZQUNsQyxXQUFXLEVBQUUsYUFBYSxDQUFDLFFBQVEsQ0FBQyxXQUFXO1NBQ2hELENBQUMsQ0FBQztRQUVILE1BQU0sSUFBSSxFQUFFLENBQUM7SUFDZixDQUFDLENBQUM7QUFDSixDQUFDIn0=
@@ -0,0 +1,16 @@
1
+ export interface ApiKeyResolveErrorParams {
2
+ status?: number;
3
+ code?: string;
4
+ name?: string;
5
+ retryAfter?: number;
6
+ unreachable?: boolean;
7
+ }
8
+ export declare class ApiKeyResolveError extends Error {
9
+ readonly status?: number;
10
+ readonly code?: string;
11
+ readonly saasName?: string;
12
+ readonly retryAfter?: number;
13
+ readonly unreachable: boolean;
14
+ constructor(params: ApiKeyResolveErrorParams);
15
+ }
16
+ //# sourceMappingURL=api-key-resolve-error.d.ts.map
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiKeyResolveError = void 0;
4
+ class ApiKeyResolveError extends Error {
5
+ constructor(params) {
6
+ super(`BFF API key resolve failed${params.status ? ` (${params.status})` : ''}`);
7
+ this.name = 'ApiKeyResolveError';
8
+ this.status = params.status;
9
+ this.code = params.code;
10
+ this.saasName = params.name;
11
+ this.retryAfter = params.retryAfter;
12
+ this.unreachable = params.unreachable ?? false;
13
+ }
14
+ }
15
+ exports.ApiKeyResolveError = ApiKeyResolveError;
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1yZXNvbHZlLWVycm9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1yZXNvbHZlLWVycm9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQVFBLE1BQWEsa0JBQW1CLFNBQVEsS0FBSztJQU8zQyxZQUFZLE1BQWdDO1FBQzFDLEtBQUssQ0FBQyw2QkFBNkIsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDakYsSUFBSSxDQUFDLElBQUksR0FBRyxvQkFBb0IsQ0FBQztRQUNqQyxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7UUFDNUIsSUFBSSxDQUFDLElBQUksR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDO1FBQ3hCLElBQUksQ0FBQyxRQUFRLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQztRQUM1QixJQUFJLENBQUMsVUFBVSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUM7UUFDcEMsSUFBSSxDQUFDLFdBQVcsR0FBRyxNQUFNLENBQUMsV0FBVyxJQUFJLEtBQUssQ0FBQztJQUNqRCxDQUFDO0NBQ0Y7QUFoQkQsZ0RBZ0JDIn0=
@@ -0,0 +1,8 @@
1
+ export interface ParsedApiKey {
2
+ keyId: string;
3
+ secret: string;
4
+ }
5
+ export declare function parseApiKey(raw: string): ParsedApiKey | null;
6
+ export declare function hashApiKey(keyId: string, secret: string): string;
7
+ export declare function fingerprintApiKey(raw: string): string;
8
+ //# sourceMappingURL=api-key.d.ts.map
@@ -0,0 +1,23 @@
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.parseApiKey = parseApiKey;
7
+ exports.hashApiKey = hashApiKey;
8
+ exports.fingerprintApiKey = fingerprintApiKey;
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ const API_KEY_PATTERN = /^fbff_([0-9a-f]{16})_([0-9a-f]{64})$/;
11
+ function parseApiKey(raw) {
12
+ const match = API_KEY_PATTERN.exec(raw);
13
+ if (!match)
14
+ return null;
15
+ return { keyId: match[1], secret: match[2] };
16
+ }
17
+ function hashApiKey(keyId, secret) {
18
+ return crypto_1.default.createHash('sha256').update(`${keyId}:${secret}`).digest('hex');
19
+ }
20
+ function fingerprintApiKey(raw) {
21
+ return crypto_1.default.createHash('sha256').update(raw).digest('hex').slice(0, 12);
22
+ }
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkta2V5L2FwaS1rZXkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFTQSxrQ0FNQztBQUVELGdDQUVDO0FBRUQsOENBRUM7QUF2QkQsb0RBQTRCO0FBRTVCLE1BQU0sZUFBZSxHQUFHLHNDQUFzQyxDQUFDO0FBTy9ELFNBQWdCLFdBQVcsQ0FBQyxHQUFXO0lBQ3JDLE1BQU0sS0FBSyxHQUFHLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFeEMsSUFBSSxDQUFDLEtBQUs7UUFBRSxPQUFPLElBQUksQ0FBQztJQUV4QixPQUFPLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7QUFDL0MsQ0FBQztBQUVELFNBQWdCLFVBQVUsQ0FBQyxLQUFhLEVBQUUsTUFBYztJQUN0RCxPQUFPLGdCQUFNLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLEtBQUssSUFBSSxNQUFNLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUNoRixDQUFDO0FBRUQsU0FBZ0IsaUJBQWlCLENBQUMsR0FBVztJQUMzQyxPQUFPLGdCQUFNLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUM1RSxDQUFDIn0=
@@ -0,0 +1,17 @@
1
+ import type { ResolvedApiKeyIdentity } from './api-key-client';
2
+ import type { ApiKeyError } from './api-key-error';
3
+ export interface ResolveCache {
4
+ getPositive(hash: string): ResolvedApiKeyIdentity | undefined;
5
+ getNegative(hash: string): ApiKeyError | undefined;
6
+ setPositive(hash: string, identity: ResolvedApiKeyIdentity): void;
7
+ setNegative(hash: string, error: ApiKeyError): void;
8
+ size(): number;
9
+ }
10
+ export interface ResolveCacheOptions {
11
+ now: () => number;
12
+ positiveTtlSeconds?: number;
13
+ negativeTtlSeconds?: number;
14
+ maxEntries?: number;
15
+ }
16
+ export default function createResolveCache({ now, positiveTtlSeconds, negativeTtlSeconds, maxEntries, }: ResolveCacheOptions): ResolveCache;
17
+ //# sourceMappingURL=resolve-cache.d.ts.map
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = createResolveCache;
4
+ const DEFAULT_POSITIVE_TTL_SECONDS = 60;
5
+ const DEFAULT_NEGATIVE_TTL_SECONDS = 10;
6
+ const DEFAULT_MAX_ENTRIES = 10000;
7
+ function createResolveCache({ now, positiveTtlSeconds = DEFAULT_POSITIVE_TTL_SECONDS, negativeTtlSeconds = DEFAULT_NEGATIVE_TTL_SECONDS, maxEntries = DEFAULT_MAX_ENTRIES, }) {
8
+ const entries = new Map();
9
+ function purgeExpired() {
10
+ const current = now();
11
+ for (const [hash, entry] of entries) {
12
+ if (current >= entry.expiresAt)
13
+ entries.delete(hash);
14
+ }
15
+ }
16
+ function liveEntry(hash) {
17
+ const entry = entries.get(hash);
18
+ if (!entry)
19
+ return undefined;
20
+ if (now() >= entry.expiresAt) {
21
+ entries.delete(hash);
22
+ return undefined;
23
+ }
24
+ return entry;
25
+ }
26
+ function store(hash, entry) {
27
+ 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
+ }
33
+ entries.set(hash, entry);
34
+ }
35
+ return {
36
+ getPositive(hash) {
37
+ const entry = liveEntry(hash);
38
+ return entry?.kind === 'positive' ? entry.identity : undefined;
39
+ },
40
+ getNegative(hash) {
41
+ const entry = liveEntry(hash);
42
+ return entry?.kind === 'negative' ? entry.error : undefined;
43
+ },
44
+ setPositive(hash, identity) {
45
+ store(hash, { kind: 'positive', identity, expiresAt: now() + positiveTtlSeconds * 1000 });
46
+ },
47
+ setNegative(hash, error) {
48
+ store(hash, { kind: 'negative', error, expiresAt: now() + negativeTtlSeconds * 1000 });
49
+ },
50
+ size() {
51
+ purgeExpired();
52
+ return entries.size;
53
+ },
54
+ };
55
+ }
56
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS1jYWNoZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkta2V5L3Jlc29sdmUtY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFvQ0EscUNBbUVDO0FBdkVELE1BQU0sNEJBQTRCLEdBQUcsRUFBRSxDQUFDO0FBQ3hDLE1BQU0sNEJBQTRCLEdBQUcsRUFBRSxDQUFDO0FBQ3hDLE1BQU0sbUJBQW1CLEdBQUcsS0FBTSxDQUFDO0FBRW5DLFNBQXdCLGtCQUFrQixDQUFDLEVBQ3pDLEdBQUcsRUFDSCxrQkFBa0IsR0FBRyw0QkFBNEIsRUFDakQsa0JBQWtCLEdBQUcsNEJBQTRCLEVBQ2pELFVBQVUsR0FBRyxtQkFBbUIsR0FDWjtJQUNwQixNQUFNLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBc0IsQ0FBQztJQUU5QyxTQUFTLFlBQVk7UUFDbkIsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFLENBQUM7UUFFdEIsS0FBSyxNQUFNLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQ3BDLElBQUksT0FBTyxJQUFJLEtBQUssQ0FBQyxTQUFTO2dCQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkQsQ0FBQztJQUNILENBQUM7SUFFRCxTQUFTLFNBQVMsQ0FBQyxJQUFZO1FBQzdCLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDaEMsSUFBSSxDQUFDLEtBQUs7WUFBRSxPQUFPLFNBQVMsQ0FBQztRQUU3QixJQUFJLEdBQUcsRUFBRSxJQUFJLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUM3QixPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBRXJCLE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7UUFFRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCxTQUFTLEtBQUssQ0FBQyxJQUFZLEVBQUUsS0FBaUI7UUFDNUMsWUFBWSxFQUFFLENBQUM7UUFFZixJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxPQUFPLENBQUMsSUFBSSxJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQ3JELE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUM7WUFDM0MsSUFBSSxNQUFNLEtBQUssU0FBUztnQkFBRSxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ25ELENBQUM7UUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztJQUMzQixDQUFDO0lBRUQsT0FBTztRQUNMLFdBQVcsQ0FBQyxJQUFJO1lBQ2QsTUFBTSxLQUFLLEdBQUcsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBRTlCLE9BQU8sS0FBSyxFQUFFLElBQUksS0FBSyxVQUFVLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztRQUNqRSxDQUFDO1FBRUQsV0FBVyxDQUFDLElBQUk7WUFDZCxNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7WUFFOUIsT0FBTyxLQUFLLEVBQUUsSUFBSSxLQUFLLFVBQVUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO1FBQzlELENBQUM7UUFFRCxXQUFXLENBQUMsSUFBSSxFQUFFLFFBQVE7WUFDeEIsS0FBSyxDQUFDLElBQUksRUFBRSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsR0FBRyxrQkFBa0IsR0FBRyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzVGLENBQUM7UUFFRCxXQUFXLENBQUMsSUFBSSxFQUFFLEtBQUs7WUFDckIsS0FBSyxDQUFDLElBQUksRUFBRSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsR0FBRyxrQkFBa0IsR0FBRyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ3pGLENBQUM7UUFFRCxJQUFJO1lBQ0YsWUFBWSxFQUFFLENBQUM7WUFFZixPQUFPLE9BQU8sQ0FBQyxJQUFJLENBQUM7UUFDdEIsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDIn0=
package/dist/cli-core.js CHANGED
@@ -7,6 +7,10 @@ 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 api_key_authenticator_1 = __importDefault(require("./api-key/api-key-authenticator"));
11
+ const api_key_client_1 = __importDefault(require("./api-key/api-key-client"));
12
+ const api_key_middleware_1 = __importDefault(require("./api-key/api-key-middleware"));
13
+ const resolve_cache_1 = __importDefault(require("./api-key/resolve-cache"));
10
14
  const env_config_1 = require("./config/env-config");
11
15
  const errors_1 = require("./errors");
12
16
  const bff_http_server_1 = __importDefault(require("./http/bff-http-server"));
@@ -52,9 +56,36 @@ async function buildOAuthMiddlewares(config, logger) {
52
56
  });
53
57
  return [(0, bodyparser_1.bodyParser)({ jsonLimit: BODY_LIMIT }), oauthRoutes];
54
58
  }
59
+ function resolveApiKeyConfig(config) {
60
+ const { forestServerUrl, forestEnvSecret, forestAuthSecret } = config;
61
+ if (forestServerUrl && forestEnvSecret && forestAuthSecret) {
62
+ return { forestServerUrl, forestEnvSecret, forestAuthSecret };
63
+ }
64
+ return undefined;
65
+ }
66
+ function buildApiKeyMiddleware(config, logger) {
67
+ const apiKeyConfig = resolveApiKeyConfig(config);
68
+ if (!apiKeyConfig) {
69
+ logger('Warn', 'API key auth disabled: required configuration is missing');
70
+ return undefined;
71
+ }
72
+ const client = new api_key_client_1.default({
73
+ forestServerUrl: apiKeyConfig.forestServerUrl,
74
+ envSecret: apiKeyConfig.forestEnvSecret,
75
+ });
76
+ const cache = (0, resolve_cache_1.default)({ now: () => Date.now() });
77
+ const authenticator = (0, api_key_authenticator_1.default)({
78
+ client,
79
+ cache,
80
+ authSecret: apiKeyConfig.forestAuthSecret,
81
+ });
82
+ return (0, api_key_middleware_1.default)({ authenticator, logger });
83
+ }
55
84
  async function runCli(env, logger = (0, console_logger_1.default)()) {
56
85
  const config = (0, env_config_1.parseConfig)(env);
57
- const middlewares = await buildOAuthMiddlewares(config, logger);
86
+ const oauthMiddlewares = await buildOAuthMiddlewares(config, logger);
87
+ const apiKeyMiddleware = buildApiKeyMiddleware(config, logger);
88
+ const middlewares = [...oauthMiddlewares, ...(apiKeyMiddleware ? [apiKeyMiddleware] : [])];
58
89
  const server = new bff_http_server_1.default({
59
90
  port: config.httpPort,
60
91
  version: version_1.default,
@@ -69,4 +100,4 @@ function reportFatalError(err) {
69
100
  process.stderr.write(`Error: ${(0, errors_1.extractErrorMessage)(err)}\n`);
70
101
  process.exitCode = 1;
71
102
  }
72
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLWNvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2xpLWNvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUE2RUEseUJBaUJDO0FBRUQsNENBR0M7QUEvRkQsZ0RBQTZDO0FBRTdDLCtFQUE0RDtBQUM1RCxvREFBa0Q7QUFDbEQscUNBQStDO0FBQy9DLDZFQUFtRDtBQUNuRCx3RkFBOEQ7QUFDOUQsd0VBQXFEO0FBQ3JELDBFQUErRDtBQUMvRCx3RUFBcUQ7QUFDckQsd0RBQWdDO0FBRWhDLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQztBQUMxQixNQUFNLG1CQUFtQixHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxDQUFDO0FBVXpDLFNBQVMsa0JBQWtCLENBQUMsTUFBaUI7SUFDM0MsTUFBTSxFQUFFLGVBQWUsRUFBRSxlQUFlLEVBQUUsWUFBWSxFQUFFLGdCQUFnQixFQUFFLGtCQUFrQixFQUFFLEdBQzVGLE1BQU0sQ0FBQztJQUVULElBQ0UsZUFBZTtRQUNmLGVBQWU7UUFDZixZQUFZO1FBQ1osZ0JBQWdCO1FBQ2hCLGtCQUFrQixFQUNsQixDQUFDO1FBQ0QsT0FBTyxFQUFFLGVBQWUsRUFBRSxlQUFlLEVBQUUsWUFBWSxFQUFFLGdCQUFnQixFQUFFLGtCQUFrQixFQUFFLENBQUM7SUFDbEcsQ0FBQztJQUVELE9BQU8sU0FBUyxDQUFDO0FBQ25CLENBQUM7QUFFRCxLQUFLLFVBQVUscUJBQXFCLENBQUMsTUFBaUIsRUFBRSxNQUFjO0lBQ3BFLE1BQU0sV0FBVyxHQUFHLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBRS9DLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUNqQixNQUFNLENBQUMsTUFBTSxFQUFFLDBEQUEwRCxDQUFDLENBQUM7UUFFM0UsT0FBTyxFQUFFLENBQUM7SUFDWixDQUFDO0lBRUQsTUFBTSxFQUFFLGVBQWUsRUFBRSxlQUFlLEVBQUUsWUFBWSxFQUFFLGdCQUFnQixFQUFFLGtCQUFrQixFQUFFLEdBQzVGLFdBQVcsQ0FBQztJQUVkLE1BQU0sWUFBWSxHQUFHLElBQUksOEJBQWtCLENBQUMsRUFBRSxlQUFlLEVBQUUsU0FBUyxFQUFFLGVBQWUsRUFBRSxDQUFDLENBQUM7SUFDN0YsTUFBTSxhQUFhLEdBQUcsTUFBTSxZQUFZLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztJQUU5RCxNQUFNLFlBQVksR0FBRyxJQUFBLHVCQUEwQixFQUFDO1FBQzlDLE1BQU0sRUFBRSxJQUFBLHNCQUFpQixFQUFDLGtCQUFrQixDQUFDO1FBQzdDLEdBQUcsRUFBRSxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFO1FBQ3JCLGlCQUFpQixFQUFFLG1CQUFtQjtLQUN2QyxDQUFDLENBQUM7SUFFSCxNQUFNLFdBQVcsR0FBRyxJQUFBLHNCQUFpQixFQUFDO1FBQ3BDLFlBQVk7UUFDWixZQUFZO1FBQ1osWUFBWTtRQUNaLFVBQVUsRUFBRSxnQkFBZ0I7UUFDNUIsYUFBYTtRQUNiLE1BQU07S0FDUCxDQUFDLENBQUM7SUFFSCxPQUFPLENBQUMsSUFBQSx1QkFBVSxFQUFDLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxDQUFDLEVBQUUsV0FBVyxDQUFDLENBQUM7QUFDOUQsQ0FBQztBQUVjLEtBQUssVUFBVSxNQUFNLENBQ2xDLEdBQXNCLEVBQ3RCLFNBQWlCLElBQUEsd0JBQW1CLEdBQUU7SUFFdEMsTUFBTSxNQUFNLEdBQUcsSUFBQSx3QkFBVyxFQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ2hDLE1BQU0sV0FBVyxHQUFHLE1BQU0scUJBQXFCLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQ2hFLE1BQU0sTUFBTSxHQUFHLElBQUkseUJBQWEsQ0FBQztRQUMvQixJQUFJLEVBQUUsTUFBTSxDQUFDLFFBQVE7UUFDckIsT0FBTyxFQUFQLGlCQUFPO1FBQ1AsTUFBTTtRQUNOLE1BQU07UUFDTixXQUFXO0tBQ1osQ0FBQyxDQUFDO0lBRUgsTUFBTSxNQUFNLENBQUMsS0FBSyxFQUFFLENBQUM7SUFFckIsT0FBTyxNQUFNLENBQUM7QUFDaEIsQ0FBQztBQUVELFNBQWdCLGdCQUFnQixDQUFDLEdBQVk7SUFDM0MsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsVUFBVSxJQUFBLDRCQUFtQixFQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM3RCxPQUFPLENBQUMsUUFBUSxHQUFHLENBQUMsQ0FBQztBQUN2QixDQUFDIn0=
103
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLWNvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2xpLWNvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUF3SEEseUJBbUJDO0FBRUQsNENBR0M7QUE1SUQsZ0RBQTZDO0FBRTdDLCtFQUE0RDtBQUM1RCw0RkFBd0U7QUFDeEUsOEVBQW9EO0FBQ3BELHNGQUFrRTtBQUNsRSw0RUFBeUQ7QUFDekQsb0RBQWtEO0FBQ2xELHFDQUErQztBQUMvQyw2RUFBbUQ7QUFDbkQsd0ZBQThEO0FBQzlELHdFQUFxRDtBQUNyRCwwRUFBK0Q7QUFDL0Qsd0VBQXFEO0FBQ3JELHdEQUFnQztBQUVoQyxNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUM7QUFDMUIsTUFBTSxtQkFBbUIsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsQ0FBQztBQVV6QyxTQUFTLGtCQUFrQixDQUFDLE1BQWlCO0lBQzNDLE1BQU0sRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFBRSxHQUM1RixNQUFNLENBQUM7SUFFVCxJQUNFLGVBQWU7UUFDZixlQUFlO1FBQ2YsWUFBWTtRQUNaLGdCQUFnQjtRQUNoQixrQkFBa0IsRUFDbEIsQ0FBQztRQUNELE9BQU8sRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFBRSxDQUFDO0lBQ2xHLENBQUM7SUFFRCxPQUFPLFNBQVMsQ0FBQztBQUNuQixDQUFDO0FBRUQsS0FBSyxVQUFVLHFCQUFxQixDQUFDLE1BQWlCLEVBQUUsTUFBYztJQUNwRSxNQUFNLFdBQVcsR0FBRyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUUvQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDakIsTUFBTSxDQUFDLE1BQU0sRUFBRSwwREFBMEQsQ0FBQyxDQUFDO1FBRTNFLE9BQU8sRUFBRSxDQUFDO0lBQ1osQ0FBQztJ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
package/dist/index.d.ts CHANGED
@@ -18,4 +18,16 @@ export { issueBffAccessToken, BFF_ACCESS_TOKEN_TYPE } from './oauth/bff-token';
18
18
  export type { BffAccessTokenPayload } from './oauth/bff-token';
19
19
  export { createPkcePair } from './oauth/pkce';
20
20
  export { OAuthRequestError } from './oauth/oauth-error';
21
+ export { parseApiKey, hashApiKey, fingerprintApiKey } from './api-key/api-key';
22
+ export type { ParsedApiKey } from './api-key/api-key';
23
+ export { issueAgentToken, AGENT_TOKEN_EXPIRES_IN } from './api-key/agent-token';
24
+ export { default as ApiKeyClient, ApiKeyResolveError } from './api-key/api-key-client';
25
+ export type { ResolvedApiKeyIdentity, ApiKeyIdentityUser, ApiKeyClientOptions, } from './api-key/api-key-client';
26
+ export { default as createResolveCache } from './api-key/resolve-cache';
27
+ export type { ResolveCache, ResolveCacheOptions } from './api-key/resolve-cache';
28
+ export { default as createApiKeyAuthenticator } from './api-key/api-key-authenticator';
29
+ export type { ApiKeyAuthenticator, AuthenticatedApiKey, ApiKeyAuthenticatorOptions, } from './api-key/api-key-authenticator';
30
+ export { default as createApiKeyMiddleware, BFF_KEY_HEADER } from './api-key/api-key-middleware';
31
+ export { ApiKeyError, invalidApiKey, forestIdentityNotAllowed, invalidRequest, keyResolutionUnavailable, toErrorBody as toApiKeyErrorBody, } from './api-key/api-key-error';
32
+ export type { ApiKeyErrorType, ApiKeyErrorBody } from './api-key/api-key-error';
21
33
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.OAuthRequestError = exports.createPkcePair = exports.BFF_ACCESS_TOKEN_TYPE = exports.issueBffAccessToken = exports.ensureFreshServerAccess = exports.createTokenCipher = exports.createInMemorySessionStore = exports.createOAuthRoutes = exports.OAuthExchangeError = exports.ForestServerClient = exports.version = exports.createConsoleLogger = exports.DEFAULT_BFF_PORT = exports.ConfigurationError = exports.runCli = exports.REQUIRED_KEYS = exports.parseConfig = exports.BFFHttpServer = void 0;
6
+ exports.toApiKeyErrorBody = exports.keyResolutionUnavailable = exports.invalidRequest = exports.forestIdentityNotAllowed = exports.invalidApiKey = exports.ApiKeyError = exports.BFF_KEY_HEADER = exports.createApiKeyMiddleware = exports.createApiKeyAuthenticator = exports.createResolveCache = exports.ApiKeyResolveError = exports.ApiKeyClient = exports.AGENT_TOKEN_EXPIRES_IN = exports.issueAgentToken = exports.fingerprintApiKey = exports.hashApiKey = exports.parseApiKey = exports.OAuthRequestError = exports.createPkcePair = exports.BFF_ACCESS_TOKEN_TYPE = exports.issueBffAccessToken = exports.ensureFreshServerAccess = exports.createTokenCipher = exports.createInMemorySessionStore = exports.createOAuthRoutes = exports.OAuthExchangeError = exports.ForestServerClient = exports.version = exports.createConsoleLogger = exports.DEFAULT_BFF_PORT = exports.ConfigurationError = exports.runCli = exports.REQUIRED_KEYS = exports.parseConfig = exports.BFFHttpServer = void 0;
7
7
  var bff_http_server_1 = require("./http/bff-http-server");
8
8
  Object.defineProperty(exports, "BFFHttpServer", { enumerable: true, get: function () { return __importDefault(bff_http_server_1).default; } });
9
9
  var env_config_1 = require("./config/env-config");
@@ -37,4 +37,28 @@ var pkce_1 = require("./oauth/pkce");
37
37
  Object.defineProperty(exports, "createPkcePair", { enumerable: true, get: function () { return pkce_1.createPkcePair; } });
38
38
  var oauth_error_1 = require("./oauth/oauth-error");
39
39
  Object.defineProperty(exports, "OAuthRequestError", { enumerable: true, get: function () { return oauth_error_1.OAuthRequestError; } });
40
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMERBQWtFO0FBQXpELGlJQUFBLE9BQU8sT0FBaUI7QUFDakMsa0RBQWlFO0FBQXhELHlHQUFBLFdBQVcsT0FBQTtBQUFFLDJHQUFBLGFBQWEsT0FBQTtBQUVuQyx1Q0FBK0M7QUFBdEMsbUhBQUEsT0FBTyxPQUFVO0FBQzFCLG1DQUE4QztBQUFyQyw0R0FBQSxrQkFBa0IsT0FBQTtBQUMzQix1Q0FBeUQ7QUFBaEQsNkhBQUEsT0FBTyxPQUFvQjtBQUNwQyw0REFBMkU7QUFBbEUsc0lBQUEsT0FBTyxPQUF1QjtBQUV2QyxxQ0FBK0M7QUFBdEMsbUhBQUEsT0FBTyxPQUFXO0FBQzNCLHFFQUFpRztBQUF4RiwySUFBQSxPQUFPLE9BQXNCO0FBQUUsMEhBQUEsa0JBQWtCLE9BQUE7QUFNMUQscURBQW9FO0FBQTNELGtJQUFBLE9BQU8sT0FBcUI7QUFDckMsdURBQThFO0FBQXJFLDRJQUFBLE9BQU8sT0FBOEI7QUFFOUMscURBQW9FO0FBQTNELGtJQUFBLE9BQU8sT0FBcUI7QUFDckMsK0RBQStFO0FBQXRFLDZJQUFBLE9BQU8sT0FBMkI7QUFDM0MsK0NBQStFO0FBQXRFLGdIQUFBLG1CQUFtQixPQUFBO0FBQUUsa0hBQUEscUJBQXFCLE9BQUE7QUFFbkQscUNBQThDO0FBQXJDLHNHQUFBLGNBQWMsT0FBQTtBQUN2QixtREFBd0Q7QUFBL0MsZ0hBQUEsaUJBQWlCLE9BQUEifQ==
40
+ var api_key_1 = require("./api-key/api-key");
41
+ Object.defineProperty(exports, "parseApiKey", { enumerable: true, get: function () { return api_key_1.parseApiKey; } });
42
+ Object.defineProperty(exports, "hashApiKey", { enumerable: true, get: function () { return api_key_1.hashApiKey; } });
43
+ Object.defineProperty(exports, "fingerprintApiKey", { enumerable: true, get: function () { return api_key_1.fingerprintApiKey; } });
44
+ var agent_token_1 = require("./api-key/agent-token");
45
+ Object.defineProperty(exports, "issueAgentToken", { enumerable: true, get: function () { return agent_token_1.issueAgentToken; } });
46
+ Object.defineProperty(exports, "AGENT_TOKEN_EXPIRES_IN", { enumerable: true, get: function () { return agent_token_1.AGENT_TOKEN_EXPIRES_IN; } });
47
+ var api_key_client_1 = require("./api-key/api-key-client");
48
+ Object.defineProperty(exports, "ApiKeyClient", { enumerable: true, get: function () { return __importDefault(api_key_client_1).default; } });
49
+ Object.defineProperty(exports, "ApiKeyResolveError", { enumerable: true, get: function () { return api_key_client_1.ApiKeyResolveError; } });
50
+ var resolve_cache_1 = require("./api-key/resolve-cache");
51
+ Object.defineProperty(exports, "createResolveCache", { enumerable: true, get: function () { return __importDefault(resolve_cache_1).default; } });
52
+ var api_key_authenticator_1 = require("./api-key/api-key-authenticator");
53
+ Object.defineProperty(exports, "createApiKeyAuthenticator", { enumerable: true, get: function () { return __importDefault(api_key_authenticator_1).default; } });
54
+ var api_key_middleware_1 = require("./api-key/api-key-middleware");
55
+ Object.defineProperty(exports, "createApiKeyMiddleware", { enumerable: true, get: function () { return __importDefault(api_key_middleware_1).default; } });
56
+ Object.defineProperty(exports, "BFF_KEY_HEADER", { enumerable: true, get: function () { return api_key_middleware_1.BFF_KEY_HEADER; } });
57
+ var api_key_error_1 = require("./api-key/api-key-error");
58
+ Object.defineProperty(exports, "ApiKeyError", { enumerable: true, get: function () { return api_key_error_1.ApiKeyError; } });
59
+ Object.defineProperty(exports, "invalidApiKey", { enumerable: true, get: function () { return api_key_error_1.invalidApiKey; } });
60
+ Object.defineProperty(exports, "forestIdentityNotAllowed", { enumerable: true, get: function () { return api_key_error_1.forestIdentityNotAllowed; } });
61
+ Object.defineProperty(exports, "invalidRequest", { enumerable: true, get: function () { return api_key_error_1.invalidRequest; } });
62
+ Object.defineProperty(exports, "keyResolutionUnavailable", { enumerable: true, get: function () { return api_key_error_1.keyResolutionUnavailable; } });
63
+ Object.defineProperty(exports, "toApiKeyErrorBody", { enumerable: true, get: function () { return api_key_error_1.toErrorBody; } });
64
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMERBQWtFO0FBQXpELGlJQUFBLE9BQU8sT0FBaUI7QUFDakMsa0RBQWlFO0FBQXhELHlHQUFBLFdBQVcsT0FBQTtBQUFFLDJHQUFBLGFBQWEsT0FBQTtBQUVuQyx1Q0FBK0M7QUFBdEMsbUhBQUEsT0FBTyxPQUFVO0FBQzFCLG1DQUE4QztBQUFyQyw0R0FBQSxrQkFBa0IsT0FBQTtBQUMzQix1Q0FBeUQ7QUFBaEQsNkhBQUEsT0FBTyxPQUFvQjtBQUNwQyw0REFBMkU7QUFBbEUsc0lBQUEsT0FBTyxPQUF1QjtBQUV2QyxxQ0FBK0M7QUFBdEMsbUhBQUEsT0FBTyxPQUFXO0FBQzNCLHFFQUFpRztBQUF4RiwySUFBQSxPQUFPLE9BQXNCO0FBQUUsMEhBQUEsa0JBQWtCLE9BQUE7QUFNMUQscURBQW9FO0FBQTNELGtJQUFBLE9BQU8sT0FBcUI7QUFDckMsdURBQThFO0FBQXJFLDRJQUFBLE9BQU8sT0FBOEI7QUFFOUMscURBQW9FO0FBQTNELGtJQUFBLE9BQU8sT0FBcUI7QUFDckMsK0RBQStFO0FBQXRFLDZJQUFBLE9BQU8sT0FBMkI7QUFDM0MsK0NBQStFO0FBQXRFLGdIQUFBLG1CQUFtQixPQUFBO0FBQUUsa0hBQUEscUJBQXFCLE9BQUE7QUFFbkQscUNBQThDO0FBQXJDLHNHQUFBLGNBQWMsT0FBQTtBQUN2QixtREFBd0Q7QUFBL0MsZ0hBQUEsaUJBQWlCLE9BQUE7QUFDMUIsNkNBQStFO0FBQXRFLHNHQUFBLFdBQVcsT0FBQTtBQUFFLHFHQUFBLFVBQVUsT0FBQTtBQUFFLDRHQUFBLGlCQUFpQixPQUFBO0FBRW5ELHFEQUFnRjtBQUF2RSw4R0FBQSxlQUFlLE9BQUE7QUFBRSxxSEFBQSxzQkFBc0IsT0FBQTtBQUNoRCwyREFBdUY7QUFBOUUsK0hBQUEsT0FBTyxPQUFnQjtBQUFFLG9IQUFBLGtCQUFrQixPQUFBO0FBTXBELHlEQUF3RTtBQUEvRCxvSUFBQSxPQUFPLE9BQXNCO0FBRXRDLHlFQUF1RjtBQUE5RSxtSkFBQSxPQUFPLE9BQTZCO0FBTTdDLG1FQUFpRztBQUF4Riw2SUFBQSxPQUFPLE9BQTBCO0FBQUUsb0hBQUEsY0FBYyxPQUFBO0FBQzFELHlEQU9pQztBQU4vQiw0R0FBQSxXQUFXLE9BQUE7QUFDWCw4R0FBQSxhQUFhLE9BQUE7QUFDYix5SEFBQSx3QkFBd0IsT0FBQTtBQUN4QiwrR0FBQSxjQUFjLE9BQUE7QUFDZCx5SEFBQSx3QkFBd0IsT0FBQTtBQUN4QixrSEFBQSxXQUFXLE9BQXFCIn0=
@@ -14,5 +14,6 @@ export declare function invalidGrant(message: string): OAuthRequestError;
14
14
  export declare function unsupportedGrantType(message: string): OAuthRequestError;
15
15
  export declare function forestIdentityNotAllowed(message: string): OAuthRequestError;
16
16
  export declare function sessionExpired(message: string): OAuthRequestError;
17
+ export declare function sessionInvalidated(message: string): OAuthRequestError;
17
18
  export declare function serverError(message: string): OAuthRequestError;
18
19
  //# sourceMappingURL=oauth-error.d.ts.map
@@ -8,6 +8,7 @@ exports.invalidGrant = invalidGrant;
8
8
  exports.unsupportedGrantType = unsupportedGrantType;
9
9
  exports.forestIdentityNotAllowed = forestIdentityNotAllowed;
10
10
  exports.sessionExpired = sessionExpired;
11
+ exports.sessionInvalidated = sessionInvalidated;
11
12
  exports.serverError = serverError;
12
13
  class OAuthRequestError extends Error {
13
14
  constructor(status, type, message) {
@@ -39,7 +40,10 @@ function forestIdentityNotAllowed(message) {
39
40
  function sessionExpired(message) {
40
41
  return new OAuthRequestError(401, 'session_expired', message);
41
42
  }
43
+ function sessionInvalidated(message) {
44
+ return new OAuthRequestError(401, 'session_invalidated', message);
45
+ }
42
46
  function serverError(message) {
43
47
  return new OAuthRequestError(502, 'server_error', message);
44
48
  }
45
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2F1dGgtZXJyb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvb2F1dGgvb2F1dGgtZXJyb3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBaUJBLGtDQUVDO0FBRUQsd0NBRUM7QUFFRCxzQ0FFQztBQUVELG9DQUVDO0FBRUQsb0RBRUM7QUFFRCw0REFFQztBQUVELHdDQUVDO0FBRUQsa0NBRUM7QUEvQ0QsTUFBYSxpQkFBa0IsU0FBUSxLQUFLO0lBSTFDLFlBQVksTUFBYyxFQUFFLElBQVksRUFBRSxPQUFlO1FBQ3ZELEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNmLElBQUksQ0FBQyxJQUFJLEdBQUcsbUJBQW1CLENBQUM7UUFDaEMsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7SUFDbkIsQ0FBQztDQUNGO0FBVkQsOENBVUM7QUFPRCxTQUFnQixXQUFXLENBQUMsS0FBd0I7SUFDbEQsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsSUFBSSxFQUFFLGlCQUFpQixFQUFFLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQztBQUNqRSxDQUFDO0FBRUQsU0FBZ0IsY0FBYyxDQUFDLE9BQWU7SUFDNUMsT0FBTyxJQUFJLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxpQkFBaUIsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUNoRSxDQUFDO0FBRUQsU0FBZ0IsYUFBYSxDQUFDLE9BQWU7SUFDM0MsT0FBTyxJQUFJLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxnQkFBZ0IsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUMvRCxDQUFDO0FBRUQsU0FBZ0IsWUFBWSxDQUFDLE9BQWU7SUFDMUMsT0FBTyxJQUFJLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxlQUFlLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDOUQsQ0FBQztBQUVELFNBQWdCLG9CQUFvQixDQUFDLE9BQWU7SUFDbEQsT0FBTyxJQUFJLGlCQUFpQixDQUFDLEdBQUcsRUFBRSx3QkFBd0IsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUN2RSxDQUFDO0FBRUQsU0FBZ0Isd0JBQXdCLENBQUMsT0FBZTtJQUN0RCxPQUFPLElBQUksaUJBQWlCLENBQUMsR0FBRyxFQUFFLDZCQUE2QixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzVFLENBQUM7QUFFRCxTQUFnQixjQUFjLENBQUMsT0FBZTtJQUM1QyxPQUFPLElBQUksaUJBQWlCLENBQUMsR0FBRyxFQUFFLGlCQUFpQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ2hFLENBQUM7QUFFRCxTQUFnQixXQUFXLENBQUMsT0FBZTtJQUN6QyxPQUFPLElBQUksaUJBQWlCLENBQUMsR0FBRyxFQUFFLGNBQWMsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUM3RCxDQUFDIn0=
49
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2F1dGgtZXJyb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvb2F1dGgvb2F1dGgtZXJyb3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBaUJBLGtDQUVDO0FBRUQsd0NBRUM7QUFFRCxzQ0FFQztBQUVELG9DQUVDO0FBRUQsb0RBRUM7QUFFRCw0REFFQztBQUVELHdDQUVDO0FBRUQsZ0RBRUM7QUFFRCxrQ0FFQztBQW5ERCxNQUFhLGlCQUFrQixTQUFRLEtBQUs7SUFJMUMsWUFBWSxNQUFjLEVBQUUsSUFBWSxFQUFFLE9BQWU7UUFDdkQsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2YsSUFBSSxDQUFDLElBQUksR0FBRyxtQkFBbUIsQ0FBQztRQUNoQyxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNyQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztJQUNuQixDQUFDO0NBQ0Y7QUFWRCw4Q0FVQztBQU9ELFNBQWdCLFdBQVcsQ0FBQyxLQUF3QjtJQUNsRCxPQUFPLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxJQUFJLEVBQUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDO0FBQ2pFLENBQUM7QUFFRCxTQUFnQixjQUFjLENBQUMsT0FBZTtJQUM1QyxPQUFPLElBQUksaUJBQWlCLENBQUMsR0FBRyxFQUFFLGlCQUFpQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ2hFLENBQUM7QUFFRCxTQUFnQixhQUFhLENBQUMsT0FBZTtJQUMzQyxPQUFPLElBQUksaUJBQWlCLENBQUMsR0FBRyxFQUFFLGdCQUFnQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQy9ELENBQUM7QUFFRCxTQUFnQixZQUFZLENBQUMsT0FBZTtJQUMxQyxPQUFPLElBQUksaUJBQWlCLENBQUMsR0FBRyxFQUFFLGVBQWUsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUM5RCxDQUFDO0FBRUQsU0FBZ0Isb0JBQW9CLENBQUMsT0FBZTtJQUNsRCxPQUFPLElBQUksaUJBQWlCLENBQUMsR0FBRyxFQUFFLHdCQUF3QixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ3ZFLENBQUM7QUFFRCxTQUFnQix3QkFBd0IsQ0FBQyxPQUFlO0lBQ3RELE9BQU8sSUFBSSxpQkFBaUIsQ0FBQyxHQUFHLEVBQUUsNkJBQTZCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDNUUsQ0FBQztBQUVELFNBQWdCLGNBQWMsQ0FBQyxPQUFlO0lBQzVDLE9BQU8sSUFBSSxpQkFBaUIsQ0FBQyxHQUFHLEVBQUUsaUJBQWlCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDaEUsQ0FBQztBQUVELFNBQWdCLGtCQUFrQixDQUFDLE9BQWU7SUFDaEQsT0FBTyxJQUFJLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxxQkFBcUIsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUNwRSxDQUFDO0FBRUQsU0FBZ0IsV0FBVyxDQUFDLE9BQWU7SUFDekMsT0FBTyxJQUFJLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDN0QsQ0FBQyJ9
@@ -1,9 +1,18 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.default = createOAuthRoutes;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
4
9
  const bff_token_1 = require("./bff-token");
5
10
  const forest_server_client_1 = require("./forest-server-client");
6
11
  const oauth_error_1 = require("./oauth-error");
12
+ const session_lifecycle_1 = __importDefault(require("./session-lifecycle"));
13
+ function hashPresentedToken(token) {
14
+ return crypto_1.default.createHash('sha256').update(token).digest('base64url');
15
+ }
7
16
  const CODE_VERIFIER_PATTERN = /^[A-Za-z0-9\-._~]{43,128}$/;
8
17
  const S256_CODE_CHALLENGE_PATTERN = /^[A-Za-z0-9_-]{43}$/;
9
18
  const SAFE_EXCHANGE_ERRORS = new Set([
@@ -123,15 +132,15 @@ function computeExpiresIn(serverTokens) {
123
132
  }
124
133
  return Math.min(bff_token_1.BFF_ACCESS_TOKEN_MAX_EXPIRES_IN, saasRemaining);
125
134
  }
126
- async function parseTokenRequest(ctx, options) {
135
+ function parseBody(ctx) {
127
136
  const { body } = ctx.request;
128
137
  if (typeof body !== 'object' || body === null) {
129
138
  throw (0, oauth_error_1.invalidRequest)('Missing request body');
130
139
  }
131
- const params = body;
132
- if (params.grant_type !== 'authorization_code') {
133
- throw (0, oauth_error_1.unsupportedGrantType)('Only authorization_code is supported');
134
- }
140
+ return body;
141
+ }
142
+ async function parseTokenRequest(ctx, options) {
143
+ const params = parseBody(ctx);
135
144
  const request = {
136
145
  code: requireBodyString(params, 'code'),
137
146
  clientId: requireBodyString(params, 'client_id'),
@@ -161,18 +170,27 @@ async function exchangeForServerTokens(request, options) {
161
170
  function isRetriableExchangeError(error) {
162
171
  return !(error instanceof oauth_error_1.OAuthRequestError) || error.status >= 500;
163
172
  }
164
- async function resolveIdentity(serverTokens, options) {
173
+ async function resolveIdentity(renderingId, saasAccessToken, options) {
165
174
  try {
166
- return await options.serverClient.getUserInfo(serverTokens.renderingId, serverTokens.saasAccessToken);
175
+ return await options.serverClient.getUserInfo(renderingId, saasAccessToken);
167
176
  }
168
177
  catch (error) {
169
- options.logger('Warn', 'Failed to resolve user info after exchange', {
170
- renderingId: serverTokens.renderingId,
171
- });
178
+ options.logger('Warn', 'Failed to resolve user info after exchange', { renderingId });
172
179
  throw mapIdentityError(error);
173
180
  }
174
181
  }
175
- async function handleToken(ctx, options) {
182
+ function writeTokenResponse(ctx, accessToken, expiresInSeconds, refreshToken) {
183
+ ctx.set('Cache-Control', 'no-store');
184
+ ctx.set('Pragma', 'no-cache');
185
+ ctx.status = 200;
186
+ ctx.body = {
187
+ access_token: accessToken,
188
+ token_type: 'Bearer',
189
+ expires_in: expiresInSeconds,
190
+ refresh_token: refreshToken,
191
+ };
192
+ }
193
+ async function handleAuthorizationCodeGrant(ctx, options) {
176
194
  const request = await parseTokenRequest(ctx, options);
177
195
  if (!options.sessionStore.claimAuthorizationCode(request.code)) {
178
196
  throw (0, oauth_error_1.invalidGrant)('Authorization code has already been used');
@@ -188,7 +206,7 @@ async function handleToken(ctx, options) {
188
206
  throw error;
189
207
  }
190
208
  const expiresInSeconds = computeExpiresIn(serverTokens);
191
- const user = await resolveIdentity(serverTokens, options);
209
+ const user = await resolveIdentity(serverTokens.renderingId, serverTokens.saasAccessToken, options);
192
210
  const { sid, refreshToken } = options.sessionStore.create({
193
211
  saasAccessToken: serverTokens.saasAccessToken,
194
212
  saasRefreshToken: serverTokens.saasRefreshToken,
@@ -206,15 +224,91 @@ async function handleToken(ctx, options) {
206
224
  renderingId: serverTokens.renderingId,
207
225
  userId: user.id,
208
226
  });
209
- ctx.set('Cache-Control', 'no-store');
210
- ctx.set('Pragma', 'no-cache');
211
- ctx.status = 200;
212
- ctx.body = {
213
- access_token: accessToken,
214
- token_type: 'Bearer',
215
- expires_in: expiresInSeconds,
216
- refresh_token: refreshToken,
217
- };
227
+ writeTokenResponse(ctx, accessToken, expiresInSeconds, refreshToken);
228
+ }
229
+ function expiresInFromAccessToken(saasAccessToken) {
230
+ const decoded = jsonwebtoken_1.default.decode(saasAccessToken);
231
+ if (typeof decoded?.exp !== 'number' || !Number.isFinite(decoded.exp)) {
232
+ return bff_token_1.BFF_ACCESS_TOKEN_MAX_EXPIRES_IN;
233
+ }
234
+ const saasRemaining = decoded.exp - Math.floor(Date.now() / 1000);
235
+ if (saasRemaining <= 0) {
236
+ throw (0, oauth_error_1.sessionExpired)('The Forest server access token is already expired');
237
+ }
238
+ return Math.min(bff_token_1.BFF_ACCESS_TOKEN_MAX_EXPIRES_IN, saasRemaining);
239
+ }
240
+ async function issueAccessForSession(sid, renderingId, refreshToken, options) {
241
+ const saasAccessToken = await (0, session_lifecycle_1.default)({
242
+ sid,
243
+ store: options.sessionStore,
244
+ serverClient: options.serverClient,
245
+ });
246
+ const expiresInSeconds = expiresInFromAccessToken(saasAccessToken);
247
+ const user = await resolveIdentity(renderingId, saasAccessToken, options);
248
+ const accessToken = (0, bff_token_1.issueBffAccessToken)({
249
+ sid,
250
+ user,
251
+ renderingId,
252
+ authSecret: options.authSecret,
253
+ expiresInSeconds,
254
+ });
255
+ return { sid, accessToken, expiresInSeconds, newRefreshToken: refreshToken };
256
+ }
257
+ async function reissueThenCommitRotation(presentedToken, sid, renderingId, newRefreshToken, options) {
258
+ const result = await issueAccessForSession(sid, renderingId, newRefreshToken, options);
259
+ if (!options.sessionStore.commitRotation(presentedToken, newRefreshToken)) {
260
+ throw (0, oauth_error_1.sessionExpired)('Session expired during refresh');
261
+ }
262
+ return result;
263
+ }
264
+ async function handleRefreshGrant(ctx, options) {
265
+ const params = parseBody(ctx);
266
+ const presentedToken = requireBodyString(params, 'refresh_token');
267
+ const presentedHash = hashPresentedToken(presentedToken);
268
+ const inFlight = options.inFlightRefreshByPresentedHash;
269
+ let pending = inFlight.get(presentedHash);
270
+ if (!pending) {
271
+ const rotation = options.sessionStore.prepareRotation(presentedToken);
272
+ if (rotation.outcome === 'reuse') {
273
+ options.sessionStore.destroy(rotation.sid);
274
+ options.logger('Warn', 'Rotated-out refresh token replayed; session invalidated', {
275
+ sid: rotation.sid,
276
+ });
277
+ throw (0, oauth_error_1.sessionInvalidated)('Refresh token was already used; the session is invalidated');
278
+ }
279
+ if (rotation.outcome === 'unknown') {
280
+ throw (0, oauth_error_1.invalidGrant)('Unknown or malformed refresh token');
281
+ }
282
+ if (rotation.outcome === 'replay') {
283
+ options.logger('Info', 'Replayed the last issued pair for a benign retry', {
284
+ sid: rotation.sid,
285
+ });
286
+ pending = issueAccessForSession(rotation.sid, rotation.renderingId, rotation.lastIssuedRefreshToken, options).finally(() => {
287
+ inFlight.delete(presentedHash);
288
+ });
289
+ }
290
+ else {
291
+ pending = reissueThenCommitRotation(presentedToken, rotation.sid, rotation.renderingId, rotation.newRefreshToken, options).finally(() => {
292
+ inFlight.delete(presentedHash);
293
+ });
294
+ }
295
+ inFlight.set(presentedHash, pending);
296
+ }
297
+ const result = await pending;
298
+ options.logger('Info', 'Refreshed BFF session token', { sid: result.sid });
299
+ writeTokenResponse(ctx, result.accessToken, result.expiresInSeconds, result.newRefreshToken);
300
+ }
301
+ async function handleToken(ctx, options) {
302
+ const params = parseBody(ctx);
303
+ if (params.grant_type === 'refresh_token') {
304
+ await handleRefreshGrant(ctx, options);
305
+ return;
306
+ }
307
+ if (params.grant_type === 'authorization_code') {
308
+ await handleAuthorizationCodeGrant(ctx, options);
309
+ return;
310
+ }
311
+ throw (0, oauth_error_1.unsupportedGrantType)('Only authorization_code and refresh_token are supported');
218
312
  }
219
313
  function matchRoute(ctx) {
220
314
  if (ctx.method === 'GET' && ctx.path === '/oauth/authorize')
@@ -237,6 +331,10 @@ function writeError(ctx, error, options) {
237
331
  ctx.body = { error: 'server_error', error_description: 'OAuth processing failed' };
238
332
  }
239
333
  function createOAuthRoutes(options) {
334
+ const runtimeOptions = {
335
+ ...options,
336
+ inFlightRefreshByPresentedHash: new Map(),
337
+ };
240
338
  return async function oauthRoutes(ctx, next) {
241
339
  const handler = matchRoute(ctx);
242
340
  if (!handler) {
@@ -244,11 +342,11 @@ function createOAuthRoutes(options) {
244
342
  return;
245
343
  }
246
344
  try {
247
- await handler(ctx, options);
345
+ await handler(ctx, runtimeOptions);
248
346
  }
249
347
  catch (error) {
250
- writeError(ctx, error, options);
348
+ writeError(ctx, error, runtimeOptions);
251
349
  }
252
350
  };
253
351
  }
254
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2F1dGgtcm91dGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL29hdXRoL29hdXRoLXJvdXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQWdXQSxvQ0FnQkM7QUF6V0QsMkNBQW1GO0FBQ25GLGlFQUE0RDtBQUM1RCwrQ0FRdUI7QUFXdkIsTUFBTSxxQkFBcUIsR0FBRyw0QkFBNEIsQ0FBQztBQUMzRCxNQUFNLDJCQUEyQixHQUFHLHFCQUFxQixDQUFDO0FBRTFELE1BQU0sb0JBQW9CLEdBQUcsSUFBSSxHQUFHLENBQUM7SUFDbkMsaUJBQWlCO0lBQ2pCLGdCQUFnQjtJQUNoQixlQUFlO0lBQ2YscUJBQXFCO0lBQ3JCLHdCQUF3QjtJQUN4QixlQUFlO0NBQ2hCLENBQUMsQ0FBQztBQUVILFNBQVMsYUFBYSxDQUFDLEdBQVksRUFBRSxHQUFXO0lBQzlDLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFN0IsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDekIsTUFBTSxJQUFBLDRCQUFjLEVBQUMsdUNBQXVDLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDckUsQ0FBQztJQUVELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQztBQUVELFNBQVMsaUJBQWlCLENBQUMsSUFBNkIsRUFBRSxHQUFXO0lBQ25FLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUV4QixJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLEtBQUssRUFBRSxFQUFFLENBQUM7UUFDOUMsTUFBTSxJQUFBLDRCQUFjLEVBQUMsK0JBQStCLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUVELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQztBQUVELFNBQVMsdUJBQXVCLENBQUMsWUFBb0I7SUFDbkQsSUFBSSxDQUFDLHFCQUFxQixDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDO1FBQzlDLE1BQU0sSUFBQSw0QkFBYyxFQUFDLDRCQUE0QixDQUFDLENBQUM7SUFDckQsQ0FBQztBQUNILENBQUM7QUFFRCxTQUFTLDJCQUEyQixDQUNsQyxZQUFrQyxFQUNsQyxXQUFtQixFQUNuQixVQUFrRDtJQUVsRCxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUMsSUFBSSxZQUFZLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQzlELE1BQU0sSUFBQSw0QkFBYyxFQUFDLHVDQUF1QyxDQUFDLENBQUM7SUFDaEUsQ0FBQztJQUVELElBQUksQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUM7UUFDeEMsTUFBTSxVQUFVLENBQUMsbURBQW1ELENBQUMsQ0FBQztJQUN4RSxDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQVMsbUJBQW1CLENBQUMsU0FBaUI7SUFDNUMsSUFBSSxvQkFBb0IsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQztRQUN4QyxPQUFPLElBQUksK0JBQWlCLENBQUMsR0FBRyxFQUFFLFNBQVMsRUFBRSxvQ0FBb0MsQ0FBQyxDQUFDO0lBQ3JGLENBQUM7SUFFRCxPQUFPLElBQUksK0JBQWlCLENBQzFCLEdBQUcsRUFDSCxjQUFjLEVBQ2QsK0NBQStDLENBQ2hELENBQUM7QUFDSixDQUFDO0FBRUQsU0FBUyxnQkFBZ0IsQ0FBQyxLQUFjO0lBQ3RDLE1BQU0sSUFBSSxHQUFHLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUV0RCxJQUFJLElBQUksS0FBSyxnQkFBZ0IsRUFBRSxDQUFDO1FBQzlCLE9BQU8sSUFBQSxzQ0FBd0IsRUFBQyxpREFBaUQsQ0FBQyxDQUFDO0lBQ3JGLENBQUM7SUFFRCxPQUFPLElBQUksK0JBQWlCLENBQUMsR0FBRyxFQUFFLDRCQUE0QixFQUFFLDRCQUE0QixDQUFDLENBQUM7QUFDaEcsQ0FBQztBQUVELFNBQVMsc0JBQXNCLENBQzdCLEdBQVksRUFDWixXQUFtQixFQUNuQixLQUF5QixFQUN6QixLQUF3QjtJQUV4QixNQUFNLE1BQU0sR0FBRyxJQUFJLEdBQUcsQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUNwQyxNQUFNLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzdDLE1BQU0sQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLG1CQUFtQixFQUFFLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUM1RCxJQUFJLEtBQUssS0FBSyxTQUFTLElBQUksS0FBSyxLQUFLLEVBQUU7UUFBRSxNQUFNLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFFakYsR0FBRyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztBQUNsQyxDQUFDO0FBRUQsS0FBSyxVQUFVLGVBQWUsQ0FBQyxHQUFZLEVBQUUsT0FBMkI7SUFDdEUsNkVBQTZFO0lBQzdFLDZFQUE2RTtJQUM3RSxNQUFNLFFBQVEsR0FBRyxhQUFhLENBQUMsR0FBRyxFQUFFLFdBQVcsQ0FBQyxDQUFDO0lBRWpELElBQUksUUFBUSxLQUFLLFNBQVMsSUFBSSxRQUFRLEtBQUssRUFBRSxFQUFFLENBQUM7UUFDOUMsTUFBTSxJQUFBLDRCQUFjLEVBQUMsdUNBQXVDLENBQUMsQ0FBQztJQUNoRSxDQUFDO0lBRUQsTUFBTSxXQUFXLEdBQUcsYUFBYSxDQUFDLEdBQUcsRUFBRSxjQUFjLENBQUMsQ0FBQztJQUV2RCxJQUFJLFdBQVcsS0FBSyxTQUFTLElBQUksV0FBVyxLQUFLLEVBQUUsRUFBRSxDQUFDO1FBQ3BELE1BQU0sSUFBQSw0QkFBYyxFQUFDLDBDQUEwQyxDQUFDLENBQUM7SUFDbkUsQ0FBQztJQUVELE1BQU0sTUFBTSxHQUFHLE1BQU0sT0FBTyxDQUFDLFlBQVksQ0FBQyxtQkFBbUIsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUV4RSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDWixNQUFNLElBQUEsNEJBQWMsRUFBQyxtQkFBbUIsQ0FBQyxDQUFDO0lBQzVDLENBQUM7SUFFRCwyQkFBMkIsQ0FBQyxNQUFNLENBQUMsYUFBYSxFQUFFLFdBQVcsRUFBRSw0QkFBYyxDQUFDLENBQUM7SUFFL0UsNEVBQTRFO0lBQzVFLHFEQUFxRDtJQUNyRCxJQUFJLEtBQXlCLENBQUM7SUFFOUIsSUFBSSxDQUFDO1FBQ0gsS0FBSyxHQUFHLGFBQWEsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDcEMsTUFBTSxZQUFZLEdBQUcsYUFBYSxDQUFDLEdBQUcsRUFBRSxlQUFlLENBQUMsQ0FBQztRQUV6RCxJQUFJLFlBQVksS0FBSyxNQUFNLEVBQUUsQ0FBQztZQUM1QixNQUFNLElBQUEsNEJBQWMsRUFBQyw4QkFBOEIsQ0FBQyxDQUFDO1FBQ3ZELENBQUM7UUFFRCxJQUFJLGFBQWEsQ0FBQyxHQUFHLEVBQUUsdUJBQXVCLENBQUMsS0FBSyxNQUFNLEVBQUUsQ0FBQztZQUMzRCxNQUFNLElBQUEsNEJBQWMsRUFBQyxvQ0FBb0MsQ0FBQyxDQUFDO1FBQzdELENBQUM7UUFFRCxNQUFNLGFBQWEsR0FBRyxhQUFhLENBQUMsR0FBRyxFQUFFLGdCQUFnQixDQUFDLENBQUM7UUFFM0QsSUFBSSxhQUFhLEtBQUssU0FBUyxJQUFJLENBQUMsMkJBQTJCLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUM7WUFDcEYsTUFBTSxJQUFBLDRCQUFjLEVBQUMsd0NBQXdDLENBQUMsQ0FBQztRQUNqRSxDQUFDO1FBRUQsSUFBSSxLQUFLLEtBQUssU0FBUyxJQUFJLEtBQUssS0FBSyxFQUFFLEVBQUUsQ0FBQztZQUN4QyxNQUFNLElBQUEsNEJBQWMsRUFBQyxtQ0FBbUMsQ0FBQyxDQUFDO1FBQzVELENBQUM7UUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDOUMsTUFBTSxZQUFZLEdBQUcsSUFBSSxHQUFHLENBQUMsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLGtCQUFrQixFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ2hHLFlBQVksQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxRQUFRLENBQUMsQ0FBQztRQUNyRCxZQUFZLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxjQUFjLEVBQUUsV0FBVyxDQUFDLENBQUM7UUFDM0QsWUFBWSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsZUFBZSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3ZELFlBQVksQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLGdCQUFnQixFQUFFLGFBQWEsQ0FBQyxDQUFDO1FBQy9ELFlBQVksQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLHVCQUF1QixFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQy9ELFlBQVksQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQztRQUM5QyxZQUFZLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxlQUFlLEVBQUUsTUFBTSxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDO1FBRTlFLEdBQUcsQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7SUFDeEMsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixJQUFJLEtBQUssWUFBWSwrQkFBaUIsRUFBRSxDQUFDO1lBQ3ZDLHNCQUFzQixDQUFDLEdBQUcsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDO1lBRXZELE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxLQUFLLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQVMsZ0JBQWdCLENBQUMsWUFBMEI7SUFDbEQsTUFBTSxhQUFhLEdBQUcsWUFBWSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztJQUU3RSxJQUFJLGFBQWEsSUFBSSxDQUFDLEVBQUUsQ0FBQztRQUN2QixNQUFNLElBQUEsMEJBQVksRUFBQyxtREFBbUQsQ0FBQyxDQUFDO0lBQzFFLENBQUM7SUFFRCxPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsMkNBQStCLEVBQUUsYUFBYSxDQUFDLENBQUM7QUFDbEUsQ0FBQztBQVNELEtBQUssVUFBVSxpQkFBaUIsQ0FBQyxHQUFZLEVBQUUsT0FBMkI7SUFDeEUsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLEdBQUcsQ0FBQyxPQUE2QixDQUFDO0lBRW5ELElBQUksT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksS0FBSyxJQUFJLEVBQUUsQ0FBQztRQUM5QyxNQUFNLElBQUEsNEJBQWMsRUFBQyxzQkFBc0IsQ0FBQyxDQUFDO0lBQy9DLENBQUM7SUFFRCxNQUFNLE1BQU0sR0FBRyxJQUErQixDQUFDO0lBRS9DLElBQUksTUFBTSxDQUFDLFVBQVUsS0FBSyxvQkFBb0IsRUFBRSxDQUFDO1FBQy9DLE1BQU0sSUFBQSxrQ0FBb0IsRUFBQyxzQ0FBc0MsQ0FBQyxDQUFDO0lBQ3JFLENBQUM7SUFFRCxNQUFNLE9BQU8sR0FBaUI7UUFDNUIsSUFBSSxFQUFFLGlCQUFpQixDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUM7UUFDdkMsUUFBUSxFQUFFLGlCQUFpQixDQUFDLE1BQU0sRUFBRSxXQUFXLENBQUM7UUFDaEQsWUFBWSxFQUFFLGlCQUFpQixDQUFDLE1BQU0sRUFBRSxlQUFlLENBQUM7UUFDeEQsV0FBVyxFQUFFLGlCQUFpQixDQUFDLE1BQU0sRUFBRSxjQUFjLENBQUM7S0FDdkQsQ0FBQztJQUVGLHVCQUF1QixDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUU5QyxNQUFNLE1BQU0sR0FBRyxNQUFNLE9BQU8sQ0FBQyxZQUFZLENBQUMsbUJBQW1CLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBRWhGLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNaLE1BQU0sSUFBQSwyQkFBYSxFQUFDLG1CQUFtQixDQUFDLENBQUM7SUFDM0MsQ0FBQztJQUVELDJCQUEyQixDQUFDLE1BQU0sQ0FBQyxhQUFhLEVBQUUsT0FBTyxDQUFDLFdBQVcsRUFBRSwwQkFBWSxDQUFDLENBQUM7SUFFckYsT0FBTyxPQUFPLENBQUM7QUFDakIsQ0FBQztBQUVELEtBQUssVUFBVSx1QkFBdUIsQ0FDcEMsT0FBcUIsRUFDckIsT0FBMkI7SUFFM0IsSUFBSSxDQUFDO1FBQ0gsT0FBTyxNQUFNLE9BQU8sQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzFELENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsSUFBSSxLQUFLLFlBQVkseUNBQWtCLEVBQUUsQ0FBQztZQUN4QyxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxzQ0FBc0MsRUFBRSxFQUFFLFNBQVMsRUFBRSxLQUFLLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQztZQUMzRixNQUFNLG1CQUFtQixDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN6QyxDQUFDO1FBRUQsTUFBTSxLQUFLLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQVMsd0JBQXdCLENBQUMsS0FBYztJQUM5QyxPQUFPLENBQUMsQ0FBQyxLQUFLLFlBQVksK0JBQWlCLENBQUMsSUFBSSxLQUFLLENBQUMsTUFBTSxJQUFJLEdBQUcsQ0FBQztBQUN0RSxDQUFDO0FBRUQsS0FBSyxVQUFVLGVBQWUsQ0FDNUIsWUFBMEIsRUFDMUIsT0FBMkI7SUFFM0IsSUFBSSxDQUFDO1FBQ0gsT0FBTyxNQUFNLE9BQU8sQ0FBQyxZQUFZLENBQUMsV0FBVyxDQUMzQyxZQUFZLENBQUMsV0FBVyxFQUN4QixZQUFZLENBQUMsZUFBZSxDQUM3QixDQUFDO0lBQ0osQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSw0Q0FBNEMsRUFBRTtZQUNuRSxXQUFXLEVBQUUsWUFBWSxDQUFDLFdBQVc7U0FDdEMsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNoQyxDQUFDO0FBQ0gsQ0FBQztBQUVELEtBQUssVUFBVSxXQUFXLENBQUMsR0FBWSxFQUFFLE9BQTJCO0lBQ2xFLE1BQU0sT0FBTyxHQUFHLE1BQU0saUJBQWlCLENBQUMsR0FBRyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBRXRELElBQUksQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLHNCQUFzQixDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQy9ELE1BQU0sSUFBQSwwQkFBWSxFQUFDLDBDQUEwQyxDQUFDLENBQUM7SUFDakUsQ0FBQztJQUVELElBQUksWUFBMEIsQ0FBQztJQUUvQixJQUFJLENBQUM7UUFDSCxZQUFZLEdBQUcsTUFBTSx1QkFBdUIsQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDakUsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixJQUFJLHdCQUF3QixDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDcEMsT0FBTyxDQUFDLFlBQVksQ0FBQyx3QkFBd0IsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDOUQsQ0FBQztRQUVELE1BQU0sS0FBSyxDQUFDO0lBQ2QsQ0FBQztJQUVELE1BQU0sZ0JBQWdCLEdBQUcsZ0JBQWdCLENBQUMsWUFBWSxDQUFDLENBQUM7SUFDeEQsTUFBTSxJQUFJLEdBQUcsTUFBTSxlQUFlLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBRTFELE1BQU0sRUFBRSxHQUFHLEVBQUUsWUFBWSxFQUFFLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUM7UUFDeEQsZUFBZSxFQUFFLFlBQVksQ0FBQyxlQUFlO1FBQzdDLGdCQUFnQixFQUFFLFlBQVksQ0FBQyxnQkFBZ0I7UUFDL0MsV0FBVyxFQUFFLFlBQVksQ0FBQyxXQUFXO1FBQ3JDLE1BQU0sRUFBRSxJQUFJLENBQUMsRUFBRTtLQUNoQixDQUFDLENBQUM7SUFFSCxNQUFNLFdBQVcsR0FBRyxJQUFBLCtCQUFtQixFQUFDO1FBQ3RDLEdBQUc7UUFDSCxJQUFJO1FBQ0osV0FBVyxFQUFFLFlBQVksQ0FBQyxXQUFXO1FBQ3JDLFVBQVUsRUFBRSxPQUFPLENBQUMsVUFBVTtRQUM5QixnQkFBZ0I7S0FDakIsQ0FBQyxDQUFDO0lBRUgsT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsMEJBQTBCLEVBQUU7UUFDakQsV0FBVyxFQUFFLFlBQVksQ0FBQyxXQUFXO1FBQ3JDLE1BQU0sRUFBRSxJQUFJLENBQUMsRUFBRTtLQUNoQixDQUFDLENBQUM7SUFFSCxHQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUNyQyxHQUFHLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUM5QixHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQztJQUNqQixHQUFHLENBQUMsSUFBSSxHQUFHO1FBQ1QsWUFBWSxFQUFFLFdBQVc7UUFDekIsVUFBVSxFQUFFLFFBQVE7UUFDcEIsVUFBVSxFQUFFLGdCQUFnQjtRQUM1QixhQUFhLEVBQUUsWUFBWTtLQUM1QixDQUFDO0FBQ0osQ0FBQztBQUlELFNBQVMsVUFBVSxDQUFDLEdBQVk7SUFDOUIsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLEtBQUssSUFBSSxHQUFHLENBQUMsSUFBSSxLQUFLLGtCQUFrQjtRQUFFLE9BQU8sZUFBZSxDQUFDO0lBQ3BGLElBQUksR0FBRyxDQUFDLE1BQU0sS0FBSyxNQUFNLElBQUksR0FBRyxDQUFDLElBQUksS0FBSyxjQUFjO1FBQUUsT0FBTyxXQUFXLENBQUM7SUFFN0UsT0FBTyxTQUFTLENBQUM7QUFDbkIsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVksRUFBRSxLQUFjLEVBQUUsT0FBMkI7SUFDM0UsSUFBSSxLQUFLLFlBQVksK0JBQWlCLEVBQUUsQ0FBQztRQUN2QyxHQUFHLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFDMUIsR0FBRyxDQUFDLElBQUksR0FBRyxJQUFBLHlCQUFXLEVBQUMsS0FBSyxDQUFDLENBQUM7UUFFOUIsT0FBTztJQUNULENBQUM7SUFFRCxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxxQkFBcUIsRUFBRTtRQUM3QyxJQUFJLEVBQUUsR0FBRyxDQUFDLElBQUk7UUFDZCxLQUFLLEVBQUUsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxLQUFLLENBQUMsSUFBSSxLQUFLLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQztLQUNsRixDQUFDLENBQUM7SUFDSCxHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQztJQUNqQixHQUFHLENBQUMsSUFBSSxHQUFHLEVBQUUsS0FBSyxFQUFFLGNBQWMsRUFBRSxpQkFBaUIsRUFBRSx5QkFBeUIsRUFBRSxDQUFDO0FBQ3JGLENBQUM7QUFFRCxTQUF3QixpQkFBaUIsQ0FBQyxPQUEyQjtJQUNuRSxPQUFPLEtBQUssVUFBVSxXQUFXLENBQUMsR0FBRyxFQUFFLElBQUk7UUFDekMsTUFBTSxPQUFPLEdBQUcsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRWhDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNiLE1BQU0sSUFBSSxFQUFFLENBQUM7WUFFYixPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksQ0FBQztZQUNILE1BQU0sT0FBTyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUM5QixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLFVBQVUsQ0FBQyxHQUFHLEVBQUUsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ2xDLENBQUM7SUFDSCxDQUFDLENBQUM7QUFDSixDQUFDIn0=
352
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2F1dGgtcm91dGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL29hdXRoL29hdXRoLXJvdXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQXFnQkEsb0NBcUJDO0FBbmhCRCxvREFBNEI7QUFDNUIsZ0VBQXdDO0FBRXhDLDJDQUFtRjtBQUNuRixpRUFBNEQ7QUFDNUQsK0NBVXVCO0FBQ3ZCLDRFQUEwRDtBQUUxRCxTQUFTLGtCQUFrQixDQUFDLEtBQWE7SUFDdkMsT0FBTyxnQkFBTSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBQ3ZFLENBQUM7QUFXRCxNQUFNLHFCQUFxQixHQUFHLDRCQUE0QixDQUFDO0FBQzNELE1BQU0sMkJBQTJCLEdBQUcscUJBQXFCLENBQUM7QUFFMUQsTUFBTSxvQkFBb0IsR0FBRyxJQUFJLEdBQUcsQ0FBQztJQUNuQyxpQkFBaUI7SUFDakIsZ0JBQWdCO0lBQ2hCLGVBQWU7SUFDZixxQkFBcUI7SUFDckIsd0JBQXdCO0lBQ3hCLGVBQWU7Q0FDaEIsQ0FBQyxDQUFDO0FBRUgsU0FBUyxhQUFhLENBQUMsR0FBWSxFQUFFLEdBQVc7SUFDOUMsTUFBTSxLQUFLLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUU3QixJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUN6QixNQUFNLElBQUEsNEJBQWMsRUFBQyx1Q0FBdUMsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNyRSxDQUFDO0lBRUQsT0FBTyxLQUFLLENBQUM7QUFDZixDQUFDO0FBRUQsU0FBUyxpQkFBaUIsQ0FBQyxJQUE2QixFQUFFLEdBQVc7SUFDbkUsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBRXhCLElBQUksT0FBTyxLQUFLLEtBQUss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
@@ -17,6 +17,22 @@ export interface CreatedSession {
17
17
  sid: string;
18
18
  refreshToken: string;
19
19
  }
20
+ export type PrepareRotationResult = {
21
+ outcome: 'ready';
22
+ sid: string;
23
+ renderingId: number;
24
+ newRefreshToken: string;
25
+ } | {
26
+ outcome: 'replay';
27
+ sid: string;
28
+ renderingId: number;
29
+ lastIssuedRefreshToken: string;
30
+ } | {
31
+ outcome: 'reuse';
32
+ sid: string;
33
+ } | {
34
+ outcome: 'unknown';
35
+ };
20
36
  export interface UpdateSaasTokensInput {
21
37
  saasAccessToken: string;
22
38
  saasRefreshToken: string;
@@ -26,6 +42,9 @@ export interface SessionStore {
26
42
  get(sid: string): StoredSession | undefined;
27
43
  getSaasRefreshToken(sid: string): string | undefined;
28
44
  updateSaasTokens(sid: string, tokens: UpdateSaasTokensInput): void;
45
+ prepareRotation(presentedToken: string): PrepareRotationResult;
46
+ commitRotation(presentedToken: string, newRefreshToken: string): boolean;
47
+ destroy(sid: string): void;
29
48
  claimAuthorizationCode(code: string): boolean;
30
49
  releaseAuthorizationCode(code: string): void;
31
50
  pendingClaimCount(): number;
@@ -20,6 +20,24 @@ function hashToken(token) {
20
20
  function createInMemorySessionStore({ cipher, now, sessionTtlSeconds, authCodeTtlSeconds = DEFAULT_AUTH_CODE_TTL_SECONDS, maxPendingCodes = DEFAULT_MAX_PENDING_CODES, }) {
21
21
  const sessions = new Map();
22
22
  const usedCodes = new Map();
23
+ const activeRefreshHashToSid = new Map();
24
+ const rotatedRefreshHashToSid = new Map();
25
+ const lastRotationBySid = new Map();
26
+ function forgetRefreshHashes(sid) {
27
+ for (const [hash, owner] of activeRefreshHashToSid) {
28
+ if (owner === sid)
29
+ activeRefreshHashToSid.delete(hash);
30
+ }
31
+ for (const [hash, owner] of rotatedRefreshHashToSid) {
32
+ if (owner === sid)
33
+ rotatedRefreshHashToSid.delete(hash);
34
+ }
35
+ lastRotationBySid.delete(sid);
36
+ }
37
+ function dropSession(sid) {
38
+ sessions.delete(sid);
39
+ forgetRefreshHashes(sid);
40
+ }
23
41
  function purgeExpiredCodes() {
24
42
  const current = now();
25
43
  for (const [code, expiresAt] of usedCodes) {
@@ -31,7 +49,7 @@ function createInMemorySessionStore({ cipher, now, sessionTtlSeconds, authCodeTt
31
49
  const current = now();
32
50
  for (const [sid, session] of sessions) {
33
51
  if (current >= session.expiresAt)
34
- sessions.delete(sid);
52
+ dropSession(sid);
35
53
  }
36
54
  }
37
55
  function liveSession(sid) {
@@ -39,7 +57,7 @@ function createInMemorySessionStore({ cipher, now, sessionTtlSeconds, authCodeTt
39
57
  if (!session)
40
58
  return undefined;
41
59
  if (now() >= session.expiresAt) {
42
- sessions.delete(sid);
60
+ dropSession(sid);
43
61
  return undefined;
44
62
  }
45
63
  return session;
@@ -49,14 +67,16 @@ function createInMemorySessionStore({ cipher, now, sessionTtlSeconds, authCodeTt
49
67
  purgeExpiredSessions();
50
68
  const sid = createSid();
51
69
  const refreshToken = createOpaqueToken();
70
+ const refreshTokenHash = hashToken(refreshToken);
52
71
  sessions.set(sid, {
53
72
  saasAccessToken: input.saasAccessToken,
54
73
  saasRefreshTokenBlob: cipher.encrypt(input.saasRefreshToken),
55
- refreshTokenHash: hashToken(refreshToken),
74
+ refreshTokenHash,
56
75
  renderingId: input.renderingId,
57
76
  userId: input.userId,
58
77
  expiresAt: now() + sessionTtlSeconds * 1000,
59
78
  });
79
+ activeRefreshHashToSid.set(refreshTokenHash, sid);
60
80
  return { sid, refreshToken };
61
81
  },
62
82
  get(sid) {
@@ -74,6 +94,59 @@ function createInMemorySessionStore({ cipher, now, sessionTtlSeconds, authCodeTt
74
94
  session.saasAccessToken = tokens.saasAccessToken;
75
95
  session.saasRefreshTokenBlob = cipher.encrypt(tokens.saasRefreshToken);
76
96
  },
97
+ prepareRotation(presentedToken) {
98
+ const hash = hashToken(presentedToken);
99
+ const reusedSid = rotatedRefreshHashToSid.get(hash);
100
+ if (reusedSid !== undefined) {
101
+ const session = liveSession(reusedSid);
102
+ if (!session)
103
+ return { outcome: 'unknown' };
104
+ const lastRotation = lastRotationBySid.get(reusedSid);
105
+ if (lastRotation?.rotatedHash === hash) {
106
+ return {
107
+ outcome: 'replay',
108
+ sid: reusedSid,
109
+ renderingId: session.renderingId,
110
+ lastIssuedRefreshToken: cipher.decrypt(lastRotation.issuedRefreshBlob),
111
+ };
112
+ }
113
+ return { outcome: 'reuse', sid: reusedSid };
114
+ }
115
+ const sid = activeRefreshHashToSid.get(hash);
116
+ if (sid === undefined)
117
+ return { outcome: 'unknown' };
118
+ const session = liveSession(sid);
119
+ if (!session)
120
+ return { outcome: 'unknown' };
121
+ return {
122
+ outcome: 'ready',
123
+ sid,
124
+ renderingId: session.renderingId,
125
+ newRefreshToken: createOpaqueToken(),
126
+ };
127
+ },
128
+ commitRotation(presentedToken, newRefreshToken) {
129
+ const hash = hashToken(presentedToken);
130
+ const sid = activeRefreshHashToSid.get(hash);
131
+ if (sid === undefined)
132
+ return false;
133
+ const session = liveSession(sid);
134
+ if (!session)
135
+ return false;
136
+ const newHash = hashToken(newRefreshToken);
137
+ activeRefreshHashToSid.delete(hash);
138
+ rotatedRefreshHashToSid.set(hash, sid);
139
+ lastRotationBySid.set(sid, {
140
+ rotatedHash: hash,
141
+ issuedRefreshBlob: cipher.encrypt(newRefreshToken),
142
+ });
143
+ activeRefreshHashToSid.set(newHash, sid);
144
+ session.refreshTokenHash = newHash;
145
+ return true;
146
+ },
147
+ destroy(sid) {
148
+ dropSession(sid);
149
+ },
77
150
  claimAuthorizationCode(code) {
78
151
  purgeExpiredCodes();
79
152
  if (usedCodes.has(code))
@@ -92,4 +165,4 @@ function createInMemorySessionStore({ cipher, now, sessionTtlSeconds, authCodeTt
92
165
  },
93
166
  };
94
167
  }
95
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vzc2lvbi1zdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9vYXV0aC9zZXNzaW9uLXN0b3JlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBbURBLDhCQUVDO0FBVUQsNkNBa0dDO0FBL0pELG9EQUE0QjtBQThDNUIsTUFBTSw2QkFBNkIsR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDO0FBQzdDLE1BQU0seUJBQXlCLEdBQUcsS0FBTSxDQUFDO0FBRXpDLFNBQWdCLFNBQVM7SUFDdkIsT0FBTyxnQkFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUM7QUFDdEQsQ0FBQztBQUVELFNBQVMsaUJBQWlCO0lBQ3hCLE9BQU8sZ0JBQU0sQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBQ3RELENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxLQUFhO0lBQzlCLE9BQU8sZ0JBQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUN2RSxDQUFDO0FBRUQsU0FBd0IsMEJBQTBCLENBQUMsRUFDakQsTUFBTSxFQUNOLEdBQUcsRUFDSCxpQkFBaUIsRUFDakIsa0JBQWtCLEdBQUcsNkJBQTZCLEVBQ2xELGVBQWUsR0FBRyx5QkFBeUIsR0FDdkI7SUFDcEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQXlCLENBQUM7SUFDbEQsTUFBTSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7SUFFNUMsU0FBUyxpQkFBaUI7UUFDeEIsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFLENBQUM7UUFFdEIsS0FBSyxNQUFNLENBQUMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxJQUFJLFNBQVMsRUFBRSxDQUFDO1lBQzFDLElBQUksT0FB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
168
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vzc2lvbi1zdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9vYXV0aC9zZXNzaW9uLXN0b3JlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBNERBLDhCQUVDO0FBVUQsNkNBMExDO0FBaFFELG9EQUE0QjtBQXVENUIsTUFBTSw2QkFBNkIsR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDO0FBQzdDLE1BQU0seUJBQXlCLEdBQUcsS0FBTSxDQUFDO0FBRXpDLFNBQWdCLFNBQVM7SUFDdkIsT0FBTyxnQkFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUM7QUFDdEQsQ0FBQztBQUVELFNBQVMsaUJBQWlCO0lBQ3hCLE9BQU8sZ0JBQU0sQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBQ3RELENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxLQUFhO0lBQzlCLE9BQU8sZ0JBQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUN2RSxDQUFDO0FBRUQsU0FBd0IsMEJBQTBCLENBQUMsRUFDakQsTUFBTSxFQUNOLEdBQUcsRUFDSCxpQkFBaUIsRUFDakIsa0JBQWtCLEdBQUcsNkJBQTZCLEVBQ2xELGVBQWUsR0FBRyx5QkFBeUIsR0FDdkI7SUFDcEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQXlCLENBQUM7SUFDbEQsTUFBTSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7SUFDNUMsTUFBTSxzQkFBc0IsR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztJQUN6RCxNQUFNLHVCQUF1QixHQUFHLElBQUksR0FBRyxFQUFrQixDQUFDO0lBQzFELE1BQU0saUJBQWlCLEdBQUcsSUFBSSxHQUFHLEVBRzlCLENB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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forestadmin/agent-bff",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "forest-bff": "dist/cli.js"