@vess-id/ai-identity 0.0.1
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/README.md +153 -0
- package/dist/index.js +4573 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4513 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4573 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
ACTION_REGISTRY: () => ACTION_REGISTRY,
|
|
35
|
+
AIdentityClient: () => AIdentityClient,
|
|
36
|
+
APIVCManager: () => APIVCManager,
|
|
37
|
+
AgentDIDManager: () => AgentDIDManager,
|
|
38
|
+
AgentManager: () => AgentManager,
|
|
39
|
+
AllowAllAbac: () => AllowAllAbac,
|
|
40
|
+
ConstraintEvaluator: () => ConstraintEvaluator,
|
|
41
|
+
DisclosureConfigManager: () => DisclosureConfigManager,
|
|
42
|
+
DummyCreds: () => DummyCreds,
|
|
43
|
+
DummyVpVerifier: () => DummyVpVerifier,
|
|
44
|
+
FilesystemKeyStorage: () => FilesystemKeyStorage,
|
|
45
|
+
KeyManager: () => KeyManager,
|
|
46
|
+
KeyRotationManager: () => KeyRotationManager,
|
|
47
|
+
MemoryKeyStorage: () => MemoryKeyStorage,
|
|
48
|
+
MemoryManager: () => MemoryManager,
|
|
49
|
+
MetricsManager: () => MetricsManager,
|
|
50
|
+
RevocationManager: () => RevocationManager,
|
|
51
|
+
SDJwtClient: () => SDJwtClient,
|
|
52
|
+
SimpleRebac: () => SimpleRebac,
|
|
53
|
+
ToolManager: () => ToolManager,
|
|
54
|
+
UserIdentityManager: () => UserIdentityManager,
|
|
55
|
+
VCManager: () => VCManager,
|
|
56
|
+
VPManager: () => VPManager,
|
|
57
|
+
checkPermissionWithVP: () => checkPermissionWithVP,
|
|
58
|
+
configure: () => configure,
|
|
59
|
+
createAjv: () => createAjv,
|
|
60
|
+
defaultConstraintEvaluator: () => defaultConstraintEvaluator,
|
|
61
|
+
evaluateConstraints: () => evaluateConstraints,
|
|
62
|
+
generateKeyPair: () => generateKeyPair,
|
|
63
|
+
generateNonce: () => generateNonce,
|
|
64
|
+
getClient: () => getClient,
|
|
65
|
+
getRequiredRelations: () => getRequiredRelations,
|
|
66
|
+
getRequiredScopes: () => getRequiredScopes,
|
|
67
|
+
indexActions: () => indexActions,
|
|
68
|
+
indexCapabilities: () => indexCapabilities,
|
|
69
|
+
loadActionRegistryFromFile: () => loadActionRegistryFromFile,
|
|
70
|
+
loadActionRegistryFromObject: () => loadActionRegistryFromObject,
|
|
71
|
+
planDelegationForVC: () => planDelegationForVC,
|
|
72
|
+
resolveActionsFromSelection: () => resolveActionsFromSelection,
|
|
73
|
+
signJWT: () => signJWT,
|
|
74
|
+
validateRegistryObject: () => validateRegistryObject,
|
|
75
|
+
verifyJWT: () => verifyJWT,
|
|
76
|
+
version: () => version
|
|
77
|
+
});
|
|
78
|
+
module.exports = __toCommonJS(index_exports);
|
|
79
|
+
|
|
80
|
+
// src/client.ts
|
|
81
|
+
var client_exports = {};
|
|
82
|
+
__export(client_exports, {
|
|
83
|
+
AIdentityClient: () => AIdentityClient,
|
|
84
|
+
configure: () => configure,
|
|
85
|
+
getClient: () => getClient
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// src/config/index.ts
|
|
89
|
+
var globalConfig = {};
|
|
90
|
+
function configure(config) {
|
|
91
|
+
globalConfig = { ...globalConfig, ...config };
|
|
92
|
+
}
|
|
93
|
+
function getConfig() {
|
|
94
|
+
return globalConfig;
|
|
95
|
+
}
|
|
96
|
+
function getDidApiUrl(path3) {
|
|
97
|
+
const baseUrl = globalConfig.didApi?.baseUrl || process.env.DID_API_BASE_URL;
|
|
98
|
+
if (!baseUrl) {
|
|
99
|
+
throw new Error("DID API base URL not configured");
|
|
100
|
+
}
|
|
101
|
+
return `${baseUrl}${path3}`;
|
|
102
|
+
}
|
|
103
|
+
function getIssuerApiUrl(path3) {
|
|
104
|
+
const baseUrl = globalConfig.issuerApi?.baseUrl || process.env.ISSUER_API_BASE_URL;
|
|
105
|
+
if (!baseUrl) {
|
|
106
|
+
throw new Error("Issuer API base URL not configured");
|
|
107
|
+
}
|
|
108
|
+
return `${baseUrl}${path3}`;
|
|
109
|
+
}
|
|
110
|
+
function getVerifierApiUrl(path3) {
|
|
111
|
+
const baseUrl = globalConfig.verifierApi?.baseUrl || process.env.VERIFIER_API_BASE_URL;
|
|
112
|
+
if (!baseUrl) {
|
|
113
|
+
throw new Error("Verifier API base URL not configured");
|
|
114
|
+
}
|
|
115
|
+
return `${baseUrl}${path3}`;
|
|
116
|
+
}
|
|
117
|
+
function getApiHeaders(apiType) {
|
|
118
|
+
const headers = {
|
|
119
|
+
"Content-Type": "application/json"
|
|
120
|
+
};
|
|
121
|
+
let apiKey;
|
|
122
|
+
let bearerToken;
|
|
123
|
+
switch (apiType) {
|
|
124
|
+
case "did":
|
|
125
|
+
apiKey = globalConfig.didApi?.apiKey || process.env.DID_API_KEY;
|
|
126
|
+
bearerToken = globalConfig.didApi?.bearerToken;
|
|
127
|
+
break;
|
|
128
|
+
case "issuer":
|
|
129
|
+
apiKey = globalConfig.issuerApi?.apiKey || process.env.ISSUER_API_KEY;
|
|
130
|
+
bearerToken = globalConfig.issuerApi?.bearerToken;
|
|
131
|
+
break;
|
|
132
|
+
case "verifier":
|
|
133
|
+
apiKey = globalConfig.verifierApi?.apiKey || process.env.VERIFIER_API_KEY;
|
|
134
|
+
bearerToken = globalConfig.verifierApi?.bearerToken;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
if (apiKey) {
|
|
138
|
+
headers["x-api-key"] = apiKey;
|
|
139
|
+
}
|
|
140
|
+
if (bearerToken) {
|
|
141
|
+
headers["Authorization"] = `Bearer ${bearerToken}`;
|
|
142
|
+
}
|
|
143
|
+
return headers;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/did/key-manager.ts
|
|
147
|
+
var crypto = __toESM(require("crypto"));
|
|
148
|
+
|
|
149
|
+
// src/storage/filesystem-key-storage.ts
|
|
150
|
+
var fs = __toESM(require("fs/promises"));
|
|
151
|
+
var path = __toESM(require("path"));
|
|
152
|
+
var os = __toESM(require("os"));
|
|
153
|
+
var FilesystemKeyStorage = class {
|
|
154
|
+
keyStorePath;
|
|
155
|
+
constructor(config) {
|
|
156
|
+
this.keyStorePath = config?.options?.path || path.join(os.homedir(), ".vess", "keys");
|
|
157
|
+
}
|
|
158
|
+
async store(id, encryptedKey) {
|
|
159
|
+
await this.ensureKeyStoreExists();
|
|
160
|
+
const keyPath = this.getKeyPath(id);
|
|
161
|
+
await fs.writeFile(keyPath, encryptedKey, "utf-8");
|
|
162
|
+
}
|
|
163
|
+
async retrieve(id) {
|
|
164
|
+
const keyPath = this.getKeyPath(id);
|
|
165
|
+
try {
|
|
166
|
+
return await fs.readFile(keyPath, "utf-8");
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (error.code === "ENOENT") {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async delete(id) {
|
|
175
|
+
const keyPath = this.getKeyPath(id);
|
|
176
|
+
try {
|
|
177
|
+
await fs.unlink(keyPath);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error.code !== "ENOENT") {
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async list() {
|
|
185
|
+
await this.ensureKeyStoreExists();
|
|
186
|
+
const files = await fs.readdir(this.keyStorePath);
|
|
187
|
+
return files.filter((f) => f.endsWith(".key")).map((f) => f.replace(".key", ""));
|
|
188
|
+
}
|
|
189
|
+
async isAvailable() {
|
|
190
|
+
try {
|
|
191
|
+
await this.ensureKeyStoreExists();
|
|
192
|
+
return true;
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async ensureKeyStoreExists() {
|
|
198
|
+
try {
|
|
199
|
+
await fs.access(this.keyStorePath);
|
|
200
|
+
} catch {
|
|
201
|
+
await fs.mkdir(this.keyStorePath, { recursive: true });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
getKeyPath(id) {
|
|
205
|
+
return path.join(this.keyStorePath, `${id}.key`);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/storage/memory-key-storage.ts
|
|
210
|
+
var MemoryKeyStorage = class {
|
|
211
|
+
keys = /* @__PURE__ */ new Map();
|
|
212
|
+
async store(id, encryptedKey) {
|
|
213
|
+
this.keys.set(id, encryptedKey);
|
|
214
|
+
}
|
|
215
|
+
async retrieve(id) {
|
|
216
|
+
return this.keys.get(id) || null;
|
|
217
|
+
}
|
|
218
|
+
async delete(id) {
|
|
219
|
+
this.keys.delete(id);
|
|
220
|
+
}
|
|
221
|
+
async list() {
|
|
222
|
+
return Array.from(this.keys.keys());
|
|
223
|
+
}
|
|
224
|
+
async isAvailable() {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Clear all stored keys (for testing)
|
|
229
|
+
*/
|
|
230
|
+
clear() {
|
|
231
|
+
this.keys.clear();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// src/did/key-manager.ts
|
|
236
|
+
var KeyManager = class {
|
|
237
|
+
encryptionKey;
|
|
238
|
+
storageProvider;
|
|
239
|
+
constructor(password, storageProvider) {
|
|
240
|
+
if (password) {
|
|
241
|
+
this.encryptionKey = crypto.scryptSync(password, "aidentity-salt", 32);
|
|
242
|
+
}
|
|
243
|
+
this.storageProvider = storageProvider || this.createDefaultStorageProvider();
|
|
244
|
+
}
|
|
245
|
+
createDefaultStorageProvider() {
|
|
246
|
+
const config = getConfig();
|
|
247
|
+
return new FilesystemKeyStorage({
|
|
248
|
+
type: "filesystem",
|
|
249
|
+
options: {
|
|
250
|
+
path: config.storage?.keyStorePath
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async storeKey(did, privateKey) {
|
|
255
|
+
const keyData = JSON.stringify(privateKey);
|
|
256
|
+
const encrypted = this.encrypt(keyData);
|
|
257
|
+
await this.storageProvider.store(did, encrypted);
|
|
258
|
+
}
|
|
259
|
+
async getKey(did) {
|
|
260
|
+
const encrypted = await this.storageProvider.retrieve(did);
|
|
261
|
+
if (!encrypted) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const decrypted = this.decrypt(encrypted);
|
|
265
|
+
return JSON.parse(decrypted);
|
|
266
|
+
}
|
|
267
|
+
async deleteKey(did) {
|
|
268
|
+
await this.storageProvider.delete(did);
|
|
269
|
+
}
|
|
270
|
+
async listDids() {
|
|
271
|
+
const keyIds = await this.storageProvider.list();
|
|
272
|
+
return keyIds.map((keyId) => this.didFromKeyId(keyId));
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Check if storage is available
|
|
276
|
+
*/
|
|
277
|
+
async isAvailable() {
|
|
278
|
+
return this.storageProvider.isAvailable();
|
|
279
|
+
}
|
|
280
|
+
// private getKeyId(did: string): string {
|
|
281
|
+
// // Use SHA256 hash to create short, storage-safe identifier
|
|
282
|
+
// return crypto.createHash('sha256').update(did).digest('hex').substring(0, 16)
|
|
283
|
+
// }
|
|
284
|
+
didFromKeyId(keyId) {
|
|
285
|
+
return keyId;
|
|
286
|
+
}
|
|
287
|
+
encrypt(data) {
|
|
288
|
+
if (!this.encryptionKey) {
|
|
289
|
+
return Buffer.from(data).toString("base64");
|
|
290
|
+
}
|
|
291
|
+
const iv = crypto.randomBytes(16);
|
|
292
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", this.encryptionKey, iv);
|
|
293
|
+
let encrypted = cipher.update(data, "utf8", "base64");
|
|
294
|
+
encrypted += cipher.final("base64");
|
|
295
|
+
const authTag = cipher.getAuthTag();
|
|
296
|
+
const combined = Buffer.concat([iv, authTag, Buffer.from(encrypted, "base64")]);
|
|
297
|
+
return combined.toString("base64");
|
|
298
|
+
}
|
|
299
|
+
decrypt(encrypted) {
|
|
300
|
+
if (!this.encryptionKey) {
|
|
301
|
+
return Buffer.from(encrypted, "base64").toString("utf-8");
|
|
302
|
+
}
|
|
303
|
+
const combined = Buffer.from(encrypted, "base64");
|
|
304
|
+
const iv = combined.subarray(0, 16);
|
|
305
|
+
const authTag = combined.subarray(16, 32);
|
|
306
|
+
const encryptedData = combined.subarray(32);
|
|
307
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", this.encryptionKey, iv);
|
|
308
|
+
decipher.setAuthTag(authTag);
|
|
309
|
+
let decrypted = decipher.update(encryptedData, void 0, "utf8");
|
|
310
|
+
decrypted += decipher.final("utf8");
|
|
311
|
+
return decrypted;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/utils/sdjwt-client.ts
|
|
316
|
+
var import_sd_jwt_vc = require("@sd-jwt/sd-jwt-vc");
|
|
317
|
+
var import_crypto_nodejs = require("@sd-jwt/crypto-nodejs");
|
|
318
|
+
|
|
319
|
+
// src/utils/crypto.ts
|
|
320
|
+
var jose = __toESM(require("jose"));
|
|
321
|
+
var import_uuid = require("uuid");
|
|
322
|
+
var import_node_crypto = require("crypto");
|
|
323
|
+
async function generateKeyPair() {
|
|
324
|
+
const keyPair = await import_node_crypto.subtle.generateKey(
|
|
325
|
+
{
|
|
326
|
+
name: "ECDSA",
|
|
327
|
+
namedCurve: "P-256"
|
|
328
|
+
},
|
|
329
|
+
true,
|
|
330
|
+
// extractable
|
|
331
|
+
["sign", "verify"]
|
|
332
|
+
);
|
|
333
|
+
const publicJwk = await import_node_crypto.subtle.exportKey("jwk", keyPair.publicKey);
|
|
334
|
+
const privateJwk = await import_node_crypto.subtle.exportKey("jwk", keyPair.privateKey);
|
|
335
|
+
const kid = (0, import_uuid.v4)();
|
|
336
|
+
publicJwk.kid = kid;
|
|
337
|
+
privateJwk.kid = kid;
|
|
338
|
+
publicJwk.alg = "ES256";
|
|
339
|
+
privateJwk.alg = "ES256";
|
|
340
|
+
return {
|
|
341
|
+
publicKey: publicJwk,
|
|
342
|
+
privateKey: privateJwk
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
async function signJWT(payload, privateKey, options) {
|
|
346
|
+
const alg = privateKey.alg || "ES256";
|
|
347
|
+
const key = await jose.importJWK(privateKey, alg);
|
|
348
|
+
const jwt = new jose.SignJWT(payload).setProtectedHeader({ alg, kid: privateKey.kid }).setIssuedAt().setJti(options?.jti ?? (0, import_uuid.v4)());
|
|
349
|
+
if (options?.issuer) jwt.setIssuer(options.issuer);
|
|
350
|
+
if (options?.audience) jwt.setAudience(options.audience);
|
|
351
|
+
if (options?.expiresIn) jwt.setExpirationTime(options.expiresIn);
|
|
352
|
+
if (options?.notBefore) jwt.setNotBefore(options.notBefore);
|
|
353
|
+
if (options?.subject) jwt.setSubject(options.subject);
|
|
354
|
+
return await jwt.sign(key);
|
|
355
|
+
}
|
|
356
|
+
async function verifyJWT(jwt, publicKey, options) {
|
|
357
|
+
const alg = publicKey.alg || "ES256";
|
|
358
|
+
const key = await jose.importJWK(publicKey, alg);
|
|
359
|
+
const verifyOptions = {};
|
|
360
|
+
if (options?.issuer) verifyOptions.issuer = options.issuer;
|
|
361
|
+
if (options?.audience) verifyOptions.audience = options.audience;
|
|
362
|
+
const { payload } = await jose.jwtVerify(jwt, key, verifyOptions);
|
|
363
|
+
return payload;
|
|
364
|
+
}
|
|
365
|
+
function generateNonce() {
|
|
366
|
+
return (0, import_uuid.v4)();
|
|
367
|
+
}
|
|
368
|
+
async function getSigner(privateKey) {
|
|
369
|
+
try {
|
|
370
|
+
const key = await import_node_crypto.subtle.importKey(
|
|
371
|
+
"jwk",
|
|
372
|
+
privateKey,
|
|
373
|
+
{
|
|
374
|
+
name: "ECDSA",
|
|
375
|
+
namedCurve: "P-256"
|
|
376
|
+
},
|
|
377
|
+
true,
|
|
378
|
+
["sign"]
|
|
379
|
+
);
|
|
380
|
+
return async (data) => {
|
|
381
|
+
const encoder = new TextEncoder();
|
|
382
|
+
const signature = await import_node_crypto.subtle.sign(
|
|
383
|
+
{
|
|
384
|
+
name: "ECDSA",
|
|
385
|
+
hash: { name: "SHA-256" }
|
|
386
|
+
},
|
|
387
|
+
key,
|
|
388
|
+
encoder.encode(data)
|
|
389
|
+
);
|
|
390
|
+
const result = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
391
|
+
return result;
|
|
392
|
+
};
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error("Error in getSigner:", error instanceof Error ? error.message : "Unknown error");
|
|
395
|
+
console.error("Key algorithm:", privateKey?.alg || "unknown");
|
|
396
|
+
console.error("Key type:", privateKey?.kty || "unknown");
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function getVerifier(publicKey) {
|
|
401
|
+
const key = await import_node_crypto.subtle.importKey(
|
|
402
|
+
"jwk",
|
|
403
|
+
publicKey,
|
|
404
|
+
{
|
|
405
|
+
name: "ECDSA",
|
|
406
|
+
namedCurve: "P-256"
|
|
407
|
+
},
|
|
408
|
+
true,
|
|
409
|
+
["verify"]
|
|
410
|
+
);
|
|
411
|
+
return async (data, signatureBase64url) => {
|
|
412
|
+
const encoder = new TextEncoder();
|
|
413
|
+
const signature = Uint8Array.from(
|
|
414
|
+
atob(signatureBase64url.replace(/-/g, "+").replace(/_/g, "/")),
|
|
415
|
+
(c) => c.charCodeAt(0)
|
|
416
|
+
);
|
|
417
|
+
const isValid = await import_node_crypto.subtle.verify(
|
|
418
|
+
{
|
|
419
|
+
name: "ECDSA",
|
|
420
|
+
hash: { name: "SHA-256" }
|
|
421
|
+
},
|
|
422
|
+
key,
|
|
423
|
+
signature,
|
|
424
|
+
encoder.encode(data)
|
|
425
|
+
);
|
|
426
|
+
return isValid;
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/utils/sdjwt-client.ts
|
|
431
|
+
var SDJwtClient = class {
|
|
432
|
+
static instances = /* @__PURE__ */ new Map();
|
|
433
|
+
static keyManager;
|
|
434
|
+
static signerCache = /* @__PURE__ */ new Map();
|
|
435
|
+
static verifierCache = /* @__PURE__ */ new Map();
|
|
436
|
+
constructor() {
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Initialize with KeyManager for DID-based key management
|
|
440
|
+
*/
|
|
441
|
+
static setKeyManager(keyManager) {
|
|
442
|
+
this.keyManager = keyManager;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Get SDJwtVcInstance for issuer role (VC issuance)
|
|
446
|
+
*/
|
|
447
|
+
static async getIssuerInstance(issuerDid) {
|
|
448
|
+
const cacheKey = `${issuerDid}:issuer`;
|
|
449
|
+
if (!this.instances.has(cacheKey)) {
|
|
450
|
+
const instance = await this.createInstance(issuerDid, "issuer");
|
|
451
|
+
this.instances.set(cacheKey, instance);
|
|
452
|
+
}
|
|
453
|
+
return this.instances.get(cacheKey);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get SDJwtVcInstance for holder role (VP presentation)
|
|
457
|
+
*/
|
|
458
|
+
static async getHolderInstance(holderDid) {
|
|
459
|
+
const cacheKey = `${holderDid}:holder`;
|
|
460
|
+
if (!this.instances.has(cacheKey)) {
|
|
461
|
+
const instance = await this.createInstance(holderDid, "holder");
|
|
462
|
+
this.instances.set(cacheKey, instance);
|
|
463
|
+
}
|
|
464
|
+
return this.instances.get(cacheKey);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Get SDJwtVcInstance with specified role (backward compatibility)
|
|
468
|
+
*/
|
|
469
|
+
static async getSDJwtInstance(did, options) {
|
|
470
|
+
const role = options?.role || "issuer";
|
|
471
|
+
return role === "holder" ? this.getHolderInstance(did) : this.getIssuerInstance(did);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Create a new SDJwtVcInstance with DID-based keys and role
|
|
475
|
+
*/
|
|
476
|
+
static async createInstance(did, role) {
|
|
477
|
+
if (!this.keyManager) {
|
|
478
|
+
this.keyManager = new KeyManager();
|
|
479
|
+
}
|
|
480
|
+
const privateKey = await this.keyManager.getKey(did);
|
|
481
|
+
if (!privateKey) {
|
|
482
|
+
throw new Error(`Private key not found for ${role}: ${did}`);
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
const signerCacheKey = `signer:${did}`;
|
|
486
|
+
const verifierCacheKey = `verifier:${did}`;
|
|
487
|
+
let signer = this.signerCache.get(signerCacheKey);
|
|
488
|
+
let verifier = this.verifierCache.get(verifierCacheKey);
|
|
489
|
+
if (!signer) {
|
|
490
|
+
signer = await getSigner(privateKey);
|
|
491
|
+
this.signerCache.set(signerCacheKey, signer);
|
|
492
|
+
}
|
|
493
|
+
if (!verifier) {
|
|
494
|
+
const { d, key_ops, ...publicKey } = privateKey;
|
|
495
|
+
const publicKeyForVerifier = {
|
|
496
|
+
...publicKey,
|
|
497
|
+
key_ops: ["verify"]
|
|
498
|
+
// Set correct key operations for verifier
|
|
499
|
+
};
|
|
500
|
+
verifier = await getVerifier(publicKeyForVerifier);
|
|
501
|
+
this.verifierCache.set(verifierCacheKey, verifier);
|
|
502
|
+
}
|
|
503
|
+
const config = {
|
|
504
|
+
signer,
|
|
505
|
+
verifier,
|
|
506
|
+
signAlg: import_crypto_nodejs.ES256.alg,
|
|
507
|
+
hasher: import_crypto_nodejs.digest,
|
|
508
|
+
hashAlg: "sha-256",
|
|
509
|
+
saltGenerator: import_crypto_nodejs.generateSalt
|
|
510
|
+
};
|
|
511
|
+
if (role === "holder") {
|
|
512
|
+
config.kbSigner = signer;
|
|
513
|
+
config.kbSignAlg = import_crypto_nodejs.ES256.alg;
|
|
514
|
+
}
|
|
515
|
+
return new import_sd_jwt_vc.SDJwtVcInstance(config);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
console.error("\u274C Error creating SDJwtVcInstance:", error);
|
|
518
|
+
throw error;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Create disclosure frame for selective disclosure
|
|
523
|
+
*/
|
|
524
|
+
static createDisclosureFrame(claims, selectivelyDisclosable = []) {
|
|
525
|
+
const frame = {};
|
|
526
|
+
const existingDisclosableFields = selectivelyDisclosable.filter(
|
|
527
|
+
(field) => claims.hasOwnProperty(field)
|
|
528
|
+
);
|
|
529
|
+
if (existingDisclosableFields.length > 0) {
|
|
530
|
+
frame._sd = existingDisclosableFields;
|
|
531
|
+
frame._sd_decoy = Math.max(0, Math.floor(existingDisclosableFields.length * 0.1));
|
|
532
|
+
}
|
|
533
|
+
for (const [key, value] of Object.entries(claims)) {
|
|
534
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
535
|
+
const nestedKeys = Object.keys(value);
|
|
536
|
+
const nestedFrame = this.createDisclosureFrame(
|
|
537
|
+
value,
|
|
538
|
+
nestedKeys
|
|
539
|
+
// Make all nested fields disclosable
|
|
540
|
+
);
|
|
541
|
+
frame[key] = nestedFrame;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return frame;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Issue an SD-JWT with selective disclosure
|
|
548
|
+
*/
|
|
549
|
+
static async issueSDJWT(payload, _privateKey, selectiveDisclosureFields = []) {
|
|
550
|
+
const issuerDid = payload.iss || "unknown";
|
|
551
|
+
const sdjwtInstance = await this.getIssuerInstance(issuerDid);
|
|
552
|
+
const vcPayload = {
|
|
553
|
+
vct: "IdentityCredential",
|
|
554
|
+
// Default VC type
|
|
555
|
+
...payload
|
|
556
|
+
};
|
|
557
|
+
const disclosureFrame = this.createDisclosureFrame(vcPayload, selectiveDisclosureFields);
|
|
558
|
+
return await sdjwtInstance.issue(vcPayload, disclosureFrame);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Verify an SD-JWT
|
|
562
|
+
*/
|
|
563
|
+
static async verifySDJWT(credential) {
|
|
564
|
+
try {
|
|
565
|
+
const parts = credential.split("~");
|
|
566
|
+
if (parts.length === 0) {
|
|
567
|
+
return { valid: false, error: "Invalid SD-JWT format" };
|
|
568
|
+
}
|
|
569
|
+
const jwt = parts[0];
|
|
570
|
+
const jwtParts = jwt.split(".");
|
|
571
|
+
if (jwtParts.length !== 3) {
|
|
572
|
+
return { valid: false, error: "Invalid JWT format in SD-JWT" };
|
|
573
|
+
}
|
|
574
|
+
const payload = JSON.parse(Buffer.from(jwtParts[1], "base64url").toString());
|
|
575
|
+
const issuerDid = payload.iss;
|
|
576
|
+
if (!issuerDid) {
|
|
577
|
+
return { valid: false, error: "Issuer DID not found in SD-JWT" };
|
|
578
|
+
}
|
|
579
|
+
const sdjwtInstance = await this.getIssuerInstance(issuerDid);
|
|
580
|
+
const verificationResult = await sdjwtInstance.verify(credential);
|
|
581
|
+
const claims = await sdjwtInstance.getClaims(credential);
|
|
582
|
+
return {
|
|
583
|
+
valid: true,
|
|
584
|
+
payload: {
|
|
585
|
+
...verificationResult.payload,
|
|
586
|
+
claims
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
} catch (error) {
|
|
590
|
+
return {
|
|
591
|
+
valid: false,
|
|
592
|
+
error: error.message
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Legacy methods for backward compatibility
|
|
598
|
+
*/
|
|
599
|
+
static async createSignerVerifier() {
|
|
600
|
+
const { privateKey, publicKey } = await this.generateKeyPair();
|
|
601
|
+
return {
|
|
602
|
+
signer: await getSigner(privateKey),
|
|
603
|
+
verifier: await getVerifier(publicKey)
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
static async generateKeyPair() {
|
|
607
|
+
return await generateKeyPair();
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Clear caches for optimization
|
|
611
|
+
*/
|
|
612
|
+
static clearCaches() {
|
|
613
|
+
this.instances.clear();
|
|
614
|
+
this.signerCache.clear();
|
|
615
|
+
this.verifierCache.clear();
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Clear cache for specific issuer
|
|
619
|
+
*/
|
|
620
|
+
static clearIssuerCache(issuerDid) {
|
|
621
|
+
this.instances.delete(issuerDid);
|
|
622
|
+
this.signerCache.delete(`signer:${issuerDid}`);
|
|
623
|
+
this.verifierCache.delete(`verifier:${issuerDid}`);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Get cache statistics
|
|
627
|
+
*/
|
|
628
|
+
static getCacheStats() {
|
|
629
|
+
return {
|
|
630
|
+
instanceCount: this.instances.size,
|
|
631
|
+
signerCount: this.signerCache.size,
|
|
632
|
+
verifierCount: this.verifierCache.size
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// src/agent/agent-did-manager.ts
|
|
638
|
+
var AgentDIDManager = class {
|
|
639
|
+
keyManager;
|
|
640
|
+
agentDIDMap = /* @__PURE__ */ new Map();
|
|
641
|
+
// agentId -> DID mapping
|
|
642
|
+
constructor(keyManager) {
|
|
643
|
+
this.keyManager = keyManager || new KeyManager();
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Generate a new DID for an AI Agent
|
|
647
|
+
*/
|
|
648
|
+
async generateAgentDID(agentId) {
|
|
649
|
+
const keyPair = await SDJwtClient.generateKeyPair();
|
|
650
|
+
const did = this.createDidJwk(keyPair.publicKey);
|
|
651
|
+
await this.keyManager.storeKey(did, keyPair.privateKey);
|
|
652
|
+
this.agentDIDMap.set(agentId, did);
|
|
653
|
+
await this.saveAgentDIDMapping(agentId, did);
|
|
654
|
+
return did;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Get DID for a specific agent
|
|
658
|
+
*/
|
|
659
|
+
async getAgentDID(agentId) {
|
|
660
|
+
if (this.agentDIDMap.has(agentId)) {
|
|
661
|
+
return this.agentDIDMap.get(agentId);
|
|
662
|
+
}
|
|
663
|
+
const did = await this.loadAgentDIDMapping(agentId);
|
|
664
|
+
if (did) {
|
|
665
|
+
this.agentDIDMap.set(agentId, did);
|
|
666
|
+
return did;
|
|
667
|
+
}
|
|
668
|
+
throw new Error(`No DID found for agent: ${agentId}`);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Check if agent has a DID
|
|
672
|
+
*/
|
|
673
|
+
async hasAgentDID(agentId) {
|
|
674
|
+
try {
|
|
675
|
+
await this.getAgentDID(agentId);
|
|
676
|
+
return true;
|
|
677
|
+
} catch {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Get agent's key pair
|
|
683
|
+
*/
|
|
684
|
+
async getAgentKeyPair(agentId) {
|
|
685
|
+
const did = await this.getAgentDID(agentId);
|
|
686
|
+
const privateKey = await this.keyManager.getKey(did);
|
|
687
|
+
if (!privateKey) {
|
|
688
|
+
throw new Error(`Private key not found for agent: ${agentId}`);
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
privateKey,
|
|
692
|
+
publicKey: this.extractPublicKey(privateKey)
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Delete agent DID and associated keys
|
|
697
|
+
*/
|
|
698
|
+
async deleteAgentDID(agentId) {
|
|
699
|
+
try {
|
|
700
|
+
const did = await this.getAgentDID(agentId);
|
|
701
|
+
await this.keyManager.deleteKey(did);
|
|
702
|
+
this.agentDIDMap.delete(agentId);
|
|
703
|
+
await this.deleteAgentDIDMapping(agentId);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
throw new Error(`Failed to delete agent DID: ${error}`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* List all agent DIDs
|
|
710
|
+
*/
|
|
711
|
+
async listAgentDIDs() {
|
|
712
|
+
const fs3 = await import("fs/promises");
|
|
713
|
+
const path3 = await import("path");
|
|
714
|
+
const os2 = await import("os");
|
|
715
|
+
const mappingDir = path3.join(os2.homedir(), ".vess", "agent-dids");
|
|
716
|
+
try {
|
|
717
|
+
const files = await fs3.readdir(mappingDir);
|
|
718
|
+
const results = [];
|
|
719
|
+
for (const file of files) {
|
|
720
|
+
if (file.endsWith(".did")) {
|
|
721
|
+
const agentId = file.replace(".did", "");
|
|
722
|
+
try {
|
|
723
|
+
const did = await this.getAgentDID(agentId);
|
|
724
|
+
results.push({ agentId, did });
|
|
725
|
+
} catch {
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return results;
|
|
730
|
+
} catch {
|
|
731
|
+
return [];
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Create did:jwk from public key
|
|
736
|
+
*/
|
|
737
|
+
createDidJwk(publicKey) {
|
|
738
|
+
const publicJwk = {
|
|
739
|
+
kty: publicKey.kty,
|
|
740
|
+
crv: publicKey.crv,
|
|
741
|
+
x: publicKey.x,
|
|
742
|
+
y: publicKey.y,
|
|
743
|
+
use: publicKey.use,
|
|
744
|
+
alg: publicKey.alg
|
|
745
|
+
};
|
|
746
|
+
const encoded = Buffer.from(JSON.stringify(publicJwk)).toString("base64url");
|
|
747
|
+
return `did:jwk:${encoded}`;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Extract public key from private key
|
|
751
|
+
*/
|
|
752
|
+
extractPublicKey(privateKey) {
|
|
753
|
+
const { d, key_ops, ...publicKey } = privateKey;
|
|
754
|
+
return {
|
|
755
|
+
...publicKey
|
|
756
|
+
// Remove key_ops for public key
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Save agent ID -> DID mapping to persistent storage
|
|
761
|
+
*/
|
|
762
|
+
async saveAgentDIDMapping(agentId, did) {
|
|
763
|
+
const fs3 = await import("fs/promises");
|
|
764
|
+
const path3 = await import("path");
|
|
765
|
+
const os2 = await import("os");
|
|
766
|
+
const mappingDir = path3.join(os2.homedir(), ".vess", "agent-dids");
|
|
767
|
+
await fs3.mkdir(mappingDir, { recursive: true });
|
|
768
|
+
const mappingFile = path3.join(mappingDir, `${agentId}.did`);
|
|
769
|
+
await fs3.writeFile(mappingFile, did, "utf-8");
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Load agent ID -> DID mapping from persistent storage
|
|
773
|
+
*/
|
|
774
|
+
async loadAgentDIDMapping(agentId) {
|
|
775
|
+
const fs3 = await import("fs/promises");
|
|
776
|
+
const path3 = await import("path");
|
|
777
|
+
const os2 = await import("os");
|
|
778
|
+
const mappingFile = path3.join(os2.homedir(), ".vess", "agent-dids", `${agentId}.did`);
|
|
779
|
+
try {
|
|
780
|
+
return await fs3.readFile(mappingFile, "utf-8");
|
|
781
|
+
} catch {
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Delete agent ID -> DID mapping from persistent storage
|
|
787
|
+
*/
|
|
788
|
+
async deleteAgentDIDMapping(agentId) {
|
|
789
|
+
const fs3 = await import("fs/promises");
|
|
790
|
+
const path3 = await import("path");
|
|
791
|
+
const os2 = await import("os");
|
|
792
|
+
const mappingFile = path3.join(os2.homedir(), ".vess", "agent-dids", `${agentId}.did`);
|
|
793
|
+
try {
|
|
794
|
+
await fs3.unlink(mappingFile);
|
|
795
|
+
} catch {
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
// src/did/agent.ts
|
|
801
|
+
var import_uuid2 = require("uuid");
|
|
802
|
+
var AgentManager = class {
|
|
803
|
+
keyManager;
|
|
804
|
+
agentDIDManager;
|
|
805
|
+
constructor(keyManager) {
|
|
806
|
+
this.keyManager = keyManager || new KeyManager();
|
|
807
|
+
this.agentDIDManager = new AgentDIDManager(this.keyManager);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Create a new AI agent with unique ID and DID
|
|
811
|
+
*/
|
|
812
|
+
async create(metadata) {
|
|
813
|
+
const agentId = (0, import_uuid2.v4)();
|
|
814
|
+
const agentDid = await this.agentDIDManager.generateAgentDID(agentId);
|
|
815
|
+
const didDocument = this.resolveDidJwkLocally(agentDid);
|
|
816
|
+
const agent = {
|
|
817
|
+
id: agentId,
|
|
818
|
+
did: agentDid,
|
|
819
|
+
didDocument,
|
|
820
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
821
|
+
metadata
|
|
822
|
+
};
|
|
823
|
+
try {
|
|
824
|
+
await this.registerDid(agent);
|
|
825
|
+
} catch (error) {
|
|
826
|
+
console.warn("Failed to register DID with API:", error);
|
|
827
|
+
}
|
|
828
|
+
return agent;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Get agent DID by agent ID
|
|
832
|
+
*/
|
|
833
|
+
async getAgentDID(agentId) {
|
|
834
|
+
return await this.agentDIDManager.getAgentDID(agentId);
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Get agent by ID
|
|
838
|
+
*/
|
|
839
|
+
async getAgent(agentId) {
|
|
840
|
+
const agentDid = await this.agentDIDManager.getAgentDID(agentId);
|
|
841
|
+
const didDocument = await this.resolve(agentDid);
|
|
842
|
+
return {
|
|
843
|
+
id: agentId,
|
|
844
|
+
did: agentDid,
|
|
845
|
+
didDocument,
|
|
846
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
847
|
+
// TODO: Store actual creation time
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Delete an agent and its DID
|
|
852
|
+
*/
|
|
853
|
+
async deleteAgent(agentId) {
|
|
854
|
+
await this.agentDIDManager.deleteAgentDID(agentId);
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Resolve a DID to get DID Document
|
|
858
|
+
*/
|
|
859
|
+
async resolve(did) {
|
|
860
|
+
if (did.startsWith("did:jwk:")) {
|
|
861
|
+
return this.resolveDidJwkLocally(did);
|
|
862
|
+
}
|
|
863
|
+
try {
|
|
864
|
+
const response = await fetch(getDidApiUrl(`/api/v1/did/${encodeURIComponent(did)}`), {
|
|
865
|
+
headers: getApiHeaders("did")
|
|
866
|
+
});
|
|
867
|
+
if (!response.ok) {
|
|
868
|
+
throw new Error(`Failed to resolve DID: ${response.statusText}`);
|
|
869
|
+
}
|
|
870
|
+
const data = await response.json();
|
|
871
|
+
return data.didDocument;
|
|
872
|
+
} catch (error) {
|
|
873
|
+
throw new Error(`Failed to resolve DID: ${error}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Export agent with private key (for backup)
|
|
878
|
+
*/
|
|
879
|
+
async export(did) {
|
|
880
|
+
const didDocument = await this.resolve(did);
|
|
881
|
+
const privateKey = await this.keyManager.getKey(did);
|
|
882
|
+
if (!privateKey) {
|
|
883
|
+
throw new Error(`Private key not found for DID: ${did}`);
|
|
884
|
+
}
|
|
885
|
+
const agent = {
|
|
886
|
+
did,
|
|
887
|
+
didDocument,
|
|
888
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
889
|
+
};
|
|
890
|
+
return { agent, privateKey };
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Import agent from backup
|
|
894
|
+
*/
|
|
895
|
+
async import(agent, privateKey) {
|
|
896
|
+
await this.keyManager.storeKey(agent.did, privateKey);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* List all locally stored agents
|
|
900
|
+
*/
|
|
901
|
+
async list() {
|
|
902
|
+
const agentDIDs = await this.agentDIDManager.listAgentDIDs();
|
|
903
|
+
const agents = [];
|
|
904
|
+
for (const { agentId, did } of agentDIDs) {
|
|
905
|
+
try {
|
|
906
|
+
const didDocument = await this.resolve(did);
|
|
907
|
+
agents.push({
|
|
908
|
+
id: agentId,
|
|
909
|
+
did,
|
|
910
|
+
didDocument,
|
|
911
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
912
|
+
// TODO: Store actual creation time
|
|
913
|
+
});
|
|
914
|
+
} catch (error) {
|
|
915
|
+
console.warn(`Failed to resolve Agent DID ${did}:`, error);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return agents;
|
|
919
|
+
}
|
|
920
|
+
createDidDocument(did, publicKey) {
|
|
921
|
+
const verificationMethodId = `${did}#0`;
|
|
922
|
+
return {
|
|
923
|
+
"@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1"],
|
|
924
|
+
id: did,
|
|
925
|
+
verificationMethod: [
|
|
926
|
+
{
|
|
927
|
+
id: verificationMethodId,
|
|
928
|
+
type: "JsonWebKey2020",
|
|
929
|
+
controller: did,
|
|
930
|
+
publicKeyJwk: publicKey
|
|
931
|
+
}
|
|
932
|
+
],
|
|
933
|
+
authentication: [verificationMethodId],
|
|
934
|
+
assertionMethod: [verificationMethodId],
|
|
935
|
+
capabilityInvocation: [verificationMethodId],
|
|
936
|
+
capabilityDelegation: [verificationMethodId]
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
resolveDidJwkLocally(did) {
|
|
940
|
+
const encoded = did.replace("did:jwk:", "");
|
|
941
|
+
const publicJwk = JSON.parse(Buffer.from(encoded, "base64url").toString());
|
|
942
|
+
return this.createDidDocument(did, publicJwk);
|
|
943
|
+
}
|
|
944
|
+
async registerDid(_agent) {
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
// src/identity/user-identity-manager.ts
|
|
949
|
+
var UserIdentityManager = class {
|
|
950
|
+
keyManager;
|
|
951
|
+
currentUserDID = null;
|
|
952
|
+
constructor(keyManager) {
|
|
953
|
+
this.keyManager = keyManager || new KeyManager();
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Get or create current user DID
|
|
957
|
+
* This represents the user who will be the issuer of VCs
|
|
958
|
+
*/
|
|
959
|
+
async getCurrentUserDID() {
|
|
960
|
+
if (this.currentUserDID) {
|
|
961
|
+
return this.currentUserDID;
|
|
962
|
+
}
|
|
963
|
+
const existingDID = await this.loadUserDID();
|
|
964
|
+
if (existingDID) {
|
|
965
|
+
this.currentUserDID = existingDID;
|
|
966
|
+
return existingDID;
|
|
967
|
+
}
|
|
968
|
+
return await this.createUserDID();
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Create a new user DID (for issuing VCs)
|
|
972
|
+
*/
|
|
973
|
+
async createUserDID() {
|
|
974
|
+
const keyPair = await SDJwtClient.generateKeyPair();
|
|
975
|
+
const did = this.createDidJwk(keyPair.publicKey);
|
|
976
|
+
await this.keyManager.storeKey(did, keyPair.privateKey);
|
|
977
|
+
await this.saveUserDID(did);
|
|
978
|
+
this.currentUserDID = did;
|
|
979
|
+
return did;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Get user's key pair
|
|
983
|
+
*/
|
|
984
|
+
async getUserKeyPair() {
|
|
985
|
+
const did = await this.getCurrentUserDID();
|
|
986
|
+
const privateKey = await this.keyManager.getKey(did);
|
|
987
|
+
if (!privateKey) {
|
|
988
|
+
throw new Error("User private key not found");
|
|
989
|
+
}
|
|
990
|
+
return {
|
|
991
|
+
privateKey,
|
|
992
|
+
publicKey: this.extractPublicKey(privateKey)
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Resolve user DID to DID Document
|
|
997
|
+
*/
|
|
998
|
+
async resolveUserDID(did) {
|
|
999
|
+
const userDid = did || await this.getCurrentUserDID();
|
|
1000
|
+
if (!userDid.startsWith("did:jwk:")) {
|
|
1001
|
+
throw new Error("Only did:jwk supported for user identity");
|
|
1002
|
+
}
|
|
1003
|
+
return this.resolveDidJwkLocally(userDid);
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Export user identity for backup
|
|
1007
|
+
*/
|
|
1008
|
+
async exportUserIdentity() {
|
|
1009
|
+
const did = await this.getCurrentUserDID();
|
|
1010
|
+
const privateKey = await this.keyManager.getKey(did);
|
|
1011
|
+
const didDocument = await this.resolveUserDID(did);
|
|
1012
|
+
if (!privateKey) {
|
|
1013
|
+
throw new Error("User private key not found");
|
|
1014
|
+
}
|
|
1015
|
+
return { did, privateKey, didDocument };
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Import user identity from backup
|
|
1019
|
+
*/
|
|
1020
|
+
async importUserIdentity(backup) {
|
|
1021
|
+
await this.keyManager.storeKey(backup.did, backup.privateKey);
|
|
1022
|
+
await this.saveUserDID(backup.did);
|
|
1023
|
+
this.currentUserDID = backup.did;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Reset user identity (create new DID)
|
|
1027
|
+
*/
|
|
1028
|
+
async resetUserIdentity() {
|
|
1029
|
+
this.currentUserDID = null;
|
|
1030
|
+
await this.clearUserDID();
|
|
1031
|
+
return await this.createUserDID();
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Create did:jwk from public key
|
|
1035
|
+
*/
|
|
1036
|
+
createDidJwk(publicKey) {
|
|
1037
|
+
const publicJwk = {
|
|
1038
|
+
kty: publicKey.kty,
|
|
1039
|
+
crv: publicKey.crv,
|
|
1040
|
+
x: publicKey.x,
|
|
1041
|
+
y: publicKey.y,
|
|
1042
|
+
use: publicKey.use,
|
|
1043
|
+
alg: publicKey.alg
|
|
1044
|
+
};
|
|
1045
|
+
const encoded = Buffer.from(JSON.stringify(publicJwk)).toString("base64url");
|
|
1046
|
+
return `did:jwk:${encoded}`;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Extract public key from private key
|
|
1050
|
+
*/
|
|
1051
|
+
extractPublicKey(privateKey) {
|
|
1052
|
+
const { d, key_ops, ...publicKey } = privateKey;
|
|
1053
|
+
return publicKey;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Resolve did:jwk locally
|
|
1057
|
+
*/
|
|
1058
|
+
resolveDidJwkLocally(did) {
|
|
1059
|
+
const encoded = did.replace("did:jwk:", "");
|
|
1060
|
+
const publicJwk = JSON.parse(Buffer.from(encoded, "base64url").toString());
|
|
1061
|
+
return this.createDidDocument(did, publicJwk);
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Create DID Document
|
|
1065
|
+
*/
|
|
1066
|
+
createDidDocument(did, publicKey) {
|
|
1067
|
+
const verificationMethodId = `${did}#0`;
|
|
1068
|
+
return {
|
|
1069
|
+
"@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1"],
|
|
1070
|
+
id: did,
|
|
1071
|
+
verificationMethod: [
|
|
1072
|
+
{
|
|
1073
|
+
id: verificationMethodId,
|
|
1074
|
+
type: "JsonWebKey2020",
|
|
1075
|
+
controller: did,
|
|
1076
|
+
publicKeyJwk: publicKey
|
|
1077
|
+
}
|
|
1078
|
+
],
|
|
1079
|
+
authentication: [verificationMethodId],
|
|
1080
|
+
assertionMethod: [verificationMethodId],
|
|
1081
|
+
capabilityInvocation: [verificationMethodId],
|
|
1082
|
+
capabilityDelegation: [verificationMethodId]
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Save current user DID to persistent storage
|
|
1087
|
+
*/
|
|
1088
|
+
async saveUserDID(did) {
|
|
1089
|
+
const fs3 = await import("fs/promises");
|
|
1090
|
+
const path3 = await import("path");
|
|
1091
|
+
const os2 = await import("os");
|
|
1092
|
+
const configDir = path3.join(os2.homedir(), ".vess");
|
|
1093
|
+
await fs3.mkdir(configDir, { recursive: true });
|
|
1094
|
+
const userDIDFile = path3.join(configDir, "user-did.txt");
|
|
1095
|
+
await fs3.writeFile(userDIDFile, did, "utf-8");
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Load current user DID from persistent storage
|
|
1099
|
+
*/
|
|
1100
|
+
async loadUserDID() {
|
|
1101
|
+
const fs3 = await import("fs/promises");
|
|
1102
|
+
const path3 = await import("path");
|
|
1103
|
+
const os2 = await import("os");
|
|
1104
|
+
const userDIDFile = path3.join(os2.homedir(), ".vess", "user-did.txt");
|
|
1105
|
+
try {
|
|
1106
|
+
return await fs3.readFile(userDIDFile, "utf-8");
|
|
1107
|
+
} catch {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Clear saved user DID
|
|
1113
|
+
*/
|
|
1114
|
+
async clearUserDID() {
|
|
1115
|
+
const fs3 = await import("fs/promises");
|
|
1116
|
+
const path3 = await import("path");
|
|
1117
|
+
const os2 = await import("os");
|
|
1118
|
+
const userDIDFile = path3.join(os2.homedir(), ".vess", "user-did.txt");
|
|
1119
|
+
try {
|
|
1120
|
+
await fs3.unlink(userDIDFile);
|
|
1121
|
+
} catch {
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
// src/vc/vc-manager.ts
|
|
1127
|
+
var VCManager = class {
|
|
1128
|
+
keyManager;
|
|
1129
|
+
templates = /* @__PURE__ */ new Map();
|
|
1130
|
+
agentManager;
|
|
1131
|
+
userIdentityManager;
|
|
1132
|
+
constructor(keyManager, agentManager, userIdentityManager) {
|
|
1133
|
+
this.keyManager = keyManager || new KeyManager();
|
|
1134
|
+
this.agentManager = agentManager || new AgentManager(this.keyManager);
|
|
1135
|
+
this.userIdentityManager = userIdentityManager || new UserIdentityManager(this.keyManager);
|
|
1136
|
+
this.registerDefaultTemplates();
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Get fields that should be selectively disclosable based on VC type
|
|
1140
|
+
*/
|
|
1141
|
+
getSelectivelyDisclosableFields(template) {
|
|
1142
|
+
switch (template) {
|
|
1143
|
+
case "ToolPermissionVC":
|
|
1144
|
+
return ["tool", "action", "scope", "conditions"];
|
|
1145
|
+
case "DataAccessVC":
|
|
1146
|
+
return ["resource", "actions", "scope", "conditions"];
|
|
1147
|
+
case "OrganizationVC":
|
|
1148
|
+
return ["organizationId", "employeeId", "department", "role", "permissions"];
|
|
1149
|
+
default:
|
|
1150
|
+
return [];
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Issue a Verifiable Credential as SD-JWT VC
|
|
1155
|
+
* Enhanced to support User/Agent DID separation
|
|
1156
|
+
*/
|
|
1157
|
+
async issue(template, claims, options) {
|
|
1158
|
+
const vcTemplate = this.templates.get(template);
|
|
1159
|
+
if (!vcTemplate) {
|
|
1160
|
+
throw new Error(`Unknown VC template: ${template}`);
|
|
1161
|
+
}
|
|
1162
|
+
const issuerDid = options.issuerDid || await this.userIdentityManager.getCurrentUserDID();
|
|
1163
|
+
let subjectDid;
|
|
1164
|
+
if (options.agentId) {
|
|
1165
|
+
subjectDid = await this.agentManager.getAgentDID(options.agentId);
|
|
1166
|
+
} else if (options.subjectDid) {
|
|
1167
|
+
subjectDid = options.subjectDid;
|
|
1168
|
+
} else {
|
|
1169
|
+
subjectDid = issuerDid;
|
|
1170
|
+
}
|
|
1171
|
+
const now = /* @__PURE__ */ new Date();
|
|
1172
|
+
const iat = Math.floor(now.getTime() / 1e3);
|
|
1173
|
+
const credentialSubject = {
|
|
1174
|
+
id: subjectDid,
|
|
1175
|
+
...claims
|
|
1176
|
+
};
|
|
1177
|
+
if (vcTemplate.defaults) {
|
|
1178
|
+
Object.assign(credentialSubject, vcTemplate.defaults);
|
|
1179
|
+
}
|
|
1180
|
+
const vcPayload = {
|
|
1181
|
+
// SD-JWT VC specific fields
|
|
1182
|
+
vct: vcTemplate.type,
|
|
1183
|
+
iss: issuerDid,
|
|
1184
|
+
iat,
|
|
1185
|
+
cnf: {
|
|
1186
|
+
// Confirmation method - binding to subject's key
|
|
1187
|
+
jwk: await this.getSubjectPublicKey(subjectDid)
|
|
1188
|
+
},
|
|
1189
|
+
// Standard VC fields
|
|
1190
|
+
credentialSubject
|
|
1191
|
+
};
|
|
1192
|
+
if (options.expiresIn) {
|
|
1193
|
+
const expDate = this.calculateExpirationDate(options.expiresIn);
|
|
1194
|
+
vcPayload.exp = Math.floor(expDate.getTime() / 1e3);
|
|
1195
|
+
}
|
|
1196
|
+
const vc = {
|
|
1197
|
+
...vcPayload,
|
|
1198
|
+
"@context": ["https://www.w3.org/ns/credentials/v2"],
|
|
1199
|
+
issuer: issuerDid,
|
|
1200
|
+
validFrom: now.toISOString()
|
|
1201
|
+
};
|
|
1202
|
+
if (vcTemplate.validate && !vcTemplate.validate(vc)) {
|
|
1203
|
+
throw new Error("VC validation failed");
|
|
1204
|
+
}
|
|
1205
|
+
SDJwtClient.setKeyManager(this.keyManager);
|
|
1206
|
+
const sdjwtInstance = await SDJwtClient.getIssuerInstance(issuerDid);
|
|
1207
|
+
const selectivelyDisclosableFields = this.getSelectivelyDisclosableFields(template);
|
|
1208
|
+
const credentialSubjectFrame = SDJwtClient.createDisclosureFrame(
|
|
1209
|
+
credentialSubject,
|
|
1210
|
+
selectivelyDisclosableFields
|
|
1211
|
+
);
|
|
1212
|
+
const disclosureFrame = {
|
|
1213
|
+
credentialSubject: credentialSubjectFrame
|
|
1214
|
+
};
|
|
1215
|
+
const sdjwtVc = await sdjwtInstance.issue(
|
|
1216
|
+
vcPayload,
|
|
1217
|
+
disclosureFrame
|
|
1218
|
+
);
|
|
1219
|
+
return sdjwtVc;
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Get subject's public key for cnf claim
|
|
1223
|
+
*/
|
|
1224
|
+
async getSubjectPublicKey(subjectDid) {
|
|
1225
|
+
if (!subjectDid.startsWith("did:jwk:")) {
|
|
1226
|
+
throw new Error(`Unsupported DID method. Expected did:jwk, got: ${subjectDid}`);
|
|
1227
|
+
}
|
|
1228
|
+
try {
|
|
1229
|
+
const jwkEncoded = subjectDid.replace("did:jwk:", "");
|
|
1230
|
+
const jwkJson = Buffer.from(jwkEncoded, "base64url").toString("utf-8");
|
|
1231
|
+
const jwk = JSON.parse(jwkJson);
|
|
1232
|
+
const { d, key_ops, ...publicJwk } = jwk;
|
|
1233
|
+
const publicKeyForCnf = {
|
|
1234
|
+
...publicJwk
|
|
1235
|
+
// Remove key_ops entirely for cnf claim as it's not needed there
|
|
1236
|
+
};
|
|
1237
|
+
return publicKeyForCnf;
|
|
1238
|
+
} catch (error) {
|
|
1239
|
+
throw new Error(
|
|
1240
|
+
`Failed to extract JWK from did:jwk: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Issue using existing Issuer API (OID4VCI)
|
|
1246
|
+
*/
|
|
1247
|
+
async issueViaAPI(credentialType, claims, options) {
|
|
1248
|
+
const offerResponse = await fetch(getIssuerApiUrl("/api/v1/credential/offer"), {
|
|
1249
|
+
method: "POST",
|
|
1250
|
+
headers: getApiHeaders("issuer"),
|
|
1251
|
+
body: JSON.stringify({
|
|
1252
|
+
credentialType,
|
|
1253
|
+
claims,
|
|
1254
|
+
subjectDid: options.subjectDid
|
|
1255
|
+
})
|
|
1256
|
+
});
|
|
1257
|
+
if (!offerResponse.ok) {
|
|
1258
|
+
throw new Error(`Failed to get credential offer: ${offerResponse.statusText}`);
|
|
1259
|
+
}
|
|
1260
|
+
const offer = await offerResponse.json();
|
|
1261
|
+
const acquireResponse = await fetch(getIssuerApiUrl("/api/v1/credential/acquire"), {
|
|
1262
|
+
method: "POST",
|
|
1263
|
+
headers: getApiHeaders("issuer"),
|
|
1264
|
+
body: JSON.stringify({
|
|
1265
|
+
offerId: offer.id,
|
|
1266
|
+
holderDid: options.subjectDid
|
|
1267
|
+
})
|
|
1268
|
+
});
|
|
1269
|
+
if (!acquireResponse.ok) {
|
|
1270
|
+
throw new Error(`Failed to acquire credential: ${acquireResponse.statusText}`);
|
|
1271
|
+
}
|
|
1272
|
+
const credential = await acquireResponse.json();
|
|
1273
|
+
return credential.jwt;
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Verify a SD-JWT VC
|
|
1277
|
+
*/
|
|
1278
|
+
async verify(sdjwtVc, options) {
|
|
1279
|
+
const parts = sdjwtVc.split("~");
|
|
1280
|
+
const jwt = parts[0];
|
|
1281
|
+
const jwtParts = jwt.split(".");
|
|
1282
|
+
const payload = JSON.parse(Buffer.from(jwtParts[1], "base64url").toString());
|
|
1283
|
+
const issuerDid = payload.iss;
|
|
1284
|
+
if (!issuerDid) {
|
|
1285
|
+
throw new Error("Issuer DID not found in SD-JWT VC");
|
|
1286
|
+
}
|
|
1287
|
+
if (options?.expectedIssuer && issuerDid !== options.expectedIssuer) {
|
|
1288
|
+
throw new Error(`Issuer mismatch: expected ${options.expectedIssuer}, got ${issuerDid}`);
|
|
1289
|
+
}
|
|
1290
|
+
SDJwtClient.setKeyManager(this.keyManager);
|
|
1291
|
+
const sdjwtInstance = await SDJwtClient.getIssuerInstance(issuerDid);
|
|
1292
|
+
const { payload: verifiedPayload } = await sdjwtInstance.verify(
|
|
1293
|
+
sdjwtVc,
|
|
1294
|
+
options?.requiredClaims ? { requiredClaimKeys: options?.requiredClaims } : void 0
|
|
1295
|
+
);
|
|
1296
|
+
const disclosures = await sdjwtInstance.getClaims(sdjwtVc);
|
|
1297
|
+
const subjectId = verifiedPayload?.credentialSubject?.id || verifiedPayload.sub;
|
|
1298
|
+
if (options?.expectedSubject) {
|
|
1299
|
+
if (subjectId !== options.expectedSubject) {
|
|
1300
|
+
throw new Error(`Subject mismatch: expected ${options.expectedSubject}, got ${subjectId}`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return {
|
|
1304
|
+
payload: verifiedPayload,
|
|
1305
|
+
disclosures,
|
|
1306
|
+
issuer: issuerDid,
|
|
1307
|
+
subject: subjectId,
|
|
1308
|
+
issuedAt: verifiedPayload?.iat && new Date(verifiedPayload.iat * 1e3),
|
|
1309
|
+
expiresAt: verifiedPayload.exp ? new Date(verifiedPayload.exp * 1e3) : null
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Revoke a Verifiable Credential
|
|
1314
|
+
*/
|
|
1315
|
+
async revoke(_vcId, _issuerDid) {
|
|
1316
|
+
throw new Error("VC revocation not yet implemented");
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Register a custom VC template
|
|
1320
|
+
*/
|
|
1321
|
+
registerTemplate(template) {
|
|
1322
|
+
this.templates.set(template.name, template);
|
|
1323
|
+
}
|
|
1324
|
+
registerDefaultTemplates() {
|
|
1325
|
+
this.templates.set("ToolPermissionVC", {
|
|
1326
|
+
type: "ToolPermissionVC",
|
|
1327
|
+
name: "ToolPermissionVC",
|
|
1328
|
+
description: "Permission to use a specific tool",
|
|
1329
|
+
validate: (vc) => {
|
|
1330
|
+
const toolVc = vc;
|
|
1331
|
+
return !!(toolVc.credentialSubject.tool && toolVc.credentialSubject.aud);
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
this.templates.set("DataAccessVC", {
|
|
1335
|
+
type: "DataAccessVC",
|
|
1336
|
+
name: "DataAccessVC",
|
|
1337
|
+
description: "Permission to access data resources",
|
|
1338
|
+
validate: (vc) => {
|
|
1339
|
+
const dataVc = vc;
|
|
1340
|
+
return !!(dataVc.credentialSubject.resource && dataVc.credentialSubject.actions && dataVc.credentialSubject.actions.length > 0);
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
calculateExpirationDate(expiresIn) {
|
|
1345
|
+
const now = /* @__PURE__ */ new Date();
|
|
1346
|
+
const match = expiresIn.match(/^(\d+)([hdm])$/);
|
|
1347
|
+
if (!match) {
|
|
1348
|
+
throw new Error(`Invalid expiresIn format: ${expiresIn}`);
|
|
1349
|
+
}
|
|
1350
|
+
const value = parseInt(match[1]);
|
|
1351
|
+
const unit = match[2];
|
|
1352
|
+
switch (unit) {
|
|
1353
|
+
case "h":
|
|
1354
|
+
now.setHours(now.getHours() + value);
|
|
1355
|
+
break;
|
|
1356
|
+
case "d":
|
|
1357
|
+
now.setDate(now.getDate() + value);
|
|
1358
|
+
break;
|
|
1359
|
+
case "m":
|
|
1360
|
+
now.setMinutes(now.getMinutes() + value);
|
|
1361
|
+
break;
|
|
1362
|
+
default:
|
|
1363
|
+
throw new Error(`Unknown time unit: ${unit}`);
|
|
1364
|
+
}
|
|
1365
|
+
return now;
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
// src/vp/vp-manager.ts
|
|
1370
|
+
var import_crypto_nodejs2 = require("@sd-jwt/crypto-nodejs");
|
|
1371
|
+
var VPManager = class {
|
|
1372
|
+
keyManager;
|
|
1373
|
+
constructor(keyManager) {
|
|
1374
|
+
this.keyManager = keyManager || new KeyManager();
|
|
1375
|
+
SDJwtClient.setKeyManager(this.keyManager);
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Create a SD-JWT presentation using the present() method
|
|
1379
|
+
* This properly binds the holder's key to the SD-JWT VC
|
|
1380
|
+
*/
|
|
1381
|
+
async create(vcs, options) {
|
|
1382
|
+
if (vcs.length === 0) {
|
|
1383
|
+
throw new Error("At least one SD-JWT VC is required for presentation");
|
|
1384
|
+
}
|
|
1385
|
+
const sdJwtInstance = await SDJwtClient.getHolderInstance(options.holderDid);
|
|
1386
|
+
const sdJwtVC = vcs[0];
|
|
1387
|
+
try {
|
|
1388
|
+
const decodedVC = await sdJwtInstance.decode(sdJwtVC);
|
|
1389
|
+
const presentableKeys = await decodedVC.presentableKeys(import_crypto_nodejs2.digest);
|
|
1390
|
+
const presentationFrame = {};
|
|
1391
|
+
presentableKeys.forEach((key) => {
|
|
1392
|
+
presentationFrame[key] = true;
|
|
1393
|
+
});
|
|
1394
|
+
const kbJwtPayload = {
|
|
1395
|
+
aud: options.domain,
|
|
1396
|
+
nonce: options.challenge,
|
|
1397
|
+
iat: Math.floor(Date.now() / 1e3)
|
|
1398
|
+
};
|
|
1399
|
+
const presentation = await sdJwtInstance.present(sdJwtVC, presentationFrame, {
|
|
1400
|
+
kb: { payload: kbJwtPayload }
|
|
1401
|
+
});
|
|
1402
|
+
return presentation;
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
console.error("ERROR: Error creating SD-JWT presentation:", error);
|
|
1405
|
+
throw new Error(`Failed to create SD-JWT presentation: ${error.message}`);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Verify a Verifiable Presentation
|
|
1410
|
+
*/
|
|
1411
|
+
async verify(vpJwt, options) {
|
|
1412
|
+
const response = await fetch(getVerifierApiUrl("/api/v1/vp/verify"), {
|
|
1413
|
+
method: "POST",
|
|
1414
|
+
headers: getApiHeaders("verifier"),
|
|
1415
|
+
body: JSON.stringify({
|
|
1416
|
+
vp: vpJwt,
|
|
1417
|
+
challenge: options.expectedChallenge,
|
|
1418
|
+
domain: options.expectedDomain
|
|
1419
|
+
})
|
|
1420
|
+
});
|
|
1421
|
+
if (!response.ok) {
|
|
1422
|
+
throw new Error(`VP verification failed: ${response.statusText}`);
|
|
1423
|
+
}
|
|
1424
|
+
const result = await response.json();
|
|
1425
|
+
if (!result?.valid) {
|
|
1426
|
+
throw new Error(`VP verification failed: ${result?.error}`);
|
|
1427
|
+
}
|
|
1428
|
+
const vp = result?.verifiablePresentation;
|
|
1429
|
+
if (options.expectedHolder && vp.holder !== options.expectedHolder) {
|
|
1430
|
+
throw new Error(`Holder mismatch. Expected: ${options.expectedHolder}, Got: ${vp.holder}`);
|
|
1431
|
+
}
|
|
1432
|
+
if (vp.proof?.challenge !== options.expectedChallenge) {
|
|
1433
|
+
throw new Error("Challenge mismatch");
|
|
1434
|
+
}
|
|
1435
|
+
if (vp.proof?.domain !== options.expectedDomain) {
|
|
1436
|
+
throw new Error("Domain mismatch");
|
|
1437
|
+
}
|
|
1438
|
+
return vp;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Create a VP request
|
|
1442
|
+
*/
|
|
1443
|
+
createRequest(domain, query) {
|
|
1444
|
+
return {
|
|
1445
|
+
challenge: generateNonce(),
|
|
1446
|
+
domain,
|
|
1447
|
+
query
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Submit VP to a verifier
|
|
1452
|
+
*/
|
|
1453
|
+
async submit(vpJwt, verifierEndpoint) {
|
|
1454
|
+
const response = await fetch(verifierEndpoint, {
|
|
1455
|
+
method: "POST",
|
|
1456
|
+
headers: {
|
|
1457
|
+
"Content-Type": "application/json"
|
|
1458
|
+
},
|
|
1459
|
+
body: JSON.stringify({ vp: vpJwt })
|
|
1460
|
+
});
|
|
1461
|
+
if (!response.ok) {
|
|
1462
|
+
throw new Error(`Failed to submit VP: ${response.statusText}`);
|
|
1463
|
+
}
|
|
1464
|
+
return response.json();
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
|
|
1468
|
+
// src/tool/tool-manager.ts
|
|
1469
|
+
var ToolManager = class {
|
|
1470
|
+
vpManager;
|
|
1471
|
+
tools = /* @__PURE__ */ new Map();
|
|
1472
|
+
proxyApiUrl;
|
|
1473
|
+
constructor(vpManager) {
|
|
1474
|
+
this.vpManager = vpManager || new VPManager();
|
|
1475
|
+
const config = getConfig();
|
|
1476
|
+
this.proxyApiUrl = config.proxyApi?.baseUrl || "http://localhost:3000";
|
|
1477
|
+
this.registerDefaultTools();
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Invoke a tool action with VC authorization
|
|
1481
|
+
*/
|
|
1482
|
+
async invoke(tool, action, params, options) {
|
|
1483
|
+
const toolDef = this.tools.get(tool);
|
|
1484
|
+
if (!toolDef) {
|
|
1485
|
+
throw new Error(`Unknown tool: ${tool}`);
|
|
1486
|
+
}
|
|
1487
|
+
const actionDef = toolDef.actions.find((a) => a.name === action);
|
|
1488
|
+
if (!actionDef) {
|
|
1489
|
+
throw new Error(`Unknown action ${action} for tool ${tool}`);
|
|
1490
|
+
}
|
|
1491
|
+
const domain = new URL(this.proxyApiUrl).hostname;
|
|
1492
|
+
const challenge = this.generateChallenge();
|
|
1493
|
+
const vpJwt = await this.vpManager.create(options.vcs, {
|
|
1494
|
+
holderDid: options.holderDid,
|
|
1495
|
+
challenge,
|
|
1496
|
+
domain,
|
|
1497
|
+
purpose: "invocation"
|
|
1498
|
+
});
|
|
1499
|
+
if (this.proxyApiUrl === "mock://demo") {
|
|
1500
|
+
const mockResponse = {
|
|
1501
|
+
success: true,
|
|
1502
|
+
data: {
|
|
1503
|
+
message: "Mock Slack response - message posted successfully!",
|
|
1504
|
+
channel: params.channel || "#general",
|
|
1505
|
+
text: params.text || "Hello from AIdentity!",
|
|
1506
|
+
ts: Date.now().toString(),
|
|
1507
|
+
ok: true
|
|
1508
|
+
},
|
|
1509
|
+
metadata: {
|
|
1510
|
+
tool,
|
|
1511
|
+
action,
|
|
1512
|
+
holder: options.holderDid,
|
|
1513
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1514
|
+
mock: true
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1518
|
+
return mockResponse;
|
|
1519
|
+
}
|
|
1520
|
+
const headers = {
|
|
1521
|
+
"Content-Type": "application/json",
|
|
1522
|
+
Authorization: `Bearer ${vpJwt}`,
|
|
1523
|
+
"X-Holder-DID": options.holderDid,
|
|
1524
|
+
"X-Auth-Challenge": challenge
|
|
1525
|
+
};
|
|
1526
|
+
if (!vpJwt.includes("~")) {
|
|
1527
|
+
headers["X-VCs"] = JSON.stringify(options.vcs);
|
|
1528
|
+
}
|
|
1529
|
+
const response = await fetch(`${this.proxyApiUrl}/api/v1/tool/invoke`, {
|
|
1530
|
+
method: "POST",
|
|
1531
|
+
headers,
|
|
1532
|
+
body: JSON.stringify({
|
|
1533
|
+
tool,
|
|
1534
|
+
action,
|
|
1535
|
+
params,
|
|
1536
|
+
challenge
|
|
1537
|
+
})
|
|
1538
|
+
});
|
|
1539
|
+
if (!response.ok) {
|
|
1540
|
+
const contentType = response.headers.get("content-type");
|
|
1541
|
+
if (contentType && contentType.includes("application/json")) {
|
|
1542
|
+
try {
|
|
1543
|
+
const errorResponse = await response.json();
|
|
1544
|
+
if (errorResponse.success === false) {
|
|
1545
|
+
return errorResponse;
|
|
1546
|
+
}
|
|
1547
|
+
return {
|
|
1548
|
+
success: false,
|
|
1549
|
+
error: errorResponse.message || errorResponse.error || "Tool invocation failed",
|
|
1550
|
+
metadata: errorResponse.metadata
|
|
1551
|
+
};
|
|
1552
|
+
} catch (parseError) {
|
|
1553
|
+
const error = await response.text();
|
|
1554
|
+
return {
|
|
1555
|
+
success: false,
|
|
1556
|
+
error: `Tool invocation failed: ${error}`
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
} else {
|
|
1560
|
+
const error = await response.text();
|
|
1561
|
+
return {
|
|
1562
|
+
success: false,
|
|
1563
|
+
error: `Tool invocation failed: ${error}`
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
const result = await response.json();
|
|
1568
|
+
return result;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* List available tools
|
|
1572
|
+
*/
|
|
1573
|
+
list() {
|
|
1574
|
+
return Array.from(this.tools.values());
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Get a specific tool definition
|
|
1578
|
+
*/
|
|
1579
|
+
getTool(name) {
|
|
1580
|
+
return this.tools.get(name);
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Register a custom tool
|
|
1584
|
+
*/
|
|
1585
|
+
registerTool(tool) {
|
|
1586
|
+
this.tools.set(tool.name, tool);
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Check if VCs authorize a tool action
|
|
1590
|
+
*/
|
|
1591
|
+
async checkAuthorization(vcs, tool, action, resourceScope) {
|
|
1592
|
+
for (const vcJwt of vcs) {
|
|
1593
|
+
try {
|
|
1594
|
+
const parts = vcJwt.split(".");
|
|
1595
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
1596
|
+
if (payload.credentialSubject?.tool === `${tool}.${action}`) {
|
|
1597
|
+
if (resourceScope) {
|
|
1598
|
+
const vcScope = payload.credentialSubject.resourceScope;
|
|
1599
|
+
if (!this.matchScope(vcScope, resourceScope)) {
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return true;
|
|
1604
|
+
}
|
|
1605
|
+
} catch {
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
matchScope(vcScope, requiredScope) {
|
|
1612
|
+
for (const [key, value] of Object.entries(requiredScope)) {
|
|
1613
|
+
if (vcScope[key] !== value && vcScope[key] !== "*") {
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return true;
|
|
1618
|
+
}
|
|
1619
|
+
generateChallenge() {
|
|
1620
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
1621
|
+
}
|
|
1622
|
+
registerDefaultTools() {
|
|
1623
|
+
this.tools.set("slack", {
|
|
1624
|
+
name: "slack",
|
|
1625
|
+
description: "Slack workspace operations",
|
|
1626
|
+
actions: [
|
|
1627
|
+
{
|
|
1628
|
+
name: "postMessage",
|
|
1629
|
+
description: "Post a message to a channel",
|
|
1630
|
+
parameters: {
|
|
1631
|
+
channel: "string",
|
|
1632
|
+
text: "string",
|
|
1633
|
+
thread_ts: "string?"
|
|
1634
|
+
}
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
name: "getChannels",
|
|
1638
|
+
description: "List channels",
|
|
1639
|
+
parameters: {}
|
|
1640
|
+
},
|
|
1641
|
+
{
|
|
1642
|
+
name: "getUserInfo",
|
|
1643
|
+
description: "Get user information",
|
|
1644
|
+
parameters: {
|
|
1645
|
+
userId: "string"
|
|
1646
|
+
}
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
name: "getChannelHistory",
|
|
1650
|
+
description: "Get channel history",
|
|
1651
|
+
parameters: {
|
|
1652
|
+
channel: "string",
|
|
1653
|
+
latest: "string?",
|
|
1654
|
+
oldest: "string?",
|
|
1655
|
+
limit: "number?",
|
|
1656
|
+
inclusive: "boolean?"
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
]
|
|
1660
|
+
});
|
|
1661
|
+
this.tools.set("github", {
|
|
1662
|
+
name: "github",
|
|
1663
|
+
description: "GitHub repository operations",
|
|
1664
|
+
actions: [
|
|
1665
|
+
{
|
|
1666
|
+
name: "createIssue",
|
|
1667
|
+
description: "Create a new issue",
|
|
1668
|
+
parameters: {
|
|
1669
|
+
owner: "string",
|
|
1670
|
+
repo: "string",
|
|
1671
|
+
title: "string",
|
|
1672
|
+
body: "string",
|
|
1673
|
+
labels: "string[]?"
|
|
1674
|
+
}
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
name: "listIssues",
|
|
1678
|
+
description: "List repository issues",
|
|
1679
|
+
parameters: {
|
|
1680
|
+
owner: "string",
|
|
1681
|
+
repo: "string",
|
|
1682
|
+
state: "string?"
|
|
1683
|
+
}
|
|
1684
|
+
},
|
|
1685
|
+
{
|
|
1686
|
+
name: "getRepo",
|
|
1687
|
+
description: "Get repository information",
|
|
1688
|
+
parameters: {
|
|
1689
|
+
owner: "string",
|
|
1690
|
+
repo: "string"
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
]
|
|
1694
|
+
});
|
|
1695
|
+
this.tools.set("gmail", {
|
|
1696
|
+
name: "gmail",
|
|
1697
|
+
description: "Gmail operations",
|
|
1698
|
+
actions: [
|
|
1699
|
+
{
|
|
1700
|
+
name: "sendMessage",
|
|
1701
|
+
description: "Send an email message",
|
|
1702
|
+
parameters: {
|
|
1703
|
+
to: "string",
|
|
1704
|
+
subject: "string",
|
|
1705
|
+
body: "string",
|
|
1706
|
+
cc: "string[]?",
|
|
1707
|
+
bcc: "string[]?",
|
|
1708
|
+
format: "string?"
|
|
1709
|
+
}
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
name: "getMessages",
|
|
1713
|
+
description: "Get email messages",
|
|
1714
|
+
parameters: {
|
|
1715
|
+
query: "string?",
|
|
1716
|
+
maxResults: "number?",
|
|
1717
|
+
pageToken: "string?",
|
|
1718
|
+
labelIds: "string[]?"
|
|
1719
|
+
}
|
|
1720
|
+
},
|
|
1721
|
+
{
|
|
1722
|
+
name: "getMessage",
|
|
1723
|
+
description: "Get a specific email message",
|
|
1724
|
+
parameters: {
|
|
1725
|
+
messageId: "string",
|
|
1726
|
+
format: "string?"
|
|
1727
|
+
}
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
name: "getLabels",
|
|
1731
|
+
description: "Get available labels",
|
|
1732
|
+
parameters: {}
|
|
1733
|
+
},
|
|
1734
|
+
{
|
|
1735
|
+
name: "searchMessages",
|
|
1736
|
+
description: "Search email messages",
|
|
1737
|
+
parameters: {
|
|
1738
|
+
query: "string",
|
|
1739
|
+
maxResults: "number?",
|
|
1740
|
+
pageToken: "string?"
|
|
1741
|
+
}
|
|
1742
|
+
},
|
|
1743
|
+
{
|
|
1744
|
+
name: "modifyMessage",
|
|
1745
|
+
description: "Modify message labels",
|
|
1746
|
+
parameters: {
|
|
1747
|
+
messageId: "string",
|
|
1748
|
+
addLabelIds: "string[]?",
|
|
1749
|
+
removeLabelIds: "string[]?"
|
|
1750
|
+
}
|
|
1751
|
+
},
|
|
1752
|
+
{
|
|
1753
|
+
name: "createDraft",
|
|
1754
|
+
description: "Create a draft email",
|
|
1755
|
+
parameters: {
|
|
1756
|
+
to: "string",
|
|
1757
|
+
subject: "string",
|
|
1758
|
+
body: "string",
|
|
1759
|
+
cc: "string[]?",
|
|
1760
|
+
bcc: "string[]?",
|
|
1761
|
+
format: "string?"
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
]
|
|
1765
|
+
});
|
|
1766
|
+
this.tools.set("drive", {
|
|
1767
|
+
name: "drive",
|
|
1768
|
+
description: "Google Drive operations",
|
|
1769
|
+
actions: [
|
|
1770
|
+
{
|
|
1771
|
+
name: "listFiles",
|
|
1772
|
+
description: "List files in folder",
|
|
1773
|
+
parameters: {
|
|
1774
|
+
folderId: "string?",
|
|
1775
|
+
query: "string?",
|
|
1776
|
+
maxResults: "number?",
|
|
1777
|
+
pageToken: "string?",
|
|
1778
|
+
orderBy: "string?"
|
|
1779
|
+
}
|
|
1780
|
+
},
|
|
1781
|
+
{
|
|
1782
|
+
name: "getFile",
|
|
1783
|
+
description: "Get file information",
|
|
1784
|
+
parameters: {
|
|
1785
|
+
fileId: "string",
|
|
1786
|
+
fields: "string?"
|
|
1787
|
+
}
|
|
1788
|
+
},
|
|
1789
|
+
{
|
|
1790
|
+
name: "createFile",
|
|
1791
|
+
description: "Create a new file",
|
|
1792
|
+
parameters: {
|
|
1793
|
+
name: "string",
|
|
1794
|
+
content: "string",
|
|
1795
|
+
mimeType: "string?",
|
|
1796
|
+
parents: "string[]?",
|
|
1797
|
+
description: "string?"
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
name: "updateFile",
|
|
1802
|
+
description: "Update an existing file",
|
|
1803
|
+
parameters: {
|
|
1804
|
+
fileId: "string",
|
|
1805
|
+
name: "string?",
|
|
1806
|
+
content: "string?",
|
|
1807
|
+
description: "string?"
|
|
1808
|
+
}
|
|
1809
|
+
},
|
|
1810
|
+
{
|
|
1811
|
+
name: "deleteFile",
|
|
1812
|
+
description: "Delete a file",
|
|
1813
|
+
parameters: {
|
|
1814
|
+
fileId: "string"
|
|
1815
|
+
}
|
|
1816
|
+
},
|
|
1817
|
+
{
|
|
1818
|
+
name: "getFolders",
|
|
1819
|
+
description: "List folders",
|
|
1820
|
+
parameters: {
|
|
1821
|
+
parentId: "string?"
|
|
1822
|
+
}
|
|
1823
|
+
},
|
|
1824
|
+
{
|
|
1825
|
+
name: "createFolder",
|
|
1826
|
+
description: "Create a new folder",
|
|
1827
|
+
parameters: {
|
|
1828
|
+
name: "string",
|
|
1829
|
+
parentId: "string?",
|
|
1830
|
+
description: "string?"
|
|
1831
|
+
}
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
name: "downloadFile",
|
|
1835
|
+
description: "Download file content",
|
|
1836
|
+
parameters: {
|
|
1837
|
+
fileId: "string",
|
|
1838
|
+
mimeType: "string?"
|
|
1839
|
+
}
|
|
1840
|
+
},
|
|
1841
|
+
{
|
|
1842
|
+
name: "shareFile",
|
|
1843
|
+
description: "Share file with permissions",
|
|
1844
|
+
parameters: {
|
|
1845
|
+
fileId: "string",
|
|
1846
|
+
type: "string",
|
|
1847
|
+
role: "string",
|
|
1848
|
+
emailAddress: "string?",
|
|
1849
|
+
domain: "string?"
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
]
|
|
1853
|
+
});
|
|
1854
|
+
this.tools.set("jira", {
|
|
1855
|
+
name: "jira",
|
|
1856
|
+
description: "Jira project and issue management",
|
|
1857
|
+
actions: [
|
|
1858
|
+
{
|
|
1859
|
+
name: "getProjects",
|
|
1860
|
+
description: "List all Jira projects",
|
|
1861
|
+
parameters: {
|
|
1862
|
+
recent: "number?"
|
|
1863
|
+
}
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
name: "getBoard",
|
|
1867
|
+
description: "Get Jira board details",
|
|
1868
|
+
parameters: {
|
|
1869
|
+
boardId: "number"
|
|
1870
|
+
}
|
|
1871
|
+
},
|
|
1872
|
+
{
|
|
1873
|
+
name: "getBoards",
|
|
1874
|
+
description: "List all Jira boards",
|
|
1875
|
+
parameters: {
|
|
1876
|
+
projectKeyOrId: "string?",
|
|
1877
|
+
type: "string?"
|
|
1878
|
+
}
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
name: "getSprints",
|
|
1882
|
+
description: "Get sprints for a Jira board",
|
|
1883
|
+
parameters: {
|
|
1884
|
+
boardId: "number",
|
|
1885
|
+
state: "string?"
|
|
1886
|
+
}
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
name: "getSprintIssues",
|
|
1890
|
+
description: "Get issues in a specific sprint",
|
|
1891
|
+
parameters: {
|
|
1892
|
+
sprintId: "number",
|
|
1893
|
+
maxResults: "number?"
|
|
1894
|
+
}
|
|
1895
|
+
},
|
|
1896
|
+
{
|
|
1897
|
+
name: "searchIssues",
|
|
1898
|
+
description: "Search for Jira issues using JQL",
|
|
1899
|
+
parameters: {
|
|
1900
|
+
jql: "string",
|
|
1901
|
+
maxResults: "number?",
|
|
1902
|
+
startAt: "number?"
|
|
1903
|
+
}
|
|
1904
|
+
},
|
|
1905
|
+
{
|
|
1906
|
+
name: "getIssue",
|
|
1907
|
+
description: "Get a specific Jira issue",
|
|
1908
|
+
parameters: {
|
|
1909
|
+
issueIdOrKey: "string"
|
|
1910
|
+
}
|
|
1911
|
+
},
|
|
1912
|
+
{
|
|
1913
|
+
name: "getIssueWorklogs",
|
|
1914
|
+
description: "Get worklogs for a specific issue",
|
|
1915
|
+
parameters: {
|
|
1916
|
+
issueKey: "string"
|
|
1917
|
+
}
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
name: "createIssue",
|
|
1921
|
+
description: "Create a new Jira issue",
|
|
1922
|
+
parameters: {
|
|
1923
|
+
projectKey: "string",
|
|
1924
|
+
summary: "string",
|
|
1925
|
+
description: "string?",
|
|
1926
|
+
issueType: "string",
|
|
1927
|
+
priority: "string?",
|
|
1928
|
+
assignee: "string?"
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
]
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
|
|
1936
|
+
// src/memory/memory-manager.ts
|
|
1937
|
+
var MemoryManager = class {
|
|
1938
|
+
vpManager;
|
|
1939
|
+
proxyApiUrl;
|
|
1940
|
+
constructor(vpManager) {
|
|
1941
|
+
this.vpManager = vpManager || new VPManager();
|
|
1942
|
+
const config = getConfig();
|
|
1943
|
+
this.proxyApiUrl = config.proxyApi?.baseUrl || "http://localhost:3000";
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Write a document to memory
|
|
1947
|
+
*/
|
|
1948
|
+
async write(content, options) {
|
|
1949
|
+
const domain = new URL(this.proxyApiUrl).hostname;
|
|
1950
|
+
const challenge = this.generateChallenge();
|
|
1951
|
+
const vpJwt = await this.vpManager.create(options.vcs, {
|
|
1952
|
+
holderDid: options.holderDid,
|
|
1953
|
+
challenge,
|
|
1954
|
+
domain,
|
|
1955
|
+
purpose: "write"
|
|
1956
|
+
});
|
|
1957
|
+
const response = await fetch(`${this.proxyApiUrl}/api/v1/memory/${options.namespace}/doc`, {
|
|
1958
|
+
method: "POST",
|
|
1959
|
+
headers: {
|
|
1960
|
+
"Content-Type": "application/json",
|
|
1961
|
+
Authorization: `Bearer ${vpJwt}`
|
|
1962
|
+
},
|
|
1963
|
+
body: JSON.stringify({
|
|
1964
|
+
content,
|
|
1965
|
+
metadata: options.metadata,
|
|
1966
|
+
challenge
|
|
1967
|
+
})
|
|
1968
|
+
});
|
|
1969
|
+
if (!response.ok) {
|
|
1970
|
+
const error = await response.text();
|
|
1971
|
+
throw new Error(`Failed to write to memory: ${error}`);
|
|
1972
|
+
}
|
|
1973
|
+
return response.json();
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Query memory with vector search
|
|
1977
|
+
*/
|
|
1978
|
+
async query(query, options) {
|
|
1979
|
+
const domain = new URL(this.proxyApiUrl).hostname;
|
|
1980
|
+
const challenge = this.generateChallenge();
|
|
1981
|
+
const vpJwt = await this.vpManager.create(options.vcs, {
|
|
1982
|
+
holderDid: options.holderDid,
|
|
1983
|
+
challenge,
|
|
1984
|
+
domain,
|
|
1985
|
+
purpose: "read"
|
|
1986
|
+
});
|
|
1987
|
+
const queryParams = {
|
|
1988
|
+
query,
|
|
1989
|
+
namespace: options.namespace,
|
|
1990
|
+
limit: options.limit || 10,
|
|
1991
|
+
filter: options.filter
|
|
1992
|
+
};
|
|
1993
|
+
const namespace = options.namespace || "default";
|
|
1994
|
+
const response = await fetch(`${this.proxyApiUrl}/api/v1/memory/${namespace}/query`, {
|
|
1995
|
+
method: "POST",
|
|
1996
|
+
headers: {
|
|
1997
|
+
"Content-Type": "application/json",
|
|
1998
|
+
Authorization: `Bearer ${vpJwt}`
|
|
1999
|
+
},
|
|
2000
|
+
body: JSON.stringify({
|
|
2001
|
+
...queryParams,
|
|
2002
|
+
challenge
|
|
2003
|
+
})
|
|
2004
|
+
});
|
|
2005
|
+
if (!response.ok) {
|
|
2006
|
+
const error = await response.text();
|
|
2007
|
+
throw new Error(`Failed to query memory: ${error}`);
|
|
2008
|
+
}
|
|
2009
|
+
return response.json();
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Delete a document from memory
|
|
2013
|
+
*/
|
|
2014
|
+
async delete(documentId, options) {
|
|
2015
|
+
const domain = new URL(this.proxyApiUrl).hostname;
|
|
2016
|
+
const challenge = this.generateChallenge();
|
|
2017
|
+
const vpJwt = await this.vpManager.create(options.vcs, {
|
|
2018
|
+
holderDid: options.holderDid,
|
|
2019
|
+
challenge,
|
|
2020
|
+
domain,
|
|
2021
|
+
purpose: "delete"
|
|
2022
|
+
});
|
|
2023
|
+
const response = await fetch(
|
|
2024
|
+
`${this.proxyApiUrl}/api/v1/memory/${options.namespace}/${documentId}`,
|
|
2025
|
+
{
|
|
2026
|
+
method: "DELETE",
|
|
2027
|
+
headers: {
|
|
2028
|
+
Authorization: `Bearer ${vpJwt}`,
|
|
2029
|
+
"X-Challenge": challenge
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
);
|
|
2033
|
+
if (!response.ok) {
|
|
2034
|
+
const error = await response.text();
|
|
2035
|
+
throw new Error(`Failed to delete from memory: ${error}`);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* List documents in a namespace
|
|
2040
|
+
*/
|
|
2041
|
+
async list(options) {
|
|
2042
|
+
const domain = new URL(this.proxyApiUrl).hostname;
|
|
2043
|
+
const challenge = this.generateChallenge();
|
|
2044
|
+
const vpJwt = await this.vpManager.create(options.vcs, {
|
|
2045
|
+
holderDid: options.holderDid,
|
|
2046
|
+
challenge,
|
|
2047
|
+
domain,
|
|
2048
|
+
purpose: "read"
|
|
2049
|
+
});
|
|
2050
|
+
const params = new URLSearchParams({
|
|
2051
|
+
limit: (options.limit || 100).toString(),
|
|
2052
|
+
offset: (options.offset || 0).toString()
|
|
2053
|
+
});
|
|
2054
|
+
const response = await fetch(
|
|
2055
|
+
`${this.proxyApiUrl}/api/v1/memory/${options.namespace}/list?${params}`,
|
|
2056
|
+
{
|
|
2057
|
+
headers: {
|
|
2058
|
+
Authorization: `Bearer ${vpJwt}`,
|
|
2059
|
+
"X-Challenge": challenge
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
);
|
|
2063
|
+
if (!response.ok) {
|
|
2064
|
+
const error = await response.text();
|
|
2065
|
+
throw new Error(`Failed to list memory documents: ${error}`);
|
|
2066
|
+
}
|
|
2067
|
+
return response.json();
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Check if VCs authorize memory access
|
|
2071
|
+
*/
|
|
2072
|
+
async checkAuthorization(vcs, action, resource) {
|
|
2073
|
+
for (const vcJwt of vcs) {
|
|
2074
|
+
try {
|
|
2075
|
+
const parts = vcJwt.split(".");
|
|
2076
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2077
|
+
const vcResource = payload.credentialSubject?.resource;
|
|
2078
|
+
const vcActions = payload.credentialSubject?.actions || [];
|
|
2079
|
+
if (this.matchResource(vcResource, resource)) {
|
|
2080
|
+
if (vcActions.includes(action)) {
|
|
2081
|
+
return true;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
} catch {
|
|
2085
|
+
continue;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return false;
|
|
2089
|
+
}
|
|
2090
|
+
matchResource(vcResource, requiredResource) {
|
|
2091
|
+
if (vcResource.endsWith("/*")) {
|
|
2092
|
+
const prefix = vcResource.slice(0, -2);
|
|
2093
|
+
return requiredResource.startsWith(prefix);
|
|
2094
|
+
}
|
|
2095
|
+
return vcResource === requiredResource;
|
|
2096
|
+
}
|
|
2097
|
+
generateChallenge() {
|
|
2098
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
2099
|
+
}
|
|
2100
|
+
};
|
|
2101
|
+
|
|
2102
|
+
// src/organization/organization-manager.ts
|
|
2103
|
+
var OrganizationManager = class {
|
|
2104
|
+
vpManager;
|
|
2105
|
+
vcManager;
|
|
2106
|
+
apiBaseUrl;
|
|
2107
|
+
constructor(vpManager, vcManager) {
|
|
2108
|
+
this.vpManager = vpManager || new VPManager();
|
|
2109
|
+
this.vcManager = vcManager || new VCManager();
|
|
2110
|
+
const config = getConfig();
|
|
2111
|
+
this.apiBaseUrl = config.proxyApi?.baseUrl || "http://localhost:3000";
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Request tool permissions using employee VC
|
|
2115
|
+
*/
|
|
2116
|
+
async requestToolPermissions(employeeVCJWT, requestedTools, holderDid) {
|
|
2117
|
+
const challenge = this.generateChallenge();
|
|
2118
|
+
const vpRequest = {
|
|
2119
|
+
employeeVC: employeeVCJWT,
|
|
2120
|
+
requestedTools,
|
|
2121
|
+
challenge,
|
|
2122
|
+
domain: new URL(this.apiBaseUrl).hostname
|
|
2123
|
+
};
|
|
2124
|
+
const response = await fetch(`${this.apiBaseUrl}/api/organization/employee/permissions`, {
|
|
2125
|
+
method: "POST",
|
|
2126
|
+
headers: { "Content-Type": "application/json" },
|
|
2127
|
+
body: JSON.stringify(vpRequest)
|
|
2128
|
+
});
|
|
2129
|
+
if (!response.ok) {
|
|
2130
|
+
throw new Error(`Permission request failed: ${response.statusText}`);
|
|
2131
|
+
}
|
|
2132
|
+
const result = await response.json();
|
|
2133
|
+
if (!result.success) {
|
|
2134
|
+
throw new Error(`Permission denied: ${result.error}`);
|
|
2135
|
+
}
|
|
2136
|
+
return {
|
|
2137
|
+
permittedPermissions: result.permittedPermissions,
|
|
2138
|
+
employee: result.employee
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Issue tool permissions to AI Agent based on organization approval
|
|
2143
|
+
*/
|
|
2144
|
+
async issueOrganizationDelegatedPermissions(agentDid, employeeVCJWT, requestedTools, issuerDid) {
|
|
2145
|
+
const { permittedPermissions } = await this.requestToolPermissions(
|
|
2146
|
+
employeeVCJWT,
|
|
2147
|
+
requestedTools,
|
|
2148
|
+
issuerDid
|
|
2149
|
+
);
|
|
2150
|
+
const issuedVCs = [];
|
|
2151
|
+
for (const permission of permittedPermissions) {
|
|
2152
|
+
for (const action of permission.actions) {
|
|
2153
|
+
const vc = await this.vcManager.issue(
|
|
2154
|
+
"ToolPermissionVC",
|
|
2155
|
+
{
|
|
2156
|
+
tool: `${permission.tool}.${action}`,
|
|
2157
|
+
aud: permission.tool,
|
|
2158
|
+
organizationDelegated: true,
|
|
2159
|
+
sourceEmployeeVC: employeeVCJWT
|
|
2160
|
+
// Include reference to source employee VC
|
|
2161
|
+
},
|
|
2162
|
+
{
|
|
2163
|
+
issuerDid,
|
|
2164
|
+
subjectDid: agentDid,
|
|
2165
|
+
expiresIn: permission.duration || "8h"
|
|
2166
|
+
}
|
|
2167
|
+
);
|
|
2168
|
+
issuedVCs.push(vc);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return issuedVCs;
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Create simplified workflow for employee to AI Agent delegation
|
|
2175
|
+
*/
|
|
2176
|
+
async delegateToAIAgent(employeeVCJWT, agentDid, tools, issuerDid, options) {
|
|
2177
|
+
const requestedTools = tools.map((tool) => ({
|
|
2178
|
+
tool,
|
|
2179
|
+
actions: ["read", "write"],
|
|
2180
|
+
// Request common actions
|
|
2181
|
+
duration: options?.duration,
|
|
2182
|
+
justification: options?.justification
|
|
2183
|
+
}));
|
|
2184
|
+
const issuedVCs = await this.issueOrganizationDelegatedPermissions(
|
|
2185
|
+
agentDid,
|
|
2186
|
+
employeeVCJWT,
|
|
2187
|
+
requestedTools,
|
|
2188
|
+
issuerDid
|
|
2189
|
+
);
|
|
2190
|
+
const permissionSummary = {};
|
|
2191
|
+
for (const tool of tools) {
|
|
2192
|
+
const relatedVCs = issuedVCs.filter((vc) => vc.includes(tool));
|
|
2193
|
+
permissionSummary[tool] = relatedVCs.map((vc) => this.extractActionFromVC(vc));
|
|
2194
|
+
}
|
|
2195
|
+
return {
|
|
2196
|
+
issuedVCs,
|
|
2197
|
+
permissionSummary
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Register organization with AIdentity
|
|
2202
|
+
*/
|
|
2203
|
+
async registerOrganization(config) {
|
|
2204
|
+
const response = await fetch(`${this.apiBaseUrl}/api/organization/register`, {
|
|
2205
|
+
method: "POST",
|
|
2206
|
+
headers: { "Content-Type": "application/json" },
|
|
2207
|
+
body: JSON.stringify(config)
|
|
2208
|
+
});
|
|
2209
|
+
if (!response.ok) {
|
|
2210
|
+
throw new Error(`Organization registration failed: ${response.statusText}`);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
generateChallenge() {
|
|
2214
|
+
return `challenge_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2215
|
+
}
|
|
2216
|
+
extractActionFromVC(vcJWT) {
|
|
2217
|
+
try {
|
|
2218
|
+
const [, payload] = vcJWT.split(".");
|
|
2219
|
+
const vc = JSON.parse(Buffer.from(payload, "base64url").toString());
|
|
2220
|
+
return vc.credentialSubject.tool.split(".").pop() || "unknown";
|
|
2221
|
+
} catch {
|
|
2222
|
+
return "unknown";
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
};
|
|
2226
|
+
|
|
2227
|
+
// src/grant/grant-manager.ts
|
|
2228
|
+
var GrantManager = class {
|
|
2229
|
+
constructor(_vpManager) {
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Grant提案を取得
|
|
2233
|
+
* @param options - 提案オプション
|
|
2234
|
+
* @param options.oauthTokenId - OAuthトークンID
|
|
2235
|
+
* @param options.targetUserId - 対象ユーザーID
|
|
2236
|
+
* @param options.projectId - プロジェクトID
|
|
2237
|
+
* @param authOptions - 認証オプション(VP or issuerDid)
|
|
2238
|
+
*/
|
|
2239
|
+
async suggest(options, authOptions) {
|
|
2240
|
+
const headers = {
|
|
2241
|
+
"Content-Type": "application/json"
|
|
2242
|
+
};
|
|
2243
|
+
if (authOptions.vpJwt) {
|
|
2244
|
+
headers["Authorization"] = `Bearer ${authOptions.vpJwt}`;
|
|
2245
|
+
} else if (authOptions.issuerDid) {
|
|
2246
|
+
headers["x-issuer-did"] = authOptions.issuerDid;
|
|
2247
|
+
} else {
|
|
2248
|
+
throw new Error("Either vpJwt or issuerDid is required for authentication");
|
|
2249
|
+
}
|
|
2250
|
+
const response = await fetch(getDidApiUrl("/api/v1/grants/suggest"), {
|
|
2251
|
+
method: "POST",
|
|
2252
|
+
headers,
|
|
2253
|
+
body: JSON.stringify(options)
|
|
2254
|
+
});
|
|
2255
|
+
if (!response.ok) {
|
|
2256
|
+
const error = await response.json();
|
|
2257
|
+
throw new Error(error.error || `Failed to get grant suggestion: ${response.statusText}`);
|
|
2258
|
+
}
|
|
2259
|
+
const result = await response.json();
|
|
2260
|
+
return result.data;
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Grant提案を確認して作成
|
|
2264
|
+
* @param request - 確認リクエスト
|
|
2265
|
+
* @param authOptions - 認証オプション
|
|
2266
|
+
*/
|
|
2267
|
+
async confirm(request, authOptions) {
|
|
2268
|
+
const headers = {
|
|
2269
|
+
"Content-Type": "application/json"
|
|
2270
|
+
};
|
|
2271
|
+
if (authOptions.vpJwt) {
|
|
2272
|
+
headers["Authorization"] = `Bearer ${authOptions.vpJwt}`;
|
|
2273
|
+
} else if (authOptions.issuerDid) {
|
|
2274
|
+
headers["x-issuer-did"] = authOptions.issuerDid;
|
|
2275
|
+
} else {
|
|
2276
|
+
throw new Error("Either vpJwt or issuerDid is required for authentication");
|
|
2277
|
+
}
|
|
2278
|
+
const response = await fetch(getDidApiUrl("/api/v1/grants/confirm"), {
|
|
2279
|
+
method: "POST",
|
|
2280
|
+
headers,
|
|
2281
|
+
body: JSON.stringify(request)
|
|
2282
|
+
});
|
|
2283
|
+
if (!response.ok) {
|
|
2284
|
+
const error = await response.json();
|
|
2285
|
+
throw new Error(error.error || `Failed to confirm grant: ${response.statusText}`);
|
|
2286
|
+
}
|
|
2287
|
+
const result = await response.json();
|
|
2288
|
+
return result.data;
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Grantを直接作成
|
|
2292
|
+
* @param request - Grant作成リクエスト
|
|
2293
|
+
* @param authOptions - 認証オプション
|
|
2294
|
+
*/
|
|
2295
|
+
async create(request, authOptions) {
|
|
2296
|
+
const headers = {
|
|
2297
|
+
"Content-Type": "application/json"
|
|
2298
|
+
};
|
|
2299
|
+
if (authOptions.vpJwt) {
|
|
2300
|
+
headers["Authorization"] = `Bearer ${authOptions.vpJwt}`;
|
|
2301
|
+
} else if (authOptions.issuerDid) {
|
|
2302
|
+
headers["x-issuer-did"] = authOptions.issuerDid;
|
|
2303
|
+
} else {
|
|
2304
|
+
throw new Error("Either vpJwt or issuerDid is required for authentication");
|
|
2305
|
+
}
|
|
2306
|
+
const response = await fetch(getDidApiUrl("/api/v1/grants"), {
|
|
2307
|
+
method: "POST",
|
|
2308
|
+
headers,
|
|
2309
|
+
body: JSON.stringify(request)
|
|
2310
|
+
});
|
|
2311
|
+
if (!response.ok) {
|
|
2312
|
+
const error = await response.json();
|
|
2313
|
+
throw new Error(error.error || `Failed to create grant: ${response.statusText}`);
|
|
2314
|
+
}
|
|
2315
|
+
const result = await response.json();
|
|
2316
|
+
return result.data;
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* ユーザー用のGrant一覧を取得
|
|
2320
|
+
* @param userId - ユーザーID
|
|
2321
|
+
* @param status - フィルタするステータス(オプション)
|
|
2322
|
+
*/
|
|
2323
|
+
async listForUser(userId, status) {
|
|
2324
|
+
const headers = {
|
|
2325
|
+
"Content-Type": "application/json"
|
|
2326
|
+
};
|
|
2327
|
+
const encodedUserId = encodeURIComponent(userId);
|
|
2328
|
+
const url = status ? getDidApiUrl(`/api/v1/grants/user/${encodedUserId}?status=${status}`) : getDidApiUrl(`/api/v1/grants/user/${encodedUserId}`);
|
|
2329
|
+
const response = await fetch(url, {
|
|
2330
|
+
method: "GET",
|
|
2331
|
+
headers
|
|
2332
|
+
});
|
|
2333
|
+
if (!response.ok) {
|
|
2334
|
+
const error = await response.json();
|
|
2335
|
+
throw new Error(error.error || `Failed to list grants for user: ${response.statusText}`);
|
|
2336
|
+
}
|
|
2337
|
+
const result = await response.json();
|
|
2338
|
+
return result.data;
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Issuer用のGrant一覧を取得
|
|
2342
|
+
* @param issuerDid - IssuerのDID
|
|
2343
|
+
* @param status - フィルタするステータス(オプション)
|
|
2344
|
+
*/
|
|
2345
|
+
async listForIssuer(issuerDid, status) {
|
|
2346
|
+
const headers = {
|
|
2347
|
+
"Content-Type": "application/json"
|
|
2348
|
+
};
|
|
2349
|
+
const encodedDid = encodeURIComponent(issuerDid);
|
|
2350
|
+
const url = status ? getDidApiUrl(`/api/v1/grants/issuer/${encodedDid}?status=${status}`) : getDidApiUrl(`/api/v1/grants/issuer/${encodedDid}`);
|
|
2351
|
+
const response = await fetch(url, {
|
|
2352
|
+
method: "GET",
|
|
2353
|
+
headers
|
|
2354
|
+
});
|
|
2355
|
+
if (!response.ok) {
|
|
2356
|
+
const error = await response.json();
|
|
2357
|
+
throw new Error(error.error || `Failed to list grants for issuer: ${response.statusText}`);
|
|
2358
|
+
}
|
|
2359
|
+
const result = await response.json();
|
|
2360
|
+
return result.data;
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Grantを取得
|
|
2364
|
+
* @param grantId - GrantのID
|
|
2365
|
+
*/
|
|
2366
|
+
async get(grantId) {
|
|
2367
|
+
const headers = {
|
|
2368
|
+
"Content-Type": "application/json"
|
|
2369
|
+
};
|
|
2370
|
+
const response = await fetch(getDidApiUrl(`/api/v1/grants/${grantId}`), {
|
|
2371
|
+
method: "GET",
|
|
2372
|
+
headers
|
|
2373
|
+
});
|
|
2374
|
+
if (!response.ok) {
|
|
2375
|
+
const error = await response.json();
|
|
2376
|
+
throw new Error(error.error || `Failed to get grant: ${response.statusText}`);
|
|
2377
|
+
}
|
|
2378
|
+
const result = await response.json();
|
|
2379
|
+
return result.data;
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Grantを取り消し
|
|
2383
|
+
* @param grantId - GrantのID
|
|
2384
|
+
* @param reason - 取り消し理由
|
|
2385
|
+
* @param authOptions - 認証オプション
|
|
2386
|
+
*/
|
|
2387
|
+
async revoke(grantId, reason, authOptions) {
|
|
2388
|
+
const headers = {
|
|
2389
|
+
"Content-Type": "application/json"
|
|
2390
|
+
};
|
|
2391
|
+
if (authOptions.vpJwt) {
|
|
2392
|
+
headers["Authorization"] = `Bearer ${authOptions.vpJwt}`;
|
|
2393
|
+
} else if (authOptions.issuerDid) {
|
|
2394
|
+
headers["x-issuer-did"] = authOptions.issuerDid;
|
|
2395
|
+
} else {
|
|
2396
|
+
throw new Error("Either vpJwt or issuerDid is required for authentication");
|
|
2397
|
+
}
|
|
2398
|
+
const response = await fetch(getDidApiUrl(`/api/v1/grants/${grantId}`), {
|
|
2399
|
+
method: "DELETE",
|
|
2400
|
+
headers,
|
|
2401
|
+
body: JSON.stringify({ reason })
|
|
2402
|
+
});
|
|
2403
|
+
if (!response.ok) {
|
|
2404
|
+
const error = await response.json();
|
|
2405
|
+
throw new Error(error.error || `Failed to revoke grant: ${response.statusText}`);
|
|
2406
|
+
}
|
|
2407
|
+
const result = await response.json();
|
|
2408
|
+
return result.data;
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Grant権限をチェック
|
|
2412
|
+
* @param request - 権限チェックリクエスト
|
|
2413
|
+
*/
|
|
2414
|
+
async checkPermission(request) {
|
|
2415
|
+
const headers = {
|
|
2416
|
+
"Content-Type": "application/json"
|
|
2417
|
+
};
|
|
2418
|
+
const response = await fetch(getDidApiUrl("/api/v1/grants/check"), {
|
|
2419
|
+
method: "POST",
|
|
2420
|
+
headers,
|
|
2421
|
+
body: JSON.stringify(request)
|
|
2422
|
+
});
|
|
2423
|
+
if (!response.ok) {
|
|
2424
|
+
const error = await response.json();
|
|
2425
|
+
throw new Error(error.error || `Failed to check grant permission: ${response.statusText}`);
|
|
2426
|
+
}
|
|
2427
|
+
const result = await response.json();
|
|
2428
|
+
return result.data;
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Grant更新
|
|
2432
|
+
* @param grantId - GrantのID
|
|
2433
|
+
* @param request - 更新リクエスト
|
|
2434
|
+
* @param authOptions - 認証オプション
|
|
2435
|
+
*/
|
|
2436
|
+
async update(grantId, request, authOptions) {
|
|
2437
|
+
const headers = {
|
|
2438
|
+
"Content-Type": "application/json"
|
|
2439
|
+
};
|
|
2440
|
+
if (authOptions.vpJwt) {
|
|
2441
|
+
headers["Authorization"] = `Bearer ${authOptions.vpJwt}`;
|
|
2442
|
+
} else if (authOptions.issuerDid) {
|
|
2443
|
+
headers["x-issuer-did"] = authOptions.issuerDid;
|
|
2444
|
+
} else {
|
|
2445
|
+
throw new Error("Either vpJwt or issuerDid is required for authentication");
|
|
2446
|
+
}
|
|
2447
|
+
const response = await fetch(getDidApiUrl(`/api/v1/grants/${grantId}`), {
|
|
2448
|
+
method: "PUT",
|
|
2449
|
+
headers,
|
|
2450
|
+
body: JSON.stringify(request)
|
|
2451
|
+
});
|
|
2452
|
+
if (!response.ok) {
|
|
2453
|
+
const error = await response.json();
|
|
2454
|
+
throw new Error(error.error || `Failed to update grant: ${response.statusText}`);
|
|
2455
|
+
}
|
|
2456
|
+
const result = await response.json();
|
|
2457
|
+
return result.data;
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
|
|
2461
|
+
// src/client.ts
|
|
2462
|
+
__reExport(client_exports, require("@vess-id/ai-identity-types"));
|
|
2463
|
+
var AIdentityClient = class {
|
|
2464
|
+
agent;
|
|
2465
|
+
user;
|
|
2466
|
+
vc;
|
|
2467
|
+
vp;
|
|
2468
|
+
tool;
|
|
2469
|
+
memory;
|
|
2470
|
+
organization;
|
|
2471
|
+
grant;
|
|
2472
|
+
keyManager;
|
|
2473
|
+
currentAgent;
|
|
2474
|
+
constructor(config, password) {
|
|
2475
|
+
if (config) {
|
|
2476
|
+
configure(config);
|
|
2477
|
+
}
|
|
2478
|
+
this.keyManager = new KeyManager(password);
|
|
2479
|
+
this.agent = new AgentManager(this.keyManager);
|
|
2480
|
+
this.user = new UserIdentityManager(this.keyManager);
|
|
2481
|
+
this.vc = new VCManager(this.keyManager, this.agent, this.user);
|
|
2482
|
+
this.vp = new VPManager(this.keyManager);
|
|
2483
|
+
this.tool = new ToolManager(this.vp);
|
|
2484
|
+
this.memory = new MemoryManager(this.vp);
|
|
2485
|
+
this.organization = new OrganizationManager(this.vp, this.vc);
|
|
2486
|
+
this.grant = new GrantManager(this.vp);
|
|
2487
|
+
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Quick setup: Create or load an agent
|
|
2490
|
+
*/
|
|
2491
|
+
async setup(did) {
|
|
2492
|
+
if (did) {
|
|
2493
|
+
this.currentAgent = await this.agent.export(did).then(({ agent }) => agent);
|
|
2494
|
+
} else {
|
|
2495
|
+
this.currentAgent = await this.agent.create();
|
|
2496
|
+
}
|
|
2497
|
+
return this.currentAgent;
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Get current agent
|
|
2501
|
+
*/
|
|
2502
|
+
getCurrentAgent() {
|
|
2503
|
+
return this.currentAgent;
|
|
2504
|
+
}
|
|
2505
|
+
/**
|
|
2506
|
+
* Get current user DID
|
|
2507
|
+
*/
|
|
2508
|
+
async getCurrentUserDID() {
|
|
2509
|
+
return this.user.getCurrentUserDID();
|
|
2510
|
+
}
|
|
2511
|
+
/**
|
|
2512
|
+
* Create or reset user identity
|
|
2513
|
+
*/
|
|
2514
|
+
async resetUserIdentity() {
|
|
2515
|
+
return this.user.resetUserIdentity();
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Issue a VC for tool permission
|
|
2519
|
+
* Enhanced to support User → Agent delegation pattern
|
|
2520
|
+
*/
|
|
2521
|
+
async issueToolPermission(tool, action, options) {
|
|
2522
|
+
return this.vc.issue(
|
|
2523
|
+
"ToolPermissionVC",
|
|
2524
|
+
{
|
|
2525
|
+
tool: `${tool}.${action}`,
|
|
2526
|
+
resourceScope: options.resourceScope,
|
|
2527
|
+
aud: tool
|
|
2528
|
+
},
|
|
2529
|
+
{
|
|
2530
|
+
issuerDid: options.issuerDid,
|
|
2531
|
+
subjectDid: options.subjectDid,
|
|
2532
|
+
agentId: options.agentId,
|
|
2533
|
+
expiresIn: options.expiresIn || "1h"
|
|
2534
|
+
}
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
/**
|
|
2538
|
+
* Issue a VC for data access
|
|
2539
|
+
* Enhanced to support User → Agent delegation pattern
|
|
2540
|
+
*/
|
|
2541
|
+
async issueDataAccess(resource, actions, options) {
|
|
2542
|
+
return this.vc.issue(
|
|
2543
|
+
"DataAccessVC",
|
|
2544
|
+
{
|
|
2545
|
+
resource,
|
|
2546
|
+
actions
|
|
2547
|
+
},
|
|
2548
|
+
{
|
|
2549
|
+
issuerDid: options.issuerDid,
|
|
2550
|
+
subjectDid: options.subjectDid,
|
|
2551
|
+
agentId: options.agentId,
|
|
2552
|
+
expiresIn: options.expiresIn || "24h"
|
|
2553
|
+
}
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
/**
|
|
2557
|
+
* Invoke a tool with automatic VP creation
|
|
2558
|
+
*/
|
|
2559
|
+
async invokeTool(tool, action, params, vcs) {
|
|
2560
|
+
const holderDid = this.currentAgent?.did;
|
|
2561
|
+
if (!holderDid) {
|
|
2562
|
+
throw new Error("No current agent available");
|
|
2563
|
+
}
|
|
2564
|
+
return this.tool.invoke(tool, action, params, {
|
|
2565
|
+
vcs,
|
|
2566
|
+
holderDid
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Write to memory with automatic VP creation
|
|
2571
|
+
*/
|
|
2572
|
+
async writeMemory(content, namespace, vcs, metadata) {
|
|
2573
|
+
const holderDid = this.currentAgent?.did;
|
|
2574
|
+
if (!holderDid) {
|
|
2575
|
+
throw new Error("No current agent available");
|
|
2576
|
+
}
|
|
2577
|
+
return this.memory.write(content, {
|
|
2578
|
+
namespace,
|
|
2579
|
+
metadata,
|
|
2580
|
+
vcs,
|
|
2581
|
+
holderDid
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Query memory with automatic VP creation
|
|
2586
|
+
*/
|
|
2587
|
+
async queryMemory(query, vcs, options) {
|
|
2588
|
+
const holderDid = this.currentAgent?.did;
|
|
2589
|
+
if (!holderDid) {
|
|
2590
|
+
throw new Error("No current agent available");
|
|
2591
|
+
}
|
|
2592
|
+
return this.memory.query(query, {
|
|
2593
|
+
...options,
|
|
2594
|
+
vcs,
|
|
2595
|
+
holderDid
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
var defaultClient;
|
|
2600
|
+
function getClient(config, password) {
|
|
2601
|
+
if (!defaultClient) {
|
|
2602
|
+
defaultClient = new AIdentityClient(config, password);
|
|
2603
|
+
}
|
|
2604
|
+
return defaultClient;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
// src/vc/api-vc-manager.ts
|
|
2608
|
+
var import_ai_identity_types2 = require("@vess-id/ai-identity-types");
|
|
2609
|
+
|
|
2610
|
+
// src/organization/disclosure-config-manager.ts
|
|
2611
|
+
var import_ai_identity_types = require("@vess-id/ai-identity-types");
|
|
2612
|
+
var DisclosureConfigManager = class {
|
|
2613
|
+
configs = /* @__PURE__ */ new Map();
|
|
2614
|
+
/**
|
|
2615
|
+
* Set disclosure configuration for an organization
|
|
2616
|
+
*/
|
|
2617
|
+
async setOrganizationConfig(organizationDid, config) {
|
|
2618
|
+
const existingConfig = this.configs.get(organizationDid);
|
|
2619
|
+
const now = /* @__PURE__ */ new Date();
|
|
2620
|
+
const newConfig = {
|
|
2621
|
+
organizationDid,
|
|
2622
|
+
defaultFields: config.defaultFields || [],
|
|
2623
|
+
credentialTypeConfigs: config.credentialTypeConfigs || /* @__PURE__ */ new Map(),
|
|
2624
|
+
createdAt: existingConfig?.createdAt || now,
|
|
2625
|
+
updatedAt: now
|
|
2626
|
+
};
|
|
2627
|
+
this.configs.set(organizationDid, newConfig);
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Get disclosure configuration for an organization
|
|
2631
|
+
*/
|
|
2632
|
+
async getOrganizationConfig(organizationDid) {
|
|
2633
|
+
return this.configs.get(organizationDid) || null;
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Set credential type specific disclosure configuration
|
|
2637
|
+
*/
|
|
2638
|
+
async setCredentialTypeConfig(organizationDid, credentialType, config) {
|
|
2639
|
+
let orgConfig = this.configs.get(organizationDid);
|
|
2640
|
+
if (!orgConfig) {
|
|
2641
|
+
const now = /* @__PURE__ */ new Date();
|
|
2642
|
+
orgConfig = {
|
|
2643
|
+
organizationDid,
|
|
2644
|
+
defaultFields: [],
|
|
2645
|
+
credentialTypeConfigs: /* @__PURE__ */ new Map(),
|
|
2646
|
+
createdAt: now,
|
|
2647
|
+
updatedAt: now
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
orgConfig.credentialTypeConfigs.set(credentialType, config);
|
|
2651
|
+
orgConfig.updatedAt = /* @__PURE__ */ new Date();
|
|
2652
|
+
this.configs.set(organizationDid, orgConfig);
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Get selective disclosure fields for a specific credential type and organization
|
|
2656
|
+
*/
|
|
2657
|
+
async getSelectiveDisclosureFields(organizationDid, credentialType, requestedFields) {
|
|
2658
|
+
const orgConfig = this.configs.get(organizationDid);
|
|
2659
|
+
if (!orgConfig) {
|
|
2660
|
+
return this.getDefaultConfiguration(credentialType, requestedFields);
|
|
2661
|
+
}
|
|
2662
|
+
const typeConfig = orgConfig.credentialTypeConfigs.get(credentialType);
|
|
2663
|
+
if (!typeConfig) {
|
|
2664
|
+
return {
|
|
2665
|
+
selectiveFields: requestedFields || orgConfig.defaultFields,
|
|
2666
|
+
mandatoryFields: [],
|
|
2667
|
+
neverDisclose: [],
|
|
2668
|
+
decoyCount: 0
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
let selectiveFields = requestedFields || typeConfig.selectiveFields;
|
|
2672
|
+
selectiveFields = selectiveFields.filter(
|
|
2673
|
+
(field) => !typeConfig.neverDisclose.includes(field)
|
|
2674
|
+
);
|
|
2675
|
+
return {
|
|
2676
|
+
selectiveFields,
|
|
2677
|
+
mandatoryFields: typeConfig.mandatoryFields,
|
|
2678
|
+
neverDisclose: typeConfig.neverDisclose,
|
|
2679
|
+
decoyCount: typeConfig.decoyFields || 0
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* Get default configuration for credential types
|
|
2684
|
+
*/
|
|
2685
|
+
getDefaultConfiguration(credentialType, requestedFields) {
|
|
2686
|
+
const defaultConfigs = {
|
|
2687
|
+
[import_ai_identity_types.CredentialType.PROJECT_ACCESS]: {
|
|
2688
|
+
selectiveFields: ["permissions", "projectId"],
|
|
2689
|
+
mandatoryFields: ["role"],
|
|
2690
|
+
neverDisclose: ["privateKey", "secret"]
|
|
2691
|
+
},
|
|
2692
|
+
[import_ai_identity_types.CredentialType.TOOL_ACCESS]: {
|
|
2693
|
+
selectiveFields: ["actions", "tool", "resourceScope"],
|
|
2694
|
+
mandatoryFields: [],
|
|
2695
|
+
neverDisclose: ["privateKey", "secret"]
|
|
2696
|
+
},
|
|
2697
|
+
[import_ai_identity_types.CredentialType.ADMIN]: {
|
|
2698
|
+
selectiveFields: ["adminLevel", "scope"],
|
|
2699
|
+
mandatoryFields: ["authorizedBy"],
|
|
2700
|
+
neverDisclose: ["privateKey", "secret", "internalId"]
|
|
2701
|
+
},
|
|
2702
|
+
[import_ai_identity_types.CredentialType.DEVELOPER]: {
|
|
2703
|
+
selectiveFields: ["skills", "experience", "projects"],
|
|
2704
|
+
mandatoryFields: ["name"],
|
|
2705
|
+
neverDisclose: ["salary", "privateInfo"]
|
|
2706
|
+
},
|
|
2707
|
+
[import_ai_identity_types.CredentialType.TEMPORARY]: {
|
|
2708
|
+
selectiveFields: requestedFields || [],
|
|
2709
|
+
mandatoryFields: [],
|
|
2710
|
+
neverDisclose: ["privateKey", "secret"]
|
|
2711
|
+
},
|
|
2712
|
+
[import_ai_identity_types.CredentialType.RECEIPT]: {
|
|
2713
|
+
selectiveFields: ["action", "resource", "outcome", "amount", "description", "date", "transactionId"],
|
|
2714
|
+
mandatoryFields: ["grantId", "auditEventId", "receiptType"],
|
|
2715
|
+
neverDisclose: ["signature", "privateKey", "secret", "internalReference"]
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2718
|
+
const config = defaultConfigs[credentialType] || defaultConfigs[import_ai_identity_types.CredentialType.TEMPORARY];
|
|
2719
|
+
return {
|
|
2720
|
+
selectiveFields: requestedFields || config.selectiveFields || [],
|
|
2721
|
+
mandatoryFields: config.mandatoryFields || [],
|
|
2722
|
+
neverDisclose: config.neverDisclose || [],
|
|
2723
|
+
decoyCount: 0
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Validate disclosure request against organization policy
|
|
2728
|
+
*/
|
|
2729
|
+
async validateDisclosureRequest(organizationDid, credentialType, requestedFields) {
|
|
2730
|
+
const config = await this.getSelectiveDisclosureFields(organizationDid, credentialType, requestedFields);
|
|
2731
|
+
const errors = [];
|
|
2732
|
+
const rejectedFields = [];
|
|
2733
|
+
const forbiddenFields = requestedFields.filter(
|
|
2734
|
+
(field) => config.neverDisclose.includes(field)
|
|
2735
|
+
);
|
|
2736
|
+
if (forbiddenFields.length > 0) {
|
|
2737
|
+
rejectedFields.push(...forbiddenFields);
|
|
2738
|
+
errors.push(`Fields not allowed for disclosure: ${forbiddenFields.join(", ")}`);
|
|
2739
|
+
}
|
|
2740
|
+
const allowedFields = requestedFields.filter(
|
|
2741
|
+
(field) => !config.neverDisclose.includes(field)
|
|
2742
|
+
);
|
|
2743
|
+
return {
|
|
2744
|
+
valid: errors.length === 0,
|
|
2745
|
+
allowedFields,
|
|
2746
|
+
rejectedFields,
|
|
2747
|
+
errors
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
/**
|
|
2751
|
+
* Get all organization configurations (for admin purposes)
|
|
2752
|
+
*/
|
|
2753
|
+
async getAllConfigurations() {
|
|
2754
|
+
return Array.from(this.configs.values());
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Delete organization configuration
|
|
2758
|
+
*/
|
|
2759
|
+
async deleteOrganizationConfig(organizationDid) {
|
|
2760
|
+
return this.configs.delete(organizationDid);
|
|
2761
|
+
}
|
|
2762
|
+
};
|
|
2763
|
+
|
|
2764
|
+
// src/vc/api-vc-manager.ts
|
|
2765
|
+
var APIVCManager = class {
|
|
2766
|
+
keyManager;
|
|
2767
|
+
disclosureManager;
|
|
2768
|
+
constructor(keyManager, disclosureManager) {
|
|
2769
|
+
this.keyManager = keyManager || new KeyManager();
|
|
2770
|
+
this.disclosureManager = disclosureManager || new DisclosureConfigManager();
|
|
2771
|
+
SDJwtClient.setKeyManager(this.keyManager);
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Issue an SD-JWT VC with selective disclosure
|
|
2775
|
+
*/
|
|
2776
|
+
async issueSDJWTVC(request) {
|
|
2777
|
+
if (!request.issuer || !request.subject) {
|
|
2778
|
+
throw new Error("Issuer and subject DIDs are required");
|
|
2779
|
+
}
|
|
2780
|
+
const privateKey = await this.keyManager.getKey(request.issuer);
|
|
2781
|
+
if (!privateKey) {
|
|
2782
|
+
throw new Error(`Private key not found for issuer: ${request.issuer}`);
|
|
2783
|
+
}
|
|
2784
|
+
const now = /* @__PURE__ */ new Date();
|
|
2785
|
+
const iat = Math.floor(now.getTime() / 1e3);
|
|
2786
|
+
const expirationDate = request.expirationDate || new Date(now.getTime() + 24 * 60 * 60 * 1e3);
|
|
2787
|
+
const exp = Math.floor(expirationDate.getTime() / 1e3);
|
|
2788
|
+
const vcPayload = {
|
|
2789
|
+
iss: request.issuer,
|
|
2790
|
+
sub: request.subject,
|
|
2791
|
+
iat,
|
|
2792
|
+
exp,
|
|
2793
|
+
vct: request.type || import_ai_identity_types2.CredentialType.TEMPORARY,
|
|
2794
|
+
// IETF SD-JWT VC credential type
|
|
2795
|
+
// Direct claims (can be selectively disclosed)
|
|
2796
|
+
...request.claims
|
|
2797
|
+
};
|
|
2798
|
+
const disclosureConfig = await this.disclosureManager.getSelectiveDisclosureFields(
|
|
2799
|
+
request.issuer,
|
|
2800
|
+
request.type || import_ai_identity_types2.CredentialType.TEMPORARY,
|
|
2801
|
+
request.selectiveDisclosureFields
|
|
2802
|
+
);
|
|
2803
|
+
const credential = await SDJwtClient.issueSDJWT(
|
|
2804
|
+
vcPayload,
|
|
2805
|
+
privateKey,
|
|
2806
|
+
disclosureConfig.selectiveFields
|
|
2807
|
+
);
|
|
2808
|
+
return {
|
|
2809
|
+
credential,
|
|
2810
|
+
issuer: request.issuer,
|
|
2811
|
+
subject: request.subject,
|
|
2812
|
+
type: request.type || import_ai_identity_types2.CredentialType.TEMPORARY,
|
|
2813
|
+
expiresAt: expirationDate
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* Verify an SD-JWT VC
|
|
2818
|
+
*/
|
|
2819
|
+
async verifySDJWTVC(credential) {
|
|
2820
|
+
try {
|
|
2821
|
+
if (!credential || typeof credential !== "string") {
|
|
2822
|
+
return {
|
|
2823
|
+
valid: false,
|
|
2824
|
+
error: "Invalid credential format"
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
const result = await SDJwtClient.verifySDJWT(credential);
|
|
2828
|
+
if (!result.valid || !result.payload) {
|
|
2829
|
+
return {
|
|
2830
|
+
valid: false,
|
|
2831
|
+
error: result.error || "Verification failed"
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
return {
|
|
2835
|
+
valid: true,
|
|
2836
|
+
payload: {
|
|
2837
|
+
iss: result.payload.iss,
|
|
2838
|
+
sub: result.payload.sub,
|
|
2839
|
+
vct: result.payload.vct,
|
|
2840
|
+
exp: result.payload.exp,
|
|
2841
|
+
iat: result.payload.iat,
|
|
2842
|
+
// Include any other claims from the payload
|
|
2843
|
+
...Object.fromEntries(
|
|
2844
|
+
Object.entries(result.payload).filter(
|
|
2845
|
+
([key]) => !["iss", "sub", "vct", "exp", "iat"].includes(key)
|
|
2846
|
+
)
|
|
2847
|
+
)
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
} catch (error) {
|
|
2851
|
+
return {
|
|
2852
|
+
valid: false,
|
|
2853
|
+
error: `Verification error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Issue a project access credential
|
|
2859
|
+
*/
|
|
2860
|
+
async issueProjectAccessCredential(agentDid, projectId, permissions, issuerDid, expirationHours = 24) {
|
|
2861
|
+
const expirationDate = new Date(Date.now() + expirationHours * 60 * 60 * 1e3);
|
|
2862
|
+
return this.issueSDJWTVC({
|
|
2863
|
+
issuer: issuerDid,
|
|
2864
|
+
subject: agentDid,
|
|
2865
|
+
type: import_ai_identity_types2.CredentialType.PROJECT_ACCESS,
|
|
2866
|
+
claims: {
|
|
2867
|
+
projectId,
|
|
2868
|
+
permissions,
|
|
2869
|
+
role: "developer"
|
|
2870
|
+
},
|
|
2871
|
+
expirationDate,
|
|
2872
|
+
projectId,
|
|
2873
|
+
selectiveDisclosureFields: ["permissions"]
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Issue a tool access credential
|
|
2878
|
+
*/
|
|
2879
|
+
async issueToolAccessCredential(agentDid, toolName, actions, projectId, issuerDid, expirationHours = 24) {
|
|
2880
|
+
const expirationDate = new Date(Date.now() + expirationHours * 60 * 60 * 1e3);
|
|
2881
|
+
return this.issueSDJWTVC({
|
|
2882
|
+
issuer: issuerDid,
|
|
2883
|
+
subject: agentDid,
|
|
2884
|
+
type: import_ai_identity_types2.CredentialType.TOOL_ACCESS,
|
|
2885
|
+
claims: {
|
|
2886
|
+
tool: toolName,
|
|
2887
|
+
actions,
|
|
2888
|
+
projectId
|
|
2889
|
+
},
|
|
2890
|
+
expirationDate,
|
|
2891
|
+
projectId,
|
|
2892
|
+
selectiveDisclosureFields: ["actions", "tool"]
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
/**
|
|
2896
|
+
* Issue a multi-tool access credential
|
|
2897
|
+
*/
|
|
2898
|
+
async issueMultiToolCredential(agentDid, toolPermissions, projectId, issuerDid, expirationHours = 24) {
|
|
2899
|
+
const expirationDate = new Date(Date.now() + expirationHours * 60 * 60 * 1e3);
|
|
2900
|
+
return this.issueSDJWTVC({
|
|
2901
|
+
issuer: issuerDid,
|
|
2902
|
+
subject: agentDid,
|
|
2903
|
+
type: import_ai_identity_types2.CredentialType.TOOL_ACCESS,
|
|
2904
|
+
claims: {
|
|
2905
|
+
toolPermissions,
|
|
2906
|
+
projectId
|
|
2907
|
+
},
|
|
2908
|
+
expirationDate,
|
|
2909
|
+
projectId,
|
|
2910
|
+
selectiveDisclosureFields: ["toolPermissions"]
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
/**
|
|
2914
|
+
* Issue an admin credential
|
|
2915
|
+
*/
|
|
2916
|
+
async issueAdminCredential(agentDid, scope, projectId, issuerDid, expirationHours = 8) {
|
|
2917
|
+
const expirationDate = new Date(Date.now() + expirationHours * 60 * 60 * 1e3);
|
|
2918
|
+
return this.issueSDJWTVC({
|
|
2919
|
+
issuer: issuerDid,
|
|
2920
|
+
subject: agentDid,
|
|
2921
|
+
type: import_ai_identity_types2.CredentialType.ADMIN,
|
|
2922
|
+
claims: {
|
|
2923
|
+
scope,
|
|
2924
|
+
projectId,
|
|
2925
|
+
adminLevel: scope === "global" ? "super" : "project"
|
|
2926
|
+
},
|
|
2927
|
+
expirationDate,
|
|
2928
|
+
projectId,
|
|
2929
|
+
selectiveDisclosureFields: ["adminLevel", "scope"]
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
};
|
|
2933
|
+
|
|
2934
|
+
// src/organization/key-rotation-manager.ts
|
|
2935
|
+
var KeyRotationManager = class {
|
|
2936
|
+
keyManager;
|
|
2937
|
+
config;
|
|
2938
|
+
constructor(keyManager, config) {
|
|
2939
|
+
this.keyManager = keyManager;
|
|
2940
|
+
this.config = {
|
|
2941
|
+
rotationInterval: 24 * 30,
|
|
2942
|
+
// 30 days default
|
|
2943
|
+
keepOldKeys: 5,
|
|
2944
|
+
// Keep 5 old keys
|
|
2945
|
+
warningThreshold: 24,
|
|
2946
|
+
// Warn 1 day before
|
|
2947
|
+
...config
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
/**
|
|
2951
|
+
* Check if organization keys need rotation
|
|
2952
|
+
*/
|
|
2953
|
+
async checkRotationStatus(organizationDid) {
|
|
2954
|
+
const now = /* @__PURE__ */ new Date();
|
|
2955
|
+
const nextRotationDate = new Date(now.getTime() + this.config.rotationInterval * 60 * 60 * 1e3);
|
|
2956
|
+
return {
|
|
2957
|
+
currentKeyId: organizationDid,
|
|
2958
|
+
nextRotationDate,
|
|
2959
|
+
oldKeys: [],
|
|
2960
|
+
needsRotation: false,
|
|
2961
|
+
warningActive: false
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Rotate organization keys
|
|
2966
|
+
* NOTE: Currently not implemented for did:jwk
|
|
2967
|
+
*/
|
|
2968
|
+
async rotateOrganizationKeys(organizationDid) {
|
|
2969
|
+
throw new Error("Key rotation is not supported for did:jwk. Consider using did:web for production environments that require key rotation.");
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Get old keys for verification (useful for grace periods)
|
|
2973
|
+
*/
|
|
2974
|
+
async getOldKeysForVerification(organizationDid) {
|
|
2975
|
+
return [];
|
|
2976
|
+
}
|
|
2977
|
+
/**
|
|
2978
|
+
* Plan future key rotation (for did:web or other mutable DID methods)
|
|
2979
|
+
*/
|
|
2980
|
+
async planKeyRotation(organizationDid) {
|
|
2981
|
+
const now = /* @__PURE__ */ new Date();
|
|
2982
|
+
const plannedRotationDate = new Date(now.getTime() + this.config.rotationInterval * 60 * 60 * 1e3);
|
|
2983
|
+
const currentKeyAge = 0;
|
|
2984
|
+
return {
|
|
2985
|
+
plannedRotationDate,
|
|
2986
|
+
currentKeyAge,
|
|
2987
|
+
recommendedAction: "none"
|
|
2988
|
+
// No rotation needed for did:jwk
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Update rotation configuration
|
|
2993
|
+
*/
|
|
2994
|
+
updateConfig(newConfig) {
|
|
2995
|
+
this.config = {
|
|
2996
|
+
...this.config,
|
|
2997
|
+
...newConfig
|
|
2998
|
+
};
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* Get current configuration
|
|
3002
|
+
*/
|
|
3003
|
+
getConfig() {
|
|
3004
|
+
return { ...this.config };
|
|
3005
|
+
}
|
|
3006
|
+
};
|
|
3007
|
+
|
|
3008
|
+
// src/monitoring/metrics-manager.ts
|
|
3009
|
+
var MetricsManager = class {
|
|
3010
|
+
metrics = /* @__PURE__ */ new Map();
|
|
3011
|
+
operations = [];
|
|
3012
|
+
maxOperationHistory = 1e3;
|
|
3013
|
+
/**
|
|
3014
|
+
* Start tracking an operation
|
|
3015
|
+
*/
|
|
3016
|
+
startOperation(operation, metadata) {
|
|
3017
|
+
const operationId = `${operation}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
3018
|
+
const startTime = performance.now();
|
|
3019
|
+
this.operations.push({
|
|
3020
|
+
operation,
|
|
3021
|
+
startTime,
|
|
3022
|
+
endTime: 0,
|
|
3023
|
+
success: false,
|
|
3024
|
+
...metadata
|
|
3025
|
+
});
|
|
3026
|
+
return operationId;
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* End tracking an operation
|
|
3030
|
+
*/
|
|
3031
|
+
endOperation(operationId, success, error) {
|
|
3032
|
+
const endTime = performance.now();
|
|
3033
|
+
const operation = this.operations[this.operations.length - 1];
|
|
3034
|
+
if (operation) {
|
|
3035
|
+
operation.endTime = endTime;
|
|
3036
|
+
operation.success = success;
|
|
3037
|
+
operation.error = error;
|
|
3038
|
+
this.updateMetrics(operation);
|
|
3039
|
+
}
|
|
3040
|
+
if (this.operations.length > this.maxOperationHistory) {
|
|
3041
|
+
this.operations = this.operations.slice(-this.maxOperationHistory);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
/**
|
|
3045
|
+
* Update aggregated metrics
|
|
3046
|
+
*/
|
|
3047
|
+
updateMetrics(operation) {
|
|
3048
|
+
const key = operation.issuerDid || "global";
|
|
3049
|
+
let metrics = this.metrics.get(key);
|
|
3050
|
+
if (!metrics) {
|
|
3051
|
+
metrics = {
|
|
3052
|
+
issuanceCount: 0,
|
|
3053
|
+
verificationCount: 0,
|
|
3054
|
+
failedIssuances: 0,
|
|
3055
|
+
failedVerifications: 0,
|
|
3056
|
+
averageIssuanceTime: 0,
|
|
3057
|
+
averageVerificationTime: 0,
|
|
3058
|
+
cacheHitRate: 0,
|
|
3059
|
+
lastActivity: /* @__PURE__ */ new Date()
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
const duration = operation.endTime - operation.startTime;
|
|
3063
|
+
if (operation.operation === "issue") {
|
|
3064
|
+
metrics.issuanceCount++;
|
|
3065
|
+
if (!operation.success) {
|
|
3066
|
+
metrics.failedIssuances++;
|
|
3067
|
+
}
|
|
3068
|
+
metrics.averageIssuanceTime = (metrics.averageIssuanceTime * (metrics.issuanceCount - 1) + duration) / metrics.issuanceCount;
|
|
3069
|
+
} else {
|
|
3070
|
+
metrics.verificationCount++;
|
|
3071
|
+
if (!operation.success) {
|
|
3072
|
+
metrics.failedVerifications++;
|
|
3073
|
+
}
|
|
3074
|
+
metrics.averageVerificationTime = (metrics.averageVerificationTime * (metrics.verificationCount - 1) + duration) / metrics.verificationCount;
|
|
3075
|
+
}
|
|
3076
|
+
metrics.lastActivity = /* @__PURE__ */ new Date();
|
|
3077
|
+
this.metrics.set(key, metrics);
|
|
3078
|
+
}
|
|
3079
|
+
/**
|
|
3080
|
+
* Get metrics for a specific issuer or global
|
|
3081
|
+
*/
|
|
3082
|
+
getMetrics(issuerDid) {
|
|
3083
|
+
const key = issuerDid || "global";
|
|
3084
|
+
return this.metrics.get(key) || null;
|
|
3085
|
+
}
|
|
3086
|
+
/**
|
|
3087
|
+
* Get all metrics
|
|
3088
|
+
*/
|
|
3089
|
+
getAllMetrics() {
|
|
3090
|
+
return new Map(this.metrics);
|
|
3091
|
+
}
|
|
3092
|
+
/**
|
|
3093
|
+
* Get recent operations
|
|
3094
|
+
*/
|
|
3095
|
+
getRecentOperations(limit = 100) {
|
|
3096
|
+
return this.operations.slice(-limit);
|
|
3097
|
+
}
|
|
3098
|
+
/**
|
|
3099
|
+
* Get operation statistics
|
|
3100
|
+
*/
|
|
3101
|
+
getOperationStats() {
|
|
3102
|
+
const now = Date.now();
|
|
3103
|
+
const oneMinuteAgo = now - 6e4;
|
|
3104
|
+
const recentOps = this.operations.filter((op) => op.startTime > oneMinuteAgo);
|
|
3105
|
+
const successfulOps = this.operations.filter((op) => op.success);
|
|
3106
|
+
const totalDuration = this.operations.reduce((sum, op) => sum + (op.endTime - op.startTime), 0);
|
|
3107
|
+
return {
|
|
3108
|
+
totalOperations: this.operations.length,
|
|
3109
|
+
successRate: this.operations.length > 0 ? successfulOps.length / this.operations.length : 0,
|
|
3110
|
+
averageResponseTime: this.operations.length > 0 ? totalDuration / this.operations.length : 0,
|
|
3111
|
+
operationsPerMinute: recentOps.length
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
/**
|
|
3115
|
+
* Update cache hit rate
|
|
3116
|
+
*/
|
|
3117
|
+
updateCacheHitRate(issuerDid, hit) {
|
|
3118
|
+
const key = issuerDid || "global";
|
|
3119
|
+
let metrics = this.metrics.get(key);
|
|
3120
|
+
if (!metrics) {
|
|
3121
|
+
metrics = {
|
|
3122
|
+
issuanceCount: 0,
|
|
3123
|
+
verificationCount: 0,
|
|
3124
|
+
failedIssuances: 0,
|
|
3125
|
+
failedVerifications: 0,
|
|
3126
|
+
averageIssuanceTime: 0,
|
|
3127
|
+
averageVerificationTime: 0,
|
|
3128
|
+
cacheHitRate: 0,
|
|
3129
|
+
lastActivity: /* @__PURE__ */ new Date()
|
|
3130
|
+
};
|
|
3131
|
+
}
|
|
3132
|
+
const currentRate = metrics.cacheHitRate;
|
|
3133
|
+
const newRate = hit ? Math.min(1, currentRate + 0.1) : Math.max(0, currentRate - 0.1);
|
|
3134
|
+
metrics.cacheHitRate = newRate;
|
|
3135
|
+
metrics.lastActivity = /* @__PURE__ */ new Date();
|
|
3136
|
+
this.metrics.set(key, metrics);
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Reset metrics
|
|
3140
|
+
*/
|
|
3141
|
+
resetMetrics(issuerDid) {
|
|
3142
|
+
if (issuerDid) {
|
|
3143
|
+
this.metrics.delete(issuerDid);
|
|
3144
|
+
} else {
|
|
3145
|
+
this.metrics.clear();
|
|
3146
|
+
this.operations = [];
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Export metrics as JSON
|
|
3151
|
+
*/
|
|
3152
|
+
exportMetrics() {
|
|
3153
|
+
return {
|
|
3154
|
+
aggregatedMetrics: Object.fromEntries(this.metrics),
|
|
3155
|
+
recentOperations: this.getRecentOperations(50),
|
|
3156
|
+
summary: this.getOperationStats()
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
3159
|
+
};
|
|
3160
|
+
|
|
3161
|
+
// src/revocation/revocation-manager.ts
|
|
3162
|
+
var RevocationManager = class {
|
|
3163
|
+
revocationLists = /* @__PURE__ */ new Map();
|
|
3164
|
+
credentialStatuses = /* @__PURE__ */ new Map();
|
|
3165
|
+
/**
|
|
3166
|
+
* Create a new revocation list
|
|
3167
|
+
*/
|
|
3168
|
+
async createRevocationList(issuer, type = "StatusList2021", purpose = "revocation") {
|
|
3169
|
+
const id = `${issuer}#revocation-list-${Date.now()}`;
|
|
3170
|
+
const now = /* @__PURE__ */ new Date();
|
|
3171
|
+
const revocationList = {
|
|
3172
|
+
id,
|
|
3173
|
+
issuer,
|
|
3174
|
+
type,
|
|
3175
|
+
statusPurpose: purpose,
|
|
3176
|
+
encodedList: this.createEmptyBitString(1e5),
|
|
3177
|
+
// Start with 100k slots
|
|
3178
|
+
entries: [],
|
|
3179
|
+
createdAt: now,
|
|
3180
|
+
updatedAt: now
|
|
3181
|
+
};
|
|
3182
|
+
this.revocationLists.set(id, revocationList);
|
|
3183
|
+
return revocationList;
|
|
3184
|
+
}
|
|
3185
|
+
/**
|
|
3186
|
+
* Add credential to revocation list
|
|
3187
|
+
*/
|
|
3188
|
+
async addCredentialToRevocationList(credentialId, listId, statusIndex) {
|
|
3189
|
+
const revocationList = this.revocationLists.get(listId);
|
|
3190
|
+
if (!revocationList) {
|
|
3191
|
+
throw new Error(`Revocation list not found: ${listId}`);
|
|
3192
|
+
}
|
|
3193
|
+
const index = statusIndex ?? this.findNextAvailableIndex(listId);
|
|
3194
|
+
const statusInfo = {
|
|
3195
|
+
id: `${credentialId}#status`,
|
|
3196
|
+
type: "StatusList2021Entry",
|
|
3197
|
+
statusListIndex: index,
|
|
3198
|
+
statusListCredential: listId
|
|
3199
|
+
};
|
|
3200
|
+
this.credentialStatuses.set(credentialId, statusInfo);
|
|
3201
|
+
return statusInfo;
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* Revoke a credential
|
|
3205
|
+
*/
|
|
3206
|
+
async revokeCredential(credentialId, reason, revokedBy) {
|
|
3207
|
+
const statusInfo = this.credentialStatuses.get(credentialId);
|
|
3208
|
+
if (!statusInfo) {
|
|
3209
|
+
throw new Error(`Credential not found in revocation tracking: ${credentialId}`);
|
|
3210
|
+
}
|
|
3211
|
+
const revocationList = this.revocationLists.get(statusInfo.statusListCredential);
|
|
3212
|
+
if (!revocationList) {
|
|
3213
|
+
throw new Error(`Revocation list not found: ${statusInfo.statusListCredential}`);
|
|
3214
|
+
}
|
|
3215
|
+
const updatedBitString = this.setBitInString(
|
|
3216
|
+
revocationList.encodedList,
|
|
3217
|
+
statusInfo.statusListIndex,
|
|
3218
|
+
true
|
|
3219
|
+
);
|
|
3220
|
+
const revocationEntry = {
|
|
3221
|
+
credentialId,
|
|
3222
|
+
revocationDate: /* @__PURE__ */ new Date(),
|
|
3223
|
+
reason,
|
|
3224
|
+
revokedBy: revokedBy || "system"
|
|
3225
|
+
};
|
|
3226
|
+
revocationList.encodedList = updatedBitString;
|
|
3227
|
+
revocationList.entries.push(revocationEntry);
|
|
3228
|
+
revocationList.updatedAt = /* @__PURE__ */ new Date();
|
|
3229
|
+
statusInfo.revocationReason = reason;
|
|
3230
|
+
statusInfo.revocationDate = /* @__PURE__ */ new Date();
|
|
3231
|
+
this.revocationLists.set(revocationList.id, revocationList);
|
|
3232
|
+
this.credentialStatuses.set(credentialId, statusInfo);
|
|
3233
|
+
return true;
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
3236
|
+
* Check if credential is revoked
|
|
3237
|
+
*/
|
|
3238
|
+
async isCredentialRevoked(credentialId) {
|
|
3239
|
+
const statusInfo = this.credentialStatuses.get(credentialId);
|
|
3240
|
+
if (!statusInfo) {
|
|
3241
|
+
return { revoked: false };
|
|
3242
|
+
}
|
|
3243
|
+
const revocationList = this.revocationLists.get(statusInfo.statusListCredential);
|
|
3244
|
+
if (!revocationList) {
|
|
3245
|
+
return { revoked: false };
|
|
3246
|
+
}
|
|
3247
|
+
const isRevoked = this.getBitFromString(
|
|
3248
|
+
revocationList.encodedList,
|
|
3249
|
+
statusInfo.statusListIndex
|
|
3250
|
+
);
|
|
3251
|
+
if (!isRevoked) {
|
|
3252
|
+
return { revoked: false };
|
|
3253
|
+
}
|
|
3254
|
+
const entry = revocationList.entries.find((e) => e.credentialId === credentialId);
|
|
3255
|
+
return {
|
|
3256
|
+
revoked: true,
|
|
3257
|
+
reason: entry?.reason,
|
|
3258
|
+
revokedDate: entry?.revocationDate,
|
|
3259
|
+
revokedBy: entry?.revokedBy
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Get credential status info
|
|
3264
|
+
*/
|
|
3265
|
+
async getCredentialStatus(credentialId) {
|
|
3266
|
+
return this.credentialStatuses.get(credentialId) || null;
|
|
3267
|
+
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Get revocation list
|
|
3270
|
+
*/
|
|
3271
|
+
async getRevocationList(listId) {
|
|
3272
|
+
return this.revocationLists.get(listId) || null;
|
|
3273
|
+
}
|
|
3274
|
+
/**
|
|
3275
|
+
* Get all revocation lists for an issuer
|
|
3276
|
+
*/
|
|
3277
|
+
async getIssuerRevocationLists(issuer) {
|
|
3278
|
+
return Array.from(this.revocationLists.values()).filter((list) => list.issuer === issuer);
|
|
3279
|
+
}
|
|
3280
|
+
/**
|
|
3281
|
+
* Restore/unreovke a credential
|
|
3282
|
+
*/
|
|
3283
|
+
async restoreCredential(credentialId) {
|
|
3284
|
+
const statusInfo = this.credentialStatuses.get(credentialId);
|
|
3285
|
+
if (!statusInfo) {
|
|
3286
|
+
throw new Error(`Credential not found in revocation tracking: ${credentialId}`);
|
|
3287
|
+
}
|
|
3288
|
+
const revocationList = this.revocationLists.get(statusInfo.statusListCredential);
|
|
3289
|
+
if (!revocationList) {
|
|
3290
|
+
throw new Error(`Revocation list not found: ${statusInfo.statusListCredential}`);
|
|
3291
|
+
}
|
|
3292
|
+
const updatedBitString = this.setBitInString(
|
|
3293
|
+
revocationList.encodedList,
|
|
3294
|
+
statusInfo.statusListIndex,
|
|
3295
|
+
false
|
|
3296
|
+
);
|
|
3297
|
+
revocationList.encodedList = updatedBitString;
|
|
3298
|
+
revocationList.updatedAt = /* @__PURE__ */ new Date();
|
|
3299
|
+
delete statusInfo.revocationReason;
|
|
3300
|
+
delete statusInfo.revocationDate;
|
|
3301
|
+
this.revocationLists.set(revocationList.id, revocationList);
|
|
3302
|
+
this.credentialStatuses.set(credentialId, statusInfo);
|
|
3303
|
+
return true;
|
|
3304
|
+
}
|
|
3305
|
+
/**
|
|
3306
|
+
* Create empty bit string
|
|
3307
|
+
*/
|
|
3308
|
+
createEmptyBitString(size) {
|
|
3309
|
+
const bytes = Math.ceil(size / 8);
|
|
3310
|
+
const buffer = Buffer.alloc(bytes, 0);
|
|
3311
|
+
return buffer.toString("base64");
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Set bit in encoded string
|
|
3315
|
+
*/
|
|
3316
|
+
setBitInString(encodedString, index, value) {
|
|
3317
|
+
const buffer = Buffer.from(encodedString, "base64");
|
|
3318
|
+
const byteIndex = Math.floor(index / 8);
|
|
3319
|
+
const bitIndex = index % 8;
|
|
3320
|
+
if (byteIndex >= buffer.length) {
|
|
3321
|
+
throw new Error(`Bit index ${index} is out of bounds for buffer of size ${buffer.length * 8}`);
|
|
3322
|
+
}
|
|
3323
|
+
if (value) {
|
|
3324
|
+
buffer[byteIndex] |= 1 << 7 - bitIndex;
|
|
3325
|
+
} else {
|
|
3326
|
+
buffer[byteIndex] &= ~(1 << 7 - bitIndex);
|
|
3327
|
+
}
|
|
3328
|
+
return buffer.toString("base64");
|
|
3329
|
+
}
|
|
3330
|
+
/**
|
|
3331
|
+
* Get bit from encoded string
|
|
3332
|
+
*/
|
|
3333
|
+
getBitFromString(encodedString, index) {
|
|
3334
|
+
const buffer = Buffer.from(encodedString, "base64");
|
|
3335
|
+
const byteIndex = Math.floor(index / 8);
|
|
3336
|
+
const bitIndex = index % 8;
|
|
3337
|
+
if (byteIndex >= buffer.length) {
|
|
3338
|
+
return false;
|
|
3339
|
+
}
|
|
3340
|
+
return (buffer[byteIndex] & 1 << 7 - bitIndex) !== 0;
|
|
3341
|
+
}
|
|
3342
|
+
/**
|
|
3343
|
+
* Find next available index in revocation list
|
|
3344
|
+
*/
|
|
3345
|
+
findNextAvailableIndex(listId) {
|
|
3346
|
+
const usedIndices = Array.from(this.credentialStatuses.values()).filter((status) => status.statusListCredential === listId).map((status) => status.statusListIndex);
|
|
3347
|
+
let index = 0;
|
|
3348
|
+
while (usedIndices.includes(index)) {
|
|
3349
|
+
index++;
|
|
3350
|
+
}
|
|
3351
|
+
return index;
|
|
3352
|
+
}
|
|
3353
|
+
/**
|
|
3354
|
+
* Export revocation list in standard format
|
|
3355
|
+
*/
|
|
3356
|
+
async exportRevocationList(listId) {
|
|
3357
|
+
const list = this.revocationLists.get(listId);
|
|
3358
|
+
if (!list) {
|
|
3359
|
+
return null;
|
|
3360
|
+
}
|
|
3361
|
+
return {
|
|
3362
|
+
"@context": [
|
|
3363
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
3364
|
+
"https://w3id.org/vc/status-list/2021/v1"
|
|
3365
|
+
],
|
|
3366
|
+
id: list.id,
|
|
3367
|
+
type: ["VerifiableCredential", "StatusList2021Credential"],
|
|
3368
|
+
issuer: list.issuer,
|
|
3369
|
+
validFrom: list.createdAt.toISOString(),
|
|
3370
|
+
credentialSubject: {
|
|
3371
|
+
id: `${list.id}#list`,
|
|
3372
|
+
type: list.type,
|
|
3373
|
+
statusPurpose: list.statusPurpose,
|
|
3374
|
+
encodedList: list.encodedList
|
|
3375
|
+
}
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
3379
|
+
|
|
3380
|
+
// src/constraint/constraint-evaluator.ts
|
|
3381
|
+
var DEFAULT_OPTIONS = {
|
|
3382
|
+
invocationWarningThreshold: 5,
|
|
3383
|
+
riskWarningRatio: 0.8,
|
|
3384
|
+
defaultTimezone: "UTC"
|
|
3385
|
+
};
|
|
3386
|
+
var ConstraintEvaluator = class {
|
|
3387
|
+
options;
|
|
3388
|
+
constructor(options) {
|
|
3389
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
3390
|
+
}
|
|
3391
|
+
/**
|
|
3392
|
+
* 制約を総合評価
|
|
3393
|
+
*/
|
|
3394
|
+
evaluate(constraints, context, currentInvocations, expiresAt) {
|
|
3395
|
+
const violations = [];
|
|
3396
|
+
const warnings = [];
|
|
3397
|
+
const expirationResult = this.checkExpiration(expiresAt, constraints.expiresAt);
|
|
3398
|
+
if (expirationResult.violation) {
|
|
3399
|
+
violations.push(expirationResult.violation);
|
|
3400
|
+
}
|
|
3401
|
+
const invocationResult = this.checkInvocationLimit(
|
|
3402
|
+
constraints.maxInvocations,
|
|
3403
|
+
currentInvocations
|
|
3404
|
+
);
|
|
3405
|
+
if (invocationResult.violation) {
|
|
3406
|
+
violations.push(invocationResult.violation);
|
|
3407
|
+
}
|
|
3408
|
+
if (invocationResult.warning) {
|
|
3409
|
+
warnings.push(invocationResult.warning);
|
|
3410
|
+
}
|
|
3411
|
+
if (constraints.timeWindow) {
|
|
3412
|
+
const timeResult = this.checkTimeWindow(
|
|
3413
|
+
constraints.timeWindow,
|
|
3414
|
+
new Date(context.timestamp)
|
|
3415
|
+
);
|
|
3416
|
+
if (timeResult.violation) {
|
|
3417
|
+
violations.push(timeResult.violation);
|
|
3418
|
+
}
|
|
3419
|
+
if (timeResult.warning) {
|
|
3420
|
+
warnings.push(timeResult.warning);
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
if (constraints.ipAllowlist && constraints.ipAllowlist.length > 0 && context.ipAddress) {
|
|
3424
|
+
const ipResult = this.checkIpAllowlist(constraints.ipAllowlist, context.ipAddress);
|
|
3425
|
+
if (ipResult.violation) {
|
|
3426
|
+
violations.push(ipResult.violation);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
if (constraints.riskThreshold !== void 0 && context.riskScore !== void 0) {
|
|
3430
|
+
const riskResult = this.checkRiskThreshold(constraints.riskThreshold, context.riskScore);
|
|
3431
|
+
if (riskResult.violation) {
|
|
3432
|
+
violations.push(riskResult.violation);
|
|
3433
|
+
}
|
|
3434
|
+
if (riskResult.warning) {
|
|
3435
|
+
warnings.push(riskResult.warning);
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
return {
|
|
3439
|
+
allowed: violations.length === 0,
|
|
3440
|
+
violations,
|
|
3441
|
+
warnings,
|
|
3442
|
+
evaluatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3443
|
+
context
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
/**
|
|
3447
|
+
* 期限チェック
|
|
3448
|
+
*/
|
|
3449
|
+
checkExpiration(grantExpiresAt, constraintExpiresAt) {
|
|
3450
|
+
const now = /* @__PURE__ */ new Date();
|
|
3451
|
+
if (grantExpiresAt && new Date(grantExpiresAt) < now) {
|
|
3452
|
+
return {
|
|
3453
|
+
violation: {
|
|
3454
|
+
type: "expired",
|
|
3455
|
+
message: `Grant expired at ${grantExpiresAt.toISOString()}`,
|
|
3456
|
+
details: { expiresAt: grantExpiresAt.toISOString(), now: now.toISOString() }
|
|
3457
|
+
}
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
if (constraintExpiresAt && new Date(constraintExpiresAt) < now) {
|
|
3461
|
+
return {
|
|
3462
|
+
violation: {
|
|
3463
|
+
type: "expired",
|
|
3464
|
+
message: `Constraint expired at ${constraintExpiresAt}`,
|
|
3465
|
+
details: { expiresAt: constraintExpiresAt, now: now.toISOString() }
|
|
3466
|
+
}
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
return {};
|
|
3470
|
+
}
|
|
3471
|
+
/**
|
|
3472
|
+
* 実行回数チェック
|
|
3473
|
+
*/
|
|
3474
|
+
checkInvocationLimit(maxInvocations, currentInvocations) {
|
|
3475
|
+
if (maxInvocations === void 0 || currentInvocations === void 0) {
|
|
3476
|
+
return {};
|
|
3477
|
+
}
|
|
3478
|
+
const remaining = maxInvocations - currentInvocations;
|
|
3479
|
+
if (remaining <= 0) {
|
|
3480
|
+
return {
|
|
3481
|
+
violation: {
|
|
3482
|
+
type: "max_invocations",
|
|
3483
|
+
message: `Invocation limit reached (${maxInvocations} max, ${currentInvocations} used)`,
|
|
3484
|
+
details: { maxInvocations, currentInvocations, remaining: 0 }
|
|
3485
|
+
}
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
if (remaining <= this.options.invocationWarningThreshold) {
|
|
3489
|
+
return {
|
|
3490
|
+
warning: {
|
|
3491
|
+
type: "approaching_limit",
|
|
3492
|
+
message: `Only ${remaining} invocations remaining out of ${maxInvocations}`,
|
|
3493
|
+
details: { maxInvocations, currentInvocations, remaining }
|
|
3494
|
+
}
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
return {};
|
|
3498
|
+
}
|
|
3499
|
+
/**
|
|
3500
|
+
* 時間帯チェック
|
|
3501
|
+
*/
|
|
3502
|
+
checkTimeWindow(timeWindow, currentTime) {
|
|
3503
|
+
const timezone = timeWindow.timezone || this.options.defaultTimezone;
|
|
3504
|
+
if (timeWindow.daysOfWeek && timeWindow.daysOfWeek.length > 0) {
|
|
3505
|
+
const currentDay = this.getDayOfWeekInTimezone(currentTime, timezone);
|
|
3506
|
+
if (!timeWindow.daysOfWeek.includes(currentDay)) {
|
|
3507
|
+
return {
|
|
3508
|
+
violation: {
|
|
3509
|
+
type: "time_window",
|
|
3510
|
+
message: `Current day (${this.getDayName(currentDay)}) is not in allowed days`,
|
|
3511
|
+
details: {
|
|
3512
|
+
currentDay,
|
|
3513
|
+
allowedDays: timeWindow.daysOfWeek,
|
|
3514
|
+
allowedDayNames: timeWindow.daysOfWeek.map((d) => this.getDayName(d))
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
};
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
if (timeWindow.start && timeWindow.end) {
|
|
3521
|
+
const currentTimeStr = this.getTimeInTimezone(currentTime, timezone);
|
|
3522
|
+
if (timeWindow.start <= timeWindow.end) {
|
|
3523
|
+
if (currentTimeStr < timeWindow.start || currentTimeStr > timeWindow.end) {
|
|
3524
|
+
return {
|
|
3525
|
+
violation: {
|
|
3526
|
+
type: "time_window",
|
|
3527
|
+
message: `Current time (${currentTimeStr}) is outside allowed window (${timeWindow.start}-${timeWindow.end})`,
|
|
3528
|
+
details: { currentTime: currentTimeStr, start: timeWindow.start, end: timeWindow.end, timezone }
|
|
3529
|
+
}
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
} else {
|
|
3533
|
+
if (currentTimeStr < timeWindow.start && currentTimeStr > timeWindow.end) {
|
|
3534
|
+
return {
|
|
3535
|
+
violation: {
|
|
3536
|
+
type: "time_window",
|
|
3537
|
+
message: `Current time (${currentTimeStr}) is outside allowed window (${timeWindow.start}-${timeWindow.end})`,
|
|
3538
|
+
details: { currentTime: currentTimeStr, start: timeWindow.start, end: timeWindow.end, timezone }
|
|
3539
|
+
}
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
const endMinutes = this.timeToMinutes(timeWindow.end);
|
|
3544
|
+
const currentMinutes = this.timeToMinutes(currentTimeStr);
|
|
3545
|
+
const minutesUntilEnd = endMinutes - currentMinutes;
|
|
3546
|
+
if (minutesUntilEnd > 0 && minutesUntilEnd <= 60) {
|
|
3547
|
+
return {
|
|
3548
|
+
warning: {
|
|
3549
|
+
type: "unusual_time",
|
|
3550
|
+
message: `Only ${minutesUntilEnd} minutes remaining in allowed time window`,
|
|
3551
|
+
details: { minutesUntilEnd, windowEnd: timeWindow.end }
|
|
3552
|
+
}
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
return {};
|
|
3557
|
+
}
|
|
3558
|
+
/**
|
|
3559
|
+
* IPアドレスチェック
|
|
3560
|
+
*/
|
|
3561
|
+
checkIpAllowlist(allowlist, ipAddress) {
|
|
3562
|
+
if (allowlist.includes("*")) {
|
|
3563
|
+
return {};
|
|
3564
|
+
}
|
|
3565
|
+
if (allowlist.includes(ipAddress)) {
|
|
3566
|
+
return {};
|
|
3567
|
+
}
|
|
3568
|
+
for (const entry of allowlist) {
|
|
3569
|
+
if (entry.includes("/")) {
|
|
3570
|
+
if (this.isIpInCidr(ipAddress, entry)) {
|
|
3571
|
+
return {};
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
return {
|
|
3576
|
+
violation: {
|
|
3577
|
+
type: "ip_allowlist",
|
|
3578
|
+
message: `IP address ${ipAddress} is not in the allowlist`,
|
|
3579
|
+
details: { ipAddress, allowlist }
|
|
3580
|
+
}
|
|
3581
|
+
};
|
|
3582
|
+
}
|
|
3583
|
+
/**
|
|
3584
|
+
* リスクスコアチェック
|
|
3585
|
+
*/
|
|
3586
|
+
checkRiskThreshold(threshold, currentScore) {
|
|
3587
|
+
if (currentScore > threshold) {
|
|
3588
|
+
return {
|
|
3589
|
+
violation: {
|
|
3590
|
+
type: "risk_threshold",
|
|
3591
|
+
message: `Risk score ${currentScore} exceeds threshold ${threshold}`,
|
|
3592
|
+
details: { currentScore, threshold }
|
|
3593
|
+
}
|
|
3594
|
+
};
|
|
3595
|
+
}
|
|
3596
|
+
const warningThreshold = threshold * this.options.riskWarningRatio;
|
|
3597
|
+
if (currentScore > warningThreshold) {
|
|
3598
|
+
return {
|
|
3599
|
+
warning: {
|
|
3600
|
+
type: "high_risk",
|
|
3601
|
+
message: `Risk score ${currentScore} is approaching threshold ${threshold}`,
|
|
3602
|
+
details: { currentScore, threshold, warningThreshold }
|
|
3603
|
+
}
|
|
3604
|
+
};
|
|
3605
|
+
}
|
|
3606
|
+
return {};
|
|
3607
|
+
}
|
|
3608
|
+
// ============================================================================
|
|
3609
|
+
// Helper Methods
|
|
3610
|
+
// ============================================================================
|
|
3611
|
+
getDayOfWeekInTimezone(date, timezone) {
|
|
3612
|
+
try {
|
|
3613
|
+
const options = { weekday: "short", timeZone: timezone };
|
|
3614
|
+
const dayStr = date.toLocaleDateString("en-US", options);
|
|
3615
|
+
const dayMap = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
|
|
3616
|
+
return dayMap[dayStr] ?? date.getDay();
|
|
3617
|
+
} catch {
|
|
3618
|
+
return date.getDay();
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
getTimeInTimezone(date, timezone) {
|
|
3622
|
+
try {
|
|
3623
|
+
const options = {
|
|
3624
|
+
hour: "2-digit",
|
|
3625
|
+
minute: "2-digit",
|
|
3626
|
+
hour12: false,
|
|
3627
|
+
timeZone: timezone
|
|
3628
|
+
};
|
|
3629
|
+
return date.toLocaleTimeString("en-US", options);
|
|
3630
|
+
} catch {
|
|
3631
|
+
return date.toISOString().slice(11, 16);
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
getDayName(day) {
|
|
3635
|
+
const names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
3636
|
+
return names[day] || "Unknown";
|
|
3637
|
+
}
|
|
3638
|
+
timeToMinutes(time) {
|
|
3639
|
+
const [hours, minutes] = time.split(":").map(Number);
|
|
3640
|
+
return hours * 60 + minutes;
|
|
3641
|
+
}
|
|
3642
|
+
isIpInCidr(ip, cidr) {
|
|
3643
|
+
try {
|
|
3644
|
+
const [range, bits] = cidr.split("/");
|
|
3645
|
+
const mask = parseInt(bits, 10);
|
|
3646
|
+
const ipNum = this.ipToNumber(ip);
|
|
3647
|
+
const rangeNum = this.ipToNumber(range);
|
|
3648
|
+
const maskNum = ~(2 ** (32 - mask) - 1);
|
|
3649
|
+
return (ipNum & maskNum) === (rangeNum & maskNum);
|
|
3650
|
+
} catch {
|
|
3651
|
+
return false;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
ipToNumber(ip) {
|
|
3655
|
+
const parts = ip.split(".").map(Number);
|
|
3656
|
+
return (parts[0] << 24) + (parts[1] << 16) + (parts[2] << 8) + parts[3];
|
|
3657
|
+
}
|
|
3658
|
+
};
|
|
3659
|
+
var defaultConstraintEvaluator = new ConstraintEvaluator();
|
|
3660
|
+
function evaluateConstraints(constraints, context, currentInvocations, expiresAt) {
|
|
3661
|
+
return defaultConstraintEvaluator.evaluate(constraints, context, currentInvocations, expiresAt);
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
// src/registry/action-registry.ts
|
|
3665
|
+
var import_ajv = __toESM(require("ajv"));
|
|
3666
|
+
var import_ajv_formats = __toESM(require("ajv-formats"));
|
|
3667
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
3668
|
+
var import_node_path = __toESM(require("path"));
|
|
3669
|
+
var actionMetaSchema = {
|
|
3670
|
+
$id: "https://vess.ai/schemas/action-meta.json",
|
|
3671
|
+
type: "object",
|
|
3672
|
+
additionalProperties: false,
|
|
3673
|
+
required: ["action", "resource_type", "required_relations", "required_scopes", "version"],
|
|
3674
|
+
properties: {
|
|
3675
|
+
action: { type: "string", minLength: 1 },
|
|
3676
|
+
resource_type: {
|
|
3677
|
+
type: "string",
|
|
3678
|
+
enum: ["SlackChannel", "GitHubRepo", "DriveFile"]
|
|
3679
|
+
},
|
|
3680
|
+
required_relations: {
|
|
3681
|
+
type: "array",
|
|
3682
|
+
minItems: 1,
|
|
3683
|
+
items: {
|
|
3684
|
+
type: "string",
|
|
3685
|
+
enum: ["viewer", "editor", "admin", "owner", "act_as"]
|
|
3686
|
+
}
|
|
3687
|
+
},
|
|
3688
|
+
required_scopes: {
|
|
3689
|
+
type: "array",
|
|
3690
|
+
minItems: 1,
|
|
3691
|
+
items: { type: "string", minLength: 1 }
|
|
3692
|
+
},
|
|
3693
|
+
capability: { type: "string" },
|
|
3694
|
+
input_schema: { type: "object" },
|
|
3695
|
+
// ← ここは後段で「JSON Schemaとして」別検証
|
|
3696
|
+
constraints: { type: "object", additionalProperties: true },
|
|
3697
|
+
effects: {
|
|
3698
|
+
type: "array",
|
|
3699
|
+
items: { type: "string", minLength: 1 }
|
|
3700
|
+
},
|
|
3701
|
+
risk: { type: "string", enum: ["low", "medium", "high"] },
|
|
3702
|
+
version: { type: "string", minLength: 1 }
|
|
3703
|
+
}
|
|
3704
|
+
};
|
|
3705
|
+
var capabilityMetaSchema = {
|
|
3706
|
+
$id: "https://vess.ai/schemas/capability-meta.json",
|
|
3707
|
+
type: "object",
|
|
3708
|
+
additionalProperties: false,
|
|
3709
|
+
required: ["capability", "includes", "version"],
|
|
3710
|
+
properties: {
|
|
3711
|
+
capability: { type: "string", minLength: 1 },
|
|
3712
|
+
description: { type: "string" },
|
|
3713
|
+
includes: {
|
|
3714
|
+
type: "array",
|
|
3715
|
+
minItems: 1,
|
|
3716
|
+
items: { type: "string", minLength: 1 }
|
|
3717
|
+
},
|
|
3718
|
+
version: { type: "string", minLength: 1 }
|
|
3719
|
+
}
|
|
3720
|
+
};
|
|
3721
|
+
var registrySchema = {
|
|
3722
|
+
$id: "https://vess.ai/schemas/action-registry.json",
|
|
3723
|
+
type: "object",
|
|
3724
|
+
additionalProperties: false,
|
|
3725
|
+
required: ["registry_version", "actions"],
|
|
3726
|
+
properties: {
|
|
3727
|
+
registry_version: { type: "string", minLength: 1 },
|
|
3728
|
+
actions: {
|
|
3729
|
+
type: "array",
|
|
3730
|
+
minItems: 1,
|
|
3731
|
+
items: { $ref: "https://vess.ai/schemas/action-meta.json" }
|
|
3732
|
+
},
|
|
3733
|
+
capabilities: {
|
|
3734
|
+
type: "array",
|
|
3735
|
+
items: { $ref: "https://vess.ai/schemas/capability-meta.json" }
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
};
|
|
3739
|
+
function createAjv() {
|
|
3740
|
+
const ajv = new import_ajv.default({
|
|
3741
|
+
allErrors: true,
|
|
3742
|
+
strict: true,
|
|
3743
|
+
allowUnionTypes: true
|
|
3744
|
+
// draft-2020-12 デフォルト。input_schema のメタ検証に使う。
|
|
3745
|
+
});
|
|
3746
|
+
(0, import_ajv_formats.default)(ajv);
|
|
3747
|
+
ajv.addSchema(actionMetaSchema);
|
|
3748
|
+
ajv.addSchema(capabilityMetaSchema);
|
|
3749
|
+
ajv.addSchema(registrySchema);
|
|
3750
|
+
return ajv;
|
|
3751
|
+
}
|
|
3752
|
+
function validateRegistryObject(registry) {
|
|
3753
|
+
const ajv = createAjv();
|
|
3754
|
+
const validate = ajv.getSchema("https://vess.ai/schemas/action-registry.json");
|
|
3755
|
+
if (!validate) {
|
|
3756
|
+
return { ok: false, errors: ["Ajv schema not loaded"] };
|
|
3757
|
+
}
|
|
3758
|
+
const valid = validate(registry);
|
|
3759
|
+
if (!valid) {
|
|
3760
|
+
return {
|
|
3761
|
+
ok: false,
|
|
3762
|
+
errors: validate.errors ? formatAjvErrors(validate.errors) : []
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
const typed = registry;
|
|
3766
|
+
const schemaErrors = [];
|
|
3767
|
+
for (const a of typed.actions) {
|
|
3768
|
+
if (a.input_schema) {
|
|
3769
|
+
try {
|
|
3770
|
+
const local = new import_ajv.default({ strict: true, allErrors: true });
|
|
3771
|
+
(0, import_ajv_formats.default)(local);
|
|
3772
|
+
const compiled = local.compile(a.input_schema);
|
|
3773
|
+
if (compiled.errors?.length) {
|
|
3774
|
+
schemaErrors.push(
|
|
3775
|
+
`[${a.action}] input_schema errors: ${formatAjvErrors(
|
|
3776
|
+
compiled.errors
|
|
3777
|
+
).join("; ")}`
|
|
3778
|
+
);
|
|
3779
|
+
}
|
|
3780
|
+
} catch (e) {
|
|
3781
|
+
schemaErrors.push(`[${a.action}] input_schema invalid: ${e?.message ?? String(e)}`);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
if (schemaErrors.length) {
|
|
3786
|
+
return { ok: false, errors: schemaErrors };
|
|
3787
|
+
}
|
|
3788
|
+
if (typed.capabilities?.length) {
|
|
3789
|
+
const actionsSet = new Set(typed.actions.map((x) => x.action));
|
|
3790
|
+
for (const c of typed.capabilities) {
|
|
3791
|
+
for (const act of c.includes) {
|
|
3792
|
+
if (!actionsSet.has(act)) {
|
|
3793
|
+
schemaErrors.push(`[capability:${c.capability}] includes unknown action: ${act}`);
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
if (schemaErrors.length) {
|
|
3799
|
+
return { ok: false, errors: schemaErrors };
|
|
3800
|
+
}
|
|
3801
|
+
return { ok: true };
|
|
3802
|
+
}
|
|
3803
|
+
function formatAjvErrors(errors) {
|
|
3804
|
+
if (!errors?.length) return [];
|
|
3805
|
+
return errors.map((e) => {
|
|
3806
|
+
const instancePath = e.instancePath || "/";
|
|
3807
|
+
const msg = e.message || "invalid";
|
|
3808
|
+
const params = e.params && Object.keys(e.params).length ? ` (${JSON.stringify(e.params)})` : "";
|
|
3809
|
+
return `${instancePath}: ${msg}${params}`;
|
|
3810
|
+
});
|
|
3811
|
+
}
|
|
3812
|
+
async function loadActionRegistryFromFile(filePath) {
|
|
3813
|
+
const abs = import_node_path.default.resolve(filePath);
|
|
3814
|
+
const raw = await import_promises.default.readFile(abs, "utf8");
|
|
3815
|
+
const json = JSON.parse(raw);
|
|
3816
|
+
const result = validateRegistryObject(json);
|
|
3817
|
+
if (!result.ok) {
|
|
3818
|
+
const errs = result.errors?.join("\n - ") || "";
|
|
3819
|
+
throw new Error(`ActionRegistry validation failed:
|
|
3820
|
+
- ${errs}`);
|
|
3821
|
+
}
|
|
3822
|
+
return json;
|
|
3823
|
+
}
|
|
3824
|
+
function loadActionRegistryFromObject(obj) {
|
|
3825
|
+
const result = validateRegistryObject(obj);
|
|
3826
|
+
if (!result.ok) {
|
|
3827
|
+
const errs = result.errors?.join("\n - ") || "";
|
|
3828
|
+
throw new Error(`ActionRegistry validation failed:
|
|
3829
|
+
- ${errs}`);
|
|
3830
|
+
}
|
|
3831
|
+
return obj;
|
|
3832
|
+
}
|
|
3833
|
+
function indexActions(reg) {
|
|
3834
|
+
const map = /* @__PURE__ */ new Map();
|
|
3835
|
+
for (const a of reg.actions) map.set(a.action, a);
|
|
3836
|
+
return map;
|
|
3837
|
+
}
|
|
3838
|
+
function indexCapabilities(reg) {
|
|
3839
|
+
const map = /* @__PURE__ */ new Map();
|
|
3840
|
+
for (const c of reg.capabilities ?? []) map.set(c.capability, c);
|
|
3841
|
+
return map;
|
|
3842
|
+
}
|
|
3843
|
+
function getRequiredScopes(regIndex, action) {
|
|
3844
|
+
return regIndex.get(action)?.required_scopes ?? [];
|
|
3845
|
+
}
|
|
3846
|
+
function getRequiredRelations(regIndex, action) {
|
|
3847
|
+
return regIndex.get(action)?.required_relations ?? [];
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
// src/registry/access-orchestrator.ts
|
|
3851
|
+
function resolveActionsFromSelection(registry, selection) {
|
|
3852
|
+
const aIndex = indexActions(registry);
|
|
3853
|
+
const cIndex = indexCapabilities(registry);
|
|
3854
|
+
const out = /* @__PURE__ */ new Set();
|
|
3855
|
+
for (const item of selection) {
|
|
3856
|
+
if (aIndex.has(item)) {
|
|
3857
|
+
out.add(item);
|
|
3858
|
+
continue;
|
|
3859
|
+
}
|
|
3860
|
+
const cap = cIndex.get(item);
|
|
3861
|
+
if (cap) {
|
|
3862
|
+
for (const act of cap.includes) out.add(act);
|
|
3863
|
+
continue;
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
return [...out];
|
|
3867
|
+
}
|
|
3868
|
+
async function planDelegationForVC(input) {
|
|
3869
|
+
const {
|
|
3870
|
+
registry,
|
|
3871
|
+
issuerUserDid,
|
|
3872
|
+
requested,
|
|
3873
|
+
resourceScope,
|
|
3874
|
+
rebac,
|
|
3875
|
+
abac,
|
|
3876
|
+
creds,
|
|
3877
|
+
providerByIa = {},
|
|
3878
|
+
context
|
|
3879
|
+
} = input;
|
|
3880
|
+
const requestedActions = resolveActionsFromSelection(registry, requested);
|
|
3881
|
+
const aIndex = indexActions(registry);
|
|
3882
|
+
const granted = [];
|
|
3883
|
+
const rejected = [];
|
|
3884
|
+
const traceByAction = {};
|
|
3885
|
+
const resourceScopes = resourceScope.filter((s) => s.kind === "Resource");
|
|
3886
|
+
for (const action of requestedActions) {
|
|
3887
|
+
const meta = aIndex.get(action);
|
|
3888
|
+
const t = {};
|
|
3889
|
+
traceByAction[action] = t;
|
|
3890
|
+
if (!meta) {
|
|
3891
|
+
rejected.push(action);
|
|
3892
|
+
continue;
|
|
3893
|
+
}
|
|
3894
|
+
if (resourceScopes.length) {
|
|
3895
|
+
let rebacOk = false;
|
|
3896
|
+
for (const rs of resourceScopes) {
|
|
3897
|
+
const rels = getRequiredRelations(aIndex, action);
|
|
3898
|
+
const ok = await rebac.check(
|
|
3899
|
+
issuerUserDid,
|
|
3900
|
+
rels.length ? rels : ["owner", "admin", "editor", "viewer"],
|
|
3901
|
+
{
|
|
3902
|
+
id: rs.id,
|
|
3903
|
+
type: rs.type,
|
|
3904
|
+
iaId: ""
|
|
3905
|
+
// 発行時点では空でもOK。持っているなら入れる。
|
|
3906
|
+
}
|
|
3907
|
+
);
|
|
3908
|
+
if (ok) {
|
|
3909
|
+
rebacOk = true;
|
|
3910
|
+
break;
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
t.rebac = { ok: rebacOk, relations: getRequiredRelations(aIndex, action) };
|
|
3914
|
+
if (!rebacOk) {
|
|
3915
|
+
rejected.push(action);
|
|
3916
|
+
continue;
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
const abacDec = await abac.evaluate({
|
|
3920
|
+
principal: { id: issuerUserDid, roles: ["user"] },
|
|
3921
|
+
resource: {
|
|
3922
|
+
kind: resourceScopes[0]?.type ?? meta.resource_type,
|
|
3923
|
+
id: resourceScopes[0]?.id ?? "SCOPE_ONLY",
|
|
3924
|
+
attr: {}
|
|
3925
|
+
},
|
|
3926
|
+
action,
|
|
3927
|
+
context
|
|
3928
|
+
});
|
|
3929
|
+
t.abac = { ok: abacDec.allow, ruleId: abacDec.ruleId, reason: abacDec.reason };
|
|
3930
|
+
if (!abacDec.allow) {
|
|
3931
|
+
rejected.push(action);
|
|
3932
|
+
continue;
|
|
3933
|
+
}
|
|
3934
|
+
let scopeOk = true;
|
|
3935
|
+
const required = getRequiredScopes(aIndex, action);
|
|
3936
|
+
if (resourceScopes.length && required.length) {
|
|
3937
|
+
scopeOk = false;
|
|
3938
|
+
for (const rs of resourceScopes) {
|
|
3939
|
+
const provider = inferProviderByResourceType(rs.type);
|
|
3940
|
+
const cred = await creds.pickMinimal(provider, "", required, issuerUserDid);
|
|
3941
|
+
if (cred) {
|
|
3942
|
+
scopeOk = true;
|
|
3943
|
+
break;
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
t.scope = { ok: scopeOk, required };
|
|
3948
|
+
if (!scopeOk) {
|
|
3949
|
+
rejected.push(action);
|
|
3950
|
+
continue;
|
|
3951
|
+
}
|
|
3952
|
+
granted.push(action);
|
|
3953
|
+
}
|
|
3954
|
+
return { granted_actions: granted, rejected_actions: rejected, traceByAction };
|
|
3955
|
+
}
|
|
3956
|
+
function inferProviderByResourceType(rt) {
|
|
3957
|
+
switch (rt) {
|
|
3958
|
+
case "SlackChannel":
|
|
3959
|
+
return "slack";
|
|
3960
|
+
case "GitHubRepo":
|
|
3961
|
+
return "github";
|
|
3962
|
+
case "DriveFile":
|
|
3963
|
+
return "google";
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
async function checkPermissionWithVP(input) {
|
|
3967
|
+
const { registry, actorDid, action, resource, vpToken, context, rebac, abac, creds, vpVerifier } = input;
|
|
3968
|
+
const aIndex = indexActions(registry);
|
|
3969
|
+
const meta = aIndex.get(action);
|
|
3970
|
+
if (!meta) {
|
|
3971
|
+
return { allow: false, reason: "unknown_action", trace: {} };
|
|
3972
|
+
}
|
|
3973
|
+
const trace = {};
|
|
3974
|
+
const rels = getRequiredRelations(aIndex, action);
|
|
3975
|
+
const rebacOk = await rebac.check(actorDid, rels.length ? rels : ["viewer"], resource);
|
|
3976
|
+
trace.rebac = { ok: rebacOk, relations: rels };
|
|
3977
|
+
if (!rebacOk) {
|
|
3978
|
+
return { allow: false, reason: "rebac_denied", trace };
|
|
3979
|
+
}
|
|
3980
|
+
const vc = await vpVerifier.verifyAndExtractClaims(vpToken);
|
|
3981
|
+
let matchedAction = vc.allowed_actions?.includes(action) ?? false;
|
|
3982
|
+
let inScope = isResourceInScopes(resource, vc.resource_scope || []);
|
|
3983
|
+
const notExpired = !vc.expires_at || new Date(vc.expires_at).getTime() > Date.now();
|
|
3984
|
+
trace.delegation = {
|
|
3985
|
+
ok: matchedAction && inScope && notExpired,
|
|
3986
|
+
matched_action: matchedAction,
|
|
3987
|
+
in_scope: inScope,
|
|
3988
|
+
notExpired
|
|
3989
|
+
};
|
|
3990
|
+
if (!trace.delegation.ok) {
|
|
3991
|
+
return { allow: false, reason: "delegation_denied", trace };
|
|
3992
|
+
}
|
|
3993
|
+
const abacDec = await abac.evaluate({
|
|
3994
|
+
principal: {
|
|
3995
|
+
id: actorDid,
|
|
3996
|
+
roles: ["agent"],
|
|
3997
|
+
claims: {
|
|
3998
|
+
assurance_level: vc.assurance_level,
|
|
3999
|
+
actor: vc.actor
|
|
4000
|
+
}
|
|
4001
|
+
},
|
|
4002
|
+
resource: {
|
|
4003
|
+
kind: resource.type,
|
|
4004
|
+
id: resource.id,
|
|
4005
|
+
attr: resource.attr || {}
|
|
4006
|
+
},
|
|
4007
|
+
action,
|
|
4008
|
+
context
|
|
4009
|
+
});
|
|
4010
|
+
trace.abac = { ok: abacDec.allow, ruleId: abacDec.ruleId, reason: abacDec.reason };
|
|
4011
|
+
if (!abacDec.allow) {
|
|
4012
|
+
return { allow: false, reason: "abac_denied", trace };
|
|
4013
|
+
}
|
|
4014
|
+
const provider = inferProviderByResourceType(resource.type);
|
|
4015
|
+
const requiredScopes = getRequiredScopes(aIndex, action);
|
|
4016
|
+
const cred = await creds.pickMinimal(provider, resource.iaId, requiredScopes, actorDid);
|
|
4017
|
+
trace.scope = {
|
|
4018
|
+
ok: !!cred,
|
|
4019
|
+
required: requiredScopes,
|
|
4020
|
+
chosenCredentialId: cred?.id
|
|
4021
|
+
};
|
|
4022
|
+
if (!cred) {
|
|
4023
|
+
return { allow: false, reason: "insufficient_scopes", trace, credential: null };
|
|
4024
|
+
}
|
|
4025
|
+
return { allow: true, trace, credential: cred };
|
|
4026
|
+
}
|
|
4027
|
+
function isResourceInScopes(resource, scopes) {
|
|
4028
|
+
for (const s of scopes) {
|
|
4029
|
+
if (s.kind === "Resource") {
|
|
4030
|
+
if (s.type === resource.type && s.id === resource.id) return true;
|
|
4031
|
+
} else if (s.kind === "IntegrationAccount") {
|
|
4032
|
+
if (s.id === resource.iaId) return true;
|
|
4033
|
+
} else if (s.kind === "Workspace") {
|
|
4034
|
+
return true;
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
return false;
|
|
4038
|
+
}
|
|
4039
|
+
var AllowAllAbac = class {
|
|
4040
|
+
async evaluate() {
|
|
4041
|
+
return { allow: true, ruleId: "allow_all" };
|
|
4042
|
+
}
|
|
4043
|
+
};
|
|
4044
|
+
var SimpleRebac = class {
|
|
4045
|
+
constructor(allowRelations = ["viewer", "editor", "admin", "owner", "act_as"]) {
|
|
4046
|
+
this.allowRelations = allowRelations;
|
|
4047
|
+
}
|
|
4048
|
+
async check(_sub, relations) {
|
|
4049
|
+
return relations.some((r) => this.allowRelations.includes(r));
|
|
4050
|
+
}
|
|
4051
|
+
};
|
|
4052
|
+
var DummyCreds = class {
|
|
4053
|
+
async pickMinimal(provider, _iaId, requiredScopes) {
|
|
4054
|
+
if (!requiredScopes.length) return { id: `${provider}-none`, provider, scopes: [] };
|
|
4055
|
+
return { id: `${provider}-demo`, provider, scopes: requiredScopes };
|
|
4056
|
+
}
|
|
4057
|
+
};
|
|
4058
|
+
var DummyVpVerifier = class {
|
|
4059
|
+
constructor(vc) {
|
|
4060
|
+
this.vc = vc;
|
|
4061
|
+
}
|
|
4062
|
+
async verifyAndExtractClaims() {
|
|
4063
|
+
return this.vc;
|
|
4064
|
+
}
|
|
4065
|
+
};
|
|
4066
|
+
|
|
4067
|
+
// src/registry/action-registry-json.ts
|
|
4068
|
+
var ACTION_REGISTRY = {
|
|
4069
|
+
registry_version: "2025-09-28",
|
|
4070
|
+
actions: [
|
|
4071
|
+
{
|
|
4072
|
+
action: "slack:channel.post_message",
|
|
4073
|
+
resource_type: "SlackChannel",
|
|
4074
|
+
required_relations: ["editor", "act_as"],
|
|
4075
|
+
required_scopes: ["chat:write"],
|
|
4076
|
+
capability: "slack.messaging.basic",
|
|
4077
|
+
input_schema: {
|
|
4078
|
+
type: "object",
|
|
4079
|
+
properties: {
|
|
4080
|
+
text: { type: "string", minLength: 1, maxLength: 4e4 },
|
|
4081
|
+
thread_ts: { type: "string" },
|
|
4082
|
+
attachments: { type: "array" }
|
|
4083
|
+
},
|
|
4084
|
+
required: ["text"],
|
|
4085
|
+
additionalProperties: false
|
|
4086
|
+
},
|
|
4087
|
+
constraints: { rate_bucket: "slack.post" },
|
|
4088
|
+
effects: ["Create:Message"],
|
|
4089
|
+
risk: "low",
|
|
4090
|
+
version: "1.0.0"
|
|
4091
|
+
},
|
|
4092
|
+
{
|
|
4093
|
+
action: "slack:channel.read",
|
|
4094
|
+
resource_type: "SlackChannel",
|
|
4095
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4096
|
+
required_scopes: ["channels:history", "groups:history", "im:history", "mpim:history"],
|
|
4097
|
+
capability: "slack.read.basic",
|
|
4098
|
+
input_schema: {
|
|
4099
|
+
type: "object",
|
|
4100
|
+
properties: {
|
|
4101
|
+
latest: { type: "string" },
|
|
4102
|
+
oldest: { type: "string" },
|
|
4103
|
+
limit: { type: "integer", minimum: 1, maximum: 1e3 }
|
|
4104
|
+
},
|
|
4105
|
+
additionalProperties: false
|
|
4106
|
+
},
|
|
4107
|
+
constraints: { rate_bucket: "slack.read" },
|
|
4108
|
+
effects: ["Read:Message"],
|
|
4109
|
+
risk: "low",
|
|
4110
|
+
version: "1.0.0"
|
|
4111
|
+
},
|
|
4112
|
+
{
|
|
4113
|
+
action: "slack:channel.add_reaction",
|
|
4114
|
+
resource_type: "SlackChannel",
|
|
4115
|
+
required_relations: ["editor", "act_as"],
|
|
4116
|
+
required_scopes: ["reactions:write", "chat:write"],
|
|
4117
|
+
capability: "slack.messaging.enhanced",
|
|
4118
|
+
input_schema: {
|
|
4119
|
+
type: "object",
|
|
4120
|
+
properties: {
|
|
4121
|
+
name: { type: "string", minLength: 1 },
|
|
4122
|
+
timestamp: { type: "string" }
|
|
4123
|
+
},
|
|
4124
|
+
required: ["name", "timestamp"],
|
|
4125
|
+
additionalProperties: false
|
|
4126
|
+
},
|
|
4127
|
+
constraints: { rate_bucket: "slack.post" },
|
|
4128
|
+
effects: ["Update:MessageReaction"],
|
|
4129
|
+
risk: "low",
|
|
4130
|
+
version: "1.0.0"
|
|
4131
|
+
},
|
|
4132
|
+
{
|
|
4133
|
+
action: "gh:repo.read",
|
|
4134
|
+
resource_type: "GitHubRepo",
|
|
4135
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4136
|
+
required_scopes: ["repo", "public_repo"],
|
|
4137
|
+
capability: "gh.read.basic",
|
|
4138
|
+
input_schema: {
|
|
4139
|
+
type: "object",
|
|
4140
|
+
properties: {
|
|
4141
|
+
path: { type: "string" },
|
|
4142
|
+
ref: { type: "string" }
|
|
4143
|
+
},
|
|
4144
|
+
additionalProperties: false
|
|
4145
|
+
},
|
|
4146
|
+
constraints: { rate_bucket: "github.read" },
|
|
4147
|
+
effects: ["Read:Repo"],
|
|
4148
|
+
risk: "low",
|
|
4149
|
+
version: "1.0.0"
|
|
4150
|
+
},
|
|
4151
|
+
{
|
|
4152
|
+
action: "gh:repo.create_issue",
|
|
4153
|
+
resource_type: "GitHubRepo",
|
|
4154
|
+
required_relations: ["editor", "act_as"],
|
|
4155
|
+
required_scopes: ["repo"],
|
|
4156
|
+
capability: "gh.issues.triage",
|
|
4157
|
+
input_schema: {
|
|
4158
|
+
type: "object",
|
|
4159
|
+
properties: {
|
|
4160
|
+
title: { type: "string", minLength: 1 },
|
|
4161
|
+
body: { type: "string" },
|
|
4162
|
+
labels: { type: "array", items: { type: "string" } },
|
|
4163
|
+
assignees: { type: "array", items: { type: "string" } }
|
|
4164
|
+
},
|
|
4165
|
+
required: ["title"],
|
|
4166
|
+
additionalProperties: false
|
|
4167
|
+
},
|
|
4168
|
+
constraints: { rate_bucket: "github.write" },
|
|
4169
|
+
effects: ["Create:Issue"],
|
|
4170
|
+
risk: "medium",
|
|
4171
|
+
version: "1.0.0"
|
|
4172
|
+
},
|
|
4173
|
+
{
|
|
4174
|
+
action: "gh:repo.comment",
|
|
4175
|
+
resource_type: "GitHubRepo",
|
|
4176
|
+
required_relations: ["editor", "act_as"],
|
|
4177
|
+
required_scopes: ["repo"],
|
|
4178
|
+
capability: "gh.issues.triage",
|
|
4179
|
+
input_schema: {
|
|
4180
|
+
type: "object",
|
|
4181
|
+
properties: {
|
|
4182
|
+
issue_number: { type: "integer", minimum: 1 },
|
|
4183
|
+
body: { type: "string", minLength: 1 }
|
|
4184
|
+
},
|
|
4185
|
+
required: ["issue_number", "body"],
|
|
4186
|
+
additionalProperties: false
|
|
4187
|
+
},
|
|
4188
|
+
constraints: { rate_bucket: "github.write" },
|
|
4189
|
+
effects: ["Create:IssueComment"],
|
|
4190
|
+
risk: "low",
|
|
4191
|
+
version: "1.0.0"
|
|
4192
|
+
},
|
|
4193
|
+
{
|
|
4194
|
+
action: "gh:repo.create_pr",
|
|
4195
|
+
resource_type: "GitHubRepo",
|
|
4196
|
+
required_relations: ["editor", "act_as"],
|
|
4197
|
+
required_scopes: ["repo"],
|
|
4198
|
+
capability: "gh.code.collab",
|
|
4199
|
+
input_schema: {
|
|
4200
|
+
type: "object",
|
|
4201
|
+
properties: {
|
|
4202
|
+
title: { type: "string", minLength: 1 },
|
|
4203
|
+
head: { type: "string", minLength: 1 },
|
|
4204
|
+
base: { type: "string", minLength: 1 },
|
|
4205
|
+
body: { type: "string" },
|
|
4206
|
+
draft: { type: "boolean" }
|
|
4207
|
+
},
|
|
4208
|
+
required: ["title", "head", "base"],
|
|
4209
|
+
additionalProperties: false
|
|
4210
|
+
},
|
|
4211
|
+
constraints: { rate_bucket: "github.write" },
|
|
4212
|
+
effects: ["Create:PullRequest"],
|
|
4213
|
+
risk: "medium",
|
|
4214
|
+
version: "1.0.0"
|
|
4215
|
+
},
|
|
4216
|
+
{
|
|
4217
|
+
action: "gh:repo.merge_pr",
|
|
4218
|
+
resource_type: "GitHubRepo",
|
|
4219
|
+
required_relations: ["admin", "owner"],
|
|
4220
|
+
required_scopes: ["repo"],
|
|
4221
|
+
capability: "gh.code.maintain",
|
|
4222
|
+
input_schema: {
|
|
4223
|
+
type: "object",
|
|
4224
|
+
properties: {
|
|
4225
|
+
pr_number: { type: "integer", minimum: 1 },
|
|
4226
|
+
merge_method: { type: "string", enum: ["merge", "squash", "rebase"] }
|
|
4227
|
+
},
|
|
4228
|
+
required: ["pr_number"],
|
|
4229
|
+
additionalProperties: false
|
|
4230
|
+
},
|
|
4231
|
+
constraints: { rate_bucket: "github.write", requires_reviews_passed: true },
|
|
4232
|
+
effects: ["Update:PullRequestMerge"],
|
|
4233
|
+
risk: "high",
|
|
4234
|
+
version: "1.0.0"
|
|
4235
|
+
},
|
|
4236
|
+
{
|
|
4237
|
+
action: "google:drive.file.read",
|
|
4238
|
+
resource_type: "DriveFile",
|
|
4239
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4240
|
+
required_scopes: ["https://www.googleapis.com/auth/drive.readonly"],
|
|
4241
|
+
capability: "gdrive.read.basic",
|
|
4242
|
+
input_schema: {
|
|
4243
|
+
type: "object",
|
|
4244
|
+
properties: {
|
|
4245
|
+
fields: { type: "string" }
|
|
4246
|
+
},
|
|
4247
|
+
additionalProperties: false
|
|
4248
|
+
},
|
|
4249
|
+
constraints: { rate_bucket: "gdrive.read" },
|
|
4250
|
+
effects: ["Read:FileContent"],
|
|
4251
|
+
risk: "low",
|
|
4252
|
+
version: "1.0.0"
|
|
4253
|
+
},
|
|
4254
|
+
{
|
|
4255
|
+
action: "google:drive.file.write",
|
|
4256
|
+
resource_type: "DriveFile",
|
|
4257
|
+
required_relations: ["editor", "act_as"],
|
|
4258
|
+
required_scopes: ["https://www.googleapis.com/auth/drive.file"],
|
|
4259
|
+
capability: "gdrive.write.basic",
|
|
4260
|
+
input_schema: {
|
|
4261
|
+
type: "object",
|
|
4262
|
+
properties: {
|
|
4263
|
+
mimeType: { type: "string" },
|
|
4264
|
+
content_base64: { type: "string" }
|
|
4265
|
+
},
|
|
4266
|
+
required: ["content_base64"],
|
|
4267
|
+
additionalProperties: false
|
|
4268
|
+
},
|
|
4269
|
+
constraints: { rate_bucket: "gdrive.write", max_size_mb: 50 },
|
|
4270
|
+
effects: ["Update:FileContent"],
|
|
4271
|
+
risk: "medium",
|
|
4272
|
+
version: "1.0.0"
|
|
4273
|
+
},
|
|
4274
|
+
{
|
|
4275
|
+
action: "google:drive.file.create",
|
|
4276
|
+
resource_type: "DriveFile",
|
|
4277
|
+
required_relations: ["editor", "act_as"],
|
|
4278
|
+
required_scopes: ["https://www.googleapis.com/auth/drive.file"],
|
|
4279
|
+
capability: "gdrive.write.basic",
|
|
4280
|
+
input_schema: {
|
|
4281
|
+
type: "object",
|
|
4282
|
+
properties: {
|
|
4283
|
+
name: { type: "string", minLength: 1 },
|
|
4284
|
+
mimeType: { type: "string" },
|
|
4285
|
+
parent_folder_id: { type: "string" },
|
|
4286
|
+
content_base64: { type: "string" }
|
|
4287
|
+
},
|
|
4288
|
+
required: ["name"],
|
|
4289
|
+
additionalProperties: false
|
|
4290
|
+
},
|
|
4291
|
+
constraints: { rate_bucket: "gdrive.write", max_size_mb: 50 },
|
|
4292
|
+
effects: ["Create:File"],
|
|
4293
|
+
risk: "medium",
|
|
4294
|
+
version: "1.0.0"
|
|
4295
|
+
},
|
|
4296
|
+
{
|
|
4297
|
+
action: "google:drive.file.list_in_folder",
|
|
4298
|
+
resource_type: "DriveFile",
|
|
4299
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4300
|
+
required_scopes: ["https://www.googleapis.com/auth/drive.readonly"],
|
|
4301
|
+
capability: "gdrive.read.basic",
|
|
4302
|
+
input_schema: {
|
|
4303
|
+
type: "object",
|
|
4304
|
+
properties: {
|
|
4305
|
+
folder_id: { type: "string" },
|
|
4306
|
+
q: { type: "string" },
|
|
4307
|
+
page_size: { type: "integer", minimum: 1, maximum: 1e3 }
|
|
4308
|
+
},
|
|
4309
|
+
required: ["folder_id"],
|
|
4310
|
+
additionalProperties: false
|
|
4311
|
+
},
|
|
4312
|
+
constraints: { rate_bucket: "gdrive.read" },
|
|
4313
|
+
effects: ["Read:FileList"],
|
|
4314
|
+
risk: "low",
|
|
4315
|
+
version: "1.0.0"
|
|
4316
|
+
},
|
|
4317
|
+
{
|
|
4318
|
+
action: "jira:issue.search",
|
|
4319
|
+
resource_type: "JiraIssue",
|
|
4320
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4321
|
+
required_scopes: ["read:jira-work", "read:jira-user"],
|
|
4322
|
+
capability: "jira.read.basic",
|
|
4323
|
+
input_schema: {
|
|
4324
|
+
type: "object",
|
|
4325
|
+
properties: {
|
|
4326
|
+
jql: { type: "string", minLength: 1 },
|
|
4327
|
+
maxResults: { type: "integer", minimum: 1, maximum: 100 },
|
|
4328
|
+
startAt: { type: "integer", minimum: 0 }
|
|
4329
|
+
},
|
|
4330
|
+
required: ["jql"],
|
|
4331
|
+
additionalProperties: false
|
|
4332
|
+
},
|
|
4333
|
+
constraints: { rate_bucket: "jira.read" },
|
|
4334
|
+
effects: ["Read:IssueList"],
|
|
4335
|
+
risk: "low",
|
|
4336
|
+
version: "1.0.0"
|
|
4337
|
+
},
|
|
4338
|
+
{
|
|
4339
|
+
action: "jira:issue.get",
|
|
4340
|
+
resource_type: "JiraIssue",
|
|
4341
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4342
|
+
required_scopes: ["read:jira-work"],
|
|
4343
|
+
capability: "jira.read.basic",
|
|
4344
|
+
input_schema: {
|
|
4345
|
+
type: "object",
|
|
4346
|
+
properties: {
|
|
4347
|
+
issueIdOrKey: { type: "string", minLength: 1 }
|
|
4348
|
+
},
|
|
4349
|
+
required: ["issueIdOrKey"],
|
|
4350
|
+
additionalProperties: false
|
|
4351
|
+
},
|
|
4352
|
+
constraints: { rate_bucket: "jira.read" },
|
|
4353
|
+
effects: ["Read:Issue"],
|
|
4354
|
+
risk: "low",
|
|
4355
|
+
version: "1.0.0"
|
|
4356
|
+
},
|
|
4357
|
+
{
|
|
4358
|
+
action: "jira:project.list",
|
|
4359
|
+
resource_type: "JiraProject",
|
|
4360
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4361
|
+
required_scopes: ["read:jira-work"],
|
|
4362
|
+
capability: "jira.read.basic",
|
|
4363
|
+
input_schema: {
|
|
4364
|
+
type: "object",
|
|
4365
|
+
properties: {
|
|
4366
|
+
recent: { type: "number" }
|
|
4367
|
+
},
|
|
4368
|
+
additionalProperties: false
|
|
4369
|
+
},
|
|
4370
|
+
constraints: { rate_bucket: "jira.read" },
|
|
4371
|
+
effects: ["Read:ProjectList"],
|
|
4372
|
+
risk: "low",
|
|
4373
|
+
version: "1.0.0"
|
|
4374
|
+
},
|
|
4375
|
+
{
|
|
4376
|
+
action: "jira:board.list",
|
|
4377
|
+
resource_type: "JiraBoard",
|
|
4378
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4379
|
+
required_scopes: ["read:jira-work"],
|
|
4380
|
+
capability: "jira.read.basic",
|
|
4381
|
+
input_schema: {
|
|
4382
|
+
type: "object",
|
|
4383
|
+
properties: {
|
|
4384
|
+
projectKeyOrId: { type: "string" },
|
|
4385
|
+
type: { type: "string" }
|
|
4386
|
+
},
|
|
4387
|
+
additionalProperties: false
|
|
4388
|
+
},
|
|
4389
|
+
constraints: { rate_bucket: "jira.read" },
|
|
4390
|
+
effects: ["Read:BoardList"],
|
|
4391
|
+
risk: "low",
|
|
4392
|
+
version: "1.0.0"
|
|
4393
|
+
},
|
|
4394
|
+
{
|
|
4395
|
+
action: "jira:sprint.list",
|
|
4396
|
+
resource_type: "JiraSprint",
|
|
4397
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4398
|
+
required_scopes: ["read:jira-work"],
|
|
4399
|
+
capability: "jira.read.basic",
|
|
4400
|
+
input_schema: {
|
|
4401
|
+
type: "object",
|
|
4402
|
+
properties: {
|
|
4403
|
+
boardId: { type: "number", minimum: 1 },
|
|
4404
|
+
state: { type: "string" }
|
|
4405
|
+
},
|
|
4406
|
+
required: ["boardId"],
|
|
4407
|
+
additionalProperties: false
|
|
4408
|
+
},
|
|
4409
|
+
constraints: { rate_bucket: "jira.read" },
|
|
4410
|
+
effects: ["Read:SprintList"],
|
|
4411
|
+
risk: "low",
|
|
4412
|
+
version: "1.0.0"
|
|
4413
|
+
},
|
|
4414
|
+
{
|
|
4415
|
+
action: "jira:sprint.get_issues",
|
|
4416
|
+
resource_type: "JiraSprint",
|
|
4417
|
+
required_relations: ["viewer", "editor", "admin", "owner"],
|
|
4418
|
+
required_scopes: ["read:jira-work"],
|
|
4419
|
+
capability: "jira.read.basic",
|
|
4420
|
+
input_schema: {
|
|
4421
|
+
type: "object",
|
|
4422
|
+
properties: {
|
|
4423
|
+
sprintId: { type: "number", minimum: 1 },
|
|
4424
|
+
maxResults: { type: "number", minimum: 1, maximum: 100 }
|
|
4425
|
+
},
|
|
4426
|
+
required: ["sprintId"],
|
|
4427
|
+
additionalProperties: false
|
|
4428
|
+
},
|
|
4429
|
+
constraints: { rate_bucket: "jira.read" },
|
|
4430
|
+
effects: ["Read:IssueList"],
|
|
4431
|
+
risk: "low",
|
|
4432
|
+
version: "1.0.0"
|
|
4433
|
+
},
|
|
4434
|
+
{
|
|
4435
|
+
action: "jira:issue.create",
|
|
4436
|
+
resource_type: "JiraIssue",
|
|
4437
|
+
required_relations: ["editor", "act_as"],
|
|
4438
|
+
required_scopes: ["write:jira-work"],
|
|
4439
|
+
capability: "jira.write.basic",
|
|
4440
|
+
input_schema: {
|
|
4441
|
+
type: "object",
|
|
4442
|
+
properties: {
|
|
4443
|
+
projectKey: { type: "string", minLength: 1 },
|
|
4444
|
+
summary: { type: "string", minLength: 1 },
|
|
4445
|
+
description: { type: "string" },
|
|
4446
|
+
issueType: { type: "string", minLength: 1 },
|
|
4447
|
+
priority: { type: "string" },
|
|
4448
|
+
assignee: { type: "string" }
|
|
4449
|
+
},
|
|
4450
|
+
required: ["projectKey", "summary", "issueType"],
|
|
4451
|
+
additionalProperties: false
|
|
4452
|
+
},
|
|
4453
|
+
constraints: { rate_bucket: "jira.write" },
|
|
4454
|
+
effects: ["Create:Issue"],
|
|
4455
|
+
risk: "medium",
|
|
4456
|
+
version: "1.0.0"
|
|
4457
|
+
}
|
|
4458
|
+
],
|
|
4459
|
+
capabilities: [
|
|
4460
|
+
{
|
|
4461
|
+
capability: "slack.messaging.basic",
|
|
4462
|
+
description: "Post and read messages in channels",
|
|
4463
|
+
includes: ["slack:channel.post_message", "slack:channel.read"],
|
|
4464
|
+
version: "1.0.0"
|
|
4465
|
+
},
|
|
4466
|
+
{
|
|
4467
|
+
capability: "slack.messaging.enhanced",
|
|
4468
|
+
description: "Reactions and advanced messaging",
|
|
4469
|
+
includes: ["slack:channel.add_reaction"],
|
|
4470
|
+
version: "1.0.0"
|
|
4471
|
+
},
|
|
4472
|
+
{
|
|
4473
|
+
capability: "gh.read.basic",
|
|
4474
|
+
description: "Read repository content and metadata",
|
|
4475
|
+
includes: ["gh:repo.read"],
|
|
4476
|
+
version: "1.0.0"
|
|
4477
|
+
},
|
|
4478
|
+
{
|
|
4479
|
+
capability: "gh.issues.triage",
|
|
4480
|
+
description: "Create and comment on issues",
|
|
4481
|
+
includes: ["gh:repo.create_issue", "gh:repo.comment"],
|
|
4482
|
+
version: "1.0.0"
|
|
4483
|
+
},
|
|
4484
|
+
{
|
|
4485
|
+
capability: "gh.code.collab",
|
|
4486
|
+
description: "Open pull requests for collaboration",
|
|
4487
|
+
includes: ["gh:repo.create_pr"],
|
|
4488
|
+
version: "1.0.0"
|
|
4489
|
+
},
|
|
4490
|
+
{
|
|
4491
|
+
capability: "gh.code.maintain",
|
|
4492
|
+
description: "Merge pull requests (high risk)",
|
|
4493
|
+
includes: ["gh:repo.merge_pr"],
|
|
4494
|
+
version: "1.0.0"
|
|
4495
|
+
},
|
|
4496
|
+
{
|
|
4497
|
+
capability: "gdrive.read.basic",
|
|
4498
|
+
description: "Read Drive files and listings",
|
|
4499
|
+
includes: ["google:drive.file.read", "google:drive.file.list_in_folder"],
|
|
4500
|
+
version: "1.0.0"
|
|
4501
|
+
},
|
|
4502
|
+
{
|
|
4503
|
+
capability: "gdrive.write.basic",
|
|
4504
|
+
description: "Create and update Drive files",
|
|
4505
|
+
includes: ["google:drive.file.write", "google:drive.file.create"],
|
|
4506
|
+
version: "1.0.0"
|
|
4507
|
+
},
|
|
4508
|
+
{
|
|
4509
|
+
capability: "jira.read.basic",
|
|
4510
|
+
description: "Read Jira issues, projects, boards, and sprints",
|
|
4511
|
+
includes: ["jira:issue.search", "jira:issue.get", "jira:project.list", "jira:board.list", "jira:sprint.list", "jira:sprint.get_issues"],
|
|
4512
|
+
version: "1.0.0"
|
|
4513
|
+
},
|
|
4514
|
+
{
|
|
4515
|
+
capability: "jira.write.basic",
|
|
4516
|
+
description: "Create Jira issues",
|
|
4517
|
+
includes: ["jira:issue.create"],
|
|
4518
|
+
version: "1.0.0"
|
|
4519
|
+
}
|
|
4520
|
+
]
|
|
4521
|
+
};
|
|
4522
|
+
|
|
4523
|
+
// src/index.ts
|
|
4524
|
+
__reExport(index_exports, require("@vess-id/ai-identity-types"), module.exports);
|
|
4525
|
+
var version = "0.0.1";
|
|
4526
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4527
|
+
0 && (module.exports = {
|
|
4528
|
+
ACTION_REGISTRY,
|
|
4529
|
+
AIdentityClient,
|
|
4530
|
+
APIVCManager,
|
|
4531
|
+
AgentDIDManager,
|
|
4532
|
+
AgentManager,
|
|
4533
|
+
AllowAllAbac,
|
|
4534
|
+
ConstraintEvaluator,
|
|
4535
|
+
DisclosureConfigManager,
|
|
4536
|
+
DummyCreds,
|
|
4537
|
+
DummyVpVerifier,
|
|
4538
|
+
FilesystemKeyStorage,
|
|
4539
|
+
KeyManager,
|
|
4540
|
+
KeyRotationManager,
|
|
4541
|
+
MemoryKeyStorage,
|
|
4542
|
+
MemoryManager,
|
|
4543
|
+
MetricsManager,
|
|
4544
|
+
RevocationManager,
|
|
4545
|
+
SDJwtClient,
|
|
4546
|
+
SimpleRebac,
|
|
4547
|
+
ToolManager,
|
|
4548
|
+
UserIdentityManager,
|
|
4549
|
+
VCManager,
|
|
4550
|
+
VPManager,
|
|
4551
|
+
checkPermissionWithVP,
|
|
4552
|
+
configure,
|
|
4553
|
+
createAjv,
|
|
4554
|
+
defaultConstraintEvaluator,
|
|
4555
|
+
evaluateConstraints,
|
|
4556
|
+
generateKeyPair,
|
|
4557
|
+
generateNonce,
|
|
4558
|
+
getClient,
|
|
4559
|
+
getRequiredRelations,
|
|
4560
|
+
getRequiredScopes,
|
|
4561
|
+
indexActions,
|
|
4562
|
+
indexCapabilities,
|
|
4563
|
+
loadActionRegistryFromFile,
|
|
4564
|
+
loadActionRegistryFromObject,
|
|
4565
|
+
planDelegationForVC,
|
|
4566
|
+
resolveActionsFromSelection,
|
|
4567
|
+
signJWT,
|
|
4568
|
+
validateRegistryObject,
|
|
4569
|
+
verifyJWT,
|
|
4570
|
+
version,
|
|
4571
|
+
...require("@vess-id/ai-identity-types")
|
|
4572
|
+
});
|
|
4573
|
+
//# sourceMappingURL=index.js.map
|