@aep-foundation/agent 0.1.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/index.js ADDED
@@ -0,0 +1,997 @@
1
+ // src/index.ts
2
+ import {
3
+ AEP_AUTH_SCHEME,
4
+ AEP_AUTHENTICATED_COMMANDS,
5
+ AEP_BUILT_IN_GRANT_TYPES,
6
+ AEP_MEDIA_TYPE,
7
+ AEP_SIGNING_ALGORITHMS,
8
+ AEP_WELL_KNOWN_PATH,
9
+ commandPathFromInspect,
10
+ parseBuiltInGrantResponse,
11
+ parseClientAssertionClaims,
12
+ parseEnrollResponse,
13
+ parseGrantRequest,
14
+ parseInspectDocument,
15
+ parseProblemDetails,
16
+ parseRevokeRequest,
17
+ parseRevokeResponse,
18
+ parseStatusResponse,
19
+ signClientAssertionJwt
20
+ } from "@aep-foundation/core";
21
+ import {
22
+ createPlatformProvisionRequest,
23
+ createPlatformSignRequest
24
+ } from "@aep-foundation/platform";
25
+ var AepInspectError = class extends Error {
26
+ status;
27
+ constructor(message, status) {
28
+ super(message);
29
+ this.name = "AepInspectError";
30
+ if (status !== void 0) {
31
+ this.status = status;
32
+ }
33
+ }
34
+ };
35
+ var AepCommandError = class extends Error {
36
+ problem;
37
+ status;
38
+ constructor(message, status, problem) {
39
+ super(message);
40
+ this.name = "AepCommandError";
41
+ this.status = status;
42
+ if (problem !== void 0) {
43
+ this.problem = problem;
44
+ }
45
+ }
46
+ };
47
+ function createAepAgent(options) {
48
+ const identityStore = options.identityStore ?? createInMemoryAgentIdentityStore();
49
+ const credentialStore = options.credentialStore ?? createInMemorySessionCredentialStore();
50
+ const idempotencyKeys = options.idempotencyKeys ?? createRandomIdempotencyKeyProvider();
51
+ const inspectCache = options.inspectCache ?? createInMemoryInspectCache();
52
+ const assertionOptions = assertionOptionsWithDefinedValues(options);
53
+ return {
54
+ serviceSession: (sessionOptions) => createAepServiceSession({
55
+ ...assertionOptions,
56
+ credentialStore,
57
+ idempotencyKeys,
58
+ identityProvider: options.identityProvider,
59
+ identityStore,
60
+ inspectCache,
61
+ serviceUrl: sessionOptions.serviceUrl
62
+ })
63
+ };
64
+ }
65
+ function createAepServiceSession(state) {
66
+ const serviceUrl = normalizeServiceUrl(state.serviceUrl);
67
+ const serviceUrlString = String(serviceUrl);
68
+ let inspectPromise;
69
+ let identityPromise;
70
+ let signerPromise;
71
+ const inspectOnce = async () => {
72
+ if (inspectPromise !== void 0) {
73
+ return inspectPromise;
74
+ }
75
+ inspectPromise = (async () => {
76
+ const cached = await state.inspectCache.get(serviceUrlString);
77
+ if (cached !== void 0) {
78
+ return cached;
79
+ }
80
+ const inspected = await inspectService({ serviceUrl });
81
+ const cachedResult = {
82
+ ...inspected,
83
+ cachedAt: (/* @__PURE__ */ new Date()).toISOString()
84
+ };
85
+ await state.inspectCache.set(serviceUrlString, cachedResult);
86
+ return inspected;
87
+ })();
88
+ return inspectPromise;
89
+ };
90
+ const identityOnce = async () => {
91
+ if (identityPromise !== void 0) {
92
+ return identityPromise;
93
+ }
94
+ identityPromise = (async () => {
95
+ const inspected = await inspectOnce();
96
+ const serviceDid = inspected.document.service.did;
97
+ const existing = await state.identityStore.findByServiceDid(serviceDid);
98
+ if (existing !== void 0) {
99
+ return existing;
100
+ }
101
+ const created = await state.identityProvider.getOrCreateIdentity({
102
+ inspect: inspected.document,
103
+ serviceDid,
104
+ serviceUrl: serviceUrlString
105
+ });
106
+ return state.identityStore.saveIdentity(created);
107
+ })();
108
+ return identityPromise;
109
+ };
110
+ const signerOnce = async () => {
111
+ if (signerPromise !== void 0) {
112
+ return signerPromise;
113
+ }
114
+ signerPromise = Promise.resolve(state.identityProvider.signerFor(await identityOnce()));
115
+ return signerPromise;
116
+ };
117
+ const commandOptions = async () => {
118
+ const inspected = await inspectOnce();
119
+ const identity = await identityOnce();
120
+ const signer = await signerOnce();
121
+ return {
122
+ agentDid: identity.agentDid,
123
+ clientAssertionSigner: signer,
124
+ inspect: inspected,
125
+ serviceUrl
126
+ };
127
+ };
128
+ const assertionOptions = () => ({
129
+ ...state.assertionClock === void 0 ? {} : { assertionClock: state.assertionClock },
130
+ ...state.assertionJti === void 0 ? {} : { assertionJti: state.assertionJti },
131
+ ...state.assertionTtlSeconds === void 0 ? {} : { assertionTtlSeconds: state.assertionTtlSeconds }
132
+ });
133
+ const idempotencyKey = async (operation) => {
134
+ const inspected = await inspectOnce();
135
+ return state.idempotencyKeys.createKey({
136
+ ...operation,
137
+ serviceDid: inspected.document.service.did,
138
+ serviceUrl: serviceUrlString
139
+ });
140
+ };
141
+ return {
142
+ async authenticationHeaders(options = {}) {
143
+ const inspected = await inspectOnce();
144
+ if (options.preferCredential !== false) {
145
+ const credential = await state.credentialStore.findUsableCredential(
146
+ inspected.document.service.did
147
+ );
148
+ if (credential !== void 0) {
149
+ return protectedResourceAuthenticationHeaders({
150
+ credential: parseBuiltInGrantResponse(credential.grantType, credential.credential)
151
+ });
152
+ }
153
+ }
154
+ const identity = await identityOnce();
155
+ const signer = await signerOnce();
156
+ return protectedResourceAuthenticationHeaders({
157
+ agentDid: identity.agentDid,
158
+ inspect: inspected,
159
+ ...state.assertionClock === void 0 ? {} : { clock: state.assertionClock },
160
+ ...state.assertionJti === void 0 ? {} : { jti: state.assertionJti },
161
+ signer,
162
+ ...state.assertionTtlSeconds === void 0 ? {} : { ttlSeconds: state.assertionTtlSeconds }
163
+ });
164
+ },
165
+ async enroll(options = {}) {
166
+ return enrollService({
167
+ ...await commandOptions(),
168
+ ...assertionOptions(),
169
+ ...options.claims === void 0 ? {} : { claims: options.claims },
170
+ idempotencyKey: options.idempotencyKey ?? await idempotencyKey({ command: "enroll" })
171
+ });
172
+ },
173
+ async grant(options = {}) {
174
+ const inspected = await inspectOnce();
175
+ const grantType = options.grantType ?? selectGrantType(inspected, {
176
+ ...options.preferredGrantTypes === void 0 ? {} : { preferredGrantTypes: options.preferredGrantTypes }
177
+ });
178
+ const result = await grantService({
179
+ ...await commandOptions(),
180
+ ...assertionOptions(),
181
+ grantType,
182
+ idempotencyKey: options.idempotencyKey ?? await idempotencyKey({ command: "grant", grantType }),
183
+ ...options.parameters === void 0 ? {} : { parameters: options.parameters },
184
+ ...options.requestedScopes === void 0 ? {} : { requestedScopes: options.requestedScopes }
185
+ });
186
+ await state.credentialStore.saveCredential(
187
+ sessionCredentialRecordFromGrantResult(result, {
188
+ grantType,
189
+ inspect: inspected,
190
+ serviceUrl
191
+ })
192
+ );
193
+ return result;
194
+ },
195
+ identity: identityOnce,
196
+ inspect: inspectOnce,
197
+ async revoke(options) {
198
+ const selector = revokeSessionSelector(options);
199
+ const result = await revokeService({
200
+ ...await commandOptions(),
201
+ ...assertionOptions(),
202
+ ...selector,
203
+ idempotencyKey: options.idempotencyKey ?? await idempotencyKey({
204
+ command: "revoke",
205
+ ..."credentialId" in selector ? { credentialId: selector.credentialId } : {},
206
+ ..."grantType" in selector ? { grantType: selector.grantType } : {}
207
+ }),
208
+ ...options.parameters === void 0 ? {} : { parameters: options.parameters }
209
+ });
210
+ const inspected = await inspectOnce();
211
+ if ("credentialId" in selector) {
212
+ await state.credentialStore.deleteCredential(
213
+ inspected.document.service.did,
214
+ selector.credentialId
215
+ );
216
+ }
217
+ return result;
218
+ },
219
+ async status() {
220
+ return statusService({
221
+ ...await commandOptions(),
222
+ ...assertionOptions()
223
+ });
224
+ }
225
+ };
226
+ }
227
+ function revokeSessionSelector(options) {
228
+ if (options.allGrantTypes === true) {
229
+ return { allGrantTypes: true };
230
+ }
231
+ if (options.credentialId !== void 0) {
232
+ return { credentialId: options.credentialId };
233
+ }
234
+ return { grantType: options.grantType };
235
+ }
236
+ function buildClientAssertionClaims(options) {
237
+ if (!AEP_AUTHENTICATED_COMMANDS.includes(options.command)) {
238
+ throw new TypeError(`Unsupported authenticated command: ${options.command}.`);
239
+ }
240
+ const now = Math.floor((options.clock ?? (() => /* @__PURE__ */ new Date()))().getTime() / 1e3);
241
+ const ttlSeconds = options.ttlSeconds ?? 300;
242
+ const jti = typeof options.jti === "function" ? options.jti() : options.jti ?? randomJti();
243
+ return parseClientAssertionClaims({
244
+ aud: options.serviceDid,
245
+ exp: now + ttlSeconds,
246
+ iat: now,
247
+ iss: options.agentDid,
248
+ jti,
249
+ op: options.command,
250
+ sub: options.agentDid
251
+ });
252
+ }
253
+ async function signClientAssertion(options) {
254
+ const claims = buildClientAssertionClaims(options);
255
+ return options.signer(claims, {
256
+ command: options.command,
257
+ serviceDid: options.serviceDid,
258
+ signingAlgorithms: [...options.signingAlgorithms ?? AEP_SIGNING_ALGORITHMS]
259
+ });
260
+ }
261
+ function createJwtClientAssertionSigner(options) {
262
+ return (claims, context) => signClientAssertionJwt(claims, {
263
+ alg: options.alg ?? preferredSigningAlgorithm(context.signingAlgorithms),
264
+ key: options.key,
265
+ ...options.kid === void 0 ? {} : { kid: options.kid },
266
+ ...options.typ === void 0 ? {} : { typ: options.typ }
267
+ });
268
+ }
269
+ async function discoverPlatform(options) {
270
+ const fetchImpl = requireFetch();
271
+ const platformUrl = normalizePlatformUrl(options.platformUrl);
272
+ const discoveryUrl = new URL("/.well-known/aep-platform", platformUrl);
273
+ const response = await fetchImpl(discoveryUrl, {
274
+ headers: {
275
+ Accept: AEP_MEDIA_TYPE
276
+ }
277
+ });
278
+ await throwCommandError(response, "Platform discovery");
279
+ const document = parsePlatformDiscoveryDocument(await response.json());
280
+ return {
281
+ document,
282
+ discoveryUrl,
283
+ endpointUrl: (endpoint) => new URL(requireEndpointPath(document, endpoint), platformUrl)
284
+ };
285
+ }
286
+ async function provisionPlatformIdentity(options) {
287
+ const fetchImpl = requireFetch();
288
+ const discovery = options.discovery ?? await discoverPlatform({
289
+ platformUrl: options.platformUrl
290
+ });
291
+ const commandUrl = discovery.endpointUrl("provision");
292
+ const body = createPlatformProvisionRequest({
293
+ idempotencyKey: options.idempotencyKey,
294
+ serviceDid: options.serviceDid
295
+ });
296
+ const response = await fetchImpl(commandUrl, {
297
+ body: JSON.stringify(body),
298
+ headers: {
299
+ Accept: AEP_MEDIA_TYPE,
300
+ ...options.authorization === void 0 ? {} : { Authorization: options.authorization },
301
+ "Content-Type": AEP_MEDIA_TYPE,
302
+ "Idempotency-Key": options.idempotencyKey
303
+ },
304
+ method: "POST"
305
+ });
306
+ await throwCommandError(response, "Platform provision");
307
+ return {
308
+ body: parsePlatformAgentIdentity(await response.json()),
309
+ commandUrl,
310
+ status: response.status
311
+ };
312
+ }
313
+ function createPlatformDelegatedSigner(options) {
314
+ return async (claims) => {
315
+ const fetchImpl = requireFetch();
316
+ const discovery = options.discovery ?? await discoverPlatform({
317
+ platformUrl: options.platformUrl
318
+ });
319
+ const commandUrl = endpointUrlWithIdentity(
320
+ discovery,
321
+ "sign",
322
+ options.identity.agent_identity_id
323
+ );
324
+ const lifetimeSeconds = claims.exp - claims.iat;
325
+ const request = createPlatformSignRequest({
326
+ command: claims.op,
327
+ jti: claims.jti,
328
+ lifetimeSeconds,
329
+ serviceDid: claims.aud
330
+ });
331
+ const response = await fetchImpl(commandUrl, {
332
+ body: JSON.stringify(request),
333
+ headers: {
334
+ Accept: AEP_MEDIA_TYPE,
335
+ ...options.authorization === void 0 ? {} : { Authorization: options.authorization },
336
+ "Content-Type": AEP_MEDIA_TYPE
337
+ },
338
+ method: "POST"
339
+ });
340
+ await throwCommandError(response, "Platform sign");
341
+ return parsePlatformSignResponse(await response.json()).client_assertion;
342
+ };
343
+ }
344
+ function createPlatformIdentityProvider(options) {
345
+ let discoveryPromise;
346
+ const discovery = () => {
347
+ discoveryPromise ??= discoverPlatform({ platformUrl: options.platformUrl });
348
+ return discoveryPromise;
349
+ };
350
+ return {
351
+ async getOrCreateIdentity(input) {
352
+ const platformDiscovery = await discovery();
353
+ const idempotencyKey = typeof options.idempotencyKey === "function" ? options.idempotencyKey(input) : options.idempotencyKey ?? randomJti();
354
+ const result = await provisionPlatformIdentity({
355
+ ...options.authorization === void 0 ? {} : { authorization: options.authorization },
356
+ discovery: platformDiscovery,
357
+ idempotencyKey,
358
+ platformUrl: options.platformUrl,
359
+ serviceDid: input.serviceDid
360
+ });
361
+ return agentServiceIdentityFromPlatform(result.body, options.platformUrl);
362
+ },
363
+ async signerFor(identity) {
364
+ if (identity.identityKind !== "platform-hosted") {
365
+ throw new TypeError("Platform identity provider cannot sign for a sovereign identity.");
366
+ }
367
+ const platformDiscovery = await discovery();
368
+ return createPlatformDelegatedSigner({
369
+ ...options.authorization === void 0 ? {} : { authorization: options.authorization },
370
+ discovery: platformDiscovery,
371
+ identity: platformAgentIdentityFromAgentIdentity(identity),
372
+ platformUrl: options.platformUrl
373
+ });
374
+ }
375
+ };
376
+ }
377
+ function agentServiceIdentityFromPlatform(identity, platformUrl) {
378
+ return {
379
+ agentDid: identity.agent_did,
380
+ identityKind: "platform-hosted",
381
+ metadata: {
382
+ agentIdentityId: identity.agent_identity_id,
383
+ createdAt: identity.created_at,
384
+ didDocumentUrl: identity.did_document_url,
385
+ keyId: identity.key_id,
386
+ platformUrl: String(platformUrl),
387
+ status: identity.status,
388
+ updatedAt: identity.updated_at
389
+ },
390
+ serviceDid: identity.service_did,
391
+ signingAlgorithms: [...identity.signing_algorithms]
392
+ };
393
+ }
394
+ function platformAgentIdentityFromAgentIdentity(identity) {
395
+ const metadata = identity.metadata ?? {};
396
+ return {
397
+ agent_did: identity.agentDid,
398
+ agent_identity_id: metadataString(metadata, "agentIdentityId"),
399
+ created_at: metadataString(metadata, "createdAt"),
400
+ did_document_url: metadataString(metadata, "didDocumentUrl"),
401
+ key_id: metadataString(metadata, "keyId"),
402
+ service_did: identity.serviceDid,
403
+ signing_algorithms: [...identity.signingAlgorithms],
404
+ status: parseManagedAgentStatus(metadataString(metadata, "status")),
405
+ updated_at: metadataString(metadata, "updatedAt")
406
+ };
407
+ }
408
+ function selectGrantType(inspect, options = {}) {
409
+ const document = isInspectServiceResult(inspect) ? inspect.document : inspect;
410
+ const advertised = document.commands.grant_types ?? [];
411
+ const preferred = options.preferredGrantTypes ?? advertised;
412
+ for (const grantType of preferred) {
413
+ if (advertised.includes(grantType)) {
414
+ return grantType;
415
+ }
416
+ }
417
+ throw new TypeError("AEP Service does not advertise a compatible grant type.");
418
+ }
419
+ function createInMemorySessionCredentialStore(records = []) {
420
+ const credentials = /* @__PURE__ */ new Map();
421
+ records.forEach(
422
+ (record) => credentials.set(sessionCredentialKey(record), cloneCredential(record))
423
+ );
424
+ return {
425
+ deleteCredential(serviceDid, credentialId) {
426
+ for (const [key, record] of credentials) {
427
+ if (record.serviceDid === serviceDid && record.credentialId === credentialId) {
428
+ credentials.delete(key);
429
+ }
430
+ }
431
+ },
432
+ findCredential(serviceDid, credentialId) {
433
+ for (const record of credentials.values()) {
434
+ if (record.serviceDid === serviceDid && record.credentialId === credentialId) {
435
+ return cloneCredential(record);
436
+ }
437
+ }
438
+ return void 0;
439
+ },
440
+ findUsableCredential(serviceDid, now = /* @__PURE__ */ new Date()) {
441
+ const nowMs = now.getTime();
442
+ for (const record of credentials.values()) {
443
+ if (record.serviceDid !== serviceDid) {
444
+ continue;
445
+ }
446
+ if (record.expiresAt !== void 0 && Date.parse(record.expiresAt) <= nowMs) {
447
+ continue;
448
+ }
449
+ return cloneCredential(record);
450
+ }
451
+ return void 0;
452
+ },
453
+ listCredentials(serviceDid) {
454
+ return [...credentials.values()].filter((record) => record.serviceDid === serviceDid).map(cloneCredential);
455
+ },
456
+ saveCredential(record) {
457
+ const cloned = cloneCredential(record);
458
+ credentials.set(sessionCredentialKey(cloned), cloned);
459
+ return cloneCredential(cloned);
460
+ }
461
+ };
462
+ }
463
+ function createInMemoryAgentIdentityStore(records = []) {
464
+ const identities = /* @__PURE__ */ new Map();
465
+ records.forEach((record) => identities.set(record.serviceDid, cloneAgentIdentity(record)));
466
+ return {
467
+ findByServiceDid(serviceDid) {
468
+ const identity = identities.get(serviceDid);
469
+ return identity === void 0 ? void 0 : cloneAgentIdentity(identity);
470
+ },
471
+ saveIdentity(identity) {
472
+ const cloned = cloneAgentIdentity(identity);
473
+ identities.set(cloned.serviceDid, cloned);
474
+ return cloneAgentIdentity(cloned);
475
+ }
476
+ };
477
+ }
478
+ function createRandomIdempotencyKeyProvider(generator = randomJti) {
479
+ return {
480
+ createKey: () => generator()
481
+ };
482
+ }
483
+ function createInMemoryInspectCache(records = []) {
484
+ const cache = /* @__PURE__ */ new Map();
485
+ records.forEach((record) => cache.set(record.serviceUrl, cloneCachedInspect(record.result)));
486
+ return {
487
+ get(serviceUrl) {
488
+ const cached = cache.get(serviceUrl);
489
+ return cached === void 0 ? void 0 : cloneCachedInspect(cached);
490
+ },
491
+ set(serviceUrl, result) {
492
+ cache.set(serviceUrl, cloneCachedInspect(result));
493
+ }
494
+ };
495
+ }
496
+ function sessionCredentialRecordFromGrantResult(result, options) {
497
+ const credentialId = credentialIdFromGrantResult(result.body);
498
+ const document = isInspectServiceResult(options.inspect) ? options.inspect.document : options.inspect;
499
+ const expiresAt = expiresAtFromGrantResult(result.body);
500
+ return {
501
+ credential: structuredClone(result.body),
502
+ credentialId,
503
+ grantType: options.grantType,
504
+ issuedAt: (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
505
+ serviceDid: document.service.did,
506
+ ...expiresAt === void 0 ? {} : { expiresAt },
507
+ ...options.serviceUrl === void 0 ? {} : { serviceUrl: String(options.serviceUrl) }
508
+ };
509
+ }
510
+ function credentialPresentationHeaders(credential) {
511
+ if (isOAuthBearerGrantResponse(credential)) {
512
+ return {
513
+ Authorization: `Bearer ${credential.access_token}`
514
+ };
515
+ }
516
+ if (isApiKeyGrantResponse(credential)) {
517
+ return {
518
+ [credential.header]: credential.api_key
519
+ };
520
+ }
521
+ if (isBasicGrantResponse(credential)) {
522
+ return {
523
+ Authorization: `Basic ${base64(`${credential.username}:${credential.password}`)}`
524
+ };
525
+ }
526
+ throw new TypeError("Unsupported AEP built-in credential.");
527
+ }
528
+ async function clientAssertionAuthenticationHeaders(options) {
529
+ const document = options.inspect === void 0 ? void 0 : inspectDocument(options.inspect);
530
+ const serviceDid = options.serviceDid ?? document?.service.did;
531
+ if (serviceDid === void 0) {
532
+ throw new TypeError("AEP client assertion authentication headers require a service DID.");
533
+ }
534
+ const signingAlgorithms = options.signingAlgorithms ?? document?.core.signing_algorithms;
535
+ const clientAssertion = await signClientAssertion({
536
+ agentDid: options.agentDid,
537
+ command: options.command ?? "status",
538
+ ...options.clock === void 0 ? {} : { clock: options.clock },
539
+ ...options.jti === void 0 ? {} : { jti: options.jti },
540
+ serviceDid,
541
+ signer: options.signer,
542
+ ...signingAlgorithms === void 0 ? {} : { signingAlgorithms },
543
+ ...options.ttlSeconds === void 0 ? {} : { ttlSeconds: options.ttlSeconds }
544
+ });
545
+ return {
546
+ Authorization: `${AEP_AUTH_SCHEME} ${clientAssertion}`
547
+ };
548
+ }
549
+ async function protectedResourceAuthenticationHeaders(options) {
550
+ if ("credential" in options) {
551
+ return credentialPresentationHeaders(options.credential);
552
+ }
553
+ return clientAssertionAuthenticationHeaders(options);
554
+ }
555
+ async function inspectService(options) {
556
+ const fetchImpl = requireFetch();
557
+ const serviceUrl = normalizeServiceUrl(options.serviceUrl);
558
+ const inspectUrl = new URL(AEP_WELL_KNOWN_PATH, serviceUrl);
559
+ const response = await fetchImpl(inspectUrl, {
560
+ headers: {
561
+ Accept: AEP_MEDIA_TYPE
562
+ }
563
+ });
564
+ if (!response.ok) {
565
+ throw new AepInspectError(
566
+ `AEP Inspect failed with HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}.`,
567
+ response.status
568
+ );
569
+ }
570
+ const document = parseInspectDocument(await response.json());
571
+ const cacheControl = response.headers.get("cache-control") ?? void 0;
572
+ const etag = response.headers.get("etag") ?? void 0;
573
+ return {
574
+ document,
575
+ inspectUrl,
576
+ commandUrl: (command) => new URL(commandPathFromInspect(document, command), serviceUrl),
577
+ ...cacheControl === void 0 ? {} : { cacheControl },
578
+ ...etag === void 0 ? {} : { etag }
579
+ };
580
+ }
581
+ async function enrollService(options) {
582
+ const fetchImpl = requireFetch();
583
+ const inspect = await resolveInspect(options);
584
+ const commandUrl = commandUrlFromInspect(options.serviceUrl, inspect, "enroll");
585
+ const clientAssertion = await resolveClientAssertion(options, inspect, "enroll");
586
+ const body = {
587
+ agent_did: options.agentDid,
588
+ ...options.claims === void 0 ? {} : { claims: structuredClone(options.claims) },
589
+ idempotency_key: options.idempotencyKey
590
+ };
591
+ const response = await fetchImpl(commandUrl, {
592
+ body: JSON.stringify(body),
593
+ headers: {
594
+ Accept: AEP_MEDIA_TYPE,
595
+ Authorization: `${AEP_AUTH_SCHEME} ${clientAssertion}`,
596
+ "Content-Type": AEP_MEDIA_TYPE,
597
+ "Idempotency-Key": options.idempotencyKey
598
+ },
599
+ method: "POST"
600
+ });
601
+ await throwCommandError(response, "Enroll");
602
+ return {
603
+ body: parseEnrollResponse(await response.json()),
604
+ commandUrl,
605
+ status: response.status
606
+ };
607
+ }
608
+ async function grantService(options) {
609
+ const fetchImpl = requireFetch();
610
+ const inspect = await resolveInspect(options);
611
+ const commandUrl = commandUrlFromInspect(options.serviceUrl, inspect, "grant");
612
+ const clientAssertion = await resolveClientAssertion(options, inspect, "grant");
613
+ const body = parseGrantRequest({
614
+ ...options.parameters ?? {},
615
+ grant_type: options.grantType,
616
+ ...options.requestedScopes === void 0 ? {} : { requested_scopes: [...options.requestedScopes] }
617
+ });
618
+ const response = await fetchImpl(commandUrl, {
619
+ body: JSON.stringify(body),
620
+ headers: {
621
+ Accept: AEP_MEDIA_TYPE,
622
+ Authorization: `${AEP_AUTH_SCHEME} ${clientAssertion}`,
623
+ "Content-Type": AEP_MEDIA_TYPE,
624
+ "Idempotency-Key": options.idempotencyKey
625
+ },
626
+ method: "POST"
627
+ });
628
+ await throwCommandError(response, "Grant");
629
+ return {
630
+ body: parseGrantResponse(body.grant_type, await response.json()),
631
+ commandUrl,
632
+ status: response.status
633
+ };
634
+ }
635
+ async function revokeService(options) {
636
+ const fetchImpl = requireFetch();
637
+ const inspect = await resolveInspect(options);
638
+ const commandUrl = commandUrlFromInspect(options.serviceUrl, inspect, "revoke");
639
+ const clientAssertion = await resolveClientAssertion(options, inspect, "revoke");
640
+ const body = parseRevokeRequest({
641
+ ...options.parameters ?? {},
642
+ ...revokeSelectorBody(options)
643
+ });
644
+ const response = await fetchImpl(commandUrl, {
645
+ body: JSON.stringify(body),
646
+ headers: {
647
+ Accept: AEP_MEDIA_TYPE,
648
+ Authorization: `${AEP_AUTH_SCHEME} ${clientAssertion}`,
649
+ "Content-Type": AEP_MEDIA_TYPE,
650
+ "Idempotency-Key": options.idempotencyKey
651
+ },
652
+ method: "POST"
653
+ });
654
+ await throwCommandError(response, "Revoke");
655
+ return {
656
+ body: parseRevokeResponse(await response.json()),
657
+ commandUrl,
658
+ status: response.status
659
+ };
660
+ }
661
+ async function statusService(options) {
662
+ const fetchImpl = requireFetch();
663
+ const inspect = await resolveInspect(options);
664
+ const commandUrl = commandUrlFromInspect(options.serviceUrl, inspect, "status");
665
+ const clientAssertion = await resolveClientAssertion(options, inspect, "status");
666
+ const response = await fetchImpl(commandUrl, {
667
+ headers: {
668
+ Accept: AEP_MEDIA_TYPE,
669
+ Authorization: `${AEP_AUTH_SCHEME} ${clientAssertion}`
670
+ },
671
+ method: "GET"
672
+ });
673
+ await throwCommandError(response, "Status");
674
+ return {
675
+ body: parseStatusResponse(await response.json()),
676
+ commandUrl,
677
+ status: response.status
678
+ };
679
+ }
680
+ function normalizePlatformUrl(platformUrl) {
681
+ const url = platformUrl instanceof URL ? new URL(platformUrl) : new URL(platformUrl);
682
+ if (url.pathname !== "/" || url.search || url.hash) {
683
+ url.pathname = "/";
684
+ url.search = "";
685
+ url.hash = "";
686
+ }
687
+ return url;
688
+ }
689
+ function normalizeServiceUrl(serviceUrl) {
690
+ const url = serviceUrl instanceof URL ? new URL(serviceUrl) : new URL(serviceUrl);
691
+ if (url.pathname !== "/" || url.search || url.hash) {
692
+ url.pathname = "/";
693
+ url.search = "";
694
+ url.hash = "";
695
+ }
696
+ return url;
697
+ }
698
+ function parsePlatformDiscoveryDocument(value) {
699
+ const document = requireRecord(value, "Platform discovery document");
700
+ const endpoints = requireRecord(document["endpoints"], "Platform discovery endpoints");
701
+ const http = requireRecord(document["http"], "Platform discovery HTTP metadata");
702
+ const identity = requireRecord(document["identity"], "Platform discovery identity metadata");
703
+ const platform = requireRecord(document["platform"], "Platform discovery platform metadata");
704
+ const signing = requireRecord(document["signing"], "Platform discovery signing metadata");
705
+ const algorithms = requireStringArray(signing, "algorithms");
706
+ const didMethods = requireStringArray(identity, "did_methods");
707
+ return {
708
+ ...document,
709
+ aep_version: requireString(document, "aep_version"),
710
+ endpoints: {
711
+ ...endpoints,
712
+ ...endpoints["hosted_verification"] === void 0 ? {} : { hosted_verification: requireString(endpoints, "hosted_verification") },
713
+ lifecycle: requireString(endpoints, "lifecycle"),
714
+ list: requireString(endpoints, "list"),
715
+ provision: requireString(endpoints, "provision"),
716
+ sign: requireString(endpoints, "sign")
717
+ },
718
+ http: {
719
+ ...http,
720
+ endpoint_base: requireString(http, "endpoint_base")
721
+ },
722
+ identity: {
723
+ ...identity,
724
+ did_methods: didMethods,
725
+ did_url_template: requireString(identity, "did_url_template")
726
+ },
727
+ platform: {
728
+ ...platform,
729
+ ...platform["did"] === void 0 ? {} : { did: requireString(platform, "did") },
730
+ hosted_verification: requireBoolean(platform, "hosted_verification"),
731
+ name: requireString(platform, "name")
732
+ },
733
+ signing: {
734
+ ...signing,
735
+ algorithms,
736
+ default_lifetime_seconds: requireString(signing, "default_lifetime_seconds")
737
+ }
738
+ };
739
+ }
740
+ function parsePlatformAgentIdentity(value) {
741
+ const body = requireRecord(value, "Platform Agent identity");
742
+ return {
743
+ agent_did: requireString(body, "agent_did"),
744
+ agent_identity_id: requireString(body, "agent_identity_id"),
745
+ created_at: requireString(body, "created_at"),
746
+ did_document_url: requireString(body, "did_document_url"),
747
+ key_id: requireString(body, "key_id"),
748
+ service_did: requireString(body, "service_did"),
749
+ signing_algorithms: requireStringArray(body, "signing_algorithms"),
750
+ status: parseManagedAgentStatus(requireString(body, "status")),
751
+ updated_at: requireString(body, "updated_at")
752
+ };
753
+ }
754
+ function parsePlatformSignResponse(value) {
755
+ const body = requireRecord(value, "Platform sign response");
756
+ return {
757
+ agent_did: requireString(body, "agent_did"),
758
+ client_assertion: requireString(body, "client_assertion"),
759
+ expires_at: requireString(body, "expires_at"),
760
+ issued_at: requireString(body, "issued_at"),
761
+ jti: requireString(body, "jti"),
762
+ service_did: requireString(body, "service_did")
763
+ };
764
+ }
765
+ function endpointUrlWithIdentity(discovery, endpoint, agentIdentityId) {
766
+ const endpointUrl = discovery.endpointUrl(endpoint);
767
+ const encodedAgentIdentityTemplate = "%7Bagent_identity_id%7D";
768
+ endpointUrl.pathname = endpointUrl.pathname.replace("{agent_identity_id}", encodeURIComponent(agentIdentityId)).replace(encodedAgentIdentityTemplate, encodeURIComponent(agentIdentityId));
769
+ return endpointUrl;
770
+ }
771
+ function requireEndpointPath(document, endpoint) {
772
+ const value = document.endpoints[endpoint];
773
+ if (typeof value !== "string" || value.length === 0) {
774
+ throw new TypeError(`Platform discovery does not advertise ${String(endpoint)}.`);
775
+ }
776
+ return value;
777
+ }
778
+ function inspectDocument(inspect) {
779
+ return isInspectServiceResult(inspect) ? inspect.document : inspect;
780
+ }
781
+ function requireFetch() {
782
+ const resolved = globalThis.fetch;
783
+ if (typeof resolved !== "function") {
784
+ throw new TypeError("AEP agent requires a fetch implementation.");
785
+ }
786
+ return resolved;
787
+ }
788
+ async function resolveInspect(options) {
789
+ return options.inspect ?? await inspectService({
790
+ serviceUrl: options.serviceUrl
791
+ });
792
+ }
793
+ function commandUrlFromInspect(serviceUrl, inspect, command) {
794
+ if (!inspect.document.commands.supported.includes(command)) {
795
+ throw new TypeError(`AEP Service does not advertise ${command}.`);
796
+ }
797
+ return new URL(
798
+ commandPathFromInspect(inspect.document, command),
799
+ normalizeServiceUrl(serviceUrl)
800
+ );
801
+ }
802
+ async function resolveClientAssertion(options, inspect, command) {
803
+ if (options.clientAssertion !== void 0) {
804
+ return options.clientAssertion;
805
+ }
806
+ if (options.clientAssertionSigner === void 0) {
807
+ throw new TypeError("AEP command requires clientAssertion or clientAssertionSigner.");
808
+ }
809
+ if (options.agentDid === void 0) {
810
+ throw new TypeError("AEP command signing requires agentDid.");
811
+ }
812
+ return signClientAssertion({
813
+ agentDid: options.agentDid,
814
+ command,
815
+ serviceDid: inspect.document.service.did,
816
+ signer: options.clientAssertionSigner,
817
+ ...options.assertionClock === void 0 ? {} : { clock: options.assertionClock },
818
+ ...options.assertionJti === void 0 ? {} : { jti: options.assertionJti },
819
+ ...inspect.document.core.signing_algorithms === void 0 ? {} : { signingAlgorithms: inspect.document.core.signing_algorithms },
820
+ ...options.assertionTtlSeconds === void 0 ? {} : { ttlSeconds: options.assertionTtlSeconds }
821
+ });
822
+ }
823
+ function assertionOptionsWithDefinedValues(options) {
824
+ return {
825
+ ...options.assertionClock === void 0 ? {} : { assertionClock: options.assertionClock },
826
+ ...options.assertionJti === void 0 ? {} : { assertionJti: options.assertionJti },
827
+ ...options.assertionTtlSeconds === void 0 ? {} : { assertionTtlSeconds: options.assertionTtlSeconds }
828
+ };
829
+ }
830
+ function revokeSelectorBody(options) {
831
+ if (options.allGrantTypes === true) {
832
+ return {
833
+ all_grant_types: "true"
834
+ };
835
+ }
836
+ if (options.credentialId !== void 0) {
837
+ return {
838
+ credential_id: options.credentialId
839
+ };
840
+ }
841
+ return {
842
+ grant_type: options.grantType
843
+ };
844
+ }
845
+ function parseGrantResponse(grantType, value) {
846
+ if (AEP_BUILT_IN_GRANT_TYPES.includes(grantType)) {
847
+ return parseBuiltInGrantResponse(grantType, value);
848
+ }
849
+ if (isRecord(value)) {
850
+ return value;
851
+ }
852
+ throw new TypeError("Invalid AEP Grant response.");
853
+ }
854
+ function isRecord(value) {
855
+ return value !== null && typeof value === "object" && !Array.isArray(value);
856
+ }
857
+ function requireRecord(value, label) {
858
+ if (!isRecord(value)) {
859
+ throw new TypeError(`${label} must be an object.`);
860
+ }
861
+ return value;
862
+ }
863
+ function requireString(record, key) {
864
+ const value = record[key];
865
+ if (typeof value !== "string" || value.length === 0) {
866
+ throw new TypeError(`${key} must be a non-empty string.`);
867
+ }
868
+ return value;
869
+ }
870
+ function requireBoolean(record, key) {
871
+ const value = record[key];
872
+ if (typeof value !== "boolean") {
873
+ throw new TypeError(`${key} must be a boolean.`);
874
+ }
875
+ return value;
876
+ }
877
+ function requireStringArray(record, key) {
878
+ const value = record[key];
879
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
880
+ throw new TypeError(`${key} must be a string array.`);
881
+ }
882
+ return [...value];
883
+ }
884
+ function parseManagedAgentStatus(value) {
885
+ if (value !== "active" && value !== "revoked" && value !== "suspended" && value !== "terminated") {
886
+ throw new TypeError("status must be a supported managed Agent status.");
887
+ }
888
+ return value;
889
+ }
890
+ function isInspectServiceResult(value) {
891
+ return isRecord(value) && isRecord(value["document"]) && value["inspectUrl"] instanceof URL && typeof value["commandUrl"] === "function";
892
+ }
893
+ function randomJti() {
894
+ return `jti_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
895
+ }
896
+ function credentialIdFromGrantResult(credential) {
897
+ if (typeof credential.credential_id !== "string" || credential.credential_id.length === 0) {
898
+ throw new TypeError("AEP Grant response does not include credential_id.");
899
+ }
900
+ return credential.credential_id;
901
+ }
902
+ function expiresAtFromGrantResult(credential) {
903
+ return typeof credential.expires_at === "string" ? credential.expires_at : void 0;
904
+ }
905
+ function sessionCredentialKey(record) {
906
+ return `${record.serviceDid}\0${record.credentialId}`;
907
+ }
908
+ function cloneCredential(record) {
909
+ return {
910
+ ...record,
911
+ credential: structuredClone(record.credential)
912
+ };
913
+ }
914
+ function cloneAgentIdentity(identity) {
915
+ return {
916
+ ...identity,
917
+ ...identity.metadata === void 0 ? {} : { metadata: structuredClone(identity.metadata) },
918
+ signingAlgorithms: [...identity.signingAlgorithms]
919
+ };
920
+ }
921
+ function cloneCachedInspect(result) {
922
+ return {
923
+ ...result,
924
+ document: structuredClone(result.document)
925
+ };
926
+ }
927
+ function metadataString(metadata, key) {
928
+ const value = metadata[key];
929
+ if (typeof value !== "string" || value.length === 0) {
930
+ throw new TypeError(`Platform-hosted Agent identity metadata is missing ${key}.`);
931
+ }
932
+ return value;
933
+ }
934
+ function isOAuthBearerGrantResponse(credential) {
935
+ return "access_token" in credential && credential.token_type === "Bearer";
936
+ }
937
+ function isApiKeyGrantResponse(credential) {
938
+ return "api_key" in credential && "header" in credential;
939
+ }
940
+ function isBasicGrantResponse(credential) {
941
+ return "username" in credential && "password" in credential;
942
+ }
943
+ function base64(value) {
944
+ return Buffer.from(value, "utf8").toString("base64");
945
+ }
946
+ function preferredSigningAlgorithm(algorithms) {
947
+ const algorithm = algorithms.find((candidate) => candidate === "ES256" || candidate === "EdDSA");
948
+ if (algorithm === void 0) {
949
+ throw new TypeError("AEP Service does not advertise a supported JOSE signing algorithm.");
950
+ }
951
+ return algorithm;
952
+ }
953
+ async function throwCommandError(response, command) {
954
+ if (response.ok) {
955
+ return;
956
+ }
957
+ const problem = await readProblemDetails(response);
958
+ throw new AepCommandError(
959
+ `AEP ${command} failed with HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}.`,
960
+ response.status,
961
+ problem
962
+ );
963
+ }
964
+ async function readProblemDetails(response) {
965
+ try {
966
+ return parseProblemDetails(await response.json());
967
+ } catch {
968
+ return void 0;
969
+ }
970
+ }
971
+ export {
972
+ AepCommandError,
973
+ AepInspectError,
974
+ buildClientAssertionClaims,
975
+ clientAssertionAuthenticationHeaders,
976
+ createAepAgent,
977
+ createInMemoryAgentIdentityStore,
978
+ createInMemoryInspectCache,
979
+ createInMemorySessionCredentialStore,
980
+ createJwtClientAssertionSigner,
981
+ createPlatformDelegatedSigner,
982
+ createPlatformIdentityProvider,
983
+ createRandomIdempotencyKeyProvider,
984
+ credentialPresentationHeaders,
985
+ discoverPlatform,
986
+ enrollService,
987
+ grantService,
988
+ inspectService,
989
+ protectedResourceAuthenticationHeaders,
990
+ provisionPlatformIdentity,
991
+ revokeService,
992
+ selectGrantType,
993
+ sessionCredentialRecordFromGrantResult,
994
+ signClientAssertion,
995
+ statusService
996
+ };
997
+ //# sourceMappingURL=index.js.map