@aep-foundation/service 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,821 @@
1
+ // src/index.ts
2
+ import {
3
+ AEP_AUTH_SCHEME,
4
+ AEP_BINDINGS,
5
+ AEP_BUILT_IN_GRANT_TYPES,
6
+ AEP_IDENTITY_METHOD_DID_WEB,
7
+ AEP_MEDIA_TYPE,
8
+ AEP_PROBLEM_MEDIA_TYPE,
9
+ AEP_SIGNING_ALGORITHMS,
10
+ AEP_VERSION,
11
+ DEFAULT_HTTP_ENDPOINT_BASE,
12
+ createProblemDetails,
13
+ decodeJwtUnverified,
14
+ parseBuiltInGrantResponse,
15
+ parseClientAssertionClaims,
16
+ parseEnrollRequest,
17
+ parseGrantRequest,
18
+ parseInspectDocument,
19
+ parseRevokeRequest,
20
+ resolveDidWebPublicKey,
21
+ verifyClientAssertionJwt
22
+ } from "@aep-foundation/core";
23
+ function createAepService(options) {
24
+ const inspectDocument = buildInspectDocument(options);
25
+ const commandIdempotencyStore = options.commandIdempotencyStore ?? createInMemoryCommandIdempotencyStore();
26
+ const enrollmentPolicy = options.enrollmentPolicy ?? createStaticEnrollmentPolicy();
27
+ const enrollmentStore = options.enrollmentStore ?? createInMemoryEnrollmentStore();
28
+ const grantHandlers = createGrantHandlerMap(options.grantTypes ?? []);
29
+ const replayStore = options.replayStore ?? createInMemoryClientAssertionReplayStore();
30
+ const signingAlgorithms = [...options.signingAlgorithms ?? AEP_SIGNING_ALGORITHMS];
31
+ const authenticationOptions = () => ({
32
+ replayStore,
33
+ serviceDid: options.serviceDid,
34
+ signingAlgorithms,
35
+ ...options.clientAssertion === void 0 ? {} : { config: options.clientAssertion },
36
+ ...options.clientAssertionVerifier === void 0 ? {} : { verifier: options.clientAssertionVerifier }
37
+ });
38
+ return {
39
+ enroll: async (request, commandOptions) => {
40
+ const authentication = await authenticateClientAssertion(
41
+ "enroll",
42
+ commandOptions,
43
+ authenticationOptions()
44
+ );
45
+ if (!authentication.ok) {
46
+ return authentication.response;
47
+ }
48
+ try {
49
+ if (parseEnrollRequest(request).agent_did !== authentication.agentDid) {
50
+ return problem("not_recognized", "Not recognized", 401);
51
+ }
52
+ } catch {
53
+ return problem("invalid_request", "Invalid request", 400);
54
+ }
55
+ return handleEnrollRequest(request, {
56
+ commandIdempotencyStore,
57
+ store: enrollmentStore,
58
+ ...options.clock === void 0 ? {} : { clock: options.clock },
59
+ policy: enrollmentPolicy,
60
+ ...commandOptions.idempotencyKey === void 0 ? {} : { idempotencyKey: commandOptions.idempotencyKey }
61
+ });
62
+ },
63
+ grant: async (request, commandOptions) => {
64
+ const authentication = await authenticateClientAssertion(
65
+ "grant",
66
+ commandOptions,
67
+ authenticationOptions()
68
+ );
69
+ if (!authentication.ok) {
70
+ return authentication.response;
71
+ }
72
+ return handleGrantRequest(request, {
73
+ agentDid: authentication.agentDid,
74
+ commandIdempotencyStore,
75
+ handlers: grantHandlers,
76
+ store: enrollmentStore,
77
+ ...commandOptions.idempotencyKey === void 0 ? {} : { idempotencyKey: commandOptions.idempotencyKey }
78
+ });
79
+ },
80
+ inspectDocument: () => structuredClone(inspectDocument),
81
+ revoke: async (request, commandOptions) => {
82
+ const authentication = await authenticateClientAssertion(
83
+ "revoke",
84
+ commandOptions,
85
+ authenticationOptions()
86
+ );
87
+ if (!authentication.ok) {
88
+ return authentication.response;
89
+ }
90
+ return handleRevokeRequest(request, {
91
+ agentDid: authentication.agentDid,
92
+ commandIdempotencyStore,
93
+ handlers: grantHandlers,
94
+ store: enrollmentStore,
95
+ ...commandOptions.idempotencyKey === void 0 ? {} : { idempotencyKey: commandOptions.idempotencyKey }
96
+ });
97
+ },
98
+ status: async (commandOptions) => {
99
+ const authentication = await authenticateClientAssertion(
100
+ "status",
101
+ commandOptions,
102
+ authenticationOptions()
103
+ );
104
+ if (!authentication.ok) {
105
+ return authentication.response;
106
+ }
107
+ return handleStatusRequest(authentication.agentDid, {
108
+ store: enrollmentStore
109
+ });
110
+ }
111
+ };
112
+ }
113
+ function buildInspectDocument(options) {
114
+ const identityMethods = uniqueBy(options.identityMethods, "method", "identity method").map(
115
+ (definition) => definition.method
116
+ );
117
+ const grantTypes = uniqueBy(options.grantTypes ?? [], "grantType", "grant type");
118
+ const supportedCommands = grantTypes.length > 0 ? ["enroll", "grant", "inspect", "revoke", "status"] : ["enroll", "inspect", "status"];
119
+ if (identityMethods.length === 0) {
120
+ throw new TypeError("AEP Services must enable at least one identity method.");
121
+ }
122
+ const grantTypeConfig = Object.fromEntries(
123
+ grantTypes.filter((definition) => definition.config !== void 0).map((definition) => [definition.grantType, definition.config])
124
+ );
125
+ const document = {
126
+ aep_version: AEP_VERSION,
127
+ bindings: {
128
+ supported: [...AEP_BINDINGS]
129
+ },
130
+ claims: {
131
+ required: [...options.claims?.required ?? []],
132
+ preferred: [...options.claims?.preferred ?? []],
133
+ optional: [...options.claims?.optional ?? []]
134
+ },
135
+ commands: {
136
+ supported: supportedCommands,
137
+ ...grantTypes.length > 0 ? {
138
+ grant_types: grantTypes.map((definition) => definition.grantType)
139
+ } : {},
140
+ ...Object.keys(grantTypeConfig).length > 0 ? {
141
+ grant_types_config: grantTypeConfig
142
+ } : {}
143
+ },
144
+ core: {
145
+ signing_algorithms: [...options.signingAlgorithms ?? AEP_SIGNING_ALGORITHMS]
146
+ },
147
+ extensions: {
148
+ supported: [...options.extensions ?? []]
149
+ },
150
+ http: {
151
+ endpoint_base: options.endpointBase ?? DEFAULT_HTTP_ENDPOINT_BASE
152
+ },
153
+ identity: {
154
+ methods: identityMethods
155
+ },
156
+ service: {
157
+ did: options.serviceDid
158
+ }
159
+ };
160
+ return parseInspectDocument(document);
161
+ }
162
+ function didWebIdentityMethod() {
163
+ return {
164
+ method: AEP_IDENTITY_METHOD_DID_WEB
165
+ };
166
+ }
167
+ function oauthBearerGrantType(config) {
168
+ return grantType(AEP_BUILT_IN_GRANT_TYPES[0], config);
169
+ }
170
+ function apiKeyGrantType(config) {
171
+ return grantType(AEP_BUILT_IN_GRANT_TYPES[1], config);
172
+ }
173
+ function basicGrantType(config) {
174
+ return grantType(AEP_BUILT_IN_GRANT_TYPES[2], config);
175
+ }
176
+ function grantType(grantType2, config, handler) {
177
+ return {
178
+ grantType: grantType2,
179
+ ...config === void 0 ? {} : { config },
180
+ ...handler === void 0 ? {} : { handler }
181
+ };
182
+ }
183
+ function storedOAuthBearerGrantType(options) {
184
+ return storedBuiltInGrantType(AEP_BUILT_IN_GRANT_TYPES[0], options);
185
+ }
186
+ function storedApiKeyGrantType(options) {
187
+ return storedBuiltInGrantType(AEP_BUILT_IN_GRANT_TYPES[1], options);
188
+ }
189
+ function storedBasicGrantType(options) {
190
+ return storedBuiltInGrantType(AEP_BUILT_IN_GRANT_TYPES[2], options);
191
+ }
192
+ function createInMemoryEnrollmentStore(records = []) {
193
+ const enrollments = /* @__PURE__ */ new Map();
194
+ records.forEach((record) => enrollments.set(record.agentDid, cloneEnrollmentRecord(record)));
195
+ return {
196
+ findEnrollment(agentDid) {
197
+ const record = enrollments.get(agentDid);
198
+ return record === void 0 ? void 0 : cloneEnrollmentRecord(record);
199
+ },
200
+ saveEnrollment(record) {
201
+ const cloned = cloneEnrollmentRecord(record);
202
+ enrollments.set(cloned.agentDid, cloned);
203
+ return cloneEnrollmentRecord(cloned);
204
+ }
205
+ };
206
+ }
207
+ function createInMemoryClientAssertionReplayStore() {
208
+ const records = /* @__PURE__ */ new Map();
209
+ return {
210
+ consumeReplay(record, now) {
211
+ for (const [key, existing] of records) {
212
+ if (existing.expiresAt <= now) {
213
+ records.delete(key);
214
+ }
215
+ }
216
+ if (records.has(replayKey(record.sub, record.jti))) {
217
+ return false;
218
+ }
219
+ records.set(replayKey(record.sub, record.jti), { ...record });
220
+ return true;
221
+ }
222
+ };
223
+ }
224
+ function createInMemoryCommandIdempotencyStore(records = []) {
225
+ const idempotency = /* @__PURE__ */ new Map();
226
+ const pending = /* @__PURE__ */ new Map();
227
+ records.forEach(
228
+ (record) => idempotency.set(
229
+ idempotencyLookupKey(record.agentDid, record.command, record.idempotencyKey),
230
+ cloneIdempotencyResponseRecord(record)
231
+ )
232
+ );
233
+ return {
234
+ async executeIdempotentCommand(input, execute) {
235
+ const key = idempotencyLookupKey(input.agentDid, input.command, input.idempotencyKey);
236
+ for (; ; ) {
237
+ const existing = idempotency.get(key);
238
+ if (existing !== void 0) {
239
+ if (existing.requestHash !== input.requestHash) {
240
+ return {
241
+ state: "conflict"
242
+ };
243
+ }
244
+ return {
245
+ record: cloneIdempotencyResponseRecord(existing),
246
+ state: "replayed"
247
+ };
248
+ }
249
+ const pendingCommand2 = pending.get(key);
250
+ if (pendingCommand2 === void 0) {
251
+ break;
252
+ }
253
+ await pendingCommand2;
254
+ }
255
+ let releasePending;
256
+ const pendingCommand = new Promise((resolve) => {
257
+ releasePending = resolve;
258
+ });
259
+ pending.set(key, pendingCommand);
260
+ try {
261
+ const response = await execute();
262
+ const record = {
263
+ agentDid: input.agentDid,
264
+ body: structuredClone(response.body),
265
+ command: input.command,
266
+ contentType: response.contentType,
267
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
268
+ idempotencyKey: input.idempotencyKey,
269
+ requestHash: input.requestHash,
270
+ status: response.status
271
+ };
272
+ idempotency.set(key, cloneIdempotencyResponseRecord(record));
273
+ return {
274
+ response,
275
+ state: "created"
276
+ };
277
+ } finally {
278
+ pending.delete(key);
279
+ releasePending?.();
280
+ }
281
+ }
282
+ };
283
+ }
284
+ function createStaticEnrollmentPolicy(decision = {}) {
285
+ return {
286
+ decideEnrollment: () => ({
287
+ ownerActionRequired: decision.ownerActionRequired ?? false,
288
+ requirementsPending: [...decision.requirementsPending ?? []],
289
+ status: decision.status ?? "active"
290
+ })
291
+ };
292
+ }
293
+ function createJwtClientAssertionVerifier(options) {
294
+ return (clientAssertion, context) => verifyClientAssertionJwt(clientAssertion, {
295
+ algorithms: options.algorithms ?? context.signingAlgorithms,
296
+ audience: context.serviceDid,
297
+ key: options.key,
298
+ ...options.clockTolerance === void 0 ? {} : { clockTolerance: options.clockTolerance },
299
+ ...options.currentDate === void 0 ? {} : { currentDate: options.currentDate }
300
+ });
301
+ }
302
+ function createDidWebClientAssertionVerifier(options = {}) {
303
+ return async (clientAssertion, context) => {
304
+ const untrusted = decodeJwtUnverified(clientAssertion);
305
+ const issuer = stringField(untrusted.payload, "iss");
306
+ const key = await resolveDidWebPublicKey({
307
+ did: issuer,
308
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch },
309
+ ...typeof untrusted.header["kid"] === "string" ? { kid: untrusted.header["kid"] } : {}
310
+ });
311
+ return verifyClientAssertionJwt(clientAssertion, {
312
+ algorithms: context.signingAlgorithms,
313
+ audience: context.serviceDid,
314
+ key
315
+ });
316
+ };
317
+ }
318
+ function createHostedPlatformClientAssertionVerifier(options) {
319
+ const fetchImpl = platformVerificationFetch(options.fetch);
320
+ return async (clientAssertion, context) => {
321
+ const response = await fetchImpl(options.endpoint, {
322
+ body: JSON.stringify({
323
+ client_assertion: clientAssertion,
324
+ op: context.command,
325
+ service_did: context.serviceDid
326
+ }),
327
+ headers: {
328
+ Accept: AEP_MEDIA_TYPE,
329
+ ...options.authorization === void 0 ? {} : { Authorization: options.authorization },
330
+ "Content-Type": AEP_MEDIA_TYPE
331
+ },
332
+ method: "POST"
333
+ });
334
+ if (!response.ok) {
335
+ throw new Error(`AEP hosted verification failed with HTTP ${response.status}.`);
336
+ }
337
+ const verification = parseHostedPlatformVerificationResponse(await response.json());
338
+ const claims = parseClientAssertionClaims(decodeJwtUnverified(clientAssertion).payload);
339
+ if (!verification.verified || verification.agent_did !== claims.sub || verification.op !== context.command || verification.service_did !== context.serviceDid) {
340
+ throw new Error("AEP hosted verification did not recognize the client assertion.");
341
+ }
342
+ return claims;
343
+ };
344
+ }
345
+ function clientAssertionFromAepAuthorization(authorization) {
346
+ const prefix = `${AEP_AUTH_SCHEME} `;
347
+ if (authorization?.startsWith(prefix)) {
348
+ return authorization.slice(prefix.length);
349
+ }
350
+ return "";
351
+ }
352
+ async function authenticateProtectedResource(service, authorization) {
353
+ return service.status({
354
+ clientAssertion: clientAssertionFromAepAuthorization(authorization)
355
+ });
356
+ }
357
+ function isActiveProtectedResourceAuthentication(result) {
358
+ return isRecord(result.body) && result.body["status"] === "active";
359
+ }
360
+ function createInMemoryServiceCredentialStore(records = []) {
361
+ const credentials = /* @__PURE__ */ new Map();
362
+ records.forEach(
363
+ (record) => credentials.set(serviceCredentialKey(record), cloneCredential(record))
364
+ );
365
+ return {
366
+ findCredential(agentDid, grantTypeName, credentialId) {
367
+ const record = credentials.get(
368
+ serviceCredentialLookupKey(agentDid, grantTypeName, credentialId)
369
+ );
370
+ return record === void 0 ? void 0 : cloneCredential(record);
371
+ },
372
+ listCredentials(agentDid, grantTypeName) {
373
+ return [...credentials.values()].filter(
374
+ (record) => record.agentDid === agentDid && (grantTypeName === void 0 || record.grantType === grantTypeName)
375
+ ).map(cloneCredential);
376
+ },
377
+ revokeCredential(agentDid, grantTypeName, credentialId, revokedAt) {
378
+ const key = serviceCredentialLookupKey(agentDid, grantTypeName, credentialId);
379
+ const record = credentials.get(key);
380
+ if (record !== void 0) {
381
+ credentials.set(key, {
382
+ ...record,
383
+ revokedAt
384
+ });
385
+ }
386
+ },
387
+ revokeGrantType(agentDid, grantTypeName, revokedAt) {
388
+ for (const [key, record] of credentials) {
389
+ if (record.agentDid === agentDid && record.grantType === grantTypeName) {
390
+ credentials.set(key, {
391
+ ...record,
392
+ revokedAt
393
+ });
394
+ }
395
+ }
396
+ },
397
+ saveCredential(record) {
398
+ const parsed = credentialRecordWithParsedCredential(record);
399
+ credentials.set(serviceCredentialKey(parsed), cloneCredential(parsed));
400
+ return cloneCredential(parsed);
401
+ }
402
+ };
403
+ }
404
+ async function handleEnrollRequest(request, options) {
405
+ let parsed;
406
+ try {
407
+ parsed = parseEnrollRequest(request);
408
+ } catch {
409
+ return problem("invalid_request", "Invalid request", 400);
410
+ }
411
+ if (options.idempotencyKey !== void 0 && options.idempotencyKey !== parsed.idempotency_key) {
412
+ return problem("invalid_request", "Invalid request", 400);
413
+ }
414
+ return withIdempotency(
415
+ "enroll",
416
+ parsed.agent_did,
417
+ parsed.idempotency_key,
418
+ parsed,
419
+ options.commandIdempotencyStore,
420
+ async () => {
421
+ const now = options.clock ?? (() => /* @__PURE__ */ new Date());
422
+ const nowDate = now();
423
+ const nowIso = nowDate.toISOString();
424
+ const decision = await options.policy.decideEnrollment(parsed, { now: nowDate });
425
+ const record = await options.store.saveEnrollment({
426
+ agentDid: parsed.agent_did,
427
+ claims: structuredClone(parsed.claims ?? {}),
428
+ createdAt: nowIso,
429
+ ownerActionRequired: decision.ownerActionRequired ?? false,
430
+ requirementsPending: [...decision.requirementsPending ?? []],
431
+ since: nowIso,
432
+ status: decision.status ?? "active",
433
+ updatedAt: nowIso
434
+ });
435
+ return aepResponse(200, enrollmentResponseFromRecord(record));
436
+ }
437
+ );
438
+ }
439
+ async function handleStatusRequest(agentDid, options) {
440
+ const record = await options.store.findEnrollment(agentDid);
441
+ if (record === void 0) {
442
+ return problem("not_recognized", "Not recognized", 401);
443
+ }
444
+ return aepResponse(200, statusResponseFromRecord(record));
445
+ }
446
+ async function handleGrantRequest(request, options) {
447
+ let parsed;
448
+ try {
449
+ parsed = parseGrantRequest(request);
450
+ } catch {
451
+ return problem("invalid_request", "Invalid request", 400);
452
+ }
453
+ const enrollment = await options.store.findEnrollment(options.agentDid);
454
+ if (enrollment === void 0) {
455
+ return problem("not_recognized", "Not recognized", 401);
456
+ }
457
+ if (enrollment.status !== "active") {
458
+ return problem("verification_pending", "Verification pending", 403);
459
+ }
460
+ const handler = options.handlers.get(parsed.grant_type);
461
+ if (handler === void 0) {
462
+ return problem("unsupported_grant_type", "Unsupported grant type", 400);
463
+ }
464
+ return withIdempotency(
465
+ "grant",
466
+ options.agentDid,
467
+ options.idempotencyKey,
468
+ parsed,
469
+ options.commandIdempotencyStore,
470
+ async () => aepResponse(
471
+ 200,
472
+ await handler.grant(parsed, {
473
+ agentDid: options.agentDid,
474
+ enrollment,
475
+ grantType: parsed.grant_type
476
+ })
477
+ )
478
+ );
479
+ }
480
+ async function handleRevokeRequest(request, options) {
481
+ let parsed;
482
+ try {
483
+ parsed = parseRevokeRequest(request);
484
+ } catch {
485
+ return problem("invalid_request", "Invalid request", 400);
486
+ }
487
+ const enrollment = await options.store.findEnrollment(options.agentDid);
488
+ if (enrollment === void 0) {
489
+ return problem("not_recognized", "Not recognized", 401);
490
+ }
491
+ if ("grant_type" in parsed) {
492
+ const grantTypeName = parsed.grant_type;
493
+ const handler = options.handlers.get(grantTypeName);
494
+ if (handler === void 0) {
495
+ return problem("unsupported_grant_type", "Unsupported grant type", 400);
496
+ }
497
+ return withIdempotency(
498
+ "revoke",
499
+ options.agentDid,
500
+ options.idempotencyKey,
501
+ parsed,
502
+ options.commandIdempotencyStore,
503
+ async () => {
504
+ await handler.revoke(parsed, {
505
+ agentDid: options.agentDid,
506
+ enrollment,
507
+ grantType: grantTypeName
508
+ });
509
+ return aepResponse(200, {});
510
+ }
511
+ );
512
+ }
513
+ return withIdempotency(
514
+ "revoke",
515
+ options.agentDid,
516
+ options.idempotencyKey,
517
+ parsed,
518
+ options.commandIdempotencyStore,
519
+ async () => {
520
+ for (const [grantTypeName, handler] of options.handlers) {
521
+ await handler.revoke(parsed, {
522
+ agentDid: options.agentDid,
523
+ enrollment,
524
+ grantType: grantTypeName
525
+ });
526
+ }
527
+ return aepResponse(200, {});
528
+ }
529
+ );
530
+ }
531
+ async function authenticateClientAssertion(command, commandOptions, options) {
532
+ if (options.verifier === void 0) {
533
+ return notRecognized();
534
+ }
535
+ let claims;
536
+ try {
537
+ claims = parseClientAssertionClaims(
538
+ await options.verifier(commandOptions.clientAssertion, {
539
+ clientAssertion: commandOptions.clientAssertion,
540
+ command,
541
+ serviceDid: options.serviceDid,
542
+ signingAlgorithms: options.signingAlgorithms
543
+ })
544
+ );
545
+ } catch {
546
+ return notRecognized();
547
+ }
548
+ if (!validateClientAssertionClaimsForCommand(claims, command, options)) {
549
+ return notRecognized();
550
+ }
551
+ const clock = options.config?.clock ?? (() => /* @__PURE__ */ new Date());
552
+ const now = Math.floor(clock().getTime() / 1e3);
553
+ const consumed = await options.replayStore.consumeReplay(
554
+ {
555
+ expiresAt: claims.exp + (options.config?.clockSkewSeconds ?? 30),
556
+ jti: claims.jti,
557
+ sub: claims.sub
558
+ },
559
+ now
560
+ );
561
+ if (!consumed) {
562
+ return notRecognized();
563
+ }
564
+ return {
565
+ agentDid: claims.sub,
566
+ claims,
567
+ ok: true
568
+ };
569
+ }
570
+ function validateClientAssertionClaimsForCommand(claims, command, options) {
571
+ const clock = options.config?.clock ?? (() => /* @__PURE__ */ new Date());
572
+ const clockSkewSeconds = options.config?.clockSkewSeconds ?? 30;
573
+ const maxTtlSeconds = options.config?.maxTtlSeconds ?? 300;
574
+ const now = Math.floor(clock().getTime() / 1e3);
575
+ return claims.iss === claims.sub && claims.aud === options.serviceDid && claims.op === command && claims.exp > claims.iat && claims.exp - claims.iat <= maxTtlSeconds && claims.iat <= now + clockSkewSeconds && claims.exp >= now - clockSkewSeconds;
576
+ }
577
+ function platformVerificationFetch(fetchImpl) {
578
+ const resolved = fetchImpl ?? globalThis.fetch;
579
+ if (typeof resolved !== "function") {
580
+ throw new TypeError("AEP hosted Platform verification requires a fetch implementation.");
581
+ }
582
+ return resolved;
583
+ }
584
+ function parseHostedPlatformVerificationResponse(value) {
585
+ if (!isRecord(value)) {
586
+ throw new TypeError("AEP hosted verification response must be an object.");
587
+ }
588
+ return {
589
+ ...typeof value["agent_did"] === "string" ? { agent_did: value["agent_did"] } : {},
590
+ ...typeof value["agent_identity_id"] === "string" ? { agent_identity_id: value["agent_identity_id"] } : {},
591
+ ...isAuthenticatedServiceCommand(value["op"]) ? { op: value["op"] } : {},
592
+ reason: stringField(value, "reason"),
593
+ service_did: stringField(value, "service_did"),
594
+ ...typeof value["status"] === "string" ? { status: value["status"] } : {},
595
+ verified: booleanField(value, "verified")
596
+ };
597
+ }
598
+ function uniqueBy(items, key, label) {
599
+ const seen = /* @__PURE__ */ new Set();
600
+ return items.map((item) => {
601
+ const value = item[key];
602
+ if (seen.has(value)) {
603
+ throw new TypeError(`Duplicate AEP ${label}: ${value}.`);
604
+ }
605
+ seen.add(value);
606
+ return item;
607
+ });
608
+ }
609
+ function createGrantHandlerMap(grantTypes) {
610
+ const handlers = /* @__PURE__ */ new Map();
611
+ for (const definition of grantTypes) {
612
+ if (definition.handler !== void 0) {
613
+ handlers.set(definition.grantType, definition.handler);
614
+ }
615
+ }
616
+ return handlers;
617
+ }
618
+ function storedBuiltInGrantType(grantTypeName, options) {
619
+ return grantType(grantTypeName, options.config, {
620
+ async grant(request, context) {
621
+ const credential = parseBuiltInGrantResponse(
622
+ grantTypeName,
623
+ await options.issue(request, context)
624
+ );
625
+ const record = await options.store.saveCredential({
626
+ agentDid: context.agentDid,
627
+ createdAt: (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
628
+ credential,
629
+ credentialId: credential.credential_id,
630
+ expiresAt: credential.expires_at,
631
+ grantType: grantTypeName
632
+ });
633
+ return structuredClone(record.credential);
634
+ },
635
+ async revoke(request, context) {
636
+ const revokedAt = (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString();
637
+ if ("credential_id" in request) {
638
+ await options.store.revokeCredential(
639
+ context.agentDid,
640
+ grantTypeName,
641
+ request.credential_id,
642
+ revokedAt
643
+ );
644
+ return;
645
+ }
646
+ await options.store.revokeGrantType(context.agentDid, grantTypeName, revokedAt);
647
+ }
648
+ });
649
+ }
650
+ function enrollmentResponseFromRecord(record) {
651
+ return {
652
+ status: record.status,
653
+ ...record.ownerActionRequired ? {
654
+ owner_action_required: "true"
655
+ } : {},
656
+ ...record.requirementsPending.length > 0 ? {
657
+ requirements_pending: [...record.requirementsPending]
658
+ } : {}
659
+ };
660
+ }
661
+ function statusResponseFromRecord(record) {
662
+ return {
663
+ owner_action_required: record.ownerActionRequired ? "true" : "false",
664
+ requirements_pending: [...record.requirementsPending],
665
+ since: record.since,
666
+ status: record.status
667
+ };
668
+ }
669
+ function aepResponse(status, body) {
670
+ return {
671
+ body,
672
+ contentType: AEP_MEDIA_TYPE,
673
+ status
674
+ };
675
+ }
676
+ function problem(code, title, status) {
677
+ return {
678
+ body: createProblemDetails({
679
+ code,
680
+ status,
681
+ title
682
+ }),
683
+ contentType: AEP_PROBLEM_MEDIA_TYPE,
684
+ status
685
+ };
686
+ }
687
+ function notRecognized() {
688
+ return {
689
+ ok: false,
690
+ response: problem("not_recognized", "Not recognized", 401)
691
+ };
692
+ }
693
+ async function withIdempotency(command, agentDid, idempotencyKey, request, store, execute) {
694
+ if (idempotencyKey === void 0 || store === void 0) {
695
+ return execute();
696
+ }
697
+ const requestHash = hashRequest(request);
698
+ const result = await store.executeIdempotentCommand(
699
+ {
700
+ agentDid,
701
+ command,
702
+ idempotencyKey,
703
+ requestHash
704
+ },
705
+ execute
706
+ );
707
+ if (result.state === "replayed") {
708
+ return {
709
+ body: structuredClone(result.record.body),
710
+ contentType: result.record.contentType,
711
+ status: result.record.status
712
+ };
713
+ }
714
+ if (result.state === "conflict") {
715
+ return problem("idempotency_conflict", "Idempotency conflict", 409);
716
+ }
717
+ return result.response;
718
+ }
719
+ function replayKey(sub, jti) {
720
+ return `${sub}\0${jti}`;
721
+ }
722
+ function idempotencyLookupKey(agentDid, command, idempotencyKey) {
723
+ return command === "enroll" ? `${command}\0${idempotencyKey}` : `${agentDid}\0${command}\0${idempotencyKey}`;
724
+ }
725
+ function cloneEnrollmentRecord(record) {
726
+ return {
727
+ ...record,
728
+ claims: structuredClone(record.claims),
729
+ requirementsPending: [...record.requirementsPending]
730
+ };
731
+ }
732
+ function cloneIdempotencyResponseRecord(record) {
733
+ return {
734
+ ...record,
735
+ body: structuredClone(record.body)
736
+ };
737
+ }
738
+ function serviceCredentialLookupKey(agentDid, grantTypeName, credentialId) {
739
+ return `${agentDid}\0${grantTypeName}\0${credentialId}`;
740
+ }
741
+ function serviceCredentialKey(record) {
742
+ return serviceCredentialLookupKey(record.agentDid, record.grantType, record.credentialId);
743
+ }
744
+ function cloneCredential(record) {
745
+ return {
746
+ ...record,
747
+ credential: structuredClone(record.credential)
748
+ };
749
+ }
750
+ function credentialRecordWithParsedCredential(record) {
751
+ const credential = parseBuiltInGrantResponse(record.grantType, record.credential);
752
+ if (credential.credential_id !== record.credentialId) {
753
+ throw new TypeError("AEP credential record credentialId does not match credential body.");
754
+ }
755
+ if (credential.expires_at !== record.expiresAt) {
756
+ throw new TypeError("AEP credential record expiresAt does not match credential body.");
757
+ }
758
+ return {
759
+ ...record,
760
+ credential
761
+ };
762
+ }
763
+ function hashRequest(request) {
764
+ return `json:${stableStringify(request)}`;
765
+ }
766
+ function stableStringify(value) {
767
+ if (Array.isArray(value)) {
768
+ return `[${value.map(stableStringify).join(",")}]`;
769
+ }
770
+ if (value !== null && typeof value === "object") {
771
+ const record = value;
772
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
773
+ }
774
+ return JSON.stringify(value);
775
+ }
776
+ function stringField(value, field) {
777
+ if (!isRecord(value) || typeof value[field] !== "string") {
778
+ throw new Error(`Expected string field: ${field}`);
779
+ }
780
+ return value[field];
781
+ }
782
+ function booleanField(value, field) {
783
+ if (!isRecord(value) || typeof value[field] !== "boolean") {
784
+ throw new Error(`Expected boolean field: ${field}`);
785
+ }
786
+ return value[field];
787
+ }
788
+ function isAuthenticatedServiceCommand(value) {
789
+ return value === "enroll" || value === "grant" || value === "revoke" || value === "status";
790
+ }
791
+ function isRecord(value) {
792
+ return typeof value === "object" && value !== null && !Array.isArray(value);
793
+ }
794
+ export {
795
+ apiKeyGrantType,
796
+ authenticateProtectedResource,
797
+ basicGrantType,
798
+ buildInspectDocument,
799
+ clientAssertionFromAepAuthorization,
800
+ createAepService,
801
+ createDidWebClientAssertionVerifier,
802
+ createHostedPlatformClientAssertionVerifier,
803
+ createInMemoryClientAssertionReplayStore,
804
+ createInMemoryCommandIdempotencyStore,
805
+ createInMemoryEnrollmentStore,
806
+ createInMemoryServiceCredentialStore,
807
+ createJwtClientAssertionVerifier,
808
+ createStaticEnrollmentPolicy,
809
+ didWebIdentityMethod,
810
+ grantType,
811
+ handleEnrollRequest,
812
+ handleGrantRequest,
813
+ handleRevokeRequest,
814
+ handleStatusRequest,
815
+ isActiveProtectedResourceAuthentication,
816
+ oauthBearerGrantType,
817
+ storedApiKeyGrantType,
818
+ storedBasicGrantType,
819
+ storedOAuthBearerGrantType
820
+ };
821
+ //# sourceMappingURL=index.js.map