@kya-os/mcp-i-cloudflare 1.6.11 → 1.6.13

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.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Provides reusable OAuth callback handler for agents using delegation flow
5
5
  */
6
- import { STORAGE_KEYS } from '../constants/storage-keys';
6
+ import { STORAGE_KEYS } from "../constants/storage-keys";
7
7
  /**
8
8
  * Default success page template
9
9
  */
@@ -94,7 +94,7 @@ const defaultSuccessTemplate = (data) => `
94
94
 
95
95
  <div class="info">
96
96
  <p>Authorized Scopes:</p>
97
- <div class="code">${data.scopes.join(', ')}</div>
97
+ <div class="code">${data.scopes.join(", ")}</div>
98
98
  </div>
99
99
 
100
100
  <p>You can now close this window and return to Claude Desktop. The authorized tools are ready to use.</p>
@@ -184,39 +184,39 @@ export function createOAuthCallbackHandler(config = {}) {
184
184
  return async (c) => {
185
185
  const env = c.env;
186
186
  // Get configuration with defaults
187
- const { agentShieldApiUrl = env.AGENTSHIELD_API_URL || 'https://hobbs.work', delegationStorage, consentService, oauthSecurityService, successTemplate = defaultSuccessTemplate, errorTemplate = defaultErrorTemplate, autoClose = true, autoCloseDelay = 5000, agentName, // MCP Server name for delegation notifications
187
+ const { agentShieldApiUrl = env.AGENTSHIELD_API_URL || "https://hobbs.work", delegationStorage, consentService, oauthSecurityService, successTemplate = defaultSuccessTemplate, errorTemplate = defaultErrorTemplate, autoClose = true, autoCloseDelay = 5000, agentName, // MCP Server name for delegation notifications
188
188
  } = config;
189
189
  // Get query parameters
190
- const code = c.req.query('code');
191
- const stateParam = c.req.query('state');
192
- const error = c.req.query('error');
190
+ const code = c.req.query("code");
191
+ const stateParam = c.req.query("state");
192
+ const error = c.req.query("error");
193
193
  // Handle OAuth errors
194
194
  if (error) {
195
- const errorDescription = c.req.query('error_description') || 'Authorization failed';
196
- console.error('[OAuth] 🔒 SECURITY EVENT: Error from provider:', {
195
+ const errorDescription = c.req.query("error_description") || "Authorization failed";
196
+ console.error("[OAuth] 🔒 SECURITY EVENT: Error from provider:", {
197
197
  error,
198
198
  errorDescription,
199
199
  timestamp: new Date().toISOString(),
200
- eventType: 'oauth_provider_error'
200
+ eventType: "oauth_provider_error",
201
201
  });
202
202
  const html = errorTemplate({
203
203
  error,
204
- description: errorDescription
204
+ description: errorDescription,
205
205
  });
206
206
  return c.html(html, 400);
207
207
  }
208
208
  // Validate required parameters
209
209
  if (!code || !stateParam) {
210
- console.error('[OAuth] 🔒 SECURITY EVENT: Missing required parameters:', {
210
+ console.error("[OAuth] 🔒 SECURITY EVENT: Missing required parameters:", {
211
211
  hasCode: !!code,
212
212
  hasState: !!stateParam,
213
213
  timestamp: new Date().toISOString(),
214
- eventType: 'oauth_validation_failed',
215
- reason: 'missing_parameters'
214
+ eventType: "oauth_validation_failed",
215
+ reason: "missing_parameters",
216
216
  });
217
217
  const html = errorTemplate({
218
- error: 'invalid_request',
219
- description: 'Missing authorization code or state parameter'
218
+ error: "invalid_request",
219
+ description: "Missing authorization code or state parameter",
220
220
  });
221
221
  return c.html(html, 400);
222
222
  }
@@ -228,15 +228,15 @@ export function createOAuthCallbackHandler(config = {}) {
228
228
  try {
229
229
  stateData = await oauthSecurityService.getOAuthState(stateParam);
230
230
  if (!stateData) {
231
- console.error('[OAuth] 🔒 SECURITY EVENT: State validation failed - state not found or expired:', {
232
- stateParam: stateParam.substring(0, 20) + '...',
231
+ console.error("[OAuth] 🔒 SECURITY EVENT: State validation failed - state not found or expired:", {
232
+ stateParam: stateParam.substring(0, 20) + "...",
233
233
  timestamp: new Date().toISOString(),
234
- eventType: 'csrf_protection_failed',
235
- reason: 'state_not_found_or_expired'
234
+ eventType: "csrf_protection_failed",
235
+ reason: "state_not_found_or_expired",
236
236
  });
237
237
  const html = errorTemplate({
238
- error: 'invalid_state',
239
- description: 'Invalid or expired state parameter. This may be a CSRF attack or the authorization request has expired.'
238
+ error: "invalid_state",
239
+ description: "Invalid or expired state parameter. This may be a CSRF attack or the authorization request has expired.",
240
240
  });
241
241
  return c.html(html, 400);
242
242
  }
@@ -247,46 +247,46 @@ export function createOAuthCallbackHandler(config = {}) {
247
247
  session_id: stateData.session_id,
248
248
  delegation_id: stateData.delegation_id,
249
249
  };
250
- console.log('[OAuth] 🔒 SECURITY EVENT: State validated successfully:', {
250
+ console.log("[OAuth] 🔒 SECURITY EVENT: State validated successfully:", {
251
251
  projectId: state.project_id,
252
- agentDid: state.agent_did.substring(0, 20) + '...',
253
- sessionId: state.session_id?.substring(0, 20) + '...',
252
+ agentDid: state.agent_did.substring(0, 20) + "...",
253
+ sessionId: state.session_id?.substring(0, 20) + "...",
254
254
  timestamp: new Date().toISOString(),
255
- eventType: 'csrf_protection_success',
256
- stateStoredAt: stateData.storedAt
255
+ eventType: "csrf_protection_success",
256
+ stateStoredAt: stateData.storedAt,
257
257
  });
258
258
  }
259
259
  catch (err) {
260
- console.error('[OAuth] 🔒 SECURITY EVENT: State validation error:', {
260
+ console.error("[OAuth] 🔒 SECURITY EVENT: State validation error:", {
261
261
  error: err instanceof Error ? err.message : String(err),
262
- stateParam: stateParam.substring(0, 20) + '...',
262
+ stateParam: stateParam.substring(0, 20) + "...",
263
263
  timestamp: new Date().toISOString(),
264
- eventType: 'csrf_protection_error',
265
- reason: 'validation_exception'
264
+ eventType: "csrf_protection_error",
265
+ reason: "validation_exception",
266
266
  });
267
267
  const html = errorTemplate({
268
- error: 'invalid_state',
269
- description: 'Failed to validate state parameter'
268
+ error: "invalid_state",
269
+ description: "Failed to validate state parameter",
270
270
  });
271
271
  return c.html(html, 400);
272
272
  }
273
273
  }
274
274
  else {
275
275
  // Fallback: Decode state parameter directly (less secure, but backward compatible)
276
- console.warn('[OAuth] ⚠️ SECURITY WARNING: OAuthSecurityService not provided, using insecure state decoding');
276
+ console.warn("[OAuth] ⚠️ SECURITY WARNING: OAuthSecurityService not provided, using insecure state decoding");
277
277
  try {
278
278
  state = JSON.parse(atob(stateParam));
279
279
  }
280
280
  catch (err) {
281
- console.error('[OAuth] 🔒 SECURITY EVENT: Failed to decode state:', {
281
+ console.error("[OAuth] 🔒 SECURITY EVENT: Failed to decode state:", {
282
282
  error: err instanceof Error ? err.message : String(err),
283
283
  timestamp: new Date().toISOString(),
284
- eventType: 'oauth_validation_failed',
285
- reason: 'state_decode_error'
284
+ eventType: "oauth_validation_failed",
285
+ reason: "state_decode_error",
286
286
  });
287
287
  const html = errorTemplate({
288
- error: 'invalid_state',
289
- description: 'Invalid state parameter'
288
+ error: "invalid_state",
289
+ description: "Invalid state parameter",
290
290
  });
291
291
  return c.html(html, 400);
292
292
  }
@@ -294,77 +294,233 @@ export function createOAuthCallbackHandler(config = {}) {
294
294
  const { project_id, agent_did, session_id, delegation_id } = state;
295
295
  // Validate session ID
296
296
  if (!session_id) {
297
- console.error('[OAuth] No session ID in state');
297
+ console.error("[OAuth] No session ID in state");
298
298
  const html = errorTemplate({
299
- error: 'missing_session',
300
- description: 'No session ID provided in OAuth state'
299
+ error: "missing_session",
300
+ description: "No session ID provided in OAuth state",
301
301
  });
302
302
  return c.html(html, 400);
303
303
  }
304
- console.log('[OAuth] 🔒 SECURITY EVENT: Processing authorization code exchange:', {
304
+ // Extract direct PKCE flow data from state
305
+ const isDirectPKCE = stateData?.direct_pkce === true;
306
+ const stateProvider = stateData?.provider;
307
+ const codeVerifier = stateData?.code_verifier;
308
+ const redirectUri = stateData?.redirect_uri;
309
+ const requestedScopes = stateData?.scopes || [];
310
+ console.log("[OAuth] 🔒 SECURITY EVENT: Processing authorization code exchange:", {
305
311
  projectId: project_id,
306
- agentDid: agent_did.substring(0, 20) + '...',
307
- sessionId: session_id?.substring(0, 20) + '...',
312
+ agentDid: agent_did.substring(0, 20) + "...",
313
+ sessionId: session_id?.substring(0, 20) + "...",
308
314
  delegationId: delegation_id,
309
315
  timestamp: new Date().toISOString(),
310
- eventType: 'oauth_code_exchange_start',
311
- hasSecureState: !!oauthSecurityService
316
+ eventType: "oauth_code_exchange_start",
317
+ hasSecureState: !!oauthSecurityService,
318
+ isDirectPKCE,
319
+ provider: stateProvider,
320
+ hasCodeVerifier: !!codeVerifier,
312
321
  });
322
+ let tokenData;
313
323
  try {
314
- // Exchange authorization code for delegation token
315
- const tokenEndpoint = `${agentShieldApiUrl}/api/v1/bouncer/oauth/token`;
316
- const tokenResponse = await fetch(tokenEndpoint, {
317
- method: 'POST',
318
- headers: {
319
- 'Content-Type': 'application/json',
320
- 'Accept': 'application/json'
321
- },
322
- body: JSON.stringify({
323
- grant_type: 'authorization_code',
324
- code: code,
325
- agent_did: agent_did,
326
- project_id: project_id
327
- })
328
- });
329
- if (!tokenResponse.ok) {
330
- const errorText = await tokenResponse.text();
331
- console.error('[OAuth] 🔒 SECURITY EVENT: Token exchange failed:', {
332
- status: tokenResponse.status,
333
- error: errorText.substring(0, 200),
324
+ // Check if this is a direct PKCE flow (exchange with provider directly)
325
+ if (isDirectPKCE && stateProvider && codeVerifier && redirectUri) {
326
+ console.log("[OAuth] 🔐 Direct PKCE flow detected, exchanging with provider directly:", {
327
+ provider: stateProvider,
328
+ redirectUri,
334
329
  projectId: project_id,
335
- agentDid: agent_did.substring(0, 20) + '...',
336
- timestamp: new Date().toISOString(),
337
- eventType: 'oauth_token_exchange_failed'
338
330
  });
339
- const html = errorTemplate({
340
- error: 'token_exchange_failed',
341
- description: 'Failed to exchange authorization code for delegation token'
331
+ // Import services for direct PKCE flow
332
+ const { OAuthService, OAuthConfigService } = await import("@kya-os/mcp-i-core");
333
+ const { KVOAuthConfigCache } = await import("../cache/kv-oauth-config-cache.js");
334
+ // Create fetch provider for OAuth services
335
+ const fetchProvider = {
336
+ fetch: globalThis.fetch.bind(globalThis),
337
+ // These methods are not needed for PKCE token exchange, stub them
338
+ resolveDID: async () => {
339
+ throw new Error("Not implemented");
340
+ },
341
+ fetchStatusList: async () => {
342
+ throw new Error("Not implemented");
343
+ },
344
+ fetchDelegationChain: async () => {
345
+ throw new Error("Not implemented");
346
+ },
347
+ };
348
+ // Initialize OAuth services
349
+ const oauthConfigCache = new KVOAuthConfigCache({
350
+ kv: delegationStorage,
351
+ });
352
+ const apiKey = env.AGENTSHIELD_API_KEY || "";
353
+ const oauthConfigService = new OAuthConfigService({
354
+ baseUrl: env.AGENTSHIELD_API_URL || "https://kya.vouched.id",
355
+ apiKey: apiKey,
356
+ fetchProvider: fetchProvider,
357
+ cache: oauthConfigCache,
358
+ });
359
+ const oauthService = new OAuthService({
360
+ configService: oauthConfigService,
361
+ fetchProvider: fetchProvider,
362
+ agentShieldApiUrl: env.AGENTSHIELD_API_URL || "https://kya.vouched.id",
363
+ agentShieldApiKey: apiKey,
364
+ projectId: project_id,
365
+ logger: (msg, data) => console.log(`[OAuthService] ${msg}`, data),
366
+ });
367
+ // Step 1: Exchange authorization code with OAuth provider (GitHub) directly
368
+ const idpTokens = await oauthService.exchangeToken(stateProvider, code, codeVerifier, redirectUri);
369
+ console.log("[OAuth] ✅ Direct PKCE token exchange successful:", {
370
+ provider: stateProvider,
371
+ hasAccessToken: !!idpTokens.access_token,
372
+ expiresAt: new Date(idpTokens.expires_at).toISOString(),
373
+ });
374
+ // Step 2: Get user info from provider (optional but useful for User DID)
375
+ let userInfo = null;
376
+ const oauthConfig = await oauthConfigService.getOAuthConfig(project_id);
377
+ const providerConfig = oauthConfig.providers[stateProvider];
378
+ if (providerConfig?.userInfoUrl) {
379
+ try {
380
+ const userInfoResponse = await fetch(providerConfig.userInfoUrl, {
381
+ headers: {
382
+ Authorization: `Bearer ${idpTokens.access_token}`,
383
+ Accept: "application/json",
384
+ },
385
+ });
386
+ if (userInfoResponse.ok) {
387
+ userInfo = await userInfoResponse.json();
388
+ console.log("[OAuth] ✅ User info retrieved:", {
389
+ provider: stateProvider,
390
+ hasEmail: !!userInfo?.email,
391
+ hasName: !!userInfo?.name,
392
+ });
393
+ }
394
+ }
395
+ catch (err) {
396
+ console.warn("[OAuth] Failed to get user info (non-fatal):", err);
397
+ }
398
+ }
399
+ // Step 3: Create delegation on AgentShield
400
+ const delegationEndpoint = `${agentShieldApiUrl}/api/v1/bouncer/delegations`;
401
+ // Build user identifier from OAuth user info
402
+ const userIdentifier = userInfo?.email || userInfo?.login || userInfo?.sub || userInfo?.id;
403
+ const delegationResponse = await fetch(delegationEndpoint, {
404
+ method: "POST",
405
+ headers: {
406
+ "Content-Type": "application/json",
407
+ Authorization: `Bearer ${env.AGENTSHIELD_API_KEY}`,
408
+ "X-Project-Id": project_id,
409
+ },
410
+ body: JSON.stringify({
411
+ agent_did: agent_did,
412
+ scopes: requestedScopes,
413
+ session_id: session_id,
414
+ provider: stateProvider,
415
+ user_identifier: userIdentifier,
416
+ // Include OAuth metadata for audit trail
417
+ metadata: {
418
+ oauth_provider: stateProvider,
419
+ oauth_flow: "direct_pkce",
420
+ oauth_user_id: userInfo?.sub || userInfo?.id || userInfo?.login,
421
+ },
422
+ }),
423
+ });
424
+ if (!delegationResponse.ok) {
425
+ const errorText = await delegationResponse.text();
426
+ console.error("[OAuth] 🔒 SECURITY EVENT: Delegation creation failed:", {
427
+ status: delegationResponse.status,
428
+ error: errorText.substring(0, 200),
429
+ projectId: project_id,
430
+ agentDid: agent_did.substring(0, 20) + "...",
431
+ timestamp: new Date().toISOString(),
432
+ eventType: "delegation_creation_failed",
433
+ });
434
+ const html = errorTemplate({
435
+ error: "delegation_failed",
436
+ description: "Failed to create delegation after OAuth authorization",
437
+ });
438
+ return c.html(html, delegationResponse.status);
439
+ }
440
+ const delegationData = await delegationResponse.json();
441
+ // Map delegation response to TokenExchangeResponse format
442
+ tokenData = {
443
+ delegation_token: delegationData.data?.delegation_token ||
444
+ delegationData.delegation_token,
445
+ token_type: "Bearer",
446
+ expires_in: delegationData.data?.expires_in ||
447
+ delegationData.expires_in ||
448
+ 3600,
449
+ delegation_id: delegationData.data?.delegation_id ||
450
+ delegationData.delegation_id ||
451
+ `del_${Date.now()}`,
452
+ scopes: requestedScopes,
453
+ session_id: session_id,
454
+ };
455
+ // Attach provider and IDP tokens for downstream processing
456
+ tokenData.provider = stateProvider;
457
+ tokenData.idp_tokens = idpTokens;
458
+ tokenData.user_info = userInfo;
459
+ console.log("[OAuth] ✅ Direct PKCE flow complete:", {
460
+ delegationId: tokenData.delegation_id,
461
+ provider: stateProvider,
462
+ hasIdpTokens: true,
463
+ hasUserInfo: !!userInfo,
342
464
  });
343
- return c.html(html, tokenResponse.status);
344
465
  }
345
- const tokenData = await tokenResponse.json();
466
+ else {
467
+ // Bouncer flow: Exchange via AgentShield's OAuth token endpoint
468
+ // This is the legacy flow for proxy mode or when PKCE is not available
469
+ console.log("[OAuth] 📡 Using bouncer flow for token exchange");
470
+ const tokenEndpoint = `${agentShieldApiUrl}/api/v1/bouncer/oauth/token`;
471
+ const tokenResponse = await fetch(tokenEndpoint, {
472
+ method: "POST",
473
+ headers: {
474
+ "Content-Type": "application/json",
475
+ Accept: "application/json",
476
+ },
477
+ body: JSON.stringify({
478
+ grant_type: "authorization_code",
479
+ code: code,
480
+ agent_did: agent_did,
481
+ project_id: project_id,
482
+ }),
483
+ });
484
+ if (!tokenResponse.ok) {
485
+ const errorText = await tokenResponse.text();
486
+ console.error("[OAuth] 🔒 SECURITY EVENT: Token exchange failed:", {
487
+ status: tokenResponse.status,
488
+ error: errorText.substring(0, 200),
489
+ projectId: project_id,
490
+ agentDid: agent_did.substring(0, 20) + "...",
491
+ timestamp: new Date().toISOString(),
492
+ eventType: "oauth_token_exchange_failed",
493
+ });
494
+ const html = errorTemplate({
495
+ error: "token_exchange_failed",
496
+ description: "Failed to exchange authorization code for delegation token",
497
+ });
498
+ return c.html(html, tokenResponse.status);
499
+ }
500
+ tokenData = await tokenResponse.json();
501
+ }
346
502
  // Validate token response
347
503
  if (!tokenData.delegation_token) {
348
- console.error('[OAuth] 🔒 SECURITY EVENT: Invalid token response:', {
504
+ console.error("[OAuth] 🔒 SECURITY EVENT: Invalid token response:", {
349
505
  hasDelegationToken: !!tokenData.delegation_token,
350
506
  responseKeys: Object.keys(tokenData),
351
507
  projectId: project_id,
352
508
  timestamp: new Date().toISOString(),
353
- eventType: 'oauth_invalid_token_response'
509
+ eventType: "oauth_invalid_token_response",
354
510
  });
355
511
  const html = errorTemplate({
356
- error: 'invalid_response',
357
- description: 'Invalid token response from authorization server'
512
+ error: "invalid_response",
513
+ description: "Invalid token response from authorization server",
358
514
  });
359
515
  return c.html(html, 500);
360
516
  }
361
- console.log('[OAuth] 🔒 SECURITY EVENT: Token exchange successful:', {
517
+ console.log("[OAuth] 🔒 SECURITY EVENT: Token exchange successful:", {
362
518
  delegationId: tokenData.delegation_id,
363
519
  sessionId: tokenData.session_id || session_id,
364
520
  expiresIn: tokenData.expires_in,
365
521
  scopes: tokenData.scopes,
366
522
  timestamp: new Date().toISOString(),
367
- eventType: 'oauth_token_exchange_success'
523
+ eventType: "oauth_token_exchange_success",
368
524
  });
369
525
  // Phase 4 PR #3: Extract OAuth user info and link to User DID
370
526
  let oauthIdentity = null;
@@ -380,13 +536,15 @@ export function createOAuthCallbackHandler(config = {}) {
380
536
  // 2. State parameter (if stored during OAuth initiation)
381
537
  // 3. Environment variable (if configured)
382
538
  // 4. Default fallback ('google')
383
- const provider = tokenData.provider
384
- || state.provider
385
- || env.DEFAULT_OAUTH_PROVIDER
386
- || 'google';
539
+ const provider = tokenData.provider ||
540
+ state.provider ||
541
+ env.DEFAULT_OAUTH_PROVIDER ||
542
+ "google";
387
543
  oauthIdentity = {
388
544
  provider: provider,
389
- subject: userInfoFromToken.sub || userInfoFromToken.id || userInfoFromToken.email,
545
+ subject: userInfoFromToken.sub ||
546
+ userInfoFromToken.id ||
547
+ userInfoFromToken.email,
390
548
  email: userInfoFromToken.email,
391
549
  name: userInfoFromToken.name || userInfoFromToken.display_name,
392
550
  };
@@ -396,8 +554,8 @@ export function createOAuthCallbackHandler(config = {}) {
396
554
  // This requires an access_token from the token exchange
397
555
  // For now, we'll log a warning and skip OAuth linking
398
556
  // TODO: Implement userinfo endpoint call if access_token is available
399
- console.warn('[OAuth] User info not available in token response. OAuth linking skipped.');
400
- console.warn('[OAuth] To enable OAuth linking, ensure AgentShield returns user info or access_token in token response.');
557
+ console.warn("[OAuth] User info not available in token response. OAuth linking skipped.");
558
+ console.warn("[OAuth] To enable OAuth linking, ensure AgentShield returns user info or access_token in token response.");
401
559
  }
402
560
  // Link OAuth identity to User DID if we have it
403
561
  if (oauthIdentity && oauthIdentity.subject) {
@@ -405,25 +563,25 @@ export function createOAuthCallbackHandler(config = {}) {
405
563
  // Set OAuth identity cookie for consent page
406
564
  const cookieValue = encodeURIComponent(JSON.stringify(oauthIdentity));
407
565
  c.header("Set-Cookie", `oauth_identity=${cookieValue}; HttpOnly; Secure; SameSite=Lax; Max-Age=604800; Path=/`);
408
- console.log('[OAuth] 🔒 SECURITY EVENT: OAuth identity linked and cookie set:', {
566
+ console.log("[OAuth] 🔒 SECURITY EVENT: OAuth identity linked and cookie set:", {
409
567
  provider: oauthIdentity.provider,
410
- subject: oauthIdentity.subject.substring(0, 20) + '...',
411
- userDid: userDid.substring(0, 20) + '...',
412
- sessionId: session_id?.substring(0, 20) + '...',
568
+ subject: oauthIdentity.subject.substring(0, 20) + "...",
569
+ userDid: userDid.substring(0, 20) + "...",
570
+ sessionId: session_id?.substring(0, 20) + "...",
413
571
  timestamp: new Date().toISOString(),
414
- eventType: 'oauth_identity_linked',
415
- cookieSet: true
572
+ eventType: "oauth_identity_linked",
573
+ cookieSet: true,
416
574
  });
417
575
  }
418
576
  }
419
577
  catch (error) {
420
578
  // OAuth linking errors are non-fatal - log but continue
421
- console.error('[OAuth] 🔒 SECURITY EVENT: Failed to link OAuth identity (non-fatal):', {
579
+ console.error("[OAuth] 🔒 SECURITY EVENT: Failed to link OAuth identity (non-fatal):", {
422
580
  error: error instanceof Error ? error.message : String(error),
423
- sessionId: session_id?.substring(0, 20) + '...',
581
+ sessionId: session_id?.substring(0, 20) + "...",
424
582
  timestamp: new Date().toISOString(),
425
- eventType: 'oauth_identity_linking_failed',
426
- severity: 'warning'
583
+ eventType: "oauth_identity_linking_failed",
584
+ severity: "warning",
427
585
  });
428
586
  }
429
587
  }
@@ -431,7 +589,9 @@ export function createOAuthCallbackHandler(config = {}) {
431
589
  // After receiving delegation token, retrieve OAuth tokens separately
432
590
  // Note: userDid may not be available if OAuth linking failed, but we still need to store tokens
433
591
  // For PKCE flows, we can use session-based userDid lookup if needed
434
- if (delegationStorage && oauthSecurityService && tokenData.delegation_id) {
592
+ if (delegationStorage &&
593
+ oauthSecurityService &&
594
+ tokenData.delegation_id) {
435
595
  try {
436
596
  // Extract provider from state or token response
437
597
  const provider = tokenData.provider ||
@@ -451,7 +611,8 @@ export function createOAuthCallbackHandler(config = {}) {
451
611
  let effectiveUserDid = userDid;
452
612
  if (!effectiveUserDid && session_id && consentService) {
453
613
  try {
454
- effectiveUserDid = await consentService.getUserDidForSession(session_id);
614
+ effectiveUserDid =
615
+ await consentService.getUserDidForSession(session_id);
455
616
  }
456
617
  catch (error) {
457
618
  console.warn("[OAuth] Failed to get userDid from session, skipping token storage:", {
@@ -566,47 +727,49 @@ export function createOAuthCallbackHandler(config = {}) {
566
727
  if (delegationStorage) {
567
728
  // Use != null to properly handle expires_in: 0 (immediate expiration)
568
729
  // Truthiness check would incorrectly treat 0 as missing
569
- const ttl = tokenData.expires_in != null ? tokenData.expires_in : (7 * 24 * 60 * 60); // Default 7 days
730
+ const ttl = tokenData.expires_in != null
731
+ ? tokenData.expires_in
732
+ : 7 * 24 * 60 * 60; // Default 7 days
570
733
  try {
571
734
  // Get userDID from session if available (Phase 4)
572
735
  // Use linked userDid if available, otherwise check session
573
736
  let sessionUserDid = userDid;
574
737
  const sessionKey = STORAGE_KEYS.session(session_id);
575
738
  if (!sessionUserDid) {
576
- const sessionData = await delegationStorage.get(sessionKey, "json");
739
+ const sessionData = (await delegationStorage.get(sessionKey, "json"));
577
740
  sessionUserDid = sessionData?.userDid;
578
741
  }
579
742
  // Primary: User+Agent scoped (no conflicts) - Phase 4
580
743
  if (sessionUserDid) {
581
744
  const userAgentKey = STORAGE_KEYS.delegation(sessionUserDid, agent_did);
582
745
  await delegationStorage.put(userAgentKey, tokenData.delegation_token, {
583
- expirationTtl: ttl
746
+ expirationTtl: ttl,
584
747
  });
585
- console.log('[OAuth] 🔒 SECURITY EVENT: Delegation token stored with user+agent DID:', {
586
- key: userAgentKey.substring(0, 50) + '...',
748
+ console.log("[OAuth] 🔒 SECURITY EVENT: Delegation token stored with user+agent DID:", {
749
+ key: userAgentKey.substring(0, 50) + "...",
587
750
  ttl,
588
- agentDid: agent_did.substring(0, 20) + '...',
589
- userDid: sessionUserDid.substring(0, 20) + '...',
751
+ agentDid: agent_did.substring(0, 20) + "...",
752
+ userDid: sessionUserDid.substring(0, 20) + "...",
590
753
  delegationId: tokenData.delegation_id,
591
754
  timestamp: new Date().toISOString(),
592
- eventType: 'delegation_token_stored',
593
- storageType: 'user_agent_scoped'
755
+ eventType: "delegation_token_stored",
756
+ storageType: "user_agent_scoped",
594
757
  });
595
758
  }
596
759
  // Backward compatibility: Agent-only key (24 hour TTL)
597
760
  const legacyKey = STORAGE_KEYS.legacyDelegation(agent_did);
598
761
  await delegationStorage.put(legacyKey, tokenData.delegation_token, {
599
- expirationTtl: 24 * 60 * 60 // 24 hours only
762
+ expirationTtl: 24 * 60 * 60, // 24 hours only
600
763
  });
601
- console.log('[OAuth] 🔒 SECURITY EVENT: Delegation token stored with legacy agent key:', {
602
- key: legacyKey.substring(0, 50) + '...',
764
+ console.log("[OAuth] 🔒 SECURITY EVENT: Delegation token stored with legacy agent key:", {
765
+ key: legacyKey.substring(0, 50) + "...",
603
766
  ttl: 24 * 60 * 60,
604
- agentDid: agent_did.substring(0, 20) + '...',
767
+ agentDid: agent_did.substring(0, 20) + "...",
605
768
  delegationId: tokenData.delegation_id,
606
769
  timestamp: new Date().toISOString(),
607
- eventType: 'delegation_token_stored',
608
- storageType: 'legacy_agent_scoped',
609
- warning: 'Legacy format - migrate to user+agent scoped tokens'
770
+ eventType: "delegation_token_stored",
771
+ storageType: "legacy_agent_scoped",
772
+ warning: "Legacy format - migrate to user+agent scoped tokens",
610
773
  });
611
774
  // Session cache for fast lookup (shorter TTL for performance)
612
775
  await delegationStorage.put(sessionKey, JSON.stringify({
@@ -615,16 +778,16 @@ export function createOAuthCallbackHandler(config = {}) {
615
778
  delegationToken: tokenData.delegation_token,
616
779
  cachedAt: Date.now(),
617
780
  }), {
618
- expirationTtl: Math.min(ttl, 1800) // 30 minutes or token TTL, whichever is shorter
781
+ expirationTtl: Math.min(ttl, 1800), // 30 minutes or token TTL, whichever is shorter
619
782
  });
620
- console.log('[OAuth] 🔒 SECURITY EVENT: Delegation token cached for session:', {
621
- key: sessionKey.substring(0, 50) + '...',
783
+ console.log("[OAuth] 🔒 SECURITY EVENT: Delegation token cached for session:", {
784
+ key: sessionKey.substring(0, 50) + "...",
622
785
  ttl: Math.min(ttl, 1800),
623
- sessionId: session_id?.substring(0, 20) + '...',
624
- userDid: sessionUserDid?.substring(0, 20) + '...',
786
+ sessionId: session_id?.substring(0, 20) + "...",
787
+ userDid: sessionUserDid?.substring(0, 20) + "...",
625
788
  timestamp: new Date().toISOString(),
626
- eventType: 'delegation_token_cached',
627
- storageType: 'session_cache'
789
+ eventType: "delegation_token_cached",
790
+ storageType: "session_cache",
628
791
  });
629
792
  // Fire-and-forget notification to AgentShield for audit trail
630
793
  // This enables dashboard visibility into delegations created via direct OAuth flow
@@ -634,13 +797,13 @@ export function createOAuthCallbackHandler(config = {}) {
634
797
  // Use != null to properly handle expires_in: 0 (immediate expiration)
635
798
  // Truthiness check would incorrectly treat 0 as missing
636
799
  const expiresAt = tokenData.expires_in != null
637
- ? new Date(Date.now() + (tokenData.expires_in * 1000)).toISOString()
800
+ ? new Date(Date.now() + tokenData.expires_in * 1000).toISOString()
638
801
  : null;
639
802
  fetch(delegationNotifyUrl, {
640
- method: 'POST',
803
+ method: "POST",
641
804
  headers: {
642
- 'Content-Type': 'application/json',
643
- 'X-AgentShield-Key': env.AGENTSHIELD_API_KEY || '',
805
+ "Content-Type": "application/json",
806
+ "X-AgentShield-Key": env.AGENTSHIELD_API_KEY || "",
644
807
  },
645
808
  // AgentShield notifyDelegationSchema uses snake_case
646
809
  body: JSON.stringify({
@@ -660,28 +823,30 @@ export function createOAuthCallbackHandler(config = {}) {
660
823
  expires_at: expiresAt || undefined,
661
824
  // Metadata for audit trail and debugging
662
825
  metadata: {
663
- source: 'mcp-i-direct-oauth',
664
- oauth_flow: 'pkce',
826
+ source: "mcp-i-direct-oauth",
827
+ oauth_flow: "pkce",
665
828
  mcp_server_name: agentName || undefined,
666
829
  session_id: session_id || undefined,
667
830
  },
668
831
  }),
669
- }).then(response => {
832
+ })
833
+ .then((response) => {
670
834
  if (response.ok) {
671
- console.log('[OAuth] Delegation notification sent to AgentShield:', {
835
+ console.log("[OAuth] Delegation notification sent to AgentShield:", {
672
836
  delegation_id: tokenData.delegation_id,
673
837
  project_id: project_id,
674
838
  agent_name: agentName,
675
839
  });
676
840
  }
677
841
  else {
678
- console.warn('[OAuth] Delegation notification failed (non-blocking):', {
842
+ console.warn("[OAuth] Delegation notification failed (non-blocking):", {
679
843
  status: response.status,
680
844
  delegation_id: tokenData.delegation_id,
681
845
  });
682
846
  }
683
- }).catch(err => {
684
- console.warn('[OAuth] Delegation notification failed (non-blocking):', {
847
+ })
848
+ .catch((err) => {
849
+ console.warn("[OAuth] Delegation notification failed (non-blocking):", {
685
850
  error: err instanceof Error ? err.message : String(err),
686
851
  delegation_id: tokenData.delegation_id,
687
852
  });
@@ -689,12 +854,14 @@ export function createOAuthCallbackHandler(config = {}) {
689
854
  }
690
855
  catch (storageError) {
691
856
  // Storage errors are non-fatal - log but continue
692
- console.error('[OAuth] 🔒 SECURITY EVENT: Storage error (non-fatal):', {
693
- error: storageError instanceof Error ? storageError.message : String(storageError),
694
- sessionId: session_id?.substring(0, 20) + '...',
857
+ console.error("[OAuth] 🔒 SECURITY EVENT: Storage error (non-fatal):", {
858
+ error: storageError instanceof Error
859
+ ? storageError.message
860
+ : String(storageError),
861
+ sessionId: session_id?.substring(0, 20) + "...",
695
862
  timestamp: new Date().toISOString(),
696
- eventType: 'delegation_storage_error',
697
- severity: 'warning'
863
+ eventType: "delegation_storage_error",
864
+ severity: "warning",
698
865
  });
699
866
  }
700
867
  }
@@ -703,21 +870,23 @@ export function createOAuthCallbackHandler(config = {}) {
703
870
  delegationId: tokenData.delegation_id || delegation_id,
704
871
  sessionId: session_id,
705
872
  scopes: tokenData.scopes || [],
706
- expiresIn: autoClose ? autoCloseDelay : 0
873
+ expiresIn: autoClose ? autoCloseDelay : 0,
707
874
  });
708
875
  return c.html(html);
709
876
  }
710
877
  catch (error) {
711
- console.error('[OAuth] 🔒 SECURITY EVENT: Unexpected error:', {
878
+ console.error("[OAuth] 🔒 SECURITY EVENT: Unexpected error:", {
712
879
  error: error instanceof Error ? error.message : String(error),
713
880
  stack: error instanceof Error ? error.stack : undefined,
714
881
  timestamp: new Date().toISOString(),
715
- eventType: 'oauth_unexpected_error',
716
- severity: 'error'
882
+ eventType: "oauth_unexpected_error",
883
+ severity: "error",
717
884
  });
718
885
  const html = errorTemplate({
719
- error: 'internal_error',
720
- description: error instanceof Error ? error.message : 'An unexpected error occurred'
886
+ error: "internal_error",
887
+ description: error instanceof Error
888
+ ? error.message
889
+ : "An unexpected error occurred",
721
890
  });
722
891
  return c.html(html, 500);
723
892
  }
@@ -736,17 +905,17 @@ export function createOAuthCallbackHandler(config = {}) {
736
905
  */
737
906
  export function extractDelegationToken(c) {
738
907
  // Check Authorization header
739
- const authHeader = c.req.header('Authorization');
740
- if (authHeader?.startsWith('Bearer ')) {
908
+ const authHeader = c.req.header("Authorization");
909
+ if (authHeader?.startsWith("Bearer ")) {
741
910
  return authHeader.substring(7);
742
911
  }
743
912
  // Check custom header
744
- const customHeader = c.req.header('x-delegation-token');
913
+ const customHeader = c.req.header("x-delegation-token");
745
914
  if (customHeader) {
746
915
  return customHeader;
747
916
  }
748
917
  // Check query parameter
749
- const queryParam = c.req.query('delegation_token');
918
+ const queryParam = c.req.query("delegation_token");
750
919
  if (queryParam) {
751
920
  return queryParam;
752
921
  }