@kya-os/mcp-i-cloudflare 1.5.1-canary.6 → 1.5.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/dist/adapter.d.ts +8 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +102 -87
- package/dist/adapter.js.map +1 -1
- package/dist/constants/storage-keys.d.ts +89 -0
- package/dist/constants/storage-keys.d.ts.map +1 -0
- package/dist/constants/storage-keys.js +142 -0
- package/dist/constants/storage-keys.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/runtime/oauth-handler.d.ts +6 -0
- package/dist/runtime/oauth-handler.d.ts.map +1 -1
- package/dist/runtime/oauth-handler.js +96 -21
- package/dist/runtime/oauth-handler.js.map +1 -1
- package/dist/services/consent-page-renderer.d.ts +5 -2
- package/dist/services/consent-page-renderer.d.ts.map +1 -1
- package/dist/services/consent-page-renderer.js +29 -8
- package/dist/services/consent-page-renderer.js.map +1 -1
- package/dist/services/consent.service.d.ts +52 -0
- package/dist/services/consent.service.d.ts.map +1 -1
- package/dist/services/consent.service.js +451 -99
- package/dist/services/consent.service.js.map +1 -1
- package/dist/services/delegation.service.d.ts.map +1 -1
- package/dist/services/delegation.service.js +54 -19
- package/dist/services/delegation.service.js.map +1 -1
- package/dist/services/oauth-security.service.d.ts +92 -0
- package/dist/services/oauth-security.service.d.ts.map +1 -0
- package/dist/services/oauth-security.service.js +260 -0
- package/dist/services/oauth-security.service.js.map +1 -0
- package/dist/services/rate-limit.service.d.ts +59 -0
- package/dist/services/rate-limit.service.d.ts.map +1 -0
- package/dist/services/rate-limit.service.js +146 -0
- package/dist/services/rate-limit.service.js.map +1 -0
- package/dist/utils/day0-config.d.ts +51 -0
- package/dist/utils/day0-config.d.ts.map +1 -0
- package/dist/utils/day0-config.js +72 -0
- package/dist/utils/day0-config.js.map +1 -0
- package/package.json +3 -3
|
@@ -9,19 +9,174 @@
|
|
|
9
9
|
import { ConsentConfigService } from "./consent-config.service";
|
|
10
10
|
import { ConsentPageRenderer } from "./consent-page-renderer";
|
|
11
11
|
import { DEFAULT_AGENTSHIELD_URL, DEFAULT_SESSION_CACHE_TTL, } from "../constants";
|
|
12
|
+
import { STORAGE_KEYS } from "../constants/storage-keys";
|
|
13
|
+
import { loadDay0Config, getDelegationFieldName } from "../utils/day0-config";
|
|
12
14
|
import { validateConsentApprovalRequest, } from "@kya-os/contracts/consent";
|
|
13
15
|
import { AGENTSHIELD_ENDPOINTS, createDelegationAPIResponseSchema, createDelegationResponseSchema, } from "@kya-os/contracts/agentshield-api";
|
|
16
|
+
import { UserDidManager } from "@kya-os/mcp-i-core";
|
|
17
|
+
import { WebCryptoProvider } from "../providers/crypto";
|
|
14
18
|
export class ConsentService {
|
|
15
19
|
configService;
|
|
16
20
|
renderer;
|
|
17
21
|
env;
|
|
18
22
|
runtime;
|
|
23
|
+
userDidManager; // Cached instance for consistent DID generation
|
|
19
24
|
constructor(env, runtime) {
|
|
20
25
|
this.env = env;
|
|
21
26
|
this.runtime = runtime;
|
|
22
27
|
this.configService = new ConsentConfigService(env);
|
|
23
28
|
this.renderer = new ConsentPageRenderer();
|
|
24
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Get or generate User DID for a session
|
|
32
|
+
*
|
|
33
|
+
* Phase 4 PR #1: Generates ephemeral DIDs for sessions
|
|
34
|
+
* Phase 4 PR #3: Checks OAuth mappings for persistent DIDs
|
|
35
|
+
*
|
|
36
|
+
* @param sessionId - Session ID
|
|
37
|
+
* @param oauthIdentity - Optional OAuth provider identity
|
|
38
|
+
* @returns User DID (did:key format)
|
|
39
|
+
*/
|
|
40
|
+
async getUserDidForSession(sessionId, oauthIdentity) {
|
|
41
|
+
// If OAuth identity provided, check for existing mapping first
|
|
42
|
+
if (oauthIdentity && this.env.DELEGATION_STORAGE) {
|
|
43
|
+
try {
|
|
44
|
+
const oauthKey = STORAGE_KEYS.oauthIdentity(oauthIdentity.provider, oauthIdentity.subject);
|
|
45
|
+
const mappedUserDid = await this.env.DELEGATION_STORAGE.get(oauthKey, "text");
|
|
46
|
+
if (mappedUserDid) {
|
|
47
|
+
console.log("[ConsentService] Found persistent User DID from OAuth mapping");
|
|
48
|
+
return mappedUserDid;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.warn("[ConsentService] Failed to check OAuth mapping:", error);
|
|
53
|
+
// Continue with ephemeral DID generation
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Continue with existing ephemeral DID generation logic
|
|
57
|
+
if (!this.env.DELEGATION_STORAGE) {
|
|
58
|
+
// No storage - use cached UserDidManager instance for consistent DID generation
|
|
59
|
+
if (!this.userDidManager) {
|
|
60
|
+
this.userDidManager = new UserDidManager({
|
|
61
|
+
crypto: new WebCryptoProvider(),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return await this.userDidManager.getOrCreateUserDid(sessionId);
|
|
65
|
+
}
|
|
66
|
+
const sessionKey = STORAGE_KEYS.session(sessionId);
|
|
67
|
+
// Try session cache first
|
|
68
|
+
try {
|
|
69
|
+
const sessionData = (await this.env.DELEGATION_STORAGE.get(sessionKey, "json"));
|
|
70
|
+
if (sessionData?.userDid) {
|
|
71
|
+
return sessionData.userDid;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.warn("[ConsentService] Failed to read session cache:", error);
|
|
76
|
+
}
|
|
77
|
+
// Generate ephemeral DID using cached UserDidManager instance
|
|
78
|
+
if (!this.userDidManager) {
|
|
79
|
+
this.userDidManager = new UserDidManager({
|
|
80
|
+
crypto: new WebCryptoProvider(),
|
|
81
|
+
storage: {
|
|
82
|
+
get: async (key) => {
|
|
83
|
+
try {
|
|
84
|
+
const data = await this.env.DELEGATION_STORAGE.get(`userDid:${key}`, "text");
|
|
85
|
+
return data || null;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
set: async (key, value, ttl) => {
|
|
92
|
+
await this.env.DELEGATION_STORAGE.put(`userDid:${key}`, value, {
|
|
93
|
+
expirationTtl: ttl || DEFAULT_SESSION_CACHE_TTL,
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
delete: async (key) => {
|
|
97
|
+
await this.env.DELEGATION_STORAGE.delete(`userDid:${key}`);
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const userDid = await this.userDidManager.getOrCreateUserDid(sessionId);
|
|
103
|
+
// Cache in session storage
|
|
104
|
+
try {
|
|
105
|
+
const existingSession = (await this.env.DELEGATION_STORAGE.get(sessionKey, "json"));
|
|
106
|
+
await this.env.DELEGATION_STORAGE.put(sessionKey, JSON.stringify({
|
|
107
|
+
...existingSession,
|
|
108
|
+
userDid,
|
|
109
|
+
}), { expirationTtl: DEFAULT_SESSION_CACHE_TTL });
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.warn("[ConsentService] Failed to cache userDid in session:", error);
|
|
113
|
+
// Non-fatal - continue with generated DID
|
|
114
|
+
}
|
|
115
|
+
return userDid;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Link OAuth identity to User DID
|
|
119
|
+
*
|
|
120
|
+
* Maps OAuth provider identity (provider + subject) to a persistent User DID.
|
|
121
|
+
* If an ephemeral DID exists for the session, it becomes persistent.
|
|
122
|
+
*
|
|
123
|
+
* Phase 4 PR #3: OAuth Identity Linking
|
|
124
|
+
*
|
|
125
|
+
* @param oauthIdentity - OAuth provider identity
|
|
126
|
+
* @param sessionId - Current session ID (for ephemeral DID lookup)
|
|
127
|
+
* @returns Persistent User DID
|
|
128
|
+
*/
|
|
129
|
+
async linkOAuthToUserDid(oauthIdentity, sessionId) {
|
|
130
|
+
if (!this.env.DELEGATION_STORAGE) {
|
|
131
|
+
// No storage - can't persist mapping, return ephemeral DID
|
|
132
|
+
console.warn("[ConsentService] No storage available for OAuth linking");
|
|
133
|
+
return await this.getUserDidForSession(sessionId);
|
|
134
|
+
}
|
|
135
|
+
const oauthKey = STORAGE_KEYS.oauthIdentity(oauthIdentity.provider, oauthIdentity.subject);
|
|
136
|
+
// Check if OAuth identity already mapped
|
|
137
|
+
try {
|
|
138
|
+
const existingUserDid = await this.env.DELEGATION_STORAGE.get(oauthKey, "text");
|
|
139
|
+
if (existingUserDid) {
|
|
140
|
+
console.log("[ConsentService] OAuth identity already mapped:", {
|
|
141
|
+
provider: oauthIdentity.provider,
|
|
142
|
+
subject: oauthIdentity.subject.substring(0, 20) + "...",
|
|
143
|
+
userDid: existingUserDid.substring(0, 20) + "...",
|
|
144
|
+
});
|
|
145
|
+
return existingUserDid;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.warn("[ConsentService] Failed to check OAuth mapping:", error);
|
|
150
|
+
// Continue to create new mapping
|
|
151
|
+
}
|
|
152
|
+
// Get/create User DID for session (may be ephemeral)
|
|
153
|
+
const userDid = await this.getUserDidForSession(sessionId);
|
|
154
|
+
// Store OAuth identity mapping (persistent - 90 days)
|
|
155
|
+
try {
|
|
156
|
+
await this.env.DELEGATION_STORAGE.put(oauthKey, userDid, {
|
|
157
|
+
expirationTtl: 90 * 24 * 60 * 60, // 90 days
|
|
158
|
+
});
|
|
159
|
+
// Also store full OAuth identity info for reference
|
|
160
|
+
const oauthIdentityKey = STORAGE_KEYS.userDid(oauthIdentity.provider, oauthIdentity.subject);
|
|
161
|
+
await this.env.DELEGATION_STORAGE.put(oauthIdentityKey, JSON.stringify(oauthIdentity), {
|
|
162
|
+
expirationTtl: 90 * 24 * 60 * 60, // 90 days
|
|
163
|
+
});
|
|
164
|
+
console.log("[ConsentService] OAuth identity linked to User DID:", {
|
|
165
|
+
provider: oauthIdentity.provider,
|
|
166
|
+
subject: oauthIdentity.subject.substring(0, 20) + "...",
|
|
167
|
+
userDid: userDid.substring(0, 20) + "...",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error("[ConsentService] Failed to store OAuth mapping:", error);
|
|
172
|
+
// Non-fatal - continue with User DID
|
|
173
|
+
}
|
|
174
|
+
// Note: Ephemeral → persistent migration happens automatically
|
|
175
|
+
// The ephemeral DID becomes persistent when linked to OAuth identity
|
|
176
|
+
// Existing delegations with ephemeral DID will continue to work
|
|
177
|
+
// New delegations will use the persistent DID
|
|
178
|
+
return userDid;
|
|
179
|
+
}
|
|
25
180
|
/**
|
|
26
181
|
* Handle consent requests
|
|
27
182
|
*
|
|
@@ -135,8 +290,40 @@ export class ConsentService {
|
|
|
135
290
|
customFields: consentConfig.customFields,
|
|
136
291
|
autoClose: consentConfig.ui?.autoClose,
|
|
137
292
|
};
|
|
138
|
-
//
|
|
139
|
-
|
|
293
|
+
// Phase 4 PR #4: Extract OAuth identity from cookie (server-side)
|
|
294
|
+
let oauthIdentity = undefined;
|
|
295
|
+
try {
|
|
296
|
+
const cookieHeader = request.headers.get("Cookie");
|
|
297
|
+
if (cookieHeader) {
|
|
298
|
+
const cookies = cookieHeader.split("; ").map((c) => c.trim());
|
|
299
|
+
const oauthCookie = cookies.find((c) => c.startsWith("oauth_identity="));
|
|
300
|
+
if (oauthCookie) {
|
|
301
|
+
// Extract cookie value correctly - handle values that may contain '=' characters
|
|
302
|
+
// Use indexOf to find first '=' and take everything after it
|
|
303
|
+
const equalsIndex = oauthCookie.indexOf("=");
|
|
304
|
+
const cookieValue = equalsIndex >= 0
|
|
305
|
+
? oauthCookie.substring(equalsIndex + 1)
|
|
306
|
+
: "";
|
|
307
|
+
// Validate it's valid JSON before passing to client
|
|
308
|
+
try {
|
|
309
|
+
const parsed = JSON.parse(decodeURIComponent(cookieValue));
|
|
310
|
+
// Basic validation - ensure it has required fields
|
|
311
|
+
if (parsed && parsed.provider && parsed.subject) {
|
|
312
|
+
oauthIdentity = parsed;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (parseError) {
|
|
316
|
+
console.warn("[ConsentService] Invalid OAuth cookie format:", parseError);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
console.warn("[ConsentService] Failed to extract OAuth cookie:", error);
|
|
323
|
+
// Non-fatal - continue without OAuth identity
|
|
324
|
+
}
|
|
325
|
+
// Render page with OAuth identity (if available)
|
|
326
|
+
const html = this.renderer.render(pageConfig, oauthIdentity);
|
|
140
327
|
return new Response(html, {
|
|
141
328
|
status: 200,
|
|
142
329
|
headers: {
|
|
@@ -250,78 +437,41 @@ export class ConsentService {
|
|
|
250
437
|
};
|
|
251
438
|
}
|
|
252
439
|
try {
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// - user_identifier: string (optional user identifier)
|
|
269
|
-
// - agent_name: string (optional agent name)
|
|
270
|
-
// - constraints: object (optional constraints)
|
|
271
|
-
// - custom_fields: object (optional custom fields)
|
|
272
|
-
// - metadata: object (optional metadata)
|
|
273
|
-
const expiresInDays = 7; // Default to 7 days
|
|
274
|
-
const delegationRequest = {
|
|
275
|
-
agent_did: request.agent_did,
|
|
276
|
-
scopes: request.scopes,
|
|
277
|
-
expires_in_days: expiresInDays,
|
|
278
|
-
// NOTE: expires_at and expires_in_days are mutually exclusive.
|
|
279
|
-
// We use expires_in_days for simplicity. Do not include both fields.
|
|
280
|
-
};
|
|
281
|
-
// Include session_id if provided
|
|
282
|
-
if (request.session_id) {
|
|
283
|
-
delegationRequest.session_id = request.session_id;
|
|
284
|
-
}
|
|
285
|
-
// Include project_id if provided
|
|
286
|
-
if (request.project_id) {
|
|
287
|
-
delegationRequest.project_id = request.project_id;
|
|
288
|
-
}
|
|
289
|
-
// Include custom_fields if provided (send as custom_fields, not metadata)
|
|
290
|
-
if (request.customFields &&
|
|
291
|
-
Object.keys(request.customFields).length > 0) {
|
|
292
|
-
delegationRequest.custom_fields = request.customFields;
|
|
440
|
+
// Load Day0 configuration to determine field name and API capabilities
|
|
441
|
+
await loadDay0Config(this.env.DELEGATION_STORAGE);
|
|
442
|
+
const fieldName = await getDelegationFieldName(this.env.DELEGATION_STORAGE);
|
|
443
|
+
// Get userDID from session or generate new ephemeral DID
|
|
444
|
+
// Phase 4 PR #3: Use OAuth identity if provided in approval request
|
|
445
|
+
let userDid;
|
|
446
|
+
if (this.env.DELEGATION_STORAGE && request.session_id) {
|
|
447
|
+
try {
|
|
448
|
+
// Pass OAuth identity if available in approval request
|
|
449
|
+
userDid = await this.getUserDidForSession(request.session_id, request.oauth_identity);
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
console.warn("[ConsentService] Failed to get/generate userDid:", error);
|
|
453
|
+
// Continue without userDid - delegation will use ephemeral placeholder
|
|
454
|
+
}
|
|
293
455
|
}
|
|
456
|
+
const expiresInDays = 7; // Default to 7 days
|
|
457
|
+
// Build delegation request with error-based format detection
|
|
458
|
+
// Try full format first, fallback to simplified format on error
|
|
459
|
+
const delegationRequest = await this.buildDelegationRequest(request, userDid, expiresInDays, fieldName);
|
|
294
460
|
console.log("[ConsentService] Creating delegation:", {
|
|
295
461
|
url: `${agentShieldUrl}${AGENTSHIELD_ENDPOINTS.DELEGATIONS_CREATE}`,
|
|
296
462
|
agentDid: request.agent_did.substring(0, 20) + "...",
|
|
297
463
|
scopes: request.scopes,
|
|
298
464
|
expiresInDays,
|
|
299
465
|
hasApiKey: !!apiKey,
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const response = await fetch(`${agentShieldUrl}${AGENTSHIELD_ENDPOINTS.DELEGATIONS_CREATE}`, {
|
|
303
|
-
method: "POST",
|
|
304
|
-
headers: {
|
|
305
|
-
Authorization: `Bearer ${apiKey}`,
|
|
306
|
-
"Content-Type": "application/json",
|
|
307
|
-
},
|
|
308
|
-
body: JSON.stringify(delegationRequest),
|
|
466
|
+
fieldName,
|
|
467
|
+
hasUserDid: !!userDid,
|
|
309
468
|
});
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
statusText: response.statusText,
|
|
315
|
-
error: errorText,
|
|
316
|
-
requestBody: JSON.stringify(delegationRequest, null, 2),
|
|
317
|
-
});
|
|
318
|
-
return {
|
|
319
|
-
success: false,
|
|
320
|
-
error: `API error: ${response.status}`,
|
|
321
|
-
error_code: "api_error",
|
|
322
|
-
};
|
|
469
|
+
// Error-based format detection: try request format, fallback on error
|
|
470
|
+
const response = await this.tryAPICall(agentShieldUrl, apiKey, delegationRequest);
|
|
471
|
+
if (!response.success) {
|
|
472
|
+
return response;
|
|
323
473
|
}
|
|
324
|
-
const responseData =
|
|
474
|
+
const responseData = response.data;
|
|
325
475
|
// Canonical format per @kya-os/contracts/agentshield-api:
|
|
326
476
|
// Wrapped: { success: true, data: { delegation_id: string, agent_did: string, ... } }
|
|
327
477
|
// Unwrapped: { delegation_id: string, agent_did: string, ... }
|
|
@@ -415,38 +565,16 @@ export class ConsentService {
|
|
|
415
565
|
try {
|
|
416
566
|
// Default TTL: 7 days (same as delegation expiration)
|
|
417
567
|
const ttl = 7 * 24 * 60 * 60; // 7 days in seconds
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
// 2. Store tokens with userDID in the key: `user:${userDid}:agent:${agentDid}:delegation`
|
|
427
|
-
// 3. Also store with agent-scoped key for backward compatibility
|
|
428
|
-
//
|
|
429
|
-
// Note: The consent approval request currently doesn't include userDID, so we need to:
|
|
430
|
-
// - Extract userDID from OAuth callback (if using OAuth flow)
|
|
431
|
-
// - Or extract userDID from session (if session has userDID)
|
|
432
|
-
// - Or add userDID to ConsentApprovalRequest interface
|
|
433
|
-
const userDid = undefined; // TODO: Get userDID from approval request or OAuth callback
|
|
434
|
-
// Store using agent DID (primary, survives session changes)
|
|
435
|
-
// WARNING: This is shared across all users - if multiple users delegate to the same
|
|
436
|
-
// agent, each delegation will overwrite the previous one. This is acceptable for
|
|
437
|
-
// single-user agents, but multi-user agents should use user+agent scoped keys.
|
|
438
|
-
const agentKey = `agent:${agentDid}:delegation`;
|
|
439
|
-
await delegationStorage.put(agentKey, token, {
|
|
440
|
-
expirationTtl: ttl,
|
|
441
|
-
});
|
|
442
|
-
console.log("[ConsentService] ✅ Token stored with agent DID:", {
|
|
443
|
-
key: agentKey,
|
|
444
|
-
ttl,
|
|
445
|
-
delegationId,
|
|
446
|
-
});
|
|
447
|
-
// Store using user+agent DID if userDID is available (preferred for multi-user scenarios)
|
|
568
|
+
// Get userDID from approval request or session (Phase 4)
|
|
569
|
+
// For now, try to extract from request or session cache
|
|
570
|
+
let userDid;
|
|
571
|
+
// Try to get userDID from session cache
|
|
572
|
+
const sessionKey = STORAGE_KEYS.session(sessionId);
|
|
573
|
+
const sessionData = (await delegationStorage.get(sessionKey, "json"));
|
|
574
|
+
userDid = sessionData?.userDid;
|
|
575
|
+
// Primary: User+Agent scoped (no conflicts) - Phase 4
|
|
448
576
|
if (userDid) {
|
|
449
|
-
const userAgentKey =
|
|
577
|
+
const userAgentKey = STORAGE_KEYS.delegation(userDid, agentDid);
|
|
450
578
|
await delegationStorage.put(userAgentKey, token, {
|
|
451
579
|
expirationTtl: ttl,
|
|
452
580
|
});
|
|
@@ -456,15 +584,31 @@ export class ConsentService {
|
|
|
456
584
|
delegationId,
|
|
457
585
|
});
|
|
458
586
|
}
|
|
459
|
-
//
|
|
460
|
-
const
|
|
461
|
-
await delegationStorage.put(
|
|
587
|
+
// Backward compatibility: Agent-only key (24 hour TTL)
|
|
588
|
+
const legacyKey = STORAGE_KEYS.legacyDelegation(agentDid);
|
|
589
|
+
await delegationStorage.put(legacyKey, token, {
|
|
590
|
+
expirationTtl: 24 * 60 * 60, // 24 hours only
|
|
591
|
+
});
|
|
592
|
+
console.log("[ConsentService] ✅ Token stored with legacy agent key:", {
|
|
593
|
+
key: legacyKey,
|
|
594
|
+
ttl: 24 * 60 * 60,
|
|
595
|
+
delegationId,
|
|
596
|
+
});
|
|
597
|
+
// Session cache for fast lookup (shorter TTL for performance)
|
|
598
|
+
const sessionDataToStore = {
|
|
599
|
+
userDid,
|
|
600
|
+
agentDid,
|
|
601
|
+
delegationToken: token,
|
|
602
|
+
cachedAt: Date.now(),
|
|
603
|
+
};
|
|
604
|
+
await delegationStorage.put(sessionKey, JSON.stringify(sessionDataToStore), {
|
|
462
605
|
expirationTtl: Math.min(ttl, DEFAULT_SESSION_CACHE_TTL),
|
|
463
606
|
});
|
|
464
607
|
console.log("[ConsentService] ✅ Token cached for session:", {
|
|
465
608
|
key: sessionKey,
|
|
466
609
|
ttl: Math.min(ttl, DEFAULT_SESSION_CACHE_TTL),
|
|
467
610
|
sessionId,
|
|
611
|
+
userDid,
|
|
468
612
|
});
|
|
469
613
|
}
|
|
470
614
|
catch (error) {
|
|
@@ -506,5 +650,213 @@ export class ConsentService {
|
|
|
506
650
|
},
|
|
507
651
|
});
|
|
508
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* Build delegation request with error-based format detection
|
|
655
|
+
*
|
|
656
|
+
* Uses Day0 config to determine field name and includes issuerDid when available.
|
|
657
|
+
*/
|
|
658
|
+
async buildDelegationRequest(request, userDid, expiresInDays, fieldName) {
|
|
659
|
+
const baseRequest = {
|
|
660
|
+
agent_did: request.agent_did,
|
|
661
|
+
scopes: request.scopes,
|
|
662
|
+
expires_in_days: expiresInDays,
|
|
663
|
+
};
|
|
664
|
+
// Include session_id if provided
|
|
665
|
+
if (request.session_id) {
|
|
666
|
+
baseRequest.session_id = request.session_id;
|
|
667
|
+
}
|
|
668
|
+
// Include project_id if provided
|
|
669
|
+
if (request.project_id) {
|
|
670
|
+
baseRequest.project_id = request.project_id;
|
|
671
|
+
}
|
|
672
|
+
// Check cached format preference
|
|
673
|
+
const cacheKey = STORAGE_KEYS.formatPreference();
|
|
674
|
+
let cachedFormat = null;
|
|
675
|
+
if (this.env.DELEGATION_STORAGE) {
|
|
676
|
+
try {
|
|
677
|
+
const cached = (await this.env.DELEGATION_STORAGE.get(cacheKey, "json"));
|
|
678
|
+
if (cached &&
|
|
679
|
+
cached.timestamp &&
|
|
680
|
+
Date.now() - cached.timestamp < 3600000) {
|
|
681
|
+
cachedFormat = cached.format;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
catch {
|
|
685
|
+
// Ignore cache errors
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// If we have a cached preference, use it directly
|
|
689
|
+
if (cachedFormat === "full") {
|
|
690
|
+
return this.buildFullFormatRequest(request, userDid, expiresInDays);
|
|
691
|
+
}
|
|
692
|
+
else if (cachedFormat === "simplified") {
|
|
693
|
+
return this.buildSimplifiedFormatRequest(request, userDid, expiresInDays, fieldName);
|
|
694
|
+
}
|
|
695
|
+
// No cache - return request that will be tried with error-based detection
|
|
696
|
+
return {
|
|
697
|
+
_tryFormats: true,
|
|
698
|
+
fullFormat: await this.buildFullFormatRequest(request, userDid, expiresInDays),
|
|
699
|
+
simplifiedFormat: this.buildSimplifiedFormatRequest(request, userDid, expiresInDays, fieldName),
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Build full DelegationRecord format request (future format)
|
|
704
|
+
*/
|
|
705
|
+
async buildFullFormatRequest(request, userDid, expiresInDays) {
|
|
706
|
+
const notAfter = Date.now() + expiresInDays * 24 * 60 * 60 * 1000;
|
|
707
|
+
return {
|
|
708
|
+
delegation: {
|
|
709
|
+
id: crypto.randomUUID(),
|
|
710
|
+
issuerDid: userDid || "did:key:z6MkEphemeral", // Use ephemeral if no userDid
|
|
711
|
+
subjectDid: request.agent_did,
|
|
712
|
+
constraints: {
|
|
713
|
+
scopes: request.scopes,
|
|
714
|
+
notAfter,
|
|
715
|
+
notBefore: Date.now(),
|
|
716
|
+
},
|
|
717
|
+
status: "active",
|
|
718
|
+
createdAt: Date.now(),
|
|
719
|
+
},
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Build simplified format request with proper field name
|
|
724
|
+
*/
|
|
725
|
+
buildSimplifiedFormatRequest(request, userDid, expiresInDays, fieldName) {
|
|
726
|
+
const simplifiedRequest = {
|
|
727
|
+
agent_did: request.agent_did,
|
|
728
|
+
scopes: request.scopes,
|
|
729
|
+
expires_in_days: expiresInDays,
|
|
730
|
+
};
|
|
731
|
+
if (request.session_id) {
|
|
732
|
+
simplifiedRequest.session_id = request.session_id;
|
|
733
|
+
}
|
|
734
|
+
if (request.project_id) {
|
|
735
|
+
simplifiedRequest.project_id = request.project_id;
|
|
736
|
+
}
|
|
737
|
+
// Use the correct field name (metadata or custom_fields) from Day0 config
|
|
738
|
+
if (userDid) {
|
|
739
|
+
simplifiedRequest[fieldName] = {
|
|
740
|
+
issuer_did: userDid,
|
|
741
|
+
subject_did: request.agent_did,
|
|
742
|
+
format_version: "simplified_v1",
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
// Include custom_fields from request if provided
|
|
746
|
+
if (request.customFields && Object.keys(request.customFields).length > 0) {
|
|
747
|
+
simplifiedRequest.custom_fields = {
|
|
748
|
+
...(simplifiedRequest.custom_fields || {}),
|
|
749
|
+
...request.customFields,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
return simplifiedRequest;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Try API call with error-based format detection
|
|
756
|
+
*/
|
|
757
|
+
async tryAPICall(agentShieldUrl, apiKey, request) {
|
|
758
|
+
// Handle format detection
|
|
759
|
+
if (request._tryFormats && request.fullFormat && request.simplifiedFormat) {
|
|
760
|
+
// Try full format first
|
|
761
|
+
const fullResponse = await this.makeAPICall(agentShieldUrl, apiKey, request.fullFormat);
|
|
762
|
+
if (fullResponse.success ||
|
|
763
|
+
fullResponse.error_code !== "validation_error") {
|
|
764
|
+
// Full format worked or failed for non-format reasons
|
|
765
|
+
await this.cacheFormatPreference("full");
|
|
766
|
+
return fullResponse;
|
|
767
|
+
}
|
|
768
|
+
// Full format failed with validation error, try simplified
|
|
769
|
+
console.log("[ConsentService] Full format failed, trying simplified format...");
|
|
770
|
+
const simplifiedResponse = await this.makeAPICall(agentShieldUrl, apiKey, request.simplifiedFormat);
|
|
771
|
+
if (simplifiedResponse.success) {
|
|
772
|
+
await this.cacheFormatPreference("simplified");
|
|
773
|
+
}
|
|
774
|
+
return simplifiedResponse;
|
|
775
|
+
}
|
|
776
|
+
// Direct call (format already determined)
|
|
777
|
+
return this.makeAPICall(agentShieldUrl, apiKey, request);
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Make API call and parse response
|
|
781
|
+
*/
|
|
782
|
+
async makeAPICall(agentShieldUrl, apiKey, requestBody) {
|
|
783
|
+
try {
|
|
784
|
+
const response = await fetch(`${agentShieldUrl}${AGENTSHIELD_ENDPOINTS.DELEGATIONS_CREATE}`, {
|
|
785
|
+
method: "POST",
|
|
786
|
+
headers: {
|
|
787
|
+
Authorization: `Bearer ${apiKey}`,
|
|
788
|
+
"Content-Type": "application/json",
|
|
789
|
+
"X-Request-ID": crypto.randomUUID(),
|
|
790
|
+
},
|
|
791
|
+
body: JSON.stringify(requestBody),
|
|
792
|
+
});
|
|
793
|
+
const responseText = await response.text();
|
|
794
|
+
let responseData;
|
|
795
|
+
try {
|
|
796
|
+
responseData = JSON.parse(responseText);
|
|
797
|
+
}
|
|
798
|
+
catch {
|
|
799
|
+
responseData = responseText;
|
|
800
|
+
}
|
|
801
|
+
// Check for validation error specifically
|
|
802
|
+
if (response.status === 400) {
|
|
803
|
+
const errorMessage = responseData
|
|
804
|
+
?.error?.message ||
|
|
805
|
+
responseData?.message ||
|
|
806
|
+
"Validation failed";
|
|
807
|
+
if (errorMessage.includes("format") ||
|
|
808
|
+
errorMessage.includes("schema") ||
|
|
809
|
+
errorMessage.includes("invalid")) {
|
|
810
|
+
return {
|
|
811
|
+
success: false,
|
|
812
|
+
error: errorMessage,
|
|
813
|
+
error_code: "validation_error",
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (!response.ok) {
|
|
818
|
+
const errorData = responseData;
|
|
819
|
+
return {
|
|
820
|
+
success: false,
|
|
821
|
+
error: errorData.error?.message ||
|
|
822
|
+
errorData.message ||
|
|
823
|
+
"API request failed",
|
|
824
|
+
error_code: errorData.error?.code || "api_error",
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
success: true,
|
|
829
|
+
data: responseData,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
console.error("[ConsentService] API call failed:", error);
|
|
834
|
+
return {
|
|
835
|
+
success: false,
|
|
836
|
+
error: error instanceof Error ? error.message : "Network request failed",
|
|
837
|
+
error_code: "network_error",
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Cache successful format preference
|
|
843
|
+
*/
|
|
844
|
+
async cacheFormatPreference(format) {
|
|
845
|
+
if (!this.env.DELEGATION_STORAGE)
|
|
846
|
+
return;
|
|
847
|
+
const cacheKey = STORAGE_KEYS.formatPreference();
|
|
848
|
+
try {
|
|
849
|
+
await this.env.DELEGATION_STORAGE.put(cacheKey, JSON.stringify({
|
|
850
|
+
format,
|
|
851
|
+
timestamp: Date.now(),
|
|
852
|
+
}), {
|
|
853
|
+
expirationTtl: 3600, // 1 hour
|
|
854
|
+
});
|
|
855
|
+
console.log(`[ConsentService] Cached format preference: ${format}`);
|
|
856
|
+
}
|
|
857
|
+
catch (error) {
|
|
858
|
+
console.warn("[ConsentService] Failed to cache format preference:", error);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
509
861
|
}
|
|
510
862
|
//# sourceMappingURL=consent.service.js.map
|