@aep-foundation/platform 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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/index.cjs +849 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +432 -0
- package/dist/index.d.ts +432 -0
- package/dist/index.js +810 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
AEP_MEDIA_TYPE,
|
|
4
|
+
AEP_PROBLEM_MEDIA_TYPE,
|
|
5
|
+
createProblemDetails,
|
|
6
|
+
decodeJwtUnverified,
|
|
7
|
+
didWebDocumentUrl,
|
|
8
|
+
signClientAssertionJwt,
|
|
9
|
+
verifyClientAssertionJwt
|
|
10
|
+
} from "@aep-foundation/core";
|
|
11
|
+
var packageName = "@aep-foundation/platform";
|
|
12
|
+
var platformHostedIdentityDraft = "draft-kavian-aep-platform-hosted-identity-00";
|
|
13
|
+
var defaultAssertionLifetimeSeconds = 300;
|
|
14
|
+
var InMemoryManagedAgentRegistry = class {
|
|
15
|
+
#identities = /* @__PURE__ */ new Map();
|
|
16
|
+
constructor(identities = []) {
|
|
17
|
+
identities.forEach((identity) => this.upsert(identity));
|
|
18
|
+
}
|
|
19
|
+
get(agentDid) {
|
|
20
|
+
const identity = this.#identities.get(agentDid);
|
|
21
|
+
return identity === void 0 ? void 0 : cloneManagedAgentIdentity(identity);
|
|
22
|
+
}
|
|
23
|
+
list(filter = {}) {
|
|
24
|
+
return Array.from(this.#identities.values()).filter(
|
|
25
|
+
(identity) => filter.accountId === void 0 || identity.accountId === filter.accountId
|
|
26
|
+
).filter((identity) => filter.status === void 0 || identity.status === filter.status).filter((identity) => filter.tenantId === void 0 || identity.tenantId === filter.tenantId).map(cloneManagedAgentIdentity);
|
|
27
|
+
}
|
|
28
|
+
remove(agentDid) {
|
|
29
|
+
return this.#identities.delete(agentDid);
|
|
30
|
+
}
|
|
31
|
+
setStatus(agentDid, status) {
|
|
32
|
+
const identity = this.#identities.get(agentDid);
|
|
33
|
+
if (identity === void 0) {
|
|
34
|
+
throw new Error(`Managed Agent identity not found: ${agentDid}.`);
|
|
35
|
+
}
|
|
36
|
+
const updated = {
|
|
37
|
+
...identity,
|
|
38
|
+
status,
|
|
39
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
40
|
+
};
|
|
41
|
+
this.#identities.set(agentDid, updated);
|
|
42
|
+
return cloneManagedAgentIdentity(updated);
|
|
43
|
+
}
|
|
44
|
+
upsert(identity) {
|
|
45
|
+
assertNonEmpty("agentDid", identity.agentDid);
|
|
46
|
+
const cloned = cloneManagedAgentIdentity(identity);
|
|
47
|
+
this.#identities.set(cloned.agentDid, cloned);
|
|
48
|
+
return cloneManagedAgentIdentity(cloned);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function createManagedAgentIdentity(options) {
|
|
52
|
+
assertNonEmpty("agentDid", options.agentDid);
|
|
53
|
+
const now = (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
54
|
+
return {
|
|
55
|
+
agentDid: options.agentDid,
|
|
56
|
+
createdAt: now,
|
|
57
|
+
metadata: structuredClone(options.metadata ?? {}),
|
|
58
|
+
status: options.status ?? "active",
|
|
59
|
+
updatedAt: now,
|
|
60
|
+
...options.accountId === void 0 ? {} : { accountId: options.accountId },
|
|
61
|
+
...options.subjectDid === void 0 ? {} : { subjectDid: options.subjectDid },
|
|
62
|
+
...options.tenantId === void 0 ? {} : { tenantId: options.tenantId }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function updateManagedAgentIdentity(identity, update, clock = () => /* @__PURE__ */ new Date()) {
|
|
66
|
+
return {
|
|
67
|
+
...cloneManagedAgentIdentity(identity),
|
|
68
|
+
...update,
|
|
69
|
+
metadata: update.metadata === void 0 ? cloneRecord(identity.metadata) : {
|
|
70
|
+
...cloneRecord(identity.metadata),
|
|
71
|
+
...cloneRecord(update.metadata)
|
|
72
|
+
},
|
|
73
|
+
updatedAt: clock().toISOString()
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function createPlatformDiscoveryDocument(options) {
|
|
77
|
+
assertNonEmpty("didUrlTemplate", options.didUrlTemplate);
|
|
78
|
+
assertNonEmpty("endpointBase", options.endpointBase);
|
|
79
|
+
assertNonEmpty("lifecycle endpoint", options.endpoints.lifecycle);
|
|
80
|
+
assertNonEmpty("list endpoint", options.endpoints.list);
|
|
81
|
+
assertNonEmpty("platformName", options.platformName);
|
|
82
|
+
assertNonEmpty("provision endpoint", options.endpoints.provision);
|
|
83
|
+
assertNonEmpty("sign endpoint", options.endpoints.sign);
|
|
84
|
+
const defaultLifetimeSeconds = options.defaultLifetimeSeconds ?? defaultAssertionLifetimeSeconds;
|
|
85
|
+
validateLifetimeSeconds(defaultLifetimeSeconds, options.maxLifetimeSeconds);
|
|
86
|
+
if (options.signingAlgorithms.length === 0) {
|
|
87
|
+
throw new TypeError("signingAlgorithms must include at least one algorithm.");
|
|
88
|
+
}
|
|
89
|
+
const didMethods = [...options.didMethods ?? ["did:web"]];
|
|
90
|
+
if (didMethods.length === 0) {
|
|
91
|
+
throw new TypeError("didMethods must include at least one DID method.");
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
aep_version: options.aepVersion ?? "1.0",
|
|
95
|
+
endpoints: {
|
|
96
|
+
...options.endpoints.hostedVerification === void 0 ? {} : { hosted_verification: options.endpoints.hostedVerification },
|
|
97
|
+
lifecycle: options.endpoints.lifecycle,
|
|
98
|
+
list: options.endpoints.list,
|
|
99
|
+
provision: options.endpoints.provision,
|
|
100
|
+
sign: options.endpoints.sign
|
|
101
|
+
},
|
|
102
|
+
http: {
|
|
103
|
+
endpoint_base: options.endpointBase
|
|
104
|
+
},
|
|
105
|
+
identity: {
|
|
106
|
+
did_methods: didMethods,
|
|
107
|
+
did_url_template: options.didUrlTemplate
|
|
108
|
+
},
|
|
109
|
+
platform: {
|
|
110
|
+
...options.platformDid === void 0 ? {} : { did: options.platformDid },
|
|
111
|
+
hosted_verification: options.hostedVerification ?? false,
|
|
112
|
+
name: options.platformName
|
|
113
|
+
},
|
|
114
|
+
signing: {
|
|
115
|
+
algorithms: [...options.signingAlgorithms],
|
|
116
|
+
default_lifetime_seconds: String(defaultLifetimeSeconds)
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function createServiceScopedAgentDid(options) {
|
|
121
|
+
assertNonEmpty("agentDidId", options.agentDidId);
|
|
122
|
+
assertNonEmpty("host", options.host);
|
|
123
|
+
const encodedHost = encodeURIComponent(options.host);
|
|
124
|
+
const pathPrefix = options.pathPrefix ?? "agents";
|
|
125
|
+
const pathParts = pathPrefix.split("/").filter((part) => part.length > 0).map((part) => encodeURIComponent(part));
|
|
126
|
+
return ["did:web", encodedHost, ...pathParts, encodeURIComponent(options.agentDidId)].join(":");
|
|
127
|
+
}
|
|
128
|
+
function createPlatformAgentIdentity(options) {
|
|
129
|
+
assertNonEmpty("agentDid", options.agentDid);
|
|
130
|
+
assertNonEmpty("agentIdentityId", options.agentIdentityId);
|
|
131
|
+
assertNonEmpty("serviceDid", options.serviceDid);
|
|
132
|
+
if (options.signingAlgorithms.length === 0) {
|
|
133
|
+
throw new TypeError("signingAlgorithms must include at least one algorithm.");
|
|
134
|
+
}
|
|
135
|
+
const now = (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
136
|
+
const keyId = options.keyId ?? options.agentDid;
|
|
137
|
+
return {
|
|
138
|
+
agent_did: options.agentDid,
|
|
139
|
+
agent_identity_id: options.agentIdentityId,
|
|
140
|
+
created_at: now,
|
|
141
|
+
did_document_url: options.didDocumentUrl ?? didWebDocumentUrl(options.agentDid).toString(),
|
|
142
|
+
key_id: keyId,
|
|
143
|
+
service_did: options.serviceDid,
|
|
144
|
+
signing_algorithms: [...options.signingAlgorithms],
|
|
145
|
+
status: options.status ?? "active",
|
|
146
|
+
updated_at: now
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function createPlatformAgentIdentityListResponse(identities, total = identities.length) {
|
|
150
|
+
return {
|
|
151
|
+
count: String(identities.length),
|
|
152
|
+
data: identities.map((identity) => structuredClone(identity)),
|
|
153
|
+
total: String(total)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function createPlatformLifecycleRequest(options) {
|
|
157
|
+
return {
|
|
158
|
+
status: options.status
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function createPlatformProvisionRequest(options) {
|
|
162
|
+
assertNonEmpty("idempotencyKey", options.idempotencyKey);
|
|
163
|
+
assertNonEmpty("serviceDid", options.serviceDid);
|
|
164
|
+
return {
|
|
165
|
+
idempotency_key: options.idempotencyKey,
|
|
166
|
+
service_did: options.serviceDid
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function createPlatformEnrollRequest(options) {
|
|
170
|
+
assertUsableIdentity(options.identity);
|
|
171
|
+
assertNonEmpty("idempotencyKey", options.idempotencyKey);
|
|
172
|
+
return {
|
|
173
|
+
agent_did: options.identity.agentDid,
|
|
174
|
+
...options.claims === void 0 ? {} : { claims: cloneRecord(options.claims) },
|
|
175
|
+
idempotency_key: options.idempotencyKey
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function createPlatformClientAssertionClaims(options) {
|
|
179
|
+
assertUsableIdentity(options.identity);
|
|
180
|
+
assertNonEmpty("serviceDid", options.serviceDid);
|
|
181
|
+
assertNonEmpty("jti", options.jti);
|
|
182
|
+
const issuedAt = toEpochSeconds(options.issuedAt ?? /* @__PURE__ */ new Date());
|
|
183
|
+
const lifetimeSeconds = validateLifetimeSeconds(
|
|
184
|
+
options.lifetimeSeconds ?? defaultAssertionLifetimeSeconds,
|
|
185
|
+
options.maxLifetimeSeconds
|
|
186
|
+
);
|
|
187
|
+
return {
|
|
188
|
+
aud: options.serviceDid,
|
|
189
|
+
exp: issuedAt + lifetimeSeconds,
|
|
190
|
+
iat: issuedAt,
|
|
191
|
+
iss: options.identity.agentDid,
|
|
192
|
+
jti: options.jti,
|
|
193
|
+
op: options.command,
|
|
194
|
+
sub: options.identity.agentDid
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function createManagedAgentDidDocument(options) {
|
|
198
|
+
assertUsableIdentity(options.identity);
|
|
199
|
+
if (options.verificationMethods.length === 0) {
|
|
200
|
+
throw new TypeError("verificationMethods must include at least one method.");
|
|
201
|
+
}
|
|
202
|
+
const document = {
|
|
203
|
+
"@context": ["https://www.w3.org/ns/did/v1", ...options.additionalContexts ?? []],
|
|
204
|
+
id: options.identity.agentDid,
|
|
205
|
+
...options.controller === void 0 ? {} : { controller: options.controller },
|
|
206
|
+
verificationMethod: options.verificationMethods.map(
|
|
207
|
+
(method, index) => createDidVerificationMethod(options.identity.agentDid, method, index)
|
|
208
|
+
),
|
|
209
|
+
...options.service === void 0 ? {} : { service: options.service.map(cloneDidService) }
|
|
210
|
+
};
|
|
211
|
+
for (const relationship of relationshipNames()) {
|
|
212
|
+
const methodIds = document.verificationMethod?.filter(
|
|
213
|
+
(method, index) => (options.verificationMethods[index]?.relationships ?? ["authentication"]).includes(
|
|
214
|
+
relationship
|
|
215
|
+
)
|
|
216
|
+
).map((method) => method.id);
|
|
217
|
+
if (methodIds !== void 0 && methodIds.length > 0) {
|
|
218
|
+
document[relationship] = methodIds;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return document;
|
|
222
|
+
}
|
|
223
|
+
async function publishManagedAgentDidDocument(options) {
|
|
224
|
+
const document = createManagedAgentDidDocument(options);
|
|
225
|
+
return options.publisher.publish(document);
|
|
226
|
+
}
|
|
227
|
+
function createJwtPlatformDelegatedSigner(options) {
|
|
228
|
+
return (claims, context) => signClientAssertionJwt(claims, {
|
|
229
|
+
alg: options.alg ?? preferredSigningAlgorithm(context.signingAlgorithms),
|
|
230
|
+
key: options.key,
|
|
231
|
+
...options.kid === void 0 ? {} : { kid: options.kid },
|
|
232
|
+
...options.typ === void 0 ? {} : { typ: options.typ }
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async function signPlatformClientAssertion(options) {
|
|
236
|
+
const claims = createPlatformClientAssertionClaims(options);
|
|
237
|
+
return options.signer(claims, {
|
|
238
|
+
identity: options.identity,
|
|
239
|
+
signingAlgorithms: [...options.signingAlgorithms ?? ["EdDSA", "ES256"]]
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function createPlatformSignRequest(options) {
|
|
243
|
+
assertNonEmpty("jti", options.jti);
|
|
244
|
+
assertNonEmpty("serviceDid", options.serviceDid);
|
|
245
|
+
const lifetimeSeconds = options.lifetimeSeconds === void 0 ? void 0 : validateLifetimeSeconds(options.lifetimeSeconds, options.maxLifetimeSeconds);
|
|
246
|
+
return {
|
|
247
|
+
jti: options.jti,
|
|
248
|
+
...lifetimeSeconds === void 0 ? {} : { lifetime_seconds: String(lifetimeSeconds) },
|
|
249
|
+
op: options.command,
|
|
250
|
+
service_did: options.serviceDid
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function createPlatformSignResponse(options) {
|
|
254
|
+
assertUsableIdentity(options.identity);
|
|
255
|
+
assertNonEmpty("clientAssertion", options.clientAssertion);
|
|
256
|
+
assertNonEmpty("jti", options.jti);
|
|
257
|
+
assertNonEmpty("serviceDid", options.serviceDid);
|
|
258
|
+
const issuedAt = toEpochSeconds(options.issuedAt ?? /* @__PURE__ */ new Date());
|
|
259
|
+
const lifetimeSeconds = validateLifetimeSeconds(
|
|
260
|
+
options.lifetimeSeconds ?? defaultAssertionLifetimeSeconds,
|
|
261
|
+
options.maxLifetimeSeconds
|
|
262
|
+
);
|
|
263
|
+
return {
|
|
264
|
+
agent_did: options.identity.agentDid,
|
|
265
|
+
client_assertion: options.clientAssertion,
|
|
266
|
+
expires_at: epochSecondsToIso(issuedAt + lifetimeSeconds),
|
|
267
|
+
issued_at: epochSecondsToIso(issuedAt),
|
|
268
|
+
jti: options.jti,
|
|
269
|
+
service_did: options.serviceDid
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function createPlatformVerificationRequest(options) {
|
|
273
|
+
assertNonEmpty("clientAssertion", options.clientAssertion);
|
|
274
|
+
assertNonEmpty("serviceDid", options.serviceDid);
|
|
275
|
+
return {
|
|
276
|
+
client_assertion: options.clientAssertion,
|
|
277
|
+
op: options.command,
|
|
278
|
+
service_did: options.serviceDid
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function createPlatformVerificationResponse(options) {
|
|
282
|
+
assertNonEmpty("reason", options.reason);
|
|
283
|
+
assertNonEmpty("serviceDid", options.serviceDid);
|
|
284
|
+
return {
|
|
285
|
+
...options.agentDid === void 0 ? {} : { agent_did: options.agentDid },
|
|
286
|
+
...options.agentIdentityId === void 0 ? {} : { agent_identity_id: options.agentIdentityId },
|
|
287
|
+
...options.command === void 0 ? {} : { op: options.command },
|
|
288
|
+
reason: options.reason,
|
|
289
|
+
service_did: options.serviceDid,
|
|
290
|
+
...options.status === void 0 ? {} : { status: options.status },
|
|
291
|
+
verified: options.verified
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function createAepPlatform(options) {
|
|
295
|
+
assertNonEmpty("didHost", options.didHost);
|
|
296
|
+
assertNonEmpty("didUrlTemplate", options.didUrlTemplate);
|
|
297
|
+
if (options.signingAlgorithms.length === 0) {
|
|
298
|
+
throw new TypeError("signingAlgorithms must include at least one algorithm.");
|
|
299
|
+
}
|
|
300
|
+
const clock = options.clock ?? (() => /* @__PURE__ */ new Date());
|
|
301
|
+
const idGenerator = options.idGenerator ?? randomPlatformId;
|
|
302
|
+
const lifecyclePolicy = options.lifecyclePolicy ?? defaultLifecyclePolicy;
|
|
303
|
+
const authorizer = options.authorizer ?? {};
|
|
304
|
+
const discoveryDocument = createPlatformDiscoveryDocument({
|
|
305
|
+
...options.discovery,
|
|
306
|
+
defaultLifetimeSeconds: options.defaultLifetimeSeconds ?? defaultAssertionLifetimeSeconds,
|
|
307
|
+
didUrlTemplate: options.didUrlTemplate,
|
|
308
|
+
signingAlgorithms: options.signingAlgorithms
|
|
309
|
+
});
|
|
310
|
+
return {
|
|
311
|
+
discovery() {
|
|
312
|
+
return ok(200, discoveryDocument);
|
|
313
|
+
},
|
|
314
|
+
async getDidDocument(agentIdentityId, context = {}) {
|
|
315
|
+
const identity = await authorizedIdentity(
|
|
316
|
+
agentIdentityId,
|
|
317
|
+
authorizer,
|
|
318
|
+
options.identityStore,
|
|
319
|
+
context
|
|
320
|
+
);
|
|
321
|
+
if (identity === void 0) {
|
|
322
|
+
return problem(404, "not_recognized", "Identity not recognized.");
|
|
323
|
+
}
|
|
324
|
+
const verificationMethod = await options.keyStore.didVerificationMethod(identity, context);
|
|
325
|
+
return ok(
|
|
326
|
+
200,
|
|
327
|
+
createManagedAgentDidDocument({
|
|
328
|
+
identity: managedIdentityFromRecord(identity),
|
|
329
|
+
verificationMethods: [verificationMethod]
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
},
|
|
333
|
+
async getIdentity(agentIdentityId, context = {}) {
|
|
334
|
+
const identity = await authorizedIdentity(
|
|
335
|
+
agentIdentityId,
|
|
336
|
+
authorizer,
|
|
337
|
+
options.identityStore,
|
|
338
|
+
context
|
|
339
|
+
);
|
|
340
|
+
if (identity === void 0) {
|
|
341
|
+
return problem(404, "not_recognized", "Identity not recognized.");
|
|
342
|
+
}
|
|
343
|
+
return ok(200, platformIdentityFromRecord(identity));
|
|
344
|
+
},
|
|
345
|
+
async list(query = {}, context = {}) {
|
|
346
|
+
if (await authorizer.authorizeList?.(context) === false) {
|
|
347
|
+
return problem(404, "not_recognized", "Identity not recognized.");
|
|
348
|
+
}
|
|
349
|
+
const result = await options.identityStore.list(query, context);
|
|
350
|
+
return ok(
|
|
351
|
+
200,
|
|
352
|
+
createPlatformAgentIdentityListResponse(
|
|
353
|
+
result.identities.map(platformIdentityFromRecord),
|
|
354
|
+
result.total
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
},
|
|
358
|
+
async provision(body, context = {}) {
|
|
359
|
+
const request = parsePlatformProvisionRequestBody(body);
|
|
360
|
+
if (await authorizer.authorizeProvision?.(request, context) === false) {
|
|
361
|
+
return problem(404, "not_recognized", "Identity not recognized.");
|
|
362
|
+
}
|
|
363
|
+
if (!await options.serviceDidResolver.resolve(request.service_did, context)) {
|
|
364
|
+
return problem(400, "invalid_request", "Service DID could not be resolved.");
|
|
365
|
+
}
|
|
366
|
+
const requestHash = stableStringify(request);
|
|
367
|
+
const existing = await options.idempotencyStore.get(request.idempotency_key, context);
|
|
368
|
+
if (existing !== void 0) {
|
|
369
|
+
if (existing.requestHash !== requestHash) {
|
|
370
|
+
return problem(409, "idempotency_conflict", "Idempotency key already used.");
|
|
371
|
+
}
|
|
372
|
+
return ok(200, existing.response);
|
|
373
|
+
}
|
|
374
|
+
const generatedId = idGenerator();
|
|
375
|
+
assertNonEmpty("generated identity id", generatedId);
|
|
376
|
+
const now = clock().toISOString();
|
|
377
|
+
const agentDidId = generatedId;
|
|
378
|
+
const agentDid = createServiceScopedAgentDid({
|
|
379
|
+
agentDidId,
|
|
380
|
+
host: options.didHost,
|
|
381
|
+
pathPrefix: options.didPathPrefix ?? "agents"
|
|
382
|
+
});
|
|
383
|
+
const identity = {
|
|
384
|
+
agentDid,
|
|
385
|
+
agentDidId,
|
|
386
|
+
agentIdentityId: generatedId.startsWith("pai_") ? generatedId : `pai_${generatedId}`,
|
|
387
|
+
createdAt: now,
|
|
388
|
+
didDocumentUrl: renderDidUrlTemplate(options.didUrlTemplate, agentDidId),
|
|
389
|
+
keyId: agentDid,
|
|
390
|
+
serviceDid: request.service_did,
|
|
391
|
+
signingAlgorithms: [...options.signingAlgorithms],
|
|
392
|
+
status: "active",
|
|
393
|
+
updatedAt: now
|
|
394
|
+
};
|
|
395
|
+
await options.keyStore.create(identity, context);
|
|
396
|
+
await options.identityStore.create(identity, context);
|
|
397
|
+
const response = platformIdentityFromRecord(identity);
|
|
398
|
+
await options.idempotencyStore.set(
|
|
399
|
+
{
|
|
400
|
+
idempotencyKey: request.idempotency_key,
|
|
401
|
+
requestHash,
|
|
402
|
+
response
|
|
403
|
+
},
|
|
404
|
+
context
|
|
405
|
+
);
|
|
406
|
+
return ok(200, response);
|
|
407
|
+
},
|
|
408
|
+
async sign(agentIdentityId, body, context = {}) {
|
|
409
|
+
const request = parsePlatformSignRequestBody(body);
|
|
410
|
+
const identity = await authorizedIdentity(
|
|
411
|
+
agentIdentityId,
|
|
412
|
+
authorizer,
|
|
413
|
+
options.identityStore,
|
|
414
|
+
context
|
|
415
|
+
);
|
|
416
|
+
if (identity === void 0 || identity.serviceDid !== request.service_did) {
|
|
417
|
+
return problem(404, "not_recognized", "Identity not recognized.");
|
|
418
|
+
}
|
|
419
|
+
if (!await lifecyclePolicy.canSign(identity, context)) {
|
|
420
|
+
return problem(403, lifecycleProblemCode(identity.status), "Identity cannot sign.");
|
|
421
|
+
}
|
|
422
|
+
if (!await options.serviceDidResolver.resolve(request.service_did, context)) {
|
|
423
|
+
return problem(400, "invalid_request", "Service DID could not be resolved.");
|
|
424
|
+
}
|
|
425
|
+
const issuedAt = context.now ?? clock();
|
|
426
|
+
const lifetimeSeconds = request.lifetime_seconds === void 0 ? void 0 : Number(request.lifetime_seconds);
|
|
427
|
+
const managedIdentity = managedIdentityFromRecord(identity);
|
|
428
|
+
const claims = createPlatformClientAssertionClaims({
|
|
429
|
+
command: request.op,
|
|
430
|
+
identity: managedIdentity,
|
|
431
|
+
issuedAt,
|
|
432
|
+
jti: request.jti,
|
|
433
|
+
...options.maxLifetimeSeconds === void 0 ? {} : { maxLifetimeSeconds: options.maxLifetimeSeconds },
|
|
434
|
+
serviceDid: request.service_did,
|
|
435
|
+
...lifetimeSeconds === void 0 ? {} : { lifetimeSeconds }
|
|
436
|
+
});
|
|
437
|
+
const clientAssertion = await options.keyStore.sign(identity, claims, context);
|
|
438
|
+
return ok(
|
|
439
|
+
200,
|
|
440
|
+
createPlatformSignResponse({
|
|
441
|
+
clientAssertion,
|
|
442
|
+
identity: managedIdentity,
|
|
443
|
+
issuedAt,
|
|
444
|
+
jti: request.jti,
|
|
445
|
+
...options.maxLifetimeSeconds === void 0 ? {} : { maxLifetimeSeconds: options.maxLifetimeSeconds },
|
|
446
|
+
serviceDid: request.service_did,
|
|
447
|
+
...lifetimeSeconds === void 0 ? {} : { lifetimeSeconds }
|
|
448
|
+
})
|
|
449
|
+
);
|
|
450
|
+
},
|
|
451
|
+
async updateIdentity(agentIdentityId, body, context = {}) {
|
|
452
|
+
const request = parsePlatformLifecycleRequestBody(body);
|
|
453
|
+
const identity = await authorizedIdentity(
|
|
454
|
+
agentIdentityId,
|
|
455
|
+
authorizer,
|
|
456
|
+
options.identityStore,
|
|
457
|
+
context
|
|
458
|
+
);
|
|
459
|
+
if (identity === void 0) {
|
|
460
|
+
return problem(404, "not_recognized", "Identity not recognized.");
|
|
461
|
+
}
|
|
462
|
+
if (!await lifecyclePolicy.canTransition(identity, request.status, context)) {
|
|
463
|
+
return problem(
|
|
464
|
+
403,
|
|
465
|
+
lifecycleProblemCode(identity.status),
|
|
466
|
+
"Lifecycle transition rejected."
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
const updated = await options.identityStore.update(
|
|
470
|
+
agentIdentityId,
|
|
471
|
+
{
|
|
472
|
+
status: request.status,
|
|
473
|
+
updatedAt: clock().toISOString()
|
|
474
|
+
},
|
|
475
|
+
context
|
|
476
|
+
);
|
|
477
|
+
if (updated === void 0) {
|
|
478
|
+
return problem(404, "not_recognized", "Identity not recognized.");
|
|
479
|
+
}
|
|
480
|
+
return ok(200, platformIdentityFromRecord(updated));
|
|
481
|
+
},
|
|
482
|
+
async verify(body, context = {}) {
|
|
483
|
+
const request = parsePlatformVerificationRequestBody(body);
|
|
484
|
+
try {
|
|
485
|
+
const decoded = decodeJwtUnverified(request.client_assertion);
|
|
486
|
+
const agentDid = requireString(decoded.payload, "iss");
|
|
487
|
+
const subject = requireString(decoded.payload, "sub");
|
|
488
|
+
const jti = requireString(decoded.payload, "jti");
|
|
489
|
+
const expiresAt = requireNumber(decoded.payload, "exp");
|
|
490
|
+
if (agentDid !== subject) {
|
|
491
|
+
return ok(200, unrecognizedVerification(request, "not_recognized"));
|
|
492
|
+
}
|
|
493
|
+
const identity = await options.identityStore.findByAgentDid(agentDid, context);
|
|
494
|
+
if (identity === void 0 || identity.serviceDid !== request.service_did || !await lifecyclePolicy.canVerify(identity, context)) {
|
|
495
|
+
return ok(200, unrecognizedVerification(request, "not_recognized"));
|
|
496
|
+
}
|
|
497
|
+
const claims = await verifyClientAssertionJwt(request.client_assertion, {
|
|
498
|
+
algorithms: identity.signingAlgorithms,
|
|
499
|
+
audience: request.service_did,
|
|
500
|
+
currentDate: context.now ?? clock(),
|
|
501
|
+
issuer: agentDid,
|
|
502
|
+
key: await options.keyStore.verificationKey(identity, context),
|
|
503
|
+
subject: agentDid
|
|
504
|
+
});
|
|
505
|
+
if (claims.op !== request.op) {
|
|
506
|
+
return ok(200, unrecognizedVerification(request, "not_recognized"));
|
|
507
|
+
}
|
|
508
|
+
const replayKey = [request.service_did, request.op, agentDid, jti].join("");
|
|
509
|
+
const consumed = await options.replayStore.consume(
|
|
510
|
+
replayKey,
|
|
511
|
+
new Date(expiresAt * 1e3),
|
|
512
|
+
context
|
|
513
|
+
);
|
|
514
|
+
if (!consumed) {
|
|
515
|
+
return ok(200, unrecognizedVerification(request, "not_recognized"));
|
|
516
|
+
}
|
|
517
|
+
return ok(
|
|
518
|
+
200,
|
|
519
|
+
createPlatformVerificationResponse({
|
|
520
|
+
agentDid: identity.agentDid,
|
|
521
|
+
agentIdentityId: identity.agentIdentityId,
|
|
522
|
+
command: request.op,
|
|
523
|
+
reason: "verified",
|
|
524
|
+
serviceDid: request.service_did,
|
|
525
|
+
status: identity.status,
|
|
526
|
+
verified: true
|
|
527
|
+
})
|
|
528
|
+
);
|
|
529
|
+
} catch {
|
|
530
|
+
return ok(200, unrecognizedVerification(request, "not_recognized"));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
var defaultLifecyclePolicy = {
|
|
536
|
+
canSign(identity) {
|
|
537
|
+
return identity.status === "active";
|
|
538
|
+
},
|
|
539
|
+
canTransition() {
|
|
540
|
+
return true;
|
|
541
|
+
},
|
|
542
|
+
canVerify(identity) {
|
|
543
|
+
return identity.status === "active";
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
function ok(status, body) {
|
|
547
|
+
return {
|
|
548
|
+
body,
|
|
549
|
+
contentType: AEP_MEDIA_TYPE,
|
|
550
|
+
status
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function problem(status, code, title) {
|
|
554
|
+
return {
|
|
555
|
+
body: createProblemDetails({
|
|
556
|
+
code,
|
|
557
|
+
status,
|
|
558
|
+
title
|
|
559
|
+
}),
|
|
560
|
+
contentType: AEP_PROBLEM_MEDIA_TYPE,
|
|
561
|
+
status
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
async function authorizedIdentity(agentIdentityId, authorizer, store, context) {
|
|
565
|
+
const identity = await store.get(agentIdentityId, context);
|
|
566
|
+
if (identity === void 0) {
|
|
567
|
+
return void 0;
|
|
568
|
+
}
|
|
569
|
+
if (await authorizer.authorizeIdentityAccess?.(identity, context) === false) {
|
|
570
|
+
return void 0;
|
|
571
|
+
}
|
|
572
|
+
return clonePlatformIdentityRecord(identity);
|
|
573
|
+
}
|
|
574
|
+
function platformIdentityFromRecord(identity) {
|
|
575
|
+
return {
|
|
576
|
+
agent_did: identity.agentDid,
|
|
577
|
+
agent_identity_id: identity.agentIdentityId,
|
|
578
|
+
created_at: identity.createdAt,
|
|
579
|
+
did_document_url: identity.didDocumentUrl,
|
|
580
|
+
key_id: identity.keyId,
|
|
581
|
+
service_did: identity.serviceDid,
|
|
582
|
+
signing_algorithms: [...identity.signingAlgorithms],
|
|
583
|
+
status: identity.status,
|
|
584
|
+
updated_at: identity.updatedAt
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function managedIdentityFromRecord(identity) {
|
|
588
|
+
return {
|
|
589
|
+
agentDid: identity.agentDid,
|
|
590
|
+
createdAt: identity.createdAt,
|
|
591
|
+
metadata: {
|
|
592
|
+
agent_did_id: identity.agentDidId,
|
|
593
|
+
agent_identity_id: identity.agentIdentityId,
|
|
594
|
+
did_document_url: identity.didDocumentUrl,
|
|
595
|
+
key_id: identity.keyId,
|
|
596
|
+
service_did: identity.serviceDid
|
|
597
|
+
},
|
|
598
|
+
status: identity.status,
|
|
599
|
+
updatedAt: identity.updatedAt
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function clonePlatformIdentityRecord(identity) {
|
|
603
|
+
return {
|
|
604
|
+
...identity,
|
|
605
|
+
signingAlgorithms: [...identity.signingAlgorithms]
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function parsePlatformProvisionRequestBody(value) {
|
|
609
|
+
const body = requireRecord(value, "Platform provision request");
|
|
610
|
+
return createPlatformProvisionRequest({
|
|
611
|
+
idempotencyKey: requireString(body, "idempotency_key"),
|
|
612
|
+
serviceDid: requireString(body, "service_did")
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
function parsePlatformLifecycleRequestBody(value) {
|
|
616
|
+
const body = requireRecord(value, "Platform lifecycle request");
|
|
617
|
+
const status = requireString(body, "status");
|
|
618
|
+
if (!isManagedAgentStatus(status)) {
|
|
619
|
+
throw new TypeError("status must be a supported managed Agent status.");
|
|
620
|
+
}
|
|
621
|
+
return createPlatformLifecycleRequest({ status });
|
|
622
|
+
}
|
|
623
|
+
function parsePlatformSignRequestBody(value) {
|
|
624
|
+
const body = requireRecord(value, "Platform sign request");
|
|
625
|
+
const command = requireString(body, "op");
|
|
626
|
+
if (!isAuthenticatedCommand(command)) {
|
|
627
|
+
throw new TypeError("op must be an AEP authenticated command.");
|
|
628
|
+
}
|
|
629
|
+
const lifetimeSeconds = body["lifetime_seconds"] === void 0 ? void 0 : requireString(body, "lifetime_seconds");
|
|
630
|
+
return createPlatformSignRequest({
|
|
631
|
+
command,
|
|
632
|
+
jti: requireString(body, "jti"),
|
|
633
|
+
serviceDid: requireString(body, "service_did"),
|
|
634
|
+
...lifetimeSeconds === void 0 ? {} : { lifetimeSeconds: Number(lifetimeSeconds) }
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
function parsePlatformVerificationRequestBody(value) {
|
|
638
|
+
const body = requireRecord(value, "Platform verification request");
|
|
639
|
+
const command = requireString(body, "op");
|
|
640
|
+
if (!isAuthenticatedCommand(command)) {
|
|
641
|
+
throw new TypeError("op must be an AEP authenticated command.");
|
|
642
|
+
}
|
|
643
|
+
return createPlatformVerificationRequest({
|
|
644
|
+
clientAssertion: requireString(body, "client_assertion"),
|
|
645
|
+
command,
|
|
646
|
+
serviceDid: requireString(body, "service_did")
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
function unrecognizedVerification(request, reason) {
|
|
650
|
+
return createPlatformVerificationResponse({
|
|
651
|
+
reason,
|
|
652
|
+
serviceDid: request.service_did,
|
|
653
|
+
verified: false
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function renderDidUrlTemplate(template, agentDidId) {
|
|
657
|
+
if (!template.includes("{agent_did_id}")) {
|
|
658
|
+
throw new TypeError("didUrlTemplate must include {agent_did_id}.");
|
|
659
|
+
}
|
|
660
|
+
return template.replace("{agent_did_id}", encodeURIComponent(agentDidId));
|
|
661
|
+
}
|
|
662
|
+
function lifecycleProblemCode(status) {
|
|
663
|
+
if (status === "terminated") {
|
|
664
|
+
return "identity_terminated";
|
|
665
|
+
}
|
|
666
|
+
if (status === "suspended" || status === "revoked") {
|
|
667
|
+
return "identity_suspended";
|
|
668
|
+
}
|
|
669
|
+
return "identity_unavailable";
|
|
670
|
+
}
|
|
671
|
+
function stableStringify(value) {
|
|
672
|
+
if (Array.isArray(value)) {
|
|
673
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
674
|
+
}
|
|
675
|
+
if (typeof value === "object" && value !== null) {
|
|
676
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
|
|
677
|
+
}
|
|
678
|
+
return JSON.stringify(value);
|
|
679
|
+
}
|
|
680
|
+
function randomPlatformId() {
|
|
681
|
+
return globalThis.crypto.randomUUID().replaceAll("-", "");
|
|
682
|
+
}
|
|
683
|
+
function requireRecord(value, label) {
|
|
684
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
685
|
+
throw new TypeError(`${label} must be an object.`);
|
|
686
|
+
}
|
|
687
|
+
return value;
|
|
688
|
+
}
|
|
689
|
+
function requireString(record, key) {
|
|
690
|
+
const value = record[key];
|
|
691
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
692
|
+
throw new TypeError(`${key} must be a non-empty string.`);
|
|
693
|
+
}
|
|
694
|
+
return value;
|
|
695
|
+
}
|
|
696
|
+
function requireNumber(record, key) {
|
|
697
|
+
const value = record[key];
|
|
698
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
699
|
+
throw new TypeError(`${key} must be an integer.`);
|
|
700
|
+
}
|
|
701
|
+
return value;
|
|
702
|
+
}
|
|
703
|
+
function isAuthenticatedCommand(value) {
|
|
704
|
+
return value === "enroll" || value === "grant" || value === "revoke" || value === "status";
|
|
705
|
+
}
|
|
706
|
+
function isManagedAgentStatus(value) {
|
|
707
|
+
return value === "active" || value === "revoked" || value === "suspended" || value === "terminated";
|
|
708
|
+
}
|
|
709
|
+
function assertUsableIdentity(identity) {
|
|
710
|
+
assertNonEmpty("agentDid", identity.agentDid);
|
|
711
|
+
if (identity.status !== "active") {
|
|
712
|
+
throw new Error(`Managed Agent identity is not active: ${identity.agentDid}.`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function assertNonEmpty(label, value) {
|
|
716
|
+
if (value.trim().length === 0) {
|
|
717
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function cloneManagedAgentIdentity(identity) {
|
|
721
|
+
return {
|
|
722
|
+
...identity,
|
|
723
|
+
metadata: cloneRecord(identity.metadata)
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function cloneRecord(record) {
|
|
727
|
+
return structuredClone(record);
|
|
728
|
+
}
|
|
729
|
+
function toEpochSeconds(value) {
|
|
730
|
+
const epochSeconds = typeof value === "number" ? value : Math.floor(value.getTime() / 1e3);
|
|
731
|
+
if (!Number.isInteger(epochSeconds) || epochSeconds < 0) {
|
|
732
|
+
throw new TypeError("issuedAt must resolve to a non-negative epoch second.");
|
|
733
|
+
}
|
|
734
|
+
return epochSeconds;
|
|
735
|
+
}
|
|
736
|
+
function epochSecondsToIso(value) {
|
|
737
|
+
return new Date(value * 1e3).toISOString();
|
|
738
|
+
}
|
|
739
|
+
function validateLifetimeSeconds(lifetimeSeconds, maxLifetimeSeconds = defaultAssertionLifetimeSeconds) {
|
|
740
|
+
if (!Number.isInteger(lifetimeSeconds) || lifetimeSeconds <= 0) {
|
|
741
|
+
throw new TypeError("lifetimeSeconds must be a positive integer.");
|
|
742
|
+
}
|
|
743
|
+
if (!Number.isInteger(maxLifetimeSeconds) || maxLifetimeSeconds <= 0) {
|
|
744
|
+
throw new TypeError("maxLifetimeSeconds must be a positive integer.");
|
|
745
|
+
}
|
|
746
|
+
if (lifetimeSeconds > maxLifetimeSeconds) {
|
|
747
|
+
throw new TypeError("lifetimeSeconds must not exceed maxLifetimeSeconds.");
|
|
748
|
+
}
|
|
749
|
+
return lifetimeSeconds;
|
|
750
|
+
}
|
|
751
|
+
function createDidVerificationMethod(agentDid, method, index) {
|
|
752
|
+
const id = method.id ?? `${agentDid}#key-${index + 1}`;
|
|
753
|
+
const controller = method.controller ?? agentDid;
|
|
754
|
+
const methodBody = { ...method };
|
|
755
|
+
delete methodBody.relationships;
|
|
756
|
+
assertNonEmpty("verification method id", id);
|
|
757
|
+
assertNonEmpty("verification method type", method.type);
|
|
758
|
+
assertNonEmpty("verification method controller", controller);
|
|
759
|
+
return {
|
|
760
|
+
...structuredClone(methodBody),
|
|
761
|
+
controller,
|
|
762
|
+
id,
|
|
763
|
+
type: method.type
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function cloneDidService(service) {
|
|
767
|
+
return structuredClone(service);
|
|
768
|
+
}
|
|
769
|
+
function relationshipNames() {
|
|
770
|
+
return [
|
|
771
|
+
"authentication",
|
|
772
|
+
"assertionMethod",
|
|
773
|
+
"capabilityInvocation",
|
|
774
|
+
"capabilityDelegation",
|
|
775
|
+
"keyAgreement"
|
|
776
|
+
];
|
|
777
|
+
}
|
|
778
|
+
function preferredSigningAlgorithm(algorithms) {
|
|
779
|
+
const algorithm = algorithms.find((candidate) => candidate === "ES256" || candidate === "EdDSA");
|
|
780
|
+
if (algorithm === void 0) {
|
|
781
|
+
throw new TypeError("No supported AEP JOSE signing algorithm is available.");
|
|
782
|
+
}
|
|
783
|
+
return algorithm;
|
|
784
|
+
}
|
|
785
|
+
export {
|
|
786
|
+
InMemoryManagedAgentRegistry,
|
|
787
|
+
createAepPlatform,
|
|
788
|
+
createJwtPlatformDelegatedSigner,
|
|
789
|
+
createManagedAgentDidDocument,
|
|
790
|
+
createManagedAgentIdentity,
|
|
791
|
+
createPlatformAgentIdentity,
|
|
792
|
+
createPlatformAgentIdentityListResponse,
|
|
793
|
+
createPlatformClientAssertionClaims,
|
|
794
|
+
createPlatformDiscoveryDocument,
|
|
795
|
+
createPlatformEnrollRequest,
|
|
796
|
+
createPlatformLifecycleRequest,
|
|
797
|
+
createPlatformProvisionRequest,
|
|
798
|
+
createPlatformSignRequest,
|
|
799
|
+
createPlatformSignResponse,
|
|
800
|
+
createPlatformVerificationRequest,
|
|
801
|
+
createPlatformVerificationResponse,
|
|
802
|
+
createServiceScopedAgentDid,
|
|
803
|
+
defaultAssertionLifetimeSeconds,
|
|
804
|
+
packageName,
|
|
805
|
+
platformHostedIdentityDraft,
|
|
806
|
+
publishManagedAgentDidDocument,
|
|
807
|
+
signPlatformClientAssertion,
|
|
808
|
+
updateManagedAgentIdentity
|
|
809
|
+
};
|
|
810
|
+
//# sourceMappingURL=index.js.map
|