@forestadmin/agent-bff 1.2.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/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/package.json +1 -1
|
@@ -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=
|