@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 +19 -0
- package/dist/api-key/agent-token.d.ts +8 -0
- package/dist/api-key/agent-token.js +34 -0
- package/dist/api-key/api-key-authenticator.d.ts +17 -0
- package/dist/api-key/api-key-authenticator.js +61 -0
- package/dist/api-key/api-key-client.d.ts +37 -0
- package/dist/api-key/api-key-client.js +85 -0
- package/dist/api-key/api-key-error.d.ts +20 -0
- package/dist/api-key/api-key-error.js +34 -0
- package/dist/api-key/api-key-middleware.d.ts +10 -0
- package/dist/api-key/api-key-middleware.js +52 -0
- package/dist/api-key/api-key-resolve-error.d.ts +16 -0
- package/dist/api-key/api-key-resolve-error.js +16 -0
- package/dist/api-key/api-key.d.ts +8 -0
- package/dist/api-key/api-key.js +23 -0
- package/dist/api-key/resolve-cache.d.ts +17 -0
- package/dist/api-key/resolve-cache.js +56 -0
- package/dist/cli-core.js +33 -2
- package/dist/index.d.ts +12 -0
- package/dist/index.js +26 -2
- package/dist/oauth/oauth-error.d.ts +1 -0
- package/dist/oauth/oauth-error.js +5 -1
- package/dist/oauth/oauth-routes.js +122 -24
- package/dist/oauth/session-store.d.ts +19 -0
- package/dist/oauth/session-store.js +77 -4
- package/package.json +1 -1
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1jbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpLWtleS9hcGkta2V5LWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxtRUFBNkQ7QUFFN0QsaUVBQTZEO0FBQXBELDJIQUFBLGtCQUFrQixPQUFBO0FBd0IzQixNQUFNLGVBQWUsR0FBRyxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBVyxDQUFDO0FBQ3hFLE1BQU0sa0JBQWtCLEdBQUcsS0FBTSxDQUFDO0FBQ2xDLE1BQU0sWUFBWSxHQUFHLGdDQUFnQyxDQUFDO0FBTXRELE1BQXFCLFlBQVk7SUFJL0IsWUFBWSxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQXVCO1FBQzdELElBQUksQ0FBQyxlQUFlLEdBQUcsZUFBZSxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO0lBQzdCLENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLFNBR25CO1FBQ0MsSUFBSSxRQUFrQixDQUFDO1FBRXZCLElBQUksQ0FBQztZQUNILFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxFQUFFO2dCQUM3QyxNQUFNLEVBQUUsTUFBTTtnQkFDZCxPQUFPLEVBQUUsRUFBRSxHQUFHLGVBQWUsRUFBRSxtQkFBbUIsRUFBRSxJQUFJLENBQUMsU0FBUyxFQUFFO2dCQUNwRSxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxTQUFTLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQzFFLE1BQU0sRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDO2FBQ2hELENBQUMsQ0FBQztRQUNMLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxNQUFNLElBQUksMENBQWtCLENBQUMsRUFBRSxXQUFXLEVBQUUs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
|
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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(
|
|
173
|
+
async function resolveIdentity(renderingId, saasAccessToken, options) {
|
|
165
174
|
try {
|
|
166
|
-
return await options.serverClient.getUserInfo(
|
|
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
|
-
|
|
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
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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,
|
|
345
|
+
await handler(ctx, runtimeOptions);
|
|
248
346
|
}
|
|
249
347
|
catch (error) {
|
|
250
|
-
writeError(ctx, error,
|
|
348
|
+
writeError(ctx, error, runtimeOptions);
|
|
251
349
|
}
|
|
252
350
|
};
|
|
253
351
|
}
|
|
254
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2F1dGgtcm91dGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL29hdXRoL29hdXRoLXJvdXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQWdXQSxvQ0FnQkM7QUF6V0QsMkNBQW1GO0FBQ25GLGlFQUE0RDtBQUM1RCwrQ0FRdUI7QUFXdkIsTUFBTSxxQkFBcUIsR0FBRyw0QkFBNEIsQ0FBQztBQUMzRCxNQUFNLDJCQUEyQixHQUFHLHFCQUFxQixDQUFDO0FBRTFELE1BQU0sb0JBQW9CLEdBQUcsSUFBSSxHQUFHLENBQUM7SUFDbkMsaUJBQWlCO0lBQ2pCLGdCQUFnQjtJQUNoQixlQUFlO0lBQ2YscUJBQXFCO0lBQ3JCLHdCQUF3QjtJQUN4QixlQUFlO0NBQ2hCLENBQUMsQ0FBQztBQUVILFNBQVMsYUFBYSxDQUFDLEdBQVksRUFBRSxHQUFXO0lBQzlDLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFN0IsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDekIsTUFBTSxJQUFBLDRCQUFjLEVBQUMsdUNBQXVDLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDckUsQ0FBQztJQUVELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQztBQUVELFNBQVMsaUJBQWlCLENBQUMsSUFBNkIsRUFBRSxHQUFXO0lBQ25FLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUV4QixJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLEtBQUssRUFBRSxFQUFFLENBQUM7UUFDOUMsTUFBTSxJQUFBLDRCQUFjLEVBQUMsK0JBQStCLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUVELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQztBQUVELFNBQVMsdUJBQXVCLENBQUMsWUFBb0I7SUFDbkQsSUFBSSxDQUFDLHFCQUFx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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
168
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vzc2lvbi1zdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9vYXV0aC9zZXNzaW9uLXN0b3JlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBNERBLDhCQUVDO0FBVUQsNkNBMExDO0FBaFFELG9EQUE0QjtBQXVENUIsTUFBTSw2QkFBNkIsR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDO0FBQzdDLE1BQU0seUJBQXlCLEdBQUcsS0FBTSxDQUFDO0FBRXpDLFNBQWdCLFNBQVM7SUFDdkIsT0FBTyxnQkFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUM7QUFDdEQsQ0FBQztBQUVELFNBQVMsaUJBQWlCO0lBQ3hCLE9BQU8sZ0JBQU0sQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBQ3RELENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxLQUFhO0lBQzlCLE9BQU8sZ0JBQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUN2RSxDQUFDO0FBRUQsU0FBd0IsMEJBQTBCLENBQUMsRUFDakQsTUFBTSxFQUNOLEdBQUcsRUFDSCxpQkFBaUIsRUFDakIsa0JBQWtCLEdBQUcsNkJBQTZCLEVBQ2xELGVBQWUsR0FBRyx5QkFBeUIsR0FDdkI7SUFDcEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQXlCLENBQUM7SUFDbEQsTUFBTSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7SUFDNUMsTUFBTSxzQkFBc0IsR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztJQUN6RCxNQUFNLHVCQUF1QixHQUFHLElBQUksR0FBRyxFQUFrQixDQUFDO0lBQzFELE1BQU0saUJBQWlCLEdBQUcsSUFBSSxHQUFHLEVBRzlCLENB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
|