@forestadmin/agent-bff 1.31.0 → 1.32.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/action/action-routes-middleware.d.ts +3 -1
- package/dist/action/action-routes-middleware.js +48 -13
- package/dist/activity-log/activity-log-drainer.d.ts +24 -0
- package/dist/activity-log/activity-log-drainer.js +63 -0
- package/dist/activity-log/activity-log-writer.d.ts +26 -0
- package/dist/activity-log/activity-log-writer.js +23 -0
- package/dist/activity-log/activity-logs-creator.d.ts +42 -0
- package/dist/activity-log/activity-logs-creator.js +209 -0
- package/dist/activity-log/activity-logs-service.d.ts +18 -0
- package/dist/activity-log/activity-logs-service.js +19 -0
- package/dist/activity-log/with-activity-log.d.ts +25 -0
- package/dist/activity-log/with-activity-log.js +63 -0
- package/dist/api-key/api-key-authenticator.d.ts +11 -0
- package/dist/api-key/api-key-authenticator.js +12 -2
- package/dist/api-key/api-key-client.d.ts +6 -0
- package/dist/api-key/api-key-client.js +2 -1
- package/dist/api-key/api-key-middleware.d.ts +10 -1
- package/dist/api-key/api-key-middleware.js +18 -1
- package/dist/api-key/resolve-cache.d.ts +12 -0
- package/dist/api-key/resolve-cache.js +38 -6
- package/dist/auth/auth-mode.d.ts +1 -0
- package/dist/auth/auth-mode.js +10 -3
- package/dist/auth/forest-server-token-middleware.d.ts +25 -0
- package/dist/auth/forest-server-token-middleware.js +90 -0
- package/dist/build-bff.d.ts +8 -0
- package/dist/build-bff.js +29 -10
- package/dist/cli-core.d.ts +9 -0
- package/dist/cli-core.js +38 -3
- package/dist/data/agent-query.d.ts +12 -0
- package/dist/data/agent-query.js +19 -1
- package/dist/data/data-routes-middleware.d.ts +3 -1
- package/dist/data/data-routes-middleware.js +41 -4
- package/dist/http/bff-http-server.d.ts +22 -0
- package/dist/http/bff-http-server.js +50 -5
- package/dist/http/bff-local-errors.d.ts +15 -0
- package/dist/http/bff-local-errors.js +29 -3
- package/dist/openapi/openapi-document.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = withActivityLog;
|
|
37
|
+
const activity_logs_creator_1 = __importStar(require("./activity-logs-creator"));
|
|
38
|
+
const COMPLETED = 'completed';
|
|
39
|
+
const FAILED = 'failed';
|
|
40
|
+
/**
|
|
41
|
+
* Runs an operation under an activity log: the pending log is awaited before the operation starts,
|
|
42
|
+
* so nothing runs unaudited, and the status transition is fired without `await` afterwards.
|
|
43
|
+
*/
|
|
44
|
+
async function withActivityLog(options) {
|
|
45
|
+
const { ctx, service, drainer, action, context, logger, operation, isCompletedDespite } = options;
|
|
46
|
+
// No log line of its own when nothing came back: the creator reports the case it hit, with the
|
|
47
|
+
// rendering and the collection this one could not name. A read proceeds unaudited from here.
|
|
48
|
+
const pending = await (0, activity_logs_creator_1.default)({ ctx, service, action, context, logger });
|
|
49
|
+
try {
|
|
50
|
+
const result = await operation();
|
|
51
|
+
if (pending)
|
|
52
|
+
(0, activity_logs_creator_1.markActivityLog)({ service, drainer, pending, status: COMPLETED, logger });
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (pending) {
|
|
57
|
+
const status = isCompletedDespite?.(error) ? COMPLETED : FAILED;
|
|
58
|
+
(0, activity_logs_creator_1.markActivityLog)({ service, drainer, pending, status, logger });
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2l0aC1hY3Rpdml0eS1sb2cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYWN0aXZpdHktbG9nL3dpdGgtYWN0aXZpdHktbG9nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBOEJBLGtDQXFCQztBQTdDRCxpRkFBb0Y7QUFFcEYsTUFBTSxTQUFTLEdBQUcsV0FBVyxDQUFDO0FBQzlCLE1BQU0sTUFBTSxHQUFHLFFBQVEsQ0FBQztBQWlCeEI7OztHQUdHO0FBQ1ksS0FBSyxVQUFVLGVBQWUsQ0FBSSxPQUFrQztJQUNqRixNQUFNLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLGtCQUFrQixFQUFFLEdBQUcsT0FBTyxDQUFDO0lBRWxHLCtGQUErRjtJQUMvRiw2RkFBNkY7SUFDN0YsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFBLCtCQUF3QixFQUFDLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFFMUYsSUFBSSxDQUFDO1FBQ0gsTUFBTSxNQUFNLEdBQUcsTUFBTSxTQUFTLEVBQUUsQ0FBQztRQUVqQyxJQUFJLE9BQU87WUFBRSxJQUFBLHVDQUFlLEVBQUMsRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFFdkYsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQ1osTUFBTSxNQUFNLEdBQUcsa0JBQWtCLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUM7WUFDaEUsSUFBQSx1Q0FBZSxFQUFDLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUVELE1BQU0sS0FBSyxDQUFDO0lBQ2QsQ0FBQztBQUNILENBQUMifQ==
|
|
@@ -9,9 +9,20 @@ export interface ApiKeyAuthenticatorOptions {
|
|
|
9
9
|
export interface AuthenticatedApiKey {
|
|
10
10
|
agentToken: string;
|
|
11
11
|
identity: ResolvedApiKeyIdentity;
|
|
12
|
+
/** The Forest server token the resolve response carried, cached with the identity. */
|
|
13
|
+
forestServerToken?: string;
|
|
12
14
|
}
|
|
13
15
|
export interface ApiKeyAuthenticator {
|
|
14
16
|
authenticate(rawKey: string): Promise<AuthenticatedApiKey>;
|
|
17
|
+
/**
|
|
18
|
+
* Forgets what a key resolved to, so the next request resolves it against the Forest server
|
|
19
|
+
* again. The resolution carries a short-lived server token the BFF caches with the identity:
|
|
20
|
+
* once that token is refused, the whole entry has to go.
|
|
21
|
+
*
|
|
22
|
+
* `credential` is the refused token, which tells a repeat refusal of the same one from the
|
|
23
|
+
* refusal of the token the previous invalidation went and fetched.
|
|
24
|
+
*/
|
|
25
|
+
invalidate(rawKey: string, credential?: string): void;
|
|
15
26
|
}
|
|
16
27
|
export default function createApiKeyAuthenticator({ client, cache, authSecret, }: ApiKeyAuthenticatorOptions): ApiKeyAuthenticator;
|
|
17
28
|
//# sourceMappingURL=api-key-authenticator.d.ts.map
|
|
@@ -25,7 +25,11 @@ function mapResolveError(error) {
|
|
|
25
25
|
}
|
|
26
26
|
function createApiKeyAuthenticator({ client, cache, authSecret, }) {
|
|
27
27
|
function mint(identity) {
|
|
28
|
-
return {
|
|
28
|
+
return {
|
|
29
|
+
agentToken: (0, agent_token_1.issueAgentToken)({ identity, authSecret }),
|
|
30
|
+
identity,
|
|
31
|
+
forestServerToken: identity.saasAccessToken,
|
|
32
|
+
};
|
|
29
33
|
}
|
|
30
34
|
return {
|
|
31
35
|
async authenticate(rawKey) {
|
|
@@ -56,6 +60,12 @@ function createApiKeyAuthenticator({ client, cache, authSecret, }) {
|
|
|
56
60
|
cache.setPositive(hash, identity);
|
|
57
61
|
return authenticated;
|
|
58
62
|
},
|
|
63
|
+
invalidate(rawKey, credential) {
|
|
64
|
+
const parsed = (0, api_key_1.parseApiKey)(rawKey);
|
|
65
|
+
if (!parsed)
|
|
66
|
+
return;
|
|
67
|
+
cache.invalidate((0, api_key_1.hashApiKey)(parsed.keyId, parsed.secret), credential);
|
|
68
|
+
},
|
|
59
69
|
};
|
|
60
70
|
}
|
|
61
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
71
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1hdXRoZW50aWNhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1hdXRoZW50aWNhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBNkRBLDRDQXdEQztBQWhIRCwrQ0FBZ0Q7QUFDaEQsdUNBQW9EO0FBQ3BELHFEQUFzRDtBQUN0RCxtREFLeUI7QUFFekIsTUFBTSwrQkFBK0IsR0FBRyxDQUFDLENBQUM7QUFDMUMsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBNEJwRCxTQUFTLGVBQWUsQ0FBQyxLQUF5QjtJQUNoRCxJQUFJLEtBQUssQ0FBQyxXQUFXO1FBQUUsT0FBTyxJQUFBLHdDQUF3QixFQUFDLCtCQUErQixDQUFDLENBQUM7SUFFeEYsUUFBUSxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDckIsS0FBSyxHQUFHO1lBQ04sT0FBTyxJQUFBLDZCQUFhLEdBQUUsQ0FBQztRQUN6QixLQUFLLEdBQUc7WUFDTixPQUFPLElBQUEsd0NBQXdCLEdBQUUsQ0FBQztRQUNwQyxLQUFLLEdBQUc7WUFDTixPQUFPLElBQUEsOEJBQWMsR0FBRSxDQUFDO1FBQzFCLEtBQUssR0FBRztZQUNOLE9BQU8sSUFBQSx3Q0FBd0IsRUFBQyxLQUFLLENBQUMsVUFBVSxJQUFJLCtCQUErQixDQUFDLENBQUM7UUFDdkY7WUFDRSxPQUFPLElBQUEsd0NBQXdCLEVBQUMsK0JBQStCLENBQUMsQ0FBQztJQUNyRSxDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQXdCLHlCQUF5QixDQUFDLEVBQ2hELE1BQU0sRUFDTixLQUFLLEVBQ0wsVUFBVSxHQUNpQjtJQUMzQixTQUFTLElBQUksQ0FBQyxRQUFnQztRQUM1QyxPQUFPO1lBQ0wsVUFBVSxFQUFFLElBQUEsNkJBQWUsRUFBQyxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsQ0FBQztZQUNyRCxRQUFRO1lBQ1IsaUJBQWlCLEVBQUUsUUFBUSxDQUFDLGVBQWU7U0FDNUMsQ0FBQztJQUNKLENBQUM7SUFFRCxPQUFPO1FBQ0wsS0FBSyxDQUFDLFlBQVksQ0FBQyxNQUFNO1lBQ3ZCLE1BQU0sTUFBTSxHQUFHLElBQUEscUJBQVcsRUFBQyxNQUFNLENBQUMsQ0FBQztZQUVuQyxJQUFJLENBQUMsTUFBTTtnQkFBRSxNQUFNLElBQUEsNkJBQWEsR0FBRSxDQUFDO1lBRW5DLE1BQU0sSUFBSSxHQUFHLElBQUEsb0JBQVUsRUFBQyxNQUFNLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUVyRCxNQUFNLGNBQWMsR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQy9DLElBQUksY0FBYztnQkFBRSxPQUFPLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUVoRCxNQUFNLFdBQVcsR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzVDLElBQUksV0FBVztnQkFBRSxNQUFNLFdBQVcsQ0FBQztZQUVuQyxJQUFJLFFBQWdDLENBQUM7WUFFckMsSUFBSSxDQUFDO2dCQUNILFFBQVEsR0FBRyxNQUFNLE1BQU0sQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDaEQsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2YsSUFBSSxLQUFLLFlBQVksbUNBQWtCLEVBQUUsQ0FBQztvQkFDeEMsTUFBTSxNQUFNLEdBQUcsZUFBZSxDQUFDLEtBQUssQ0FBQyxDQUFDO29CQUN0QyxJQUFJLHVCQUF1QixDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDO3dCQUFFLEtBQUssQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDO29CQUVoRixNQUFNLE1BQU0sQ0FBQztnQkFDZixDQUFDO2dCQUVELE1BQU0sS0FBSyxDQUFDO1lBQ2QsQ0FBQztZQUVELE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUNyQyxLQUFLLENBQUMsV0FBVyxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztZQUVsQyxPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO1FBRUQsVUFBVSxDQUFDLE1BQU0sRUFBRSxVQUFVO1lBQzNCLE1BQU0sTUFBTSxHQUFHLElBQUEscUJBQVcsRUFBQyxNQUFNLENBQUMsQ0FBQztZQUVuQyxJQUFJLENBQUMsTUFBTTtnQkFBRSxPQUFPO1lBRXBCLEtBQUssQ0FBQyxVQUFVLENBQUMsSUFBQSxvQkFBVSxFQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ3hFLENBQUM7S0FDRixDQUFDO0FBQ0osQ0FBQyJ9
|
|
@@ -16,6 +16,12 @@ export interface ResolvedApiKeyIdentity {
|
|
|
16
16
|
user: ApiKeyIdentityUser;
|
|
17
17
|
renderingId: number;
|
|
18
18
|
allowedOrigins: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Short-lived, user-scoped Forest server token, used to write the activity log. Optional so a
|
|
21
|
+
* Forest server that does not send one yet still resolves keys: the audit trail then degrades on
|
|
22
|
+
* its own terms (a read proceeds unaudited, a write is blocked) instead of taking auth down.
|
|
23
|
+
*/
|
|
24
|
+
saasAccessToken?: string;
|
|
19
25
|
}
|
|
20
26
|
export interface ApiKeyClientOptions {
|
|
21
27
|
forestServerUrl: string;
|
|
@@ -56,6 +56,7 @@ class ApiKeyClient {
|
|
|
56
56
|
return (typeof candidate.renderingId === 'number' &&
|
|
57
57
|
Array.isArray(candidate.allowedOrigins) &&
|
|
58
58
|
candidate.allowedOrigins.every(entry => typeof entry === 'string') &&
|
|
59
|
+
(candidate.saasAccessToken === undefined || typeof candidate.saasAccessToken === 'string') &&
|
|
59
60
|
ApiKeyClient.isIdentityUser(candidate.user));
|
|
60
61
|
}
|
|
61
62
|
// Every field the agent-token mint reads must be present, so a partial body fails here
|
|
@@ -83,4 +84,4 @@ class ApiKeyClient {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
exports.default = ApiKeyClient;
|
|
86
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
87
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1jbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpLWtleS9hcGkta2V5LWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxtRUFBNkQ7QUFFN0QsaUVBQTZEO0FBQXBELDJIQUFBLGtCQUFrQixPQUFBO0FBOEIzQixNQUFNLGVBQWUsR0FBRyxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBVyxDQUFDO0FBQ3hFLE1BQU0sa0JBQWtCLEdBQUcsS0FBTSxDQUFDO0FBQ2xDLE1BQU0sWUFBWSxHQUFHLGdDQUFnQyxDQUFDO0FBTXRELE1BQXFCLFlBQVk7SUFJL0IsWUFBWSxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQXVCO1FBQzdELElBQUksQ0FBQyxlQUFlLEdBQUcsZUFBZSxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO0lBQzdCLENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLFNBR25CO1FBQ0MsSUFBSSxRQUFrQixDQUFDO1FBRXZCLElBQUksQ0FBQztZQUNILFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxFQUFFO2dCQUM3QyxNQUFNLEVBQUUsTUFBTTtnQkFDZCxPQUFPLEVBQUUsRUFBRSxHQUFHLGVBQWUsRUFBRSxtQkFBbUIsRUFBRSxJQUFJLENBQUMsU0FBUyxFQUFFO2dCQUNwRSxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxTQUFTLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQzFFLE1BQU0sRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDO2FBQ2hELENBQUMsQ0FBQztRQUNMLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxNQUFNLElBQUksMENBQWtCLENBQUMsRUFBRSxXQUFXLEVBQUUs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
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { ApiKeyAuthenticator } from './api-key-authenticator';
|
|
2
2
|
import type { Logger } from '../ports/logger-port';
|
|
3
|
-
import type { Middleware } from 'koa';
|
|
3
|
+
import type { Context, Middleware } from 'koa';
|
|
4
4
|
export declare const BFF_KEY_HEADER = "X-Forest-Bff-Key";
|
|
5
|
+
export type ApiKeyIdentityInvalidator = () => void;
|
|
5
6
|
export interface ApiKeyMiddlewareOptions {
|
|
6
7
|
authenticator: ApiKeyAuthenticator;
|
|
7
8
|
logger: Logger;
|
|
8
9
|
}
|
|
9
10
|
export default function createApiKeyMiddleware({ authenticator, logger, }: ApiKeyMiddlewareOptions): Middleware;
|
|
11
|
+
/**
|
|
12
|
+
* Forgets the identity this request was authenticated with. Called when the Forest server refuses
|
|
13
|
+
* the token that came with it: the token is cached with the identity, so the next request must
|
|
14
|
+
* resolve the key again instead of replaying the refused one for the rest of the cache window.
|
|
15
|
+
*
|
|
16
|
+
* A no-op outside api-key mode — nothing else lands an invalidator.
|
|
17
|
+
*/
|
|
18
|
+
export declare function invalidateApiKeyIdentity(ctx: Context): void;
|
|
10
19
|
//# sourceMappingURL=api-key-middleware.d.ts.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BFF_KEY_HEADER = void 0;
|
|
4
4
|
exports.default = createApiKeyMiddleware;
|
|
5
|
+
exports.invalidateApiKeyIdentity = invalidateApiKeyIdentity;
|
|
5
6
|
const api_key_1 = require("./api-key");
|
|
6
7
|
const api_key_error_1 = require("./api-key-error");
|
|
7
8
|
exports.BFF_KEY_HEADER = 'X-Forest-Bff-Key';
|
|
@@ -34,8 +35,13 @@ function createApiKeyMiddleware({ authenticator, logger, }) {
|
|
|
34
35
|
}
|
|
35
36
|
throw error;
|
|
36
37
|
}
|
|
38
|
+
// Bound to the token this request was authenticated with: the cache uses it to tell a repeat
|
|
39
|
+
// refusal of the same credential from the refusal of the one it just went and fetched.
|
|
40
|
+
const invalidateIdentity = () => authenticator.invalidate(rawKey, authenticated.forestServerToken);
|
|
41
|
+
ctx.state.invalidateApiKeyIdentity = invalidateIdentity;
|
|
37
42
|
ctx.state.agentToken = authenticated.agentToken;
|
|
38
43
|
ctx.state.apiKeyIdentity = authenticated.identity;
|
|
44
|
+
ctx.state.forestServerToken = authenticated.forestServerToken;
|
|
39
45
|
ctx.set('Cache-Control', 'no-store');
|
|
40
46
|
logger('Info', 'Resolved BFF API key', {
|
|
41
47
|
keyHash: (0, api_key_1.fingerprintApiKey)(rawKey),
|
|
@@ -44,4 +50,15 @@ function createApiKeyMiddleware({ authenticator, logger, }) {
|
|
|
44
50
|
await next();
|
|
45
51
|
};
|
|
46
52
|
}
|
|
47
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Forgets the identity this request was authenticated with. Called when the Forest server refuses
|
|
55
|
+
* the token that came with it: the token is cached with the identity, so the next request must
|
|
56
|
+
* resolve the key again instead of replaying the refused one for the rest of the cache window.
|
|
57
|
+
*
|
|
58
|
+
* A no-op outside api-key mode — nothing else lands an invalidator.
|
|
59
|
+
*/
|
|
60
|
+
function invalidateApiKeyIdentity(ctx) {
|
|
61
|
+
const invalidate = ctx.state.invalidateApiKeyIdentity;
|
|
62
|
+
invalidate?.();
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLWtleS1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS1rZXkvYXBpLWtleS1taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQW1CQSx5Q0FrREM7QUFTRCw0REFJQztBQTlFRCx1Q0FBOEM7QUFDOUMsbURBQThDO0FBRWpDLFFBQUEsY0FBYyxHQUFHLGtCQUFrQixDQUFDO0FBU2pELCtFQUErRTtBQUMvRSw2RUFBNkU7QUFDN0UscUVBQXFFO0FBQ3JFLFNBQXdCLHNCQUFzQixDQUFDLEVBQzdDLGFBQWEsRUFDYixNQUFNLEdBQ2tCO0lBQ3hCLE9BQU8sS0FBSyxVQUFVLGdCQUFnQixDQUFDLEdBQUcsRUFBRSxJQUFJO1FBQzlDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsc0JBQWMsQ0FBQyxDQUFDO1FBRXZDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNaLE1BQU0sSUFBSSxFQUFFLENBQUM7WUFFYixPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksYUFBa0MsQ0FBQztRQUV2QyxJQUFJLENBQUM7WUFDSCxhQUFhLEdBQUcsTUFBTSxhQUFhLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzNELENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxLQUFLLFlBQVksMkJBQVcsRUFBRSxDQUFDO2dCQUNqQyxNQUFNLENBQUMsTUFBTSxFQUFFLHNCQUFzQixFQUFFO29CQUNyQyxPQUFPLEVBQUUsSUFBQSwyQkFBaUIsRUFBQyxNQUFNLENBQUM7b0JBQ2xDLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSTtpQkFDakIsQ0FBQyxDQUFDO1lBQ0wsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLE1BQU0sQ0FBQyxPQUFPLEVBQUUsZ0NBQWdDLEVBQUU7b0JBQ2hELE9BQU8sRUFBRSxJQUFBLDJCQUFpQixFQUFDLE1BQU0sQ0FBQztvQkFDbEMsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksS0FBSyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7aUJBQ2xGLENBQUMsQ0FBQztZQUNMLENBQUM7WUFFRCxNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7UUFFRCw2RkFBNkY7UUFDN0YsdUZBQXVGO1FBQ3ZGLE1BQU0sa0JBQWtCLEdBQThCLEdBQUcsRUFBRSxDQUN6RCxhQUFhLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxhQUFhLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUVwRSxHQUFHLENBQUMsS0FBSyxDQUFDLHdCQUF3QixHQUFHLGtCQUFrQixDQUFDO1FBQ3hELEdBQUcsQ0FBQyxLQUFLLENBQUMsVUFBVSxHQUFHLGFBQWEsQ0FBQyxVQUFVLENBQUM7UUFDaEQsR0FBRyxDQUFDLEtBQUssQ0FBQyxjQUFjLEdBQUcsYUFBYSxDQUFDLFFBQVEsQ0FBQztRQUNsRCxHQUFHLENBQUMsS0FBSyxDQUFDLGlCQUFpQixHQUFHLGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQztRQUM5RCxHQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNyQyxNQUFNLENBQUMsTUFBTSxFQUFFLHNCQUFzQixFQUFFO1lBQ3JDLE9BQU8sRUFBRSxJQUFBLDJCQUFpQixFQUFDLE1BQU0sQ0FBQztZQUNsQyxXQUFXLEVBQUUsYUFBYSxDQUFDLFFBQVEsQ0FBQyxXQUFXO1NBQ2hELENBQUMsQ0FBQztRQUVILE1BQU0sSUFBSSxFQUFFLENBQUM7SUFDZixDQUFDLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsR0FBWTtJQUNuRCxNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLHdCQUFpRSxDQUFDO0lBRS9GLFVBQVUsRUFBRSxFQUFFLENBQUM7QUFDakIsQ0FBQyJ9
|
|
@@ -5,6 +5,18 @@ export interface ResolveCache {
|
|
|
5
5
|
getNegative(hash: string): ApiKeyError | undefined;
|
|
6
6
|
setPositive(hash: string, identity: ResolvedApiKeyIdentity): void;
|
|
7
7
|
setNegative(hash: string, error: ApiKeyError): void;
|
|
8
|
+
/**
|
|
9
|
+
* Forgets a key. Bounded because the caller is a refusal the Forest server may repeat on every
|
|
10
|
+
* request: invalidating each time would defeat the cache and cost two round trips per request
|
|
11
|
+
* instead of one extra per window.
|
|
12
|
+
*
|
|
13
|
+
* `credential` is the server token that was refused. A window opened by one token still lets a
|
|
14
|
+
* second, different one through: the re-resolution that followed the first refusal caches a
|
|
15
|
+
* fresh token, and suppressing its refusal too would replay a credential the server rejects for
|
|
16
|
+
* the rest of the window. Only the second is allowed, so the bound holds whatever the server
|
|
17
|
+
* hands back.
|
|
18
|
+
*/
|
|
19
|
+
invalidate(hash: string, credential?: string): void;
|
|
8
20
|
size(): number;
|
|
9
21
|
}
|
|
10
22
|
export interface ResolveCacheOptions {
|
|
@@ -6,12 +6,21 @@ const DEFAULT_NEGATIVE_TTL_SECONDS = 10;
|
|
|
6
6
|
const DEFAULT_MAX_ENTRIES = 10000;
|
|
7
7
|
function createResolveCache({ now, positiveTtlSeconds = DEFAULT_POSITIVE_TTL_SECONDS, negativeTtlSeconds = DEFAULT_NEGATIVE_TTL_SECONDS, maxEntries = DEFAULT_MAX_ENTRIES, }) {
|
|
8
8
|
const entries = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Per key, the window opened by its last invalidation. Bounded by `maxEntries` like the entries
|
|
11
|
+
* it guards: invalidations of distinct keys would otherwise grow it without limit.
|
|
12
|
+
*/
|
|
13
|
+
const invalidatedUntil = new Map();
|
|
9
14
|
function purgeExpired() {
|
|
10
15
|
const current = now();
|
|
11
16
|
for (const [hash, entry] of entries) {
|
|
12
17
|
if (current >= entry.expiresAt)
|
|
13
18
|
entries.delete(hash);
|
|
14
19
|
}
|
|
20
|
+
for (const [hash, window] of invalidatedUntil) {
|
|
21
|
+
if (current >= window.until)
|
|
22
|
+
invalidatedUntil.delete(hash);
|
|
23
|
+
}
|
|
15
24
|
}
|
|
16
25
|
function liveEntry(hash) {
|
|
17
26
|
const entry = entries.get(hash);
|
|
@@ -23,13 +32,16 @@ function createResolveCache({ now, positiveTtlSeconds = DEFAULT_POSITIVE_TTL_SEC
|
|
|
23
32
|
}
|
|
24
33
|
return entry;
|
|
25
34
|
}
|
|
35
|
+
function evictOldestIfFull(map, hash) {
|
|
36
|
+
if (map.has(hash) || map.size < maxEntries)
|
|
37
|
+
return;
|
|
38
|
+
const oldest = map.keys().next().value;
|
|
39
|
+
if (oldest !== undefined)
|
|
40
|
+
map.delete(oldest);
|
|
41
|
+
}
|
|
26
42
|
function store(hash, entry) {
|
|
27
43
|
purgeExpired();
|
|
28
|
-
|
|
29
|
-
const oldest = entries.keys().next().value;
|
|
30
|
-
if (oldest !== undefined)
|
|
31
|
-
entries.delete(oldest);
|
|
32
|
-
}
|
|
44
|
+
evictOldestIfFull(entries, hash);
|
|
33
45
|
entries.set(hash, entry);
|
|
34
46
|
}
|
|
35
47
|
return {
|
|
@@ -47,10 +59,30 @@ function createResolveCache({ now, positiveTtlSeconds = DEFAULT_POSITIVE_TTL_SEC
|
|
|
47
59
|
setNegative(hash, error) {
|
|
48
60
|
store(hash, { kind: 'negative', error, expiresAt: now() + negativeTtlSeconds * 1000 });
|
|
49
61
|
},
|
|
62
|
+
invalidate(hash, credential) {
|
|
63
|
+
const open = invalidatedUntil.get(hash);
|
|
64
|
+
const live = open !== undefined && now() < open.until;
|
|
65
|
+
if (live) {
|
|
66
|
+
if (open.retried || open.credential === credential)
|
|
67
|
+
return;
|
|
68
|
+
// The window is kept: a second refusal buys one more resolution, not a later deadline.
|
|
69
|
+
invalidatedUntil.set(hash, { ...open, credential, retried: true });
|
|
70
|
+
entries.delete(hash);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
purgeExpired();
|
|
74
|
+
evictOldestIfFull(invalidatedUntil, hash);
|
|
75
|
+
invalidatedUntil.set(hash, {
|
|
76
|
+
until: now() + positiveTtlSeconds * 1000,
|
|
77
|
+
credential,
|
|
78
|
+
retried: false,
|
|
79
|
+
});
|
|
80
|
+
entries.delete(hash);
|
|
81
|
+
},
|
|
50
82
|
size() {
|
|
51
83
|
purgeExpired();
|
|
52
84
|
return entries.size;
|
|
53
85
|
},
|
|
54
86
|
};
|
|
55
87
|
}
|
|
56
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
88
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS1jYWNoZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkta2V5L3Jlc29sdmUtY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF3REEscUNBdUdDO0FBM0dELE1BQU0sNEJBQTRCLEdBQUcsRUFBRSxDQUFDO0FBQ3hDLE1BQU0sNEJBQTRCLEdBQUcsRUFBRSxDQUFDO0FBQ3hDLE1BQU0sbUJBQW1CLEdBQUcsS0FBTSxDQUFDO0FBRW5DLFNBQXdCLGtCQUFrQixDQUFDLEVBQ3pDLEdBQUcsRUFDSCxrQkFBa0IsR0FBRyw0QkFBNEIsRUFDakQsa0JBQWtCLEdBQUcsNEJBQTRCLEVBQ2pELFVBQVUsR0FBRyxtQkFBbUIsR0FDWjtJQUNwQixNQUFNLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBc0IsQ0FBQztJQUM5Qzs7O09BR0c7SUFDSCxNQUFNLGdCQUFnQixHQUFHLElBQUksR0FBRyxFQUE4QixDQUFDO0lBRS9ELFNBQVMsWUFBWTtRQUNuQixNQUFNLE9BQU8sR0FBRyxHQUFHLEVBQUUsQ0FBQztRQUV0QixLQUFLLE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUksT0FBTyxFQUFFLENBQUM7WUFDcEMsSUFBSSxPQUFPLElBQUksS0FBSyxDQUFDLFNBQVM7Z0JBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN2RCxDQUFDO1FBRUQsS0FBSyxNQUFNLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLGdCQUFnQixFQUFFLENBQUM7WUFDOUMsSUFBSSxPQUFPLElBQUksTUFBTSxDQUFDLEtBQUs7Z0JBQUUsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzdELENBQUM7SUFDSCxDQUFDO0lBRUQsU0FBUyxTQUFTLENBQUMsSUFBWTtRQUM3QixNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2hD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
|
package/dist/auth/auth-mode.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BffAccessTokenPayload } from '../oauth/bff-token';
|
|
2
2
|
export type AuthMode = 'oauth' | 'api-key';
|
|
3
|
+
export declare function readRenderingId(principal: BffAccessTokenPayload): number | undefined;
|
|
3
4
|
export declare function requireRenderingId(principal: BffAccessTokenPayload): number;
|
|
4
5
|
export declare function extractBearerToken(authorization: string | undefined): string | undefined;
|
|
5
6
|
export declare function resolveAuthMode({ hasBearer, hasApiKey, }: {
|
package/dist/auth/auth-mode.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readRenderingId = readRenderingId;
|
|
3
4
|
exports.requireRenderingId = requireRenderingId;
|
|
4
5
|
exports.extractBearerToken = extractBearerToken;
|
|
5
6
|
exports.resolveAuthMode = resolveAuthMode;
|
|
6
7
|
const bff_http_error_1 = require("../http/bff-http-error");
|
|
7
8
|
const BEARER_PATTERN = /^Bearer[ \t]+(.+)$/i;
|
|
8
9
|
const POSITIVE_INTEGER = /^[1-9]\d*$/;
|
|
10
|
+
function readRenderingId(principal) {
|
|
11
|
+
if (!POSITIVE_INTEGER.test(String(principal.rendering_id)))
|
|
12
|
+
return undefined;
|
|
13
|
+
return Number(principal.rendering_id);
|
|
14
|
+
}
|
|
9
15
|
function requireRenderingId(principal) {
|
|
10
|
-
|
|
16
|
+
const renderingId = readRenderingId(principal);
|
|
17
|
+
if (renderingId === undefined) {
|
|
11
18
|
throw (0, bff_http_error_1.unauthorized)('The session carries no usable rendering');
|
|
12
19
|
}
|
|
13
|
-
return
|
|
20
|
+
return renderingId;
|
|
14
21
|
}
|
|
15
22
|
function extractBearerToken(authorization) {
|
|
16
23
|
if (!authorization)
|
|
@@ -30,4 +37,4 @@ function resolveAuthMode({ hasBearer, hasApiKey, }) {
|
|
|
30
37
|
return 'oauth';
|
|
31
38
|
throw (0, bff_http_error_1.unauthorized)();
|
|
32
39
|
}
|
|
33
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
40
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC1tb2RlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2F1dGgvYXV0aC1tb2RlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBU0EsMENBSUM7QUFFRCxnREFRQztBQUVELGdEQVNDO0FBRUQsMENBWUM7QUE5Q0QsMkRBQTRFO0FBSTVFLE1BQU0sY0FBYyxHQUFHLHFCQUFxQixDQUFDO0FBQzdDLE1BQU0sZ0JBQWdCLEdBQUcsWUFBWSxDQUFDO0FBRXRDLFNBQWdCLGVBQWUsQ0FBQyxTQUFnQztJQUM5RCxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsWUFBWSxDQUFDLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUU3RSxPQUFPLE1BQU0sQ0FBQyxTQUFTLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDeEMsQ0FBQztBQUVELFNBQWdCLGtCQUFrQixDQUFDLFNBQWdDO0lBQ2pFLE1BQU0sV0FBVyxHQUFHLGVBQWUsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUUvQyxJQUFJLFdBQVcsS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUM5QixNQUFNLElBQUEsNkJBQVksRUFBQyx5Q0FBeUMsQ0FBQyxDQUFDO0lBQ2hFLENBQUM7SUFFRCxPQUFPLFdBQVcsQ0FBQztBQUNyQixDQUFDO0FBRUQsU0FBZ0Isa0JBQWtCLENBQUMsYUFBaUM7SUFDbEUsSUFBSSxDQUFDLGFBQWE7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUVyQyxNQUFNLEtBQUssR0FBRyxjQUFjLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQ3hELElBQUksQ0FBQyxLQUFLO1FBQUUsT0FBTyxTQUFTLENBQUM7SUFFN0IsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0lBRTlCLE9BQU8sS0FBSyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7QUFDMUMsQ0FBQztBQUVELFNBQWdCLGVBQWUsQ0FBQyxFQUM5QixTQUFTLEVBQ1QsU0FBUyxHQUlWO0lBQ0MsSUFBSSxTQUFTLElBQUksU0FBUztRQUFFLE1BQU0sSUFBQSxxQ0FBb0IsR0FBRSxDQUFDO0lBQ3pELElBQUksU0FBUztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ2hDLElBQUksU0FBUztRQUFFLE9BQU8sT0FBTyxDQUFDO0lBRTlCLE1BQU0sSUFBQSw2QkFBWSxHQUFFLENBQUM7QUFDdkIsQ0FBQyJ9
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type ForestServerClient from '../oauth/forest-server-client';
|
|
2
|
+
import type { SessionStore } from '../oauth/session-store';
|
|
3
|
+
import type { Logger } from '../ports/logger-port';
|
|
4
|
+
import type { Context, Middleware } from 'koa';
|
|
5
|
+
export type ForestServerTokenResolver = () => Promise<string>;
|
|
6
|
+
export interface OAuthSessionAccess {
|
|
7
|
+
store: SessionStore;
|
|
8
|
+
serverClient: ForestServerClient;
|
|
9
|
+
}
|
|
10
|
+
export interface ForestServerTokenMiddlewareOptions {
|
|
11
|
+
session?: OAuthSessionAccess;
|
|
12
|
+
logger: Logger;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Lands a lazy resolver of the Forest server bearer on the context, for both auth modes. Lazy on
|
|
16
|
+
* purpose: the routes that audit nothing — /health, the permissions, context, OpenAPI and docs
|
|
17
|
+
* routes — must not pay a session lookup, and the permissions one is hit on every page load.
|
|
18
|
+
*
|
|
19
|
+
* Keeping both modes here is what lets the data and action routes read one function off the context
|
|
20
|
+
* instead of taking the session store and the Forest server client as dependencies.
|
|
21
|
+
*/
|
|
22
|
+
export default function createForestServerTokenMiddleware({ session, logger, }: ForestServerTokenMiddlewareOptions): Middleware;
|
|
23
|
+
export declare function resolveForestServerToken(ctx: Context): Promise<string>;
|
|
24
|
+
export declare function resolveRenderingId(ctx: Context): number | undefined;
|
|
25
|
+
//# sourceMappingURL=forest-server-token-middleware.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
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.default = createForestServerTokenMiddleware;
|
|
7
|
+
exports.resolveForestServerToken = resolveForestServerToken;
|
|
8
|
+
exports.resolveRenderingId = resolveRenderingId;
|
|
9
|
+
const auth_mode_1 = require("./auth-mode");
|
|
10
|
+
const errors_1 = require("../errors");
|
|
11
|
+
const bff_http_error_1 = require("../http/bff-http-error");
|
|
12
|
+
const bff_local_errors_1 = require("../http/bff-local-errors");
|
|
13
|
+
const oauth_error_1 = require("../oauth/oauth-error");
|
|
14
|
+
const session_lifecycle_1 = __importDefault(require("../oauth/session-lifecycle"));
|
|
15
|
+
const NO_SESSION_MESSAGE = 'The session behind this request could not be resolved';
|
|
16
|
+
const NO_RESOLVER_MESSAGE = 'This request carries no Forest server credentials';
|
|
17
|
+
/**
|
|
18
|
+
* Carries no `Retry-After`: the key resolution came back without an audit credential at all — a
|
|
19
|
+
* Forest server that does not mint one yet — so no retry can succeed until that server ships it.
|
|
20
|
+
*/
|
|
21
|
+
const NO_AUDIT_CREDENTIAL_MESSAGE = 'The Forest server does not provide the credential the activity log is written with, so the ' +
|
|
22
|
+
'operation was not performed';
|
|
23
|
+
const UNAUTHORIZED = 401;
|
|
24
|
+
async function resolveToken(ctx, logger, session) {
|
|
25
|
+
if (ctx.state.authMode === 'api-key') {
|
|
26
|
+
const token = ctx.state.forestServerToken;
|
|
27
|
+
if (!token)
|
|
28
|
+
throw (0, bff_local_errors_1.auditUnavailable)(undefined, NO_AUDIT_CREDENTIAL_MESSAGE);
|
|
29
|
+
return token;
|
|
30
|
+
}
|
|
31
|
+
const principal = ctx.state.principal;
|
|
32
|
+
if (!principal || !session)
|
|
33
|
+
throw (0, bff_http_error_1.sessionExpired)(NO_SESSION_MESSAGE);
|
|
34
|
+
try {
|
|
35
|
+
return await (0, session_lifecycle_1.default)({
|
|
36
|
+
sid: principal.sid,
|
|
37
|
+
store: session.store,
|
|
38
|
+
serverClient: session.serverClient,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
// The errors below carry neither the cause nor a `cause` field, so this line is the only place
|
|
43
|
+
// the operator ever sees what actually failed — a broken session store reads as an audit
|
|
44
|
+
// outage otherwise.
|
|
45
|
+
logger('Error', 'Could not resolve the Forest server access of this session', {
|
|
46
|
+
renderingId: (0, auth_mode_1.readRenderingId)(principal),
|
|
47
|
+
cause: (0, errors_1.extractErrorMessage)(error),
|
|
48
|
+
});
|
|
49
|
+
// Only a session the Forest server rejected, or one that vanished, makes re-authenticating the
|
|
50
|
+
// answer. Everything else — the server being unreachable, above all — is retryable, and a 401
|
|
51
|
+
// would log every user out over a blip instead of failing the audit write alone.
|
|
52
|
+
if (error instanceof oauth_error_1.OAuthRequestError && error.status === UNAUTHORIZED) {
|
|
53
|
+
throw (0, bff_http_error_1.sessionExpired)(NO_SESSION_MESSAGE);
|
|
54
|
+
}
|
|
55
|
+
throw (0, bff_local_errors_1.auditUnavailable)(bff_local_errors_1.AUDIT_RETRY_AFTER_SECONDS);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Lands a lazy resolver of the Forest server bearer on the context, for both auth modes. Lazy on
|
|
60
|
+
* purpose: the routes that audit nothing — /health, the permissions, context, OpenAPI and docs
|
|
61
|
+
* routes — must not pay a session lookup, and the permissions one is hit on every page load.
|
|
62
|
+
*
|
|
63
|
+
* Keeping both modes here is what lets the data and action routes read one function off the context
|
|
64
|
+
* instead of taking the session store and the Forest server client as dependencies.
|
|
65
|
+
*/
|
|
66
|
+
function createForestServerTokenMiddleware({ session, logger, }) {
|
|
67
|
+
return async function forestServerTokenMiddleware(ctx, next) {
|
|
68
|
+
let pending;
|
|
69
|
+
const resolver = () => {
|
|
70
|
+
pending ?? (pending = resolveToken(ctx, logger, session));
|
|
71
|
+
return pending;
|
|
72
|
+
};
|
|
73
|
+
ctx.state.resolveForestServerToken = resolver;
|
|
74
|
+
await next();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function resolveForestServerToken(ctx) {
|
|
78
|
+
const resolver = ctx.state.resolveForestServerToken;
|
|
79
|
+
if (!resolver)
|
|
80
|
+
throw (0, bff_http_error_1.sessionExpired)(NO_RESOLVER_MESSAGE);
|
|
81
|
+
return resolver();
|
|
82
|
+
}
|
|
83
|
+
function resolveRenderingId(ctx) {
|
|
84
|
+
if (ctx.state.authMode === 'api-key') {
|
|
85
|
+
return ctx.state.apiKeyIdentity?.renderingId;
|
|
86
|
+
}
|
|
87
|
+
const principal = ctx.state.principal;
|
|
88
|
+
return principal ? (0, auth_mode_1.readRenderingId)(principal) : undefined;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yZXN0LXNlcnZlci10b2tlbi1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2F1dGgvZm9yZXN0LXNlcnZlci10b2tlbi1taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBd0ZBLG9EQWlCQztBQUVELDREQU1DO0FBRUQsZ0RBUUM7QUFwSEQsMkNBQThDO0FBQzlDLHNDQUFnRDtBQUNoRCwyREFBd0Q7QUFDeEQsK0RBQXVGO0FBQ3ZGLHNEQUF5RDtBQUN6RCxtRkFBaUU7QUFjakUsTUFBTSxrQkFBa0IsR0FBRyx1REFBdUQsQ0FBQztBQUNuRixNQUFNLG1CQUFtQixHQUFHLG1EQUFtRCxDQUFDO0FBQ2hGOzs7R0FHRztBQUNILE1BQU0sMkJBQTJCLEdBQy9CLDZGQUE2RjtJQUM3Riw2QkFBNkIsQ0FBQztBQUNoQyxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUM7QUFFekIsS0FBSyxVQUFVLFlBQVksQ0FDekIsR0FBWSxFQUNaLE1BQWMsRUFDZCxPQUE0QjtJQUU1QixJQUFJLEdBQUcsQ0FBQyxLQUFLLENBQUMsUUFBUSxLQUFLLFNBQVMsRUFBRSxDQUFDO1FBQ3JDLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsaUJBQXVDLENBQUM7UUFFaEUsSUFBSSxDQUFDLEtBQUs7WUFBRSxNQUFNLElBQUEsbUNBQWdCLEVBQUMsU0FBUyxFQUFFLDJCQUEyQixDQUFDLENBQUM7UUFFM0UsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxTQUE4QyxDQUFDO0lBRTNFLElBQUksQ0FBQyxTQUFTLElBQUksQ0FBQyxPQUFPO1FBQUUsTUFBTSxJQUFBLCtCQUFjLEVBQUMsa0JBQWtCLENBQUMsQ0FBQztJQUVyRSxJQUFJLENBQUM7UUFDSCxPQUFPLE1BQU0sSUFBQSwyQkFBdUIsRUFBQztZQUNuQyxHQUFHLEVBQUUsU0FBUyxDQUFDLEdBQUc7WUFDbEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLO1lBQ3BCLFlBQVksRUFBRSxPQUFPLENBQUMsWUFBWTtTQUNuQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLCtGQUErRjtRQUMvRix5RkFBeUY7UUFDekYsb0JBQW9CO1FBQ3BCLE1BQU0sQ0FBQyxPQUFPLEVBQUUsNERBQTRELEVBQUU7WUFDNUUsV0FBVyxFQUFFLElBQUEsMkJBQWUsRUFBQyxTQUFTLENBQUM7WUFDdkMsS0FBSyxFQUFFLElBQUEsNEJBQW1CLEVBQUMsS0FBSyxDQUFDO1NBQ2xDLENBQUMsQ0FBQztRQUVILCtGQUErRjtRQUMvRiw4RkFBOEY7UUFDOUYsaUZBQWlGO1FBQ2pGLElBQUksS0FBSyxZQUFZLCtCQUFpQixJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssWUFBWSxFQUFFLENBQUM7WUFDeEUsTUFBTSxJQUFBLCtCQUFjLEVBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUMzQyxDQUFDO1FBRUQsTUFBTSxJQUFBLG1DQUFnQixFQUFDLDRDQUF5QixDQUFDLENBQUM7SUFDcEQsQ0FBQztBQUNILENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsU0FBd0IsaUNBQWlDLENBQUMsRUFDeEQsT0FBTyxFQUNQLE1BQU0sR0FDNkI7SUFDbkMsT0FBTyxLQUFLLFVBQVUsMkJBQTJCLENBQUMsR0FBRyxFQUFFLElBQUk7UUFDekQsSUFBSSxPQUFvQyxDQUFDO1FBRXpDLE1BQU0sUUFBUSxHQUE4QixHQUFHLEVBQUU7WUFDL0MsT0FBTyxLQUFQLE9BQU8sR0FBSyxZQUFZLENBQUMsR0FBRyxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsRUFBQztZQUUvQyxPQUFPLE9BQU8sQ0FBQztRQUNqQixDQUFDLENBQUM7UUFFRixHQUFHLENBQUMsS0FBSyxDQUFDLHdCQUF3QixHQUFHLFFBQVEsQ0FBQztRQUU5QyxNQUFNLElBQUksRUFBRSxDQUFDO0lBQ2YsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQUVELFNBQWdCLHdCQUF3QixDQUFDLEdBQVk7SUFDbkQsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyx3QkFBaUUsQ0FBQztJQUU3RixJQUFJLENBQUMsUUFBUTtRQUFFLE1BQU0sSUFBQSwrQkFBYyxFQUFDLG1CQUFtQixDQUFDLENBQUM7SUFFekQsT0FBTyxRQUFRLEVBQUUsQ0FBQztBQUNwQixDQUFDO0FBRUQsU0FBZ0Isa0JBQWtCLENBQUMsR0FBWTtJQUM3QyxJQUFJLEdBQUcsQ0FBQyxLQUFLLENBQUMsUUFBUSxLQUFLLFNBQVMsRUFBRSxDQUFDO1FBQ3JDLE9BQVEsR0FBRyxDQUFDLEtBQUssQ0FBQyxjQUFxRCxFQUFFLFdBQVcsQ0FBQztJQUN2RixDQUFDO0lBRUQsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxTQUE4QyxDQUFDO0lBRTNFLE9BQU8sU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFBLDJCQUFlLEVBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUM1RCxDQUFDIn0=
|
package/dist/build-bff.d.ts
CHANGED
|
@@ -36,6 +36,14 @@ export interface Bff {
|
|
|
36
36
|
* restarting on a customization refresh — calls this instead of waiting out the 24h TTL.
|
|
37
37
|
*/
|
|
38
38
|
invalidate(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Waits for the activity-log status transitions still in flight. They are fired without `await`,
|
|
41
|
+
* so nothing else holds them: a host that stops without calling this leaves entries `pending`.
|
|
42
|
+
* Absent when the deployment writes no activity log.
|
|
43
|
+
*
|
|
44
|
+
* `timeoutMs` is the host's shutdown deadline; the returned descriptions name what it cut short.
|
|
45
|
+
*/
|
|
46
|
+
drainActivityLogs?: (timeoutMs?: number) => Promise<string[]>;
|
|
39
47
|
}
|
|
40
48
|
interface ResolvedOAuthConfig {
|
|
41
49
|
forestServerUrl: string;
|