@oxy.so/core 4.0.0 → 4.2.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/cjs/HttpService.js +142 -33
- package/dist/cjs/api/accounts.js +49 -10
- package/dist/cjs/api/apps.js +2 -2
- package/dist/cjs/api/auth.js +24 -3
- package/dist/cjs/api/billing.js +35 -0
- package/dist/cjs/boot/sessionColdBoot.js +28 -57
- package/dist/cjs/inference/OxyInferenceClient.js +53 -20
- package/dist/cjs/server/OxyServer.js +107 -51
- package/dist/cjs/server/agentAccount.js +19 -0
- package/dist/cjs/server/auth.js +19 -0
- package/dist/cjs/server/capabilityTicket.js +3 -41
- package/dist/cjs/server/index.js +10 -2
- package/dist/cjs/server/liveCapabilityTicket.js +48 -0
- package/dist/cjs/server/middleware.js +60 -18
- package/dist/cjs/server/namespaces.js +57 -0
- package/dist/cjs/server/serviceTokenLifetime.js +13 -0
- package/dist/cjs/session/SessionClient.js +90 -12
- package/dist/cjs/session/authStateStore.js +130 -39
- package/dist/cjs/session/refresh.js +152 -30
- package/dist/cjs/session/sharedDeviceCredential.js +17 -1
- package/dist/cjs/utils/officialOrigins.js +7 -18
- package/dist/esm/HttpService.js +143 -34
- package/dist/esm/api/accounts.js +38 -0
- package/dist/esm/api/apps.js +2 -2
- package/dist/esm/api/auth.js +23 -3
- package/dist/esm/api/billing.js +35 -0
- package/dist/esm/boot/sessionColdBoot.js +29 -58
- package/dist/esm/inference/OxyInferenceClient.js +46 -13
- package/dist/esm/server/OxyServer.js +107 -51
- package/dist/esm/server/agentAccount.js +16 -0
- package/dist/esm/server/auth.js +18 -0
- package/dist/esm/server/capabilityTicket.js +1 -39
- package/dist/esm/server/index.js +4 -1
- package/dist/esm/server/liveCapabilityTicket.js +45 -0
- package/dist/esm/server/middleware.js +60 -18
- package/dist/esm/server/namespaces.js +57 -0
- package/dist/esm/server/serviceTokenLifetime.js +9 -0
- package/dist/esm/session/SessionClient.js +90 -12
- package/dist/esm/session/authStateStore.js +128 -38
- package/dist/esm/session/refresh.js +151 -30
- package/dist/esm/session/sharedDeviceCredential.js +17 -1
- package/dist/esm/utils/officialOrigins.js +2 -13
- package/dist/types/HttpService.d.ts +26 -12
- package/dist/types/api/accounts.d.ts +38 -0
- package/dist/types/api/agency.d.ts +3 -1
- package/dist/types/api/apps.d.ts +3 -1
- package/dist/types/api/auth.d.ts +12 -1
- package/dist/types/api/billing.d.ts +12 -0
- package/dist/types/api/session.d.ts +3 -1
- package/dist/types/boot/sessionColdBoot.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/inference/OxyInferenceClient.d.ts +12 -0
- package/dist/types/inference/index.d.ts +1 -1
- package/dist/types/server/OxyServer.d.ts +16 -9
- package/dist/types/server/agentAccount.d.ts +14 -0
- package/dist/types/server/auth.d.ts +18 -0
- package/dist/types/server/capabilityTicket.d.ts +2 -4
- package/dist/types/server/index.d.ts +7 -1
- package/dist/types/server/liveCapabilityTicket.d.ts +15 -0
- package/dist/types/server/middleware.d.ts +15 -6
- package/dist/types/server/namespaces.d.ts +78 -2
- package/dist/types/server/serviceTokenLifetime.d.ts +7 -0
- package/dist/types/session/SessionClient.d.ts +8 -0
- package/dist/types/session/authStateStore.d.ts +15 -0
- package/dist/types/session/index.d.ts +1 -1
- package/dist/types/session/refresh.d.ts +32 -7
- package/dist/types/session/sharedDeviceCredential.d.ts +3 -1
- package/dist/types/utils/officialOrigins.d.ts +1 -5
- package/package.json +6 -6
- package/src/HttpService.ts +138 -50
- package/src/api/accounts.ts +22 -0
- package/src/api/agency.ts +3 -1
- package/src/api/apps.ts +2 -2
- package/src/api/auth.ts +31 -2
- package/src/api/billing.ts +71 -9
- package/src/api/session.ts +3 -1
- package/src/boot/sessionColdBoot.ts +27 -72
- package/src/index.ts +1 -1
- package/src/inference/OxyInferenceClient.ts +50 -5
- package/src/inference/index.ts +1 -0
- package/src/server/OxyServer.ts +114 -53
- package/src/server/agentAccount.ts +28 -0
- package/src/server/auth.ts +25 -0
- package/src/server/capabilityTicket.ts +2 -42
- package/src/server/index.ts +13 -0
- package/src/server/liveCapabilityTicket.ts +47 -0
- package/src/server/middleware.ts +72 -25
- package/src/server/namespaces.ts +99 -2
- package/src/server/serviceTokenLifetime.ts +13 -0
- package/src/session/SessionClient.ts +85 -12
- package/src/session/authStateStore.ts +130 -46
- package/src/session/index.ts +1 -0
- package/src/session/refresh.ts +144 -28
- package/src/session/sharedDeviceCredential.ts +14 -2
- package/src/utils/officialOrigins.ts +2 -13
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _OxyInferenceClient_instances, _OxyInferenceClient_baseURL, _OxyInferenceClient_credential, _OxyInferenceClient_fetch, _OxyInferenceClient_bearer, _OxyInferenceClient_headers, _OxyInferenceClient_request;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.OxyInferenceClient = exports.OxyInferenceProtocolError = exports.OxyInferenceError = exports.OXY_INFERENCE_BASE_URL = void 0;
|
|
2
16
|
/**
|
|
3
17
|
* The Oxy inference client — one surface, two credential lanes (issue #972,
|
|
4
18
|
* workstream 15).
|
|
@@ -50,21 +64,8 @@
|
|
|
50
64
|
* schema, so a rename on either side fails a build rather than a customer's
|
|
51
65
|
* request.
|
|
52
66
|
*/
|
|
53
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
54
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
55
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
56
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
57
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
58
|
-
};
|
|
59
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
60
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
61
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
62
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
63
|
-
};
|
|
64
|
-
var _OxyInferenceClient_instances, _OxyInferenceClient_baseURL, _OxyInferenceClient_credential, _OxyInferenceClient_fetch, _OxyInferenceClient_bearer, _OxyInferenceClient_headers, _OxyInferenceClient_request;
|
|
65
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
|
-
exports.OxyInferenceClient = exports.OxyInferenceProtocolError = exports.OxyInferenceError = exports.OXY_INFERENCE_BASE_URL = void 0;
|
|
67
67
|
const contracts_1 = require("@oxy.so/contracts");
|
|
68
|
+
const contracts_2 = require("@oxy.so/contracts");
|
|
68
69
|
/** The base URL of the Oxy API, when a caller names none. */
|
|
69
70
|
exports.OXY_INFERENCE_BASE_URL = 'https://api.oxy.so';
|
|
70
71
|
/**
|
|
@@ -175,7 +176,7 @@ class OxyInferenceClient {
|
|
|
175
176
|
* "no such model".
|
|
176
177
|
*/
|
|
177
178
|
async getModel(modelId, options = {}) {
|
|
178
|
-
const parsed =
|
|
179
|
+
const parsed = contracts_2.modelIdSchema.safeParse(modelId);
|
|
179
180
|
if (!parsed.success) {
|
|
180
181
|
throw new Error(`Not a canonical model id: ${modelId}. Expected <publisher>/<model>, e.g. acme/some-model.`);
|
|
181
182
|
}
|
|
@@ -212,6 +213,23 @@ class OxyInferenceClient {
|
|
|
212
213
|
: { delegatedUserId: options.delegatedUserId }),
|
|
213
214
|
});
|
|
214
215
|
}
|
|
216
|
+
/** Typed nonstreaming decisions. Idempotency conflicts are never replayed or retried. */
|
|
217
|
+
async decide(request, options = {}) {
|
|
218
|
+
const input = contracts_1.decisionRequestSchema.parse(request);
|
|
219
|
+
const payload = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'POST', '/v1/decisions', {
|
|
220
|
+
body: input,
|
|
221
|
+
bindRequestId: true,
|
|
222
|
+
signal: options.signal,
|
|
223
|
+
idempotencyKey: options.idempotencyKey,
|
|
224
|
+
delegatedUserId: options.delegatedUserId,
|
|
225
|
+
});
|
|
226
|
+
const parsed = contracts_1.decisionSuccessSchema.safeParse(payload);
|
|
227
|
+
if (!parsed.success || parsed.data.model !== input.model ||
|
|
228
|
+
!(0, contracts_1.decisionAnswersMatch)(input, parsed.data.data)) {
|
|
229
|
+
throw new OxyInferenceProtocolError('Invalid or mismatched decisions response.');
|
|
230
|
+
}
|
|
231
|
+
return parsed.data;
|
|
232
|
+
}
|
|
215
233
|
/** Generate audio bytes through Oxy's authenticated inference edge. */
|
|
216
234
|
async speech(request, options = {}) {
|
|
217
235
|
const response = await __classPrivateFieldGet(this, _OxyInferenceClient_fetch, "f").call(this, `${__classPrivateFieldGet(this, _OxyInferenceClient_baseURL, "f")}/v1/audio/speech`, {
|
|
@@ -232,7 +250,7 @@ class OxyInferenceClient {
|
|
|
232
250
|
if (!response.ok) {
|
|
233
251
|
throw toInferenceError(await response.json().catch(() => undefined), response.status, requestId);
|
|
234
252
|
}
|
|
235
|
-
const mediaType =
|
|
253
|
+
const mediaType = contracts_2.inferenceAudioMediaTypeSchema.safeParse(mediaTypeOf(response));
|
|
236
254
|
if (!mediaType.success || !response.body) {
|
|
237
255
|
await cancelUnreadResponse(response);
|
|
238
256
|
throw protocolError('The inference API returned no supported audio body.', requestId);
|
|
@@ -249,7 +267,7 @@ class OxyInferenceClient {
|
|
|
249
267
|
break;
|
|
250
268
|
}
|
|
251
269
|
size += next.value.byteLength;
|
|
252
|
-
if (size >
|
|
270
|
+
if (size > contracts_2.MAX_INFERENCE_AUDIO_BYTES)
|
|
253
271
|
throw protocolError('The inference audio exceeds the response limit.', requestId);
|
|
254
272
|
chunks.push(next.value);
|
|
255
273
|
}
|
|
@@ -332,7 +350,7 @@ class OxyInferenceClient {
|
|
|
332
350
|
catch (_error) {
|
|
333
351
|
throw protocolError(`The inference API sent a ${frame.name || 'unnamed'} SSE event that is not JSON.`, requestId);
|
|
334
352
|
}
|
|
335
|
-
const parsed =
|
|
353
|
+
const parsed = contracts_2.inferenceStreamEventSchema.safeParse(payload);
|
|
336
354
|
if (!parsed.success) {
|
|
337
355
|
const path = parsed.error.issues[0]?.path;
|
|
338
356
|
throw protocolError(`The inference API sent an event outside the published stream contract at ${path === undefined || path.length === 0 ? 'an unknown field' : path.join('.')}.`, requestId);
|
|
@@ -366,7 +384,15 @@ class OxyInferenceClient {
|
|
|
366
384
|
* somebody else.
|
|
367
385
|
*/
|
|
368
386
|
async getGeneration(id, options = {}) {
|
|
369
|
-
const
|
|
387
|
+
const record = await this.getGenerationRecord(id, options);
|
|
388
|
+
if (record.schemaVersion !== 1) {
|
|
389
|
+
throw new OxyInferenceProtocolError('Internal usage has no financial receipt. Use getGenerationRecord().', record.requestId);
|
|
390
|
+
}
|
|
391
|
+
return record;
|
|
392
|
+
}
|
|
393
|
+
/** Read a financial receipt (v1) or an internal technical record (v2). */
|
|
394
|
+
async getGenerationRecord(id, options = {}) {
|
|
395
|
+
const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', `/v1/generations/${encodeURIComponent(id)}`, options);
|
|
370
396
|
return body.data;
|
|
371
397
|
}
|
|
372
398
|
}
|
|
@@ -428,6 +454,13 @@ async function _OxyInferenceClient_request(method, path, options) {
|
|
|
428
454
|
if (!response.ok) {
|
|
429
455
|
throw toInferenceError(payload, response.status, response.headers.get('X-Oxy-Request-Id'));
|
|
430
456
|
}
|
|
457
|
+
if (options.bindRequestId) {
|
|
458
|
+
const requestId = response.headers.get('X-Oxy-Request-Id');
|
|
459
|
+
if (!requestId || typeof payload !== 'object' || payload === null ||
|
|
460
|
+
!('requestId' in payload) || payload.requestId !== requestId) {
|
|
461
|
+
throw new OxyInferenceProtocolError('Decisions response request ID does not match its header.', requestId ?? undefined);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
431
464
|
return payload;
|
|
432
465
|
};
|
|
433
466
|
/** Bound one public SSE event before parsing it as JSON. */
|
|
@@ -574,7 +607,7 @@ const STATUS_FALLBACK_CODE = {
|
|
|
574
607
|
* together — so an unrecognised one falls back to the status map instead of
|
|
575
608
|
* being asserted into the type.
|
|
576
609
|
*/
|
|
577
|
-
const INFERENCE_ERROR_CODE_SET = new Set(
|
|
610
|
+
const INFERENCE_ERROR_CODE_SET = new Set(contracts_2.INFERENCE_ERROR_CODES);
|
|
578
611
|
/** Read whichever error shape arrived into the one this client throws. */
|
|
579
612
|
function toInferenceError(payload, status, requestIdHeader) {
|
|
580
613
|
const body = (payload ?? {});
|
|
@@ -29,6 +29,18 @@ exports.OxyServer = exports.ServiceCredentialMismatchError = exports.ANONYMOUS_S
|
|
|
29
29
|
* Node only.
|
|
30
30
|
*/
|
|
31
31
|
const jwt_decode_1 = require("jwt-decode");
|
|
32
|
+
const serviceTokenLifetime_1 = require("./serviceTokenLifetime");
|
|
33
|
+
/** Unverified cache metadata only: an old lifetime forces a new signed mint. */
|
|
34
|
+
function reusableServiceToken(token) {
|
|
35
|
+
try {
|
|
36
|
+
const claims = (0, jwt_decode_1.jwtDecode)(token);
|
|
37
|
+
return (0, serviceTokenLifetime_1.hasBoundedServiceTokenLifetime)(claims) && typeof claims.exp === 'number'
|
|
38
|
+
&& claims.exp > Math.floor(Date.now() / 1000);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
32
44
|
const protocol_1 = require("@oxy.so/protocol");
|
|
33
45
|
const OxyServices_1 = require("../OxyServices");
|
|
34
46
|
const logger_1 = require("../logger");
|
|
@@ -57,10 +69,10 @@ const WORKLOAD_CACHE_KEY = 'workload-identity';
|
|
|
57
69
|
const TOKEN_REUSE_MARGIN_MS = 60000;
|
|
58
70
|
/** Bound on remembered `appId:userId` delegation answers. */
|
|
59
71
|
const ACTING_AS_CACHE_MAX = 1000;
|
|
60
|
-
/**
|
|
61
|
-
const ACTING_AS_GRANT_TTL_MS =
|
|
62
|
-
/**
|
|
63
|
-
const ACTING_AS_DENIAL_TTL_MS =
|
|
72
|
+
/** Explicit read reuse only; effects always perform a fresh lookup. */
|
|
73
|
+
const ACTING_AS_GRANT_TTL_MS = 60000;
|
|
74
|
+
/** Explicit authoritative read denials last ten seconds; failures are never cached. */
|
|
75
|
+
const ACTING_AS_DENIAL_TTL_MS = 10000;
|
|
64
76
|
class OxyServer extends OxyServices_1.OxyServices {
|
|
65
77
|
constructor(config) {
|
|
66
78
|
super(config);
|
|
@@ -69,6 +81,7 @@ class OxyServer extends OxyServices_1.OxyServices {
|
|
|
69
81
|
this.serviceApiSecret = null;
|
|
70
82
|
this.actingAs = new Map();
|
|
71
83
|
this.actingAsPending = new Map();
|
|
84
|
+
this.actingAsGenerations = new Map();
|
|
72
85
|
/** Public keys only; never private material. */
|
|
73
86
|
this.jwksCache = { keys: new Map(), expiresAt: 0, lastAttemptAt: 0 };
|
|
74
87
|
/**
|
|
@@ -122,7 +135,7 @@ class OxyServer extends OxyServices_1.OxyServices {
|
|
|
122
135
|
},
|
|
123
136
|
jwksCache: this.jwksCache,
|
|
124
137
|
validateSession: (sessionId, options) => this.session.validate(sessionId, options),
|
|
125
|
-
verifyActingAs: (appId, userId) => this.verifyActingAs(appId, userId),
|
|
138
|
+
verifyActingAs: (appId, userId, options) => this.verifyActingAs(appId, userId, options),
|
|
126
139
|
});
|
|
127
140
|
}
|
|
128
141
|
/** Files, plus the service-only metadata and linked-URL lookups. */
|
|
@@ -208,7 +221,7 @@ class OxyServer extends OxyServices_1.OxyServices {
|
|
|
208
221
|
});
|
|
209
222
|
throw new ServiceCredentialMismatchError();
|
|
210
223
|
}
|
|
211
|
-
if (entry.token && entry.expiresAt > now + TOKEN_REUSE_MARGIN_MS) {
|
|
224
|
+
if (entry.token && reusableServiceToken(entry.token) && entry.expiresAt > now + TOKEN_REUSE_MARGIN_MS) {
|
|
212
225
|
return entry.token;
|
|
213
226
|
}
|
|
214
227
|
if (entry.pending) {
|
|
@@ -270,32 +283,49 @@ class OxyServer extends OxyServices_1.OxyServices {
|
|
|
270
283
|
}
|
|
271
284
|
// ── Delegation and account events ────────────────────────────────────────
|
|
272
285
|
/**
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* revocation window; a denial or failed lookup for 1 minute), bounded to the
|
|
279
|
-
* 1000 most recent pairs, and concurrent lookups of one pair share a single
|
|
280
|
-
* request.
|
|
286
|
+
* Resolve authority live by default. Explicit read-only reuse lasts at most
|
|
287
|
+
* 60s positive / 10s negative, anchored to request start; errors never cache.
|
|
288
|
+
* Effects do not share an older in-flight read. Durable epoch plus local
|
|
289
|
+
* completion ordering prevents ABA and late positive replies overwriting a
|
|
290
|
+
* newer refusal even when app/credential revocation leaves epoch unchanged.
|
|
281
291
|
*/
|
|
282
|
-
async verifyActingAs(appId, userId) {
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
async verifyActingAs(appId, userId, options = {}) {
|
|
293
|
+
const pair = JSON.stringify([appId, userId]);
|
|
294
|
+
const cacheKey = JSON.stringify([appId, userId, options.credentialId, options.ownerAccountId, options.environment]);
|
|
295
|
+
if (options.cache === true) {
|
|
296
|
+
const cached = this.actingAs.get(cacheKey);
|
|
297
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
298
|
+
this.actingAs.delete(cacheKey);
|
|
299
|
+
this.actingAs.set(cacheKey, cached);
|
|
300
|
+
return cached.result;
|
|
301
|
+
}
|
|
302
|
+
const pending = this.actingAsPending.get(cacheKey);
|
|
303
|
+
if (pending)
|
|
304
|
+
return pending;
|
|
305
|
+
}
|
|
306
|
+
let generation = this.actingAsGenerations.get(pair);
|
|
307
|
+
if (!generation) {
|
|
308
|
+
generation = { epoch: 0n, issued: 0, completed: 0 };
|
|
309
|
+
this.actingAsGenerations.set(pair, generation);
|
|
310
|
+
if (this.actingAsGenerations.size > ACTING_AS_CACHE_MAX) {
|
|
311
|
+
const oldest = this.actingAsGenerations.keys().next().value;
|
|
312
|
+
if (oldest !== undefined)
|
|
313
|
+
this.actingAsGenerations.delete(oldest);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const serial = ++generation.issued;
|
|
317
|
+
const startedAt = Date.now();
|
|
318
|
+
const lookup = this.lookupActingAs(appId, userId, options, pair, cacheKey, generation, serial, startedAt);
|
|
319
|
+
if (options.cache !== true)
|
|
320
|
+
return lookup;
|
|
297
321
|
this.actingAsPending.set(cacheKey, lookup);
|
|
298
|
-
|
|
322
|
+
try {
|
|
323
|
+
return await lookup;
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
if (this.actingAsPending.get(cacheKey) === lookup)
|
|
327
|
+
this.actingAsPending.delete(cacheKey);
|
|
328
|
+
}
|
|
299
329
|
}
|
|
300
330
|
// ── Internals ────────────────────────────────────────────────────────────
|
|
301
331
|
/**
|
|
@@ -351,7 +381,7 @@ class OxyServer extends OxyServices_1.OxyServices {
|
|
|
351
381
|
/** A token obtained by attestation, cached like a credential's (one identity per process). */
|
|
352
382
|
async workloadServiceToken() {
|
|
353
383
|
const entry = this.serviceTokens.get(WORKLOAD_CACHE_KEY);
|
|
354
|
-
if (entry?.token && entry.expiresAt > Date.now() + TOKEN_REUSE_MARGIN_MS)
|
|
384
|
+
if (entry?.token && reusableServiceToken(entry.token) && entry.expiresAt > Date.now() + TOKEN_REUSE_MARGIN_MS)
|
|
355
385
|
return entry.token;
|
|
356
386
|
if (entry?.pending)
|
|
357
387
|
return entry.pending;
|
|
@@ -382,36 +412,62 @@ class OxyServer extends OxyServices_1.OxyServices {
|
|
|
382
412
|
settled.pending = null;
|
|
383
413
|
}
|
|
384
414
|
}
|
|
385
|
-
async lookupActingAs(appId, userId) {
|
|
415
|
+
async lookupActingAs(appId, userId, options, pair, cacheKey, generation, serial, startedAt) {
|
|
386
416
|
try {
|
|
387
|
-
// The verify endpoint admits only a platform-trusted caller, so this
|
|
388
|
-
// carries the VERIFIER's own service token. No retry and a short timeout:
|
|
389
|
-
// it runs inside request-handling middleware, and a retry loop would
|
|
390
|
-
// multiply the latency of every delegated request. A verifier with no
|
|
391
|
-
// credential throws here and is refused below — a host that cannot prove
|
|
392
|
-
// who it is has no business learning who delegated to whom.
|
|
393
417
|
const serviceToken = await this.serviceToken();
|
|
394
|
-
const result = await this.request('GET', '/internal/service-acting-as/verify', { appId, userId
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
this.
|
|
418
|
+
const result = await this.request('GET', '/internal/service-acting-as/verify', { appId, userId, ...(options.credentialId ? { credentialId: options.credentialId,
|
|
419
|
+
ownerAccountId: options.ownerAccountId, environment: options.environment } : {}) }, { cache: false, retry: false, timeout: 5000, headers: { Authorization: `Bearer ${serviceToken}` } });
|
|
420
|
+
// A deadline bounds how long an already old response can authorize. It
|
|
421
|
+
// is not by itself a measurement of end-to-end revocation latency.
|
|
422
|
+
if (this.actingAsGenerations.get(pair) !== generation || serial < generation.completed)
|
|
423
|
+
return null;
|
|
424
|
+
generation.completed = serial;
|
|
425
|
+
this.clearActingAsPair(appId, userId);
|
|
426
|
+
if (Date.now() - startedAt >= 5000)
|
|
427
|
+
return null;
|
|
428
|
+
const rawEpoch = result?.epoch;
|
|
429
|
+
if (typeof rawEpoch !== 'string' || !/^(?:0|[1-9][0-9]{0,18})$/.test(rawEpoch))
|
|
430
|
+
return null;
|
|
431
|
+
const epoch = BigInt(rawEpoch);
|
|
432
|
+
if (epoch > 9223372036854775807n)
|
|
433
|
+
return null;
|
|
434
|
+
if (typeof result.authorized !== 'boolean' || !Array.isArray(result.scopes)
|
|
435
|
+
|| !result.scopes.every((scope) => typeof scope === 'string' && scope.length > 0 && scope.trim() === scope)
|
|
436
|
+
|| new Set(result.scopes).size !== result.scopes.length
|
|
437
|
+
|| (!result.authorized && result.scopes.length !== 0))
|
|
438
|
+
return null;
|
|
439
|
+
const older = epoch < generation.epoch;
|
|
440
|
+
if (epoch > generation.epoch)
|
|
441
|
+
generation.epoch = epoch;
|
|
442
|
+
if (result.authorized && older)
|
|
443
|
+
return null;
|
|
444
|
+
const verified = result.authorized === true
|
|
445
|
+
? { authorized: true, scopes: result.scopes, epoch: rawEpoch } : null;
|
|
446
|
+
if (options.cache === true)
|
|
447
|
+
this.rememberActingAs(cacheKey, verified, startedAt + (verified ? ACTING_AS_GRANT_TTL_MS : ACTING_AS_DENIAL_TTL_MS));
|
|
399
448
|
return verified;
|
|
400
449
|
}
|
|
401
450
|
catch (error) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
451
|
+
if (this.actingAsGenerations.get(pair) === generation && serial >= generation.completed) {
|
|
452
|
+
generation.completed = serial;
|
|
453
|
+
this.clearActingAsPair(appId, userId);
|
|
454
|
+
}
|
|
455
|
+
logger_1.logger.warn('[oxy.auth] verifyActingAs lookup failed — not cached', {
|
|
456
|
+
component: 'auth', method: 'verifyActingAs', appId, userId,
|
|
407
457
|
}, error);
|
|
408
|
-
this.rememberActingAs(`${appId}:${userId}`, null, ACTING_AS_DENIAL_TTL_MS);
|
|
409
458
|
return null;
|
|
410
459
|
}
|
|
411
460
|
}
|
|
412
|
-
|
|
461
|
+
clearActingAsPair(appId, userId) {
|
|
462
|
+
for (const key of this.actingAs.keys()) {
|
|
463
|
+
const [cachedApp, cachedUser] = JSON.parse(key);
|
|
464
|
+
if (cachedApp === appId && cachedUser === userId)
|
|
465
|
+
this.actingAs.delete(key);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
rememberActingAs(cacheKey, result, expiresAt) {
|
|
413
469
|
this.actingAs.delete(cacheKey);
|
|
414
|
-
this.actingAs.set(cacheKey, { result, expiresAt
|
|
470
|
+
this.actingAs.set(cacheKey, { result, expiresAt });
|
|
415
471
|
if (this.actingAs.size > ACTING_AS_CACHE_MAX) {
|
|
416
472
|
const oldest = this.actingAs.keys().next().value;
|
|
417
473
|
if (oldest !== undefined)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signInAgentAccount = signInAgentAccount;
|
|
4
|
+
/** Autonomous entry helper. The signer owns its private key (for example in a KMS). */
|
|
5
|
+
const contracts_1 = require("@oxy.so/contracts");
|
|
6
|
+
const secp256k1_1 = require("@oxy.so/protocol/secp256k1");
|
|
7
|
+
/** Explicit sign-in; no timer, automatic escalation or service credential lane. */
|
|
8
|
+
async function signInAgentAccount(options) {
|
|
9
|
+
const { client, accountId, signer } = options;
|
|
10
|
+
const publicKey = (0, secp256k1_1.normalizeSecp256k1PublicKey)(signer.publicKey.trim());
|
|
11
|
+
const claims = await client.auth.agent.requestChallenge(publicKey);
|
|
12
|
+
const timestamp = Date.now();
|
|
13
|
+
if (claims.action !== 'agent_signin' || claims.accountId !== accountId || claims.actorId !== accountId
|
|
14
|
+
|| (0, secp256k1_1.normalizeSecp256k1PublicKey)(claims.publicKey) !== publicKey || !claims.authMethodId || claims.expiresAt <= timestamp) {
|
|
15
|
+
throw new Error('Agent challenge does not match this account and signer');
|
|
16
|
+
}
|
|
17
|
+
const signature = await signer.signMessage((0, contracts_1.buildAgentProofMessage)(claims, timestamp));
|
|
18
|
+
return client.auth.agent.verify(publicKey, { challenge: claims.challenge, timestamp, signature });
|
|
19
|
+
}
|
package/dist/cjs/server/auth.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OXY_SERVICE_ENVIRONMENTS = void 0;
|
|
4
4
|
exports.getOxyUserId = getOxyUserId;
|
|
5
|
+
exports.getOxyActor = getOxyActor;
|
|
5
6
|
exports.isOxyAuthenticated = isOxyAuthenticated;
|
|
6
7
|
exports.getOxyBillingPrincipal = getOxyBillingPrincipal;
|
|
7
8
|
exports.getRequiredOxyBillingPrincipal = getRequiredOxyBillingPrincipal;
|
|
@@ -39,6 +40,24 @@ function getOxyUserId(req) {
|
|
|
39
40
|
normalizeId(authReq.user?.id) ??
|
|
40
41
|
normalizeId(authReq.user?._id));
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Who acted, and as whom, on this user session (issue #1520), or `null` when
|
|
45
|
+
* the session authority did not report it.
|
|
46
|
+
*
|
|
47
|
+
* An audit records `actorAccountId` as the actor and `effectiveAccountId`
|
|
48
|
+
* (equal to {@link getOxyUserId}) as the account spoken as. A bot acting with
|
|
49
|
+
* nobody operating it is its own actor; a person acting as an organization or
|
|
50
|
+
* a bot is the actor of a `delegated` chain. Never substitute the effective
|
|
51
|
+
* account's owner for either: when this is `null`, record the actor as unknown.
|
|
52
|
+
*/
|
|
53
|
+
function getOxyActor(req) {
|
|
54
|
+
const actor = req.oxyActor;
|
|
55
|
+
if (!actor)
|
|
56
|
+
return null;
|
|
57
|
+
// Defence in depth: a chain that does not describe the authenticated
|
|
58
|
+
// subject is not this request's chain.
|
|
59
|
+
return actor.effectiveAccountId === getOxyUserId(req) ? actor : null;
|
|
60
|
+
}
|
|
42
61
|
function isOxyAuthenticated(req) {
|
|
43
62
|
return getOxyUserId(req) !== null;
|
|
44
63
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CapabilityTicketError = void 0;
|
|
4
|
-
exports.inputSatisfiesCapabilityLimits = inputSatisfiesCapabilityLimits;
|
|
3
|
+
exports.inputSatisfiesCapabilityLimits = exports.CapabilityTicketError = void 0;
|
|
5
4
|
exports.issueCapabilityTicket = issueCapabilityTicket;
|
|
6
5
|
exports.verifyCapabilityTicket = verifyCapabilityTicket;
|
|
7
6
|
exports.readCapabilityAuthorization = readCapabilityAuthorization;
|
|
@@ -30,45 +29,6 @@ function decodeJsonSegment(segment) {
|
|
|
30
29
|
throw new CapabilityTicketError('malformed', 'Capability ticket is not valid base64url JSON');
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
|
-
function valuesAtPath(input, path) {
|
|
34
|
-
let current = [input];
|
|
35
|
-
for (const segment of path.split('.')) {
|
|
36
|
-
const next = [];
|
|
37
|
-
for (const value of current) {
|
|
38
|
-
const records = Array.isArray(value) ? value : [value];
|
|
39
|
-
for (const record of records) {
|
|
40
|
-
if (typeof record !== 'object' || record === null || Array.isArray(record))
|
|
41
|
-
continue;
|
|
42
|
-
if (Object.prototype.hasOwnProperty.call(record, segment)) {
|
|
43
|
-
next.push(record[segment]);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (next.length === 0)
|
|
48
|
-
return [];
|
|
49
|
-
current = next;
|
|
50
|
-
}
|
|
51
|
-
return current.flatMap((value) => Array.isArray(value) ? value : [value]);
|
|
52
|
-
}
|
|
53
|
-
/** Enforces the signed per-action bounds before a domain handler runs. */
|
|
54
|
-
function inputSatisfiesCapabilityLimits(tool, input, limits) {
|
|
55
|
-
for (const limit of limits) {
|
|
56
|
-
if (limit.tool !== tool)
|
|
57
|
-
return false;
|
|
58
|
-
const actualValues = valuesAtPath(input, limit.key);
|
|
59
|
-
if (actualValues.length === 0)
|
|
60
|
-
return false;
|
|
61
|
-
if (typeof limit.value === 'number') {
|
|
62
|
-
const maximum = limit.value;
|
|
63
|
-
if (!actualValues.every((actual) => (typeof actual === 'number' && Number.isFinite(actual) && actual <= maximum)))
|
|
64
|
-
return false;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (!actualValues.every((actual) => actual === limit.value))
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
32
|
function issueCapabilityTicket(claims, options) {
|
|
73
33
|
const ttlSeconds = options.ttlSeconds ?? 60;
|
|
74
34
|
if (!Number.isInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > DEFAULT_MAX_TTL_SECONDS) {
|
|
@@ -190,3 +150,5 @@ function createCapabilityTicketMiddleware(options) {
|
|
|
190
150
|
}
|
|
191
151
|
};
|
|
192
152
|
}
|
|
153
|
+
var contracts_2 = require("@oxy.so/contracts");
|
|
154
|
+
Object.defineProperty(exports, "inputSatisfiesCapabilityLimits", { enumerable: true, get: function () { return contracts_2.inputSatisfiesCapabilityLimits; } });
|
package/dist/cjs/server/index.js
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.
|
|
20
|
-
exports.requestWorkloadServiceToken = exports.canAttestWorkloadIdentity = exports.withoutNodeHttpObservation = exports.observeNodeHttp = exports.createEcosystemTraffic = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.registrableApex = exports.OXY_IDENTITY_CACHE_PREFIXES = exports.oxyUserByIdCacheKey = exports.evictOxyIdentityCache = exports.publishOxyUserInvalidation = exports.createOxyUserInvalidationHandler = exports.verifySecret = exports.verifyOxyRequesterAssertion = exports.signOxyRequesterAssertion = exports.readOxyRequesterAssertionKeyId = exports.oxyRequesterAssertionClaimsSchema = exports.OxyRequesterAssertionError = exports.OXY_REQUESTER_ASSERTION_TYPE = exports.OXY_REQUESTER_ASSERTION_MAX_TTL_SECONDS = exports.OXY_REQUESTER_ASSERTION_HEADER = exports.OXY_REQUESTER_ASSERTION_DEFAULT_ISSUER = exports.createOxyRequesterAssertionAuth = exports.createOxyJwksKeyResolver = void 0;
|
|
19
|
+
exports.readCapabilityAuthorization = exports.issueCapabilityTicket = exports.inputSatisfiesCapabilityLimits = exports.createCapabilityTicketMiddleware = exports.CapabilityTicketError = exports.OXY_CSP_BASELINE = exports.inlineScriptCspHash = exports.formatOxyCspPolicy = exports.extractInlineScripts = exports.cspSourcesFor = exports.createOxySecurityHeaders = exports.buildOxyPagesHeaders = exports.buildOxyCspDirectives = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.OXY_SERVICE_ENVIRONMENTS = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getRequiredOxyBillingPrincipal = exports.getOxyUserId = exports.getOxyRequestAttribution = exports.getOxyDelegatedUserId = exports.getOxyBillingPrincipal = exports.getOxyAuthRefusal = exports.getOxyActor = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = exports.OXY_ACCOUNT_DELETED_EVENT_URI = exports.OxyAccountEventError = exports.ServerReputationApi = exports.ServerNotificationsApi = exports.ServerLinkedAccountsApi = exports.ServerAssetsApi = exports.ServerAgencyApi = exports.ANONYMOUS_SERVICE_TOKEN_RETRY_MS = exports.ServiceCredentialMismatchError = exports.OxyServer = void 0;
|
|
20
|
+
exports.signInAgentAccount = exports.hasBoundedServiceTokenLifetime = exports.OXY_SERVICE_TOKEN_MAX_LIFETIME_SECONDS = exports.requestWorkloadServiceToken = exports.canAttestWorkloadIdentity = exports.withoutNodeHttpObservation = exports.observeNodeHttp = exports.createEcosystemTraffic = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.registrableApex = exports.OXY_IDENTITY_CACHE_PREFIXES = exports.oxyUserByIdCacheKey = exports.evictOxyIdentityCache = exports.publishOxyUserInvalidation = exports.createOxyUserInvalidationHandler = exports.createLiveCapabilityTicketVerifier = exports.verifySecret = exports.verifyOxyRequesterAssertion = exports.signOxyRequesterAssertion = exports.readOxyRequesterAssertionKeyId = exports.oxyRequesterAssertionClaimsSchema = exports.OxyRequesterAssertionError = exports.OXY_REQUESTER_ASSERTION_TYPE = exports.OXY_REQUESTER_ASSERTION_MAX_TTL_SECONDS = exports.OXY_REQUESTER_ASSERTION_HEADER = exports.OXY_REQUESTER_ASSERTION_DEFAULT_ISSUER = exports.createOxyRequesterAssertionAuth = exports.createOxyJwksKeyResolver = exports.verifyCapabilityTicket = void 0;
|
|
21
21
|
// The backend client: OxyServices plus the service-token lane, middleware,
|
|
22
22
|
// account events and the service-only namespace methods.
|
|
23
23
|
var OxyServer_1 = require("./OxyServer");
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "OXY_ACCOUNT_DELETED_EVENT_URI", { enumerable: tr
|
|
|
36
36
|
var auth_1 = require("./auth");
|
|
37
37
|
Object.defineProperty(exports, "createOptionalOxyAuth", { enumerable: true, get: function () { return auth_1.createOptionalOxyAuth; } });
|
|
38
38
|
Object.defineProperty(exports, "createOxyAuthMiddleware", { enumerable: true, get: function () { return auth_1.createOxyAuthMiddleware; } });
|
|
39
|
+
Object.defineProperty(exports, "getOxyActor", { enumerable: true, get: function () { return auth_1.getOxyActor; } });
|
|
39
40
|
Object.defineProperty(exports, "getOxyAuthRefusal", { enumerable: true, get: function () { return auth_1.getOxyAuthRefusal; } });
|
|
40
41
|
Object.defineProperty(exports, "getOxyBillingPrincipal", { enumerable: true, get: function () { return auth_1.getOxyBillingPrincipal; } });
|
|
41
42
|
Object.defineProperty(exports, "getOxyDelegatedUserId", { enumerable: true, get: function () { return auth_1.getOxyDelegatedUserId; } });
|
|
@@ -106,6 +107,8 @@ Object.defineProperty(exports, "verifyOxyRequesterAssertion", { enumerable: true
|
|
|
106
107
|
// Constant-time secret comparison.
|
|
107
108
|
var verifySecret_1 = require("./verifySecret");
|
|
108
109
|
Object.defineProperty(exports, "verifySecret", { enumerable: true, get: function () { return verifySecret_1.verifySecret; } });
|
|
110
|
+
var liveCapabilityTicket_1 = require("./liveCapabilityTicket");
|
|
111
|
+
Object.defineProperty(exports, "createLiveCapabilityTicketVerifier", { enumerable: true, get: function () { return liveCapabilityTicket_1.createLiveCapabilityTicketVerifier; } });
|
|
109
112
|
// Cross-service user-invalidation signal: oxy-api publishes when identity
|
|
110
113
|
// changes, every consuming backend sweeps its caches instead of waiting out a TTL.
|
|
111
114
|
var userInvalidation_1 = require("./userInvalidation");
|
|
@@ -142,3 +145,8 @@ Object.defineProperty(exports, "withoutNodeHttpObservation", { enumerable: true,
|
|
|
142
145
|
var workloadIdentity_1 = require("./workloadIdentity");
|
|
143
146
|
Object.defineProperty(exports, "canAttestWorkloadIdentity", { enumerable: true, get: function () { return workloadIdentity_1.canAttestWorkloadIdentity; } });
|
|
144
147
|
Object.defineProperty(exports, "requestWorkloadServiceToken", { enumerable: true, get: function () { return workloadIdentity_1.requestWorkloadServiceToken; } });
|
|
148
|
+
var serviceTokenLifetime_1 = require("./serviceTokenLifetime");
|
|
149
|
+
Object.defineProperty(exports, "OXY_SERVICE_TOKEN_MAX_LIFETIME_SECONDS", { enumerable: true, get: function () { return serviceTokenLifetime_1.OXY_SERVICE_TOKEN_MAX_LIFETIME_SECONDS; } });
|
|
150
|
+
Object.defineProperty(exports, "hasBoundedServiceTokenLifetime", { enumerable: true, get: function () { return serviceTokenLifetime_1.hasBoundedServiceTokenLifetime; } });
|
|
151
|
+
var agentAccount_1 = require("./agentAccount");
|
|
152
|
+
Object.defineProperty(exports, "signInAgentAccount", { enumerable: true, get: function () { return agentAccount_1.signInAgentAccount; } });
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLiveCapabilityTicketVerifier = createLiveCapabilityTicketVerifier;
|
|
4
|
+
const contracts_1 = require("@oxy.so/contracts");
|
|
5
|
+
const capabilityTicket_1 = require("./capabilityTicket");
|
|
6
|
+
/** Local signature + live receiving-service introspection; no cache or retry. */
|
|
7
|
+
function createLiveCapabilityTicketVerifier(options) {
|
|
8
|
+
if (typeof options.issuer !== 'string' || options.issuer.trim() === '')
|
|
9
|
+
throw new Error('A trusted Capability issuer is required');
|
|
10
|
+
const timeoutMs = options.timeoutMs ?? 5000;
|
|
11
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 60000) {
|
|
12
|
+
throw new Error('Capability introspection timeout must be between 1 and 60000ms');
|
|
13
|
+
}
|
|
14
|
+
return async (ticket, input = {}) => {
|
|
15
|
+
input.signal?.throwIfAborted();
|
|
16
|
+
const signed = (0, capabilityTicket_1.verifyCapabilityTicket)(ticket, options);
|
|
17
|
+
if (!signed.catalog)
|
|
18
|
+
throw new Error('Internal MCP requires a signed catalogue binding');
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
const abort = () => controller.abort(input.signal?.reason);
|
|
21
|
+
input.signal?.addEventListener('abort', abort, { once: true });
|
|
22
|
+
const timer = setTimeout(() => controller.abort(new Error('Capability introspection timed out')), timeoutMs);
|
|
23
|
+
let rejectAbort;
|
|
24
|
+
try {
|
|
25
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
26
|
+
rejectAbort = () => reject(controller.signal.reason ?? new Error('Capability introspection aborted'));
|
|
27
|
+
controller.signal.addEventListener('abort', rejectAbort, { once: true });
|
|
28
|
+
if (controller.signal.aborted)
|
|
29
|
+
rejectAbort();
|
|
30
|
+
});
|
|
31
|
+
const result = await Promise.race([options.introspect(ticket, { signal: controller.signal }), interrupted]);
|
|
32
|
+
controller.signal.throwIfAborted();
|
|
33
|
+
if (result.active !== true)
|
|
34
|
+
throw new Error('Capability ticket is inactive');
|
|
35
|
+
const live = contracts_1.capabilityTicketClaimsSchema.parse(result.claims);
|
|
36
|
+
if ((0, contracts_1.canonicalCapabilityJson)(live) !== (0, contracts_1.canonicalCapabilityJson)(signed))
|
|
37
|
+
throw new Error('Capability introspection claims mismatch');
|
|
38
|
+
// An authority response delayed past expiry cannot extend the signed grant.
|
|
39
|
+
return (0, capabilityTicket_1.verifyCapabilityTicket)(ticket, options);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
input.signal?.removeEventListener('abort', abort);
|
|
44
|
+
if (rejectAbort)
|
|
45
|
+
controller.signal.removeEventListener('abort', rejectAbort);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|