@adcp/sdk 13.0.0-rc.4 → 13.0.0-rc.5

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.
Files changed (168) hide show
  1. package/README.md +7 -1
  2. package/bin/adcp.js +7 -1
  3. package/dist/lib/adapters/property-list-adapter.d.mts +5 -1
  4. package/dist/lib/adapters/property-list-adapter.d.ts +5 -1
  5. package/dist/lib/adapters/property-list-adapter.d.ts.map +1 -1
  6. package/dist/lib/adapters/property-list-adapter.js +7 -7
  7. package/dist/lib/adapters/property-list-adapter.js.map +1 -1
  8. package/dist/lib/adapters/property-list-adapter.mjs +7 -7
  9. package/dist/lib/adapters/property-list-adapter.mjs.map +1 -1
  10. package/dist/lib/auth/oauth/CLIFlowHandler.d.mts +10 -3
  11. package/dist/lib/auth/oauth/CLIFlowHandler.d.ts +10 -3
  12. package/dist/lib/auth/oauth/CLIFlowHandler.d.ts.map +1 -1
  13. package/dist/lib/auth/oauth/CLIFlowHandler.js +40 -4
  14. package/dist/lib/auth/oauth/CLIFlowHandler.js.map +1 -1
  15. package/dist/lib/auth/oauth/CLIFlowHandler.mjs +40 -4
  16. package/dist/lib/auth/oauth/CLIFlowHandler.mjs.map +1 -1
  17. package/dist/lib/auth/oauth/index.d.mts +1 -1
  18. package/dist/lib/auth/oauth/index.d.ts +1 -1
  19. package/dist/lib/auth/oauth/index.d.ts.map +1 -1
  20. package/dist/lib/auth/oauth/index.js +2 -0
  21. package/dist/lib/auth/oauth/index.js.map +1 -1
  22. package/dist/lib/auth/oauth/index.mjs +2 -0
  23. package/dist/lib/auth/oauth/index.mjs.map +1 -1
  24. package/dist/lib/auth/oauth/web-flow.d.mts +12 -0
  25. package/dist/lib/auth/oauth/web-flow.d.ts +12 -0
  26. package/dist/lib/auth/oauth/web-flow.d.ts.map +1 -1
  27. package/dist/lib/auth/oauth/web-flow.js +20 -2
  28. package/dist/lib/auth/oauth/web-flow.js.map +1 -1
  29. package/dist/lib/auth/oauth/web-flow.mjs +19 -2
  30. package/dist/lib/auth/oauth/web-flow.mjs.map +1 -1
  31. package/dist/lib/core/AgentClient.d.ts.map +1 -1
  32. package/dist/lib/core/SingleAgentClient.d.mts +27 -1
  33. package/dist/lib/core/SingleAgentClient.d.ts +27 -1
  34. package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
  35. package/dist/lib/core/SingleAgentClient.js +46 -18
  36. package/dist/lib/core/SingleAgentClient.js.map +1 -1
  37. package/dist/lib/core/SingleAgentClient.mjs +46 -18
  38. package/dist/lib/core/SingleAgentClient.mjs.map +1 -1
  39. package/dist/lib/net/address-guards.d.ts.map +1 -1
  40. package/dist/lib/net/address-guards.js +4 -0
  41. package/dist/lib/net/address-guards.js.map +1 -1
  42. package/dist/lib/net/address-guards.mjs +4 -0
  43. package/dist/lib/net/address-guards.mjs.map +1 -1
  44. package/dist/lib/protocols/a2a.js +2 -2
  45. package/dist/lib/protocols/a2a.js.map +1 -1
  46. package/dist/lib/protocols/a2a.mjs +2 -2
  47. package/dist/lib/protocols/a2a.mjs.map +1 -1
  48. package/dist/lib/protocols/index.d.ts.map +1 -1
  49. package/dist/lib/protocols/index.js +28 -7
  50. package/dist/lib/protocols/index.js.map +1 -1
  51. package/dist/lib/protocols/index.mjs +28 -7
  52. package/dist/lib/protocols/index.mjs.map +1 -1
  53. package/dist/lib/protocols/mcp-modern.d.ts.map +1 -1
  54. package/dist/lib/protocols/mcp-modern.js +18 -5
  55. package/dist/lib/protocols/mcp-modern.js.map +1 -1
  56. package/dist/lib/protocols/mcp-modern.mjs +18 -5
  57. package/dist/lib/protocols/mcp-modern.mjs.map +1 -1
  58. package/dist/lib/protocols/mcp-tasks.d.ts.map +1 -1
  59. package/dist/lib/protocols/mcp-tasks.js +2 -12
  60. package/dist/lib/protocols/mcp-tasks.js.map +1 -1
  61. package/dist/lib/protocols/mcp-tasks.mjs +2 -12
  62. package/dist/lib/protocols/mcp-tasks.mjs.map +1 -1
  63. package/dist/lib/protocols/mcp.js +2 -2
  64. package/dist/lib/protocols/mcp.js.map +1 -1
  65. package/dist/lib/protocols/mcp.mjs +2 -2
  66. package/dist/lib/protocols/mcp.mjs.map +1 -1
  67. package/dist/lib/registry/types.generated.d.mts +547 -10
  68. package/dist/lib/registry/types.generated.d.ts +547 -10
  69. package/dist/lib/registry/types.generated.d.ts.map +1 -1
  70. package/dist/lib/registry/types.generated.js.map +1 -1
  71. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  72. package/dist/lib/server/decisioning/account.d.mts +6 -4
  73. package/dist/lib/server/decisioning/account.d.ts +6 -4
  74. package/dist/lib/server/decisioning/account.d.ts.map +1 -1
  75. package/dist/lib/server/decisioning/account.js.map +1 -1
  76. package/dist/lib/server/decisioning/account.mjs.map +1 -1
  77. package/dist/lib/server/decisioning/manifest-helpers.d.mts +6 -5
  78. package/dist/lib/server/decisioning/manifest-helpers.d.ts +6 -5
  79. package/dist/lib/server/decisioning/manifest-helpers.d.ts.map +1 -1
  80. package/dist/lib/server/decisioning/manifest-helpers.js.map +1 -1
  81. package/dist/lib/server/decisioning/manifest-helpers.mjs.map +1 -1
  82. package/dist/lib/server/decisioning/runtime/from-platform.js +4 -2
  83. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
  84. package/dist/lib/server/decisioning/runtime/from-platform.mjs +4 -2
  85. package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
  86. package/dist/lib/server/decisioning/tenant-store.d.mts +26 -14
  87. package/dist/lib/server/decisioning/tenant-store.d.ts +26 -14
  88. package/dist/lib/server/decisioning/tenant-store.d.ts.map +1 -1
  89. package/dist/lib/server/decisioning/tenant-store.js +6 -1
  90. package/dist/lib/server/decisioning/tenant-store.js.map +1 -1
  91. package/dist/lib/server/decisioning/tenant-store.mjs +6 -1
  92. package/dist/lib/server/decisioning/tenant-store.mjs.map +1 -1
  93. package/dist/lib/server/pin-and-bind-fetch.d.mts +2 -0
  94. package/dist/lib/server/pin-and-bind-fetch.d.ts +2 -0
  95. package/dist/lib/server/pin-and-bind-fetch.d.ts.map +1 -1
  96. package/dist/lib/server/pin-and-bind-fetch.js +2 -0
  97. package/dist/lib/server/pin-and-bind-fetch.js.map +1 -1
  98. package/dist/lib/server/pin-and-bind-fetch.mjs +2 -0
  99. package/dist/lib/server/pin-and-bind-fetch.mjs.map +1 -1
  100. package/dist/lib/server/structured-serialize.d.ts.map +1 -1
  101. package/dist/lib/server/structured-serialize.js +10 -2
  102. package/dist/lib/server/structured-serialize.js.map +1 -1
  103. package/dist/lib/server/structured-serialize.mjs +10 -2
  104. package/dist/lib/server/structured-serialize.mjs.map +1 -1
  105. package/dist/lib/server/webhook-emitter.d.ts.map +1 -1
  106. package/dist/lib/server/webhook-emitter.js +4 -2
  107. package/dist/lib/server/webhook-emitter.js.map +1 -1
  108. package/dist/lib/server/webhook-emitter.mjs +4 -2
  109. package/dist/lib/server/webhook-emitter.mjs.map +1 -1
  110. package/dist/lib/signing/middleware.d.mts +2 -1
  111. package/dist/lib/signing/middleware.d.ts +2 -1
  112. package/dist/lib/signing/middleware.d.ts.map +1 -1
  113. package/dist/lib/signing/middleware.js +26 -4
  114. package/dist/lib/signing/middleware.js.map +1 -1
  115. package/dist/lib/signing/middleware.mjs +26 -4
  116. package/dist/lib/signing/middleware.mjs.map +1 -1
  117. package/dist/lib/signing/webhook-verifier.d.ts.map +1 -1
  118. package/dist/lib/signing/webhook-verifier.js +4 -2
  119. package/dist/lib/signing/webhook-verifier.js.map +1 -1
  120. package/dist/lib/signing/webhook-verifier.mjs +4 -2
  121. package/dist/lib/signing/webhook-verifier.mjs.map +1 -1
  122. package/dist/lib/testing/agent-tester.d.ts.map +1 -1
  123. package/dist/lib/testing/agent-tester.js +46 -1
  124. package/dist/lib/testing/agent-tester.js.map +1 -1
  125. package/dist/lib/testing/agent-tester.mjs +46 -1
  126. package/dist/lib/testing/agent-tester.mjs.map +1 -1
  127. package/dist/lib/types/asset-instances.d.mts +8 -6
  128. package/dist/lib/types/asset-instances.d.ts +8 -6
  129. package/dist/lib/types/asset-instances.d.ts.map +1 -1
  130. package/dist/lib/types/asset-instances.js.map +1 -1
  131. package/dist/lib/types/index.d.ts.map +1 -1
  132. package/dist/lib/types/index.js.map +1 -1
  133. package/dist/lib/types/index.mjs.map +1 -1
  134. package/dist/lib/utils/probe-policy.d.mts +0 -16
  135. package/dist/lib/utils/probe-policy.d.ts +0 -16
  136. package/dist/lib/utils/probe-policy.d.ts.map +1 -1
  137. package/dist/lib/utils/probe-policy.js +15 -1
  138. package/dist/lib/utils/probe-policy.js.map +1 -1
  139. package/dist/lib/utils/probe-policy.mjs +15 -1
  140. package/dist/lib/utils/probe-policy.mjs.map +1 -1
  141. package/dist/lib/utils/redact-args.d.mts +36 -0
  142. package/dist/lib/utils/redact-args.d.ts +37 -0
  143. package/dist/lib/utils/redact-args.d.ts.map +1 -0
  144. package/dist/lib/utils/redact-args.js +46 -0
  145. package/dist/lib/utils/redact-args.js.map +1 -0
  146. package/dist/lib/utils/redact-args.mjs +22 -0
  147. package/dist/lib/utils/redact-args.mjs.map +1 -0
  148. package/dist/lib/v2/projection/creative-delivery.js +1 -5
  149. package/dist/lib/v2/projection/creative-delivery.js.map +1 -1
  150. package/dist/lib/v2/projection/creative-delivery.mjs +1 -5
  151. package/dist/lib/v2/projection/creative-delivery.mjs.map +1 -1
  152. package/dist/lib/validation/index.d.mts +15 -1
  153. package/dist/lib/validation/index.d.ts +15 -1
  154. package/dist/lib/validation/index.d.ts.map +1 -1
  155. package/dist/lib/validation/index.js +4 -8
  156. package/dist/lib/validation/index.js.map +1 -1
  157. package/dist/lib/validation/index.mjs +4 -8
  158. package/dist/lib/validation/index.mjs.map +1 -1
  159. package/dist/lib/version.d.mts +3 -3
  160. package/dist/lib/version.d.ts +3 -3
  161. package/dist/lib/version.js +3 -3
  162. package/dist/lib/version.js.map +1 -1
  163. package/dist/lib/version.mjs +3 -3
  164. package/dist/lib/version.mjs.map +1 -1
  165. package/examples/hello_seller_adapter_multi_tenant.ts +7 -0
  166. package/package.json +3 -2
  167. package/skills/build-holdco-agent/SKILL.md +19 -0
  168. package/skills/cross-cutting.md +3 -1
package/README.md CHANGED
@@ -494,9 +494,15 @@ await signingFetch('https://seller.example.com/mcp', {
494
494
  ```typescript
495
495
  import { createExpressVerifier, StaticJwksResolver, InMemoryReplayStore } from '@adcp/sdk/signing';
496
496
 
497
+ // Raw-body capture MUST be mounted ahead of the verifier — express.json()
498
+ // would otherwise consume the stream and the verifier would have no bytes to
499
+ // hash. `rawBodyVerify` comes from `createExpressAdapter()`; the inline form is
500
+ // equivalent.
501
+ app.use(express.json({ verify: adapter.rawBodyVerify }));
502
+ // or: app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf.toString('utf8'); } }));
503
+
497
504
  app.post(
498
505
  '/mcp',
499
- rawBodyMiddleware(),
500
506
  createExpressVerifier({
501
507
  capability: {
502
508
  supported: true,
package/bin/adcp.js CHANGED
@@ -27,6 +27,7 @@ const { readFileSync, statSync } = require('fs');
27
27
  const path = require('path');
28
28
  const { pathToFileURL } = require('url');
29
29
  const net = require('net');
30
+ const { randomBytes } = require('crypto');
30
31
  const { spawn } = require('child_process');
31
32
  const { AsyncWebhookHandler } = require('./adcp-async-handler.js');
32
33
  const {
@@ -6346,7 +6347,12 @@ credential material — never sync or commit.
6346
6347
  debug: debug,
6347
6348
  ...(webhookUrl && {
6348
6349
  webhookUrlTemplate: webhookUrl,
6349
- webhookSecret: 'cli-webhook-secret',
6350
+ // Per-invocation random secret. A constant here would ship in npm, and
6351
+ // because `push_notification_config.authentication` is a scheme
6352
+ // selector rather than a fallback, registering it would actively
6353
+ // downgrade every `--wait` webhook to legacy HMAC keyed by a value
6354
+ // anyone can read out of the published package.
6355
+ webhookSecret: randomBytes(32).toString('base64url'),
6350
6356
  }),
6351
6357
  });
6352
6358
 
@@ -108,7 +108,11 @@ export declare class PropertyListAdapter implements IPropertyListAdapter {
108
108
  */
109
109
  resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]>;
110
110
  /**
111
- * Generate a random token for auth
111
+ * Generate a random token for auth.
112
+ *
113
+ * Returned to callers as `auth_token`, so it is a bearer credential and must
114
+ * come from a CSPRNG — `Math.random` exposes recoverable PRNG state. 32 bytes
115
+ * of base64url is 256 bits of entropy.
112
116
  */
113
117
  protected generateToken(): string;
114
118
  }
@@ -108,7 +108,11 @@ export declare class PropertyListAdapter implements IPropertyListAdapter {
108
108
  */
109
109
  resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]>;
110
110
  /**
111
- * Generate a random token for auth
111
+ * Generate a random token for auth.
112
+ *
113
+ * Returned to callers as `auth_token`, so it is a bearer credential and must
114
+ * come from a CSPRNG — `Math.random` exposes recoverable PRNG state. 32 bytes
115
+ * of base64url is 256 bits of entropy.
112
116
  */
113
117
  protected generateToken(): string;
114
118
  }
@@ -1 +1 @@
1
- {"version":3,"file":"property-list-adapter.d.ts","sourceRoot":"","sources":["../../../src/lib/adapters/property-list-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"property-list-adapter.d.ts","sourceRoot":"","sources":["../../../src/lib/adapters/property-list-adapter.ts"],"names":[],"mappings":"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"}
@@ -25,6 +25,7 @@ __export(property_list_adapter_exports, {
25
25
  isPropertyListError: () => isPropertyListError
26
26
  });
27
27
  module.exports = __toCommonJS(property_list_adapter_exports);
28
+ var import_node_crypto = require("node:crypto");
28
29
  class PropertyListError extends Error {
29
30
  constructor(code, message) {
30
31
  super(message);
@@ -216,15 +217,14 @@ class PropertyListAdapter {
216
217
  return [];
217
218
  }
218
219
  /**
219
- * Generate a random token for auth
220
+ * Generate a random token for auth.
221
+ *
222
+ * Returned to callers as `auth_token`, so it is a bearer credential and must
223
+ * come from a CSPRNG — `Math.random` exposes recoverable PRNG state. 32 bytes
224
+ * of base64url is 256 bits of entropy.
220
225
  */
221
226
  generateToken() {
222
- const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
223
- let result = "";
224
- for (let i = 0; i < 32; i++) {
225
- result += chars[Math.floor(Math.random() * chars.length)];
226
- }
227
- return result;
227
+ return (0, import_node_crypto.randomBytes)(32).toString("base64url");
228
228
  }
229
229
  }
230
230
  function isPropertyListError(error) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/adapters/property-list-adapter.ts"],"sourcesContent":["/**\n * Property List Adapter\n *\n * Server-side adapter for implementing property list management logic.\n * Publishers use this to manage buyer-defined property lists for filtering\n * available inventory.\n *\n * This is a stub implementation that stores lists in memory.\n * Publishers should extend or replace this with persistent storage.\n */\n\nimport type {\n CreatePropertyListRequest,\n CreatePropertyListResponse,\n UpdatePropertyListRequest,\n UpdatePropertyListResponse,\n GetPropertyListRequest,\n GetPropertyListResponse,\n ListPropertyListsRequest,\n ListPropertyListsResponse,\n DeletePropertyListRequest,\n DeletePropertyListResponse,\n PropertyList,\n} from '../types/tools.generated';\n\n/**\n * Resolved property identifier\n */\nexport interface ResolvedProperty {\n identifier_type: string;\n identifier_value: string;\n publisher_domain?: string;\n display_name?: string;\n status?: 'active' | 'inactive' | 'pending';\n}\n\n/**\n * Error thrown when property list operations fail\n */\nexport class PropertyListError extends Error {\n constructor(\n public code: string,\n message: string\n ) {\n super(message);\n this.name = 'PropertyListError';\n }\n}\n\n/**\n * Abstract interface for property list adapters.\n * Publishers implement this to provide their storage and resolution logic.\n */\nexport interface IPropertyListAdapter {\n /**\n * Check if property list management is supported by this server\n */\n isSupported(): boolean;\n\n /**\n * Create a new property list\n */\n createList(request: CreatePropertyListRequest): Promise<CreatePropertyListResponse>;\n\n /**\n * Update an existing property list\n */\n updateList(request: UpdatePropertyListRequest): Promise<UpdatePropertyListResponse>;\n\n /**\n * Get a property list with optional resolution\n */\n getList(request: GetPropertyListRequest): Promise<GetPropertyListResponse>;\n\n /**\n * List all property lists for an account\n */\n listLists(request: ListPropertyListsRequest): Promise<ListPropertyListsResponse>;\n\n /**\n * Delete a property list\n */\n deleteList(request: DeletePropertyListRequest): Promise<DeletePropertyListResponse>;\n\n /**\n * Check if a property identifier is in a property list.\n * Used by get_products and create_media_buy for filtering.\n */\n isPropertyInList(listId: string, identifierType: string, identifierValue: string): Promise<boolean>;\n\n /**\n * Resolve a property list to its constituent identifiers.\n * Applies filters and returns matching properties.\n */\n resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]>;\n}\n\n/**\n * Error codes for property list operations\n */\nexport const PropertyListErrorCodes = {\n NOT_SUPPORTED: 'UNSUPPORTED_FEATURE',\n LIST_NOT_FOUND: 'REFERENCE_NOT_FOUND',\n INVALID_LIST: 'INVALID_REQUEST',\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n} as const;\n\n/**\n * Stub implementation of PropertyListAdapter.\n * Uses in-memory storage for development and testing.\n *\n * Publishers should extend this class or provide their own implementation\n * that integrates with their property management systems.\n */\nexport class PropertyListAdapter implements IPropertyListAdapter {\n private lists: Map<string, PropertyList> = new Map();\n private authTokens: Map<string, string> = new Map();\n private nextId = 1;\n\n /**\n * Check if property list management is supported.\n * Override this to return true when implementing real logic.\n */\n isSupported(): boolean {\n return false;\n }\n\n async createList(request: CreatePropertyListRequest): Promise<CreatePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n // Generate ID and auth token\n const listId = `pl_${this.nextId++}_${Date.now()}`;\n const authToken = `plat_${this.generateToken()}`;\n const now = new Date().toISOString();\n\n const list: PropertyList = {\n list_id: listId,\n name: request.name,\n description: request.description,\n base_properties: request.base_properties,\n filters: request.filters,\n brand: request.brand,\n created_at: now,\n updated_at: now,\n property_count: 0,\n };\n\n this.lists.set(listId, list);\n this.authTokens.set(listId, authToken);\n\n return {\n status: 'completed',\n list,\n auth_token: authToken,\n };\n }\n\n async updateList(request: UpdatePropertyListRequest): Promise<UpdatePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n const existing = this.lists.get(request.list_id);\n if (!existing) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n // Update fields\n const updated: PropertyList = {\n ...existing,\n name: request.name ?? existing.name,\n description: request.description ?? existing.description,\n base_properties: request.base_properties ?? existing.base_properties,\n filters: request.filters ?? existing.filters,\n brand: request.brand ?? existing.brand,\n updated_at: new Date().toISOString(),\n };\n\n this.lists.set(request.list_id, updated);\n\n return { status: 'completed', list: updated };\n }\n\n async getList(request: GetPropertyListRequest): Promise<GetPropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n const list = this.lists.get(request.list_id);\n if (!list) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n // Resolve property identifiers if requested\n let identifiers: any[] | undefined;\n if (request.resolve !== false) {\n const resolved = await this.resolveList(\n request.list_id,\n request.pagination?.max_results ?? undefined,\n request.pagination?.cursor ?? undefined\n );\n identifiers = resolved.map(p => ({\n identifier_type: p.identifier_type,\n identifier_value: p.identifier_value,\n }));\n }\n\n return {\n status: 'completed',\n list: {\n ...list,\n property_count: identifiers?.length ?? list.property_count,\n },\n identifiers,\n pagination: {\n has_more: false,\n total_count: identifiers?.length,\n },\n resolved_at: new Date().toISOString(),\n };\n }\n\n async listLists(request: ListPropertyListsRequest): Promise<ListPropertyListsResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n // Filter by account if provided. AdCP 3.0 identifies list ownership via the\n // account primitive (either account_id or brand+operator). Match on whichever\n // key the request carries.\n let lists = Array.from(this.lists.values());\n if (request.account) {\n const filter = request.account;\n lists = lists.filter(l => {\n const acct = l.account;\n if (!acct) return false;\n if ('account_id' in filter && 'account_id' in acct) {\n return filter.account_id === acct.account_id;\n }\n if ('brand' in filter && 'brand' in acct) {\n return filter.brand.domain === acct.brand.domain && filter.operator === acct.operator;\n }\n return false;\n });\n }\n\n // Filter by name if provided\n if (request.name_contains) {\n const searchTerm = request.name_contains.toLowerCase();\n lists = lists.filter(l => l.name.toLowerCase().includes(searchTerm));\n }\n\n // Apply pagination\n const maxResults = request.pagination?.max_results ?? 100;\n const paginatedLists = lists.slice(0, maxResults);\n\n return {\n status: 'completed',\n lists: paginatedLists,\n pagination: {\n has_more: paginatedLists.length < lists.length,\n total_count: lists.length,\n },\n };\n }\n\n async deleteList(request: DeletePropertyListRequest): Promise<DeletePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n if (!this.lists.has(request.list_id)) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n this.lists.delete(request.list_id);\n this.authTokens.delete(request.list_id);\n\n return {\n status: 'completed',\n deleted: true,\n list_id: request.list_id,\n };\n }\n\n /**\n * Check if a property is in a list.\n * Stub implementation returns false (property not found).\n */\n async isPropertyInList(listId: string, identifierType: string, identifierValue: string): Promise<boolean> {\n if (!this.isSupported()) {\n // When not supported, all properties are considered in-list (no filtering)\n return true;\n }\n\n // Override in subclass to implement actual lookup\n return false;\n }\n\n /**\n * Resolve a property list to identifiers.\n * Stub implementation returns empty array.\n */\n async resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]> {\n if (!this.isSupported()) {\n return [];\n }\n\n // Override in subclass to implement actual resolution\n return [];\n }\n\n /**\n * Generate a random token for auth\n */\n protected generateToken(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n for (let i = 0; i < 32; i++) {\n result += chars[Math.floor(Math.random() * chars.length)];\n }\n return result;\n }\n}\n\n/**\n * Helper to check if an error is a PropertyListError\n */\nexport function isPropertyListError(error: unknown): error is PropertyListError {\n return error instanceof PropertyListError;\n}\n\n/**\n * Default singleton instance for servers that don't need property list management\n */\nexport const defaultPropertyListAdapter = new PropertyListAdapter();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuCO,MAAM,0BAA0B,MAAM;AAAA,EAC3C,YACS,MACP,SACA;AACA,UAAM,OAAO;AAHN;AAIP,SAAK,OAAO;AAAA,EACd;AAAA,EALS;AAMX;AAqDO,MAAM,yBAAyB;AAAA,EACpC,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,gBAAgB;AAClB;AASO,MAAM,oBAAoD;AAAA,EACvD,QAAmC,oBAAI,IAAI;AAAA,EAC3C,aAAkC,oBAAI,IAAI;AAAA,EAC1C,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjB,cAAuB;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,SAAyE;AACxF,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAGA,UAAM,SAAS,MAAM,KAAK,QAAQ,IAAI,KAAK,IAAI,CAAC;AAChD,UAAM,YAAY,QAAQ,KAAK,cAAc,CAAC;AAC9C,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,UAAM,OAAqB;AAAA,MACzB,SAAS;AAAA,MACT,MAAM,QAAQ;AAAA,MACd,aAAa,QAAQ;AAAA,MACrB,iBAAiB,QAAQ;AAAA,MACzB,SAAS,QAAQ;AAAA,MACjB,OAAO,QAAQ;AAAA,MACf,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,gBAAgB;AAAA,IAClB;AAEA,SAAK,MAAM,IAAI,QAAQ,IAAI;AAC3B,SAAK,WAAW,IAAI,QAAQ,SAAS;AAErC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,SAAyE;AACxF,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,MAAM,IAAI,QAAQ,OAAO;AAC/C,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,kBAAkB,uBAAuB,gBAAgB,4BAA4B,QAAQ,OAAO,EAAE;AAAA,IAClH;AAGA,UAAM,UAAwB;AAAA,MAC5B,GAAG;AAAA,MACH,MAAM,QAAQ,QAAQ,SAAS;AAAA,MAC/B,aAAa,QAAQ,eAAe,SAAS;AAAA,MAC7C,iBAAiB,QAAQ,mBAAmB,SAAS;AAAA,MACrD,SAAS,QAAQ,WAAW,SAAS;AAAA,MACrC,OAAO,QAAQ,SAAS,SAAS;AAAA,MACjC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrC;AAEA,SAAK,MAAM,IAAI,QAAQ,SAAS,OAAO;AAEvC,WAAO,EAAE,QAAQ,aAAa,MAAM,QAAQ;AAAA,EAC9C;AAAA,EAEA,MAAM,QAAQ,SAAmE;AAC/E,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,MAAM,IAAI,QAAQ,OAAO;AAC3C,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,kBAAkB,uBAAuB,gBAAgB,4BAA4B,QAAQ,OAAO,EAAE;AAAA,IAClH;AAGA,QAAI;AACJ,QAAI,QAAQ,YAAY,OAAO;AAC7B,YAAM,WAAW,MAAM,KAAK;AAAA,QAC1B,QAAQ;AAAA,QACR,QAAQ,YAAY,eAAe;AAAA,QACnC,QAAQ,YAAY,UAAU;AAAA,MAChC;AACA,oBAAc,SAAS,IAAI,QAAM;AAAA,QAC/B,iBAAiB,EAAE;AAAA,QACnB,kBAAkB,EAAE;AAAA,MACtB,EAAE;AAAA,IACJ;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,GAAG;AAAA,QACH,gBAAgB,aAAa,UAAU,KAAK;AAAA,MAC9C;AAAA,MACA;AAAA,MACA,YAAY;AAAA,QACV,UAAU;AAAA,QACV,aAAa,aAAa;AAAA,MAC5B;AAAA,MACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,SAAuE;AACrF,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAKA,QAAI,QAAQ,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;AAC1C,QAAI,QAAQ,SAAS;AACnB,YAAM,SAAS,QAAQ;AACvB,cAAQ,MAAM,OAAO,OAAK;AACxB,cAAM,OAAO,EAAE;AACf,YAAI,CAAC,KAAM,QAAO;AAClB,YAAI,gBAAgB,UAAU,gBAAgB,MAAM;AAClD,iBAAO,OAAO,eAAe,KAAK;AAAA,QACpC;AACA,YAAI,WAAW,UAAU,WAAW,MAAM;AACxC,iBAAO,OAAO,MAAM,WAAW,KAAK,MAAM,UAAU,OAAO,aAAa,KAAK;AAAA,QAC/E;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAGA,QAAI,QAAQ,eAAe;AACzB,YAAM,aAAa,QAAQ,cAAc,YAAY;AACrD,cAAQ,MAAM,OAAO,OAAK,EAAE,KAAK,YAAY,EAAE,SAAS,UAAU,CAAC;AAAA,IACrE;AAGA,UAAM,aAAa,QAAQ,YAAY,eAAe;AACtD,UAAM,iBAAiB,MAAM,MAAM,GAAG,UAAU;AAEhD,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,YAAY;AAAA,QACV,UAAU,eAAe,SAAS,MAAM;AAAA,QACxC,aAAa,MAAM;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,SAAyE;AACxF,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,MAAM,IAAI,QAAQ,OAAO,GAAG;AACpC,YAAM,IAAI,kBAAkB,uBAAuB,gBAAgB,4BAA4B,QAAQ,OAAO,EAAE;AAAA,IAClH;AAEA,SAAK,MAAM,OAAO,QAAQ,OAAO;AACjC,SAAK,WAAW,OAAO,QAAQ,OAAO;AAEtC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS,QAAQ;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBAAiB,QAAgB,gBAAwB,iBAA2C;AACxG,QAAI,CAAC,KAAK,YAAY,GAAG;AAEvB,aAAO;AAAA,IACT;AAGA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,QAAgB,YAAqB,QAA8C;AACnG,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,aAAO,CAAC;AAAA,IACV;AAGA,WAAO,CAAC;AAAA,EACV;AAAA;AAAA;AAAA;AAAA,EAKU,gBAAwB;AAChC,UAAM,QAAQ;AACd,QAAI,SAAS;AACb,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,gBAAU,MAAM,KAAK,MAAM,KAAK,OAAO,IAAI,MAAM,MAAM,CAAC;AAAA,IAC1D;AACA,WAAO;AAAA,EACT;AACF;AAKO,SAAS,oBAAoB,OAA4C;AAC9E,SAAO,iBAAiB;AAC1B;AAKO,MAAM,6BAA6B,IAAI,oBAAoB;","names":[]}
1
+ {"version":3,"sources":["../../../src/lib/adapters/property-list-adapter.ts"],"sourcesContent":["/**\n * Property List Adapter\n *\n * Server-side adapter for implementing property list management logic.\n * Publishers use this to manage buyer-defined property lists for filtering\n * available inventory.\n *\n * This is a stub implementation that stores lists in memory.\n * Publishers should extend or replace this with persistent storage.\n */\n\nimport { randomBytes } from 'node:crypto';\n\nimport type {\n CreatePropertyListRequest,\n CreatePropertyListResponse,\n UpdatePropertyListRequest,\n UpdatePropertyListResponse,\n GetPropertyListRequest,\n GetPropertyListResponse,\n ListPropertyListsRequest,\n ListPropertyListsResponse,\n DeletePropertyListRequest,\n DeletePropertyListResponse,\n PropertyList,\n} from '../types/tools.generated';\n\n/**\n * Resolved property identifier\n */\nexport interface ResolvedProperty {\n identifier_type: string;\n identifier_value: string;\n publisher_domain?: string;\n display_name?: string;\n status?: 'active' | 'inactive' | 'pending';\n}\n\n/**\n * Error thrown when property list operations fail\n */\nexport class PropertyListError extends Error {\n constructor(\n public code: string,\n message: string\n ) {\n super(message);\n this.name = 'PropertyListError';\n }\n}\n\n/**\n * Abstract interface for property list adapters.\n * Publishers implement this to provide their storage and resolution logic.\n */\nexport interface IPropertyListAdapter {\n /**\n * Check if property list management is supported by this server\n */\n isSupported(): boolean;\n\n /**\n * Create a new property list\n */\n createList(request: CreatePropertyListRequest): Promise<CreatePropertyListResponse>;\n\n /**\n * Update an existing property list\n */\n updateList(request: UpdatePropertyListRequest): Promise<UpdatePropertyListResponse>;\n\n /**\n * Get a property list with optional resolution\n */\n getList(request: GetPropertyListRequest): Promise<GetPropertyListResponse>;\n\n /**\n * List all property lists for an account\n */\n listLists(request: ListPropertyListsRequest): Promise<ListPropertyListsResponse>;\n\n /**\n * Delete a property list\n */\n deleteList(request: DeletePropertyListRequest): Promise<DeletePropertyListResponse>;\n\n /**\n * Check if a property identifier is in a property list.\n * Used by get_products and create_media_buy for filtering.\n */\n isPropertyInList(listId: string, identifierType: string, identifierValue: string): Promise<boolean>;\n\n /**\n * Resolve a property list to its constituent identifiers.\n * Applies filters and returns matching properties.\n */\n resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]>;\n}\n\n/**\n * Error codes for property list operations\n */\nexport const PropertyListErrorCodes = {\n NOT_SUPPORTED: 'UNSUPPORTED_FEATURE',\n LIST_NOT_FOUND: 'REFERENCE_NOT_FOUND',\n INVALID_LIST: 'INVALID_REQUEST',\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n} as const;\n\n/**\n * Stub implementation of PropertyListAdapter.\n * Uses in-memory storage for development and testing.\n *\n * Publishers should extend this class or provide their own implementation\n * that integrates with their property management systems.\n */\nexport class PropertyListAdapter implements IPropertyListAdapter {\n private lists: Map<string, PropertyList> = new Map();\n private authTokens: Map<string, string> = new Map();\n private nextId = 1;\n\n /**\n * Check if property list management is supported.\n * Override this to return true when implementing real logic.\n */\n isSupported(): boolean {\n return false;\n }\n\n async createList(request: CreatePropertyListRequest): Promise<CreatePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n // Generate ID and auth token\n const listId = `pl_${this.nextId++}_${Date.now()}`;\n const authToken = `plat_${this.generateToken()}`;\n const now = new Date().toISOString();\n\n const list: PropertyList = {\n list_id: listId,\n name: request.name,\n description: request.description,\n base_properties: request.base_properties,\n filters: request.filters,\n brand: request.brand,\n created_at: now,\n updated_at: now,\n property_count: 0,\n };\n\n this.lists.set(listId, list);\n this.authTokens.set(listId, authToken);\n\n return {\n status: 'completed',\n list,\n auth_token: authToken,\n };\n }\n\n async updateList(request: UpdatePropertyListRequest): Promise<UpdatePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n const existing = this.lists.get(request.list_id);\n if (!existing) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n // Update fields\n const updated: PropertyList = {\n ...existing,\n name: request.name ?? existing.name,\n description: request.description ?? existing.description,\n base_properties: request.base_properties ?? existing.base_properties,\n filters: request.filters ?? existing.filters,\n brand: request.brand ?? existing.brand,\n updated_at: new Date().toISOString(),\n };\n\n this.lists.set(request.list_id, updated);\n\n return { status: 'completed', list: updated };\n }\n\n async getList(request: GetPropertyListRequest): Promise<GetPropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n const list = this.lists.get(request.list_id);\n if (!list) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n // Resolve property identifiers if requested\n let identifiers: any[] | undefined;\n if (request.resolve !== false) {\n const resolved = await this.resolveList(\n request.list_id,\n request.pagination?.max_results ?? undefined,\n request.pagination?.cursor ?? undefined\n );\n identifiers = resolved.map(p => ({\n identifier_type: p.identifier_type,\n identifier_value: p.identifier_value,\n }));\n }\n\n return {\n status: 'completed',\n list: {\n ...list,\n property_count: identifiers?.length ?? list.property_count,\n },\n identifiers,\n pagination: {\n has_more: false,\n total_count: identifiers?.length,\n },\n resolved_at: new Date().toISOString(),\n };\n }\n\n async listLists(request: ListPropertyListsRequest): Promise<ListPropertyListsResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n // Filter by account if provided. AdCP 3.0 identifies list ownership via the\n // account primitive (either account_id or brand+operator). Match on whichever\n // key the request carries.\n let lists = Array.from(this.lists.values());\n if (request.account) {\n const filter = request.account;\n lists = lists.filter(l => {\n const acct = l.account;\n if (!acct) return false;\n if ('account_id' in filter && 'account_id' in acct) {\n return filter.account_id === acct.account_id;\n }\n if ('brand' in filter && 'brand' in acct) {\n return filter.brand.domain === acct.brand.domain && filter.operator === acct.operator;\n }\n return false;\n });\n }\n\n // Filter by name if provided\n if (request.name_contains) {\n const searchTerm = request.name_contains.toLowerCase();\n lists = lists.filter(l => l.name.toLowerCase().includes(searchTerm));\n }\n\n // Apply pagination\n const maxResults = request.pagination?.max_results ?? 100;\n const paginatedLists = lists.slice(0, maxResults);\n\n return {\n status: 'completed',\n lists: paginatedLists,\n pagination: {\n has_more: paginatedLists.length < lists.length,\n total_count: lists.length,\n },\n };\n }\n\n async deleteList(request: DeletePropertyListRequest): Promise<DeletePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n if (!this.lists.has(request.list_id)) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n this.lists.delete(request.list_id);\n this.authTokens.delete(request.list_id);\n\n return {\n status: 'completed',\n deleted: true,\n list_id: request.list_id,\n };\n }\n\n /**\n * Check if a property is in a list.\n * Stub implementation returns false (property not found).\n */\n async isPropertyInList(listId: string, identifierType: string, identifierValue: string): Promise<boolean> {\n if (!this.isSupported()) {\n // When not supported, all properties are considered in-list (no filtering)\n return true;\n }\n\n // Override in subclass to implement actual lookup\n return false;\n }\n\n /**\n * Resolve a property list to identifiers.\n * Stub implementation returns empty array.\n */\n async resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]> {\n if (!this.isSupported()) {\n return [];\n }\n\n // Override in subclass to implement actual resolution\n return [];\n }\n\n /**\n * Generate a random token for auth.\n *\n * Returned to callers as `auth_token`, so it is a bearer credential and must\n * come from a CSPRNG — `Math.random` exposes recoverable PRNG state. 32 bytes\n * of base64url is 256 bits of entropy.\n */\n protected generateToken(): string {\n return randomBytes(32).toString('base64url');\n }\n}\n\n/**\n * Helper to check if an error is a PropertyListError\n */\nexport function isPropertyListError(error: unknown): error is PropertyListError {\n return error instanceof PropertyListError;\n}\n\n/**\n * Default singleton instance for servers that don't need property list management\n */\nexport const defaultPropertyListAdapter = new PropertyListAdapter();\n"],"mappings":"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","names":[]}
@@ -1,3 +1,4 @@
1
+ import { randomBytes } from "node:crypto";
1
2
  class PropertyListError extends Error {
2
3
  constructor(code, message) {
3
4
  super(message);
@@ -189,15 +190,14 @@ class PropertyListAdapter {
189
190
  return [];
190
191
  }
191
192
  /**
192
- * Generate a random token for auth
193
+ * Generate a random token for auth.
194
+ *
195
+ * Returned to callers as `auth_token`, so it is a bearer credential and must
196
+ * come from a CSPRNG — `Math.random` exposes recoverable PRNG state. 32 bytes
197
+ * of base64url is 256 bits of entropy.
193
198
  */
194
199
  generateToken() {
195
- const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
196
- let result = "";
197
- for (let i = 0; i < 32; i++) {
198
- result += chars[Math.floor(Math.random() * chars.length)];
199
- }
200
- return result;
200
+ return randomBytes(32).toString("base64url");
201
201
  }
202
202
  }
203
203
  function isPropertyListError(error) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/adapters/property-list-adapter.ts"],"sourcesContent":["/**\n * Property List Adapter\n *\n * Server-side adapter for implementing property list management logic.\n * Publishers use this to manage buyer-defined property lists for filtering\n * available inventory.\n *\n * This is a stub implementation that stores lists in memory.\n * Publishers should extend or replace this with persistent storage.\n */\n\nimport type {\n CreatePropertyListRequest,\n CreatePropertyListResponse,\n UpdatePropertyListRequest,\n UpdatePropertyListResponse,\n GetPropertyListRequest,\n GetPropertyListResponse,\n ListPropertyListsRequest,\n ListPropertyListsResponse,\n DeletePropertyListRequest,\n DeletePropertyListResponse,\n PropertyList,\n} from '../types/tools.generated';\n\n/**\n * Resolved property identifier\n */\nexport interface ResolvedProperty {\n identifier_type: string;\n identifier_value: string;\n publisher_domain?: string;\n display_name?: string;\n status?: 'active' | 'inactive' | 'pending';\n}\n\n/**\n * Error thrown when property list operations fail\n */\nexport class PropertyListError extends Error {\n constructor(\n public code: string,\n message: string\n ) {\n super(message);\n this.name = 'PropertyListError';\n }\n}\n\n/**\n * Abstract interface for property list adapters.\n * Publishers implement this to provide their storage and resolution logic.\n */\nexport interface IPropertyListAdapter {\n /**\n * Check if property list management is supported by this server\n */\n isSupported(): boolean;\n\n /**\n * Create a new property list\n */\n createList(request: CreatePropertyListRequest): Promise<CreatePropertyListResponse>;\n\n /**\n * Update an existing property list\n */\n updateList(request: UpdatePropertyListRequest): Promise<UpdatePropertyListResponse>;\n\n /**\n * Get a property list with optional resolution\n */\n getList(request: GetPropertyListRequest): Promise<GetPropertyListResponse>;\n\n /**\n * List all property lists for an account\n */\n listLists(request: ListPropertyListsRequest): Promise<ListPropertyListsResponse>;\n\n /**\n * Delete a property list\n */\n deleteList(request: DeletePropertyListRequest): Promise<DeletePropertyListResponse>;\n\n /**\n * Check if a property identifier is in a property list.\n * Used by get_products and create_media_buy for filtering.\n */\n isPropertyInList(listId: string, identifierType: string, identifierValue: string): Promise<boolean>;\n\n /**\n * Resolve a property list to its constituent identifiers.\n * Applies filters and returns matching properties.\n */\n resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]>;\n}\n\n/**\n * Error codes for property list operations\n */\nexport const PropertyListErrorCodes = {\n NOT_SUPPORTED: 'UNSUPPORTED_FEATURE',\n LIST_NOT_FOUND: 'REFERENCE_NOT_FOUND',\n INVALID_LIST: 'INVALID_REQUEST',\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n} as const;\n\n/**\n * Stub implementation of PropertyListAdapter.\n * Uses in-memory storage for development and testing.\n *\n * Publishers should extend this class or provide their own implementation\n * that integrates with their property management systems.\n */\nexport class PropertyListAdapter implements IPropertyListAdapter {\n private lists: Map<string, PropertyList> = new Map();\n private authTokens: Map<string, string> = new Map();\n private nextId = 1;\n\n /**\n * Check if property list management is supported.\n * Override this to return true when implementing real logic.\n */\n isSupported(): boolean {\n return false;\n }\n\n async createList(request: CreatePropertyListRequest): Promise<CreatePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n // Generate ID and auth token\n const listId = `pl_${this.nextId++}_${Date.now()}`;\n const authToken = `plat_${this.generateToken()}`;\n const now = new Date().toISOString();\n\n const list: PropertyList = {\n list_id: listId,\n name: request.name,\n description: request.description,\n base_properties: request.base_properties,\n filters: request.filters,\n brand: request.brand,\n created_at: now,\n updated_at: now,\n property_count: 0,\n };\n\n this.lists.set(listId, list);\n this.authTokens.set(listId, authToken);\n\n return {\n status: 'completed',\n list,\n auth_token: authToken,\n };\n }\n\n async updateList(request: UpdatePropertyListRequest): Promise<UpdatePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n const existing = this.lists.get(request.list_id);\n if (!existing) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n // Update fields\n const updated: PropertyList = {\n ...existing,\n name: request.name ?? existing.name,\n description: request.description ?? existing.description,\n base_properties: request.base_properties ?? existing.base_properties,\n filters: request.filters ?? existing.filters,\n brand: request.brand ?? existing.brand,\n updated_at: new Date().toISOString(),\n };\n\n this.lists.set(request.list_id, updated);\n\n return { status: 'completed', list: updated };\n }\n\n async getList(request: GetPropertyListRequest): Promise<GetPropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n const list = this.lists.get(request.list_id);\n if (!list) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n // Resolve property identifiers if requested\n let identifiers: any[] | undefined;\n if (request.resolve !== false) {\n const resolved = await this.resolveList(\n request.list_id,\n request.pagination?.max_results ?? undefined,\n request.pagination?.cursor ?? undefined\n );\n identifiers = resolved.map(p => ({\n identifier_type: p.identifier_type,\n identifier_value: p.identifier_value,\n }));\n }\n\n return {\n status: 'completed',\n list: {\n ...list,\n property_count: identifiers?.length ?? list.property_count,\n },\n identifiers,\n pagination: {\n has_more: false,\n total_count: identifiers?.length,\n },\n resolved_at: new Date().toISOString(),\n };\n }\n\n async listLists(request: ListPropertyListsRequest): Promise<ListPropertyListsResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n // Filter by account if provided. AdCP 3.0 identifies list ownership via the\n // account primitive (either account_id or brand+operator). Match on whichever\n // key the request carries.\n let lists = Array.from(this.lists.values());\n if (request.account) {\n const filter = request.account;\n lists = lists.filter(l => {\n const acct = l.account;\n if (!acct) return false;\n if ('account_id' in filter && 'account_id' in acct) {\n return filter.account_id === acct.account_id;\n }\n if ('brand' in filter && 'brand' in acct) {\n return filter.brand.domain === acct.brand.domain && filter.operator === acct.operator;\n }\n return false;\n });\n }\n\n // Filter by name if provided\n if (request.name_contains) {\n const searchTerm = request.name_contains.toLowerCase();\n lists = lists.filter(l => l.name.toLowerCase().includes(searchTerm));\n }\n\n // Apply pagination\n const maxResults = request.pagination?.max_results ?? 100;\n const paginatedLists = lists.slice(0, maxResults);\n\n return {\n status: 'completed',\n lists: paginatedLists,\n pagination: {\n has_more: paginatedLists.length < lists.length,\n total_count: lists.length,\n },\n };\n }\n\n async deleteList(request: DeletePropertyListRequest): Promise<DeletePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n if (!this.lists.has(request.list_id)) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n this.lists.delete(request.list_id);\n this.authTokens.delete(request.list_id);\n\n return {\n status: 'completed',\n deleted: true,\n list_id: request.list_id,\n };\n }\n\n /**\n * Check if a property is in a list.\n * Stub implementation returns false (property not found).\n */\n async isPropertyInList(listId: string, identifierType: string, identifierValue: string): Promise<boolean> {\n if (!this.isSupported()) {\n // When not supported, all properties are considered in-list (no filtering)\n return true;\n }\n\n // Override in subclass to implement actual lookup\n return false;\n }\n\n /**\n * Resolve a property list to identifiers.\n * Stub implementation returns empty array.\n */\n async resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]> {\n if (!this.isSupported()) {\n return [];\n }\n\n // Override in subclass to implement actual resolution\n return [];\n }\n\n /**\n * Generate a random token for auth\n */\n protected generateToken(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n for (let i = 0; i < 32; i++) {\n result += chars[Math.floor(Math.random() * chars.length)];\n }\n return result;\n }\n}\n\n/**\n * Helper to check if an error is a PropertyListError\n */\nexport function isPropertyListError(error: unknown): error is PropertyListError {\n return error instanceof PropertyListError;\n}\n\n/**\n * Default singleton instance for servers that don't need property list management\n */\nexport const defaultPropertyListAdapter = new PropertyListAdapter();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/lib/adapters/property-list-adapter.ts"],"sourcesContent":["/**\n * Property List Adapter\n *\n * Server-side adapter for implementing property list management logic.\n * Publishers use this to manage buyer-defined property lists for filtering\n * available inventory.\n *\n * This is a stub implementation that stores lists in memory.\n * Publishers should extend or replace this with persistent storage.\n */\n\nimport { randomBytes } from 'node:crypto';\n\nimport type {\n CreatePropertyListRequest,\n CreatePropertyListResponse,\n UpdatePropertyListRequest,\n UpdatePropertyListResponse,\n GetPropertyListRequest,\n GetPropertyListResponse,\n ListPropertyListsRequest,\n ListPropertyListsResponse,\n DeletePropertyListRequest,\n DeletePropertyListResponse,\n PropertyList,\n} from '../types/tools.generated';\n\n/**\n * Resolved property identifier\n */\nexport interface ResolvedProperty {\n identifier_type: string;\n identifier_value: string;\n publisher_domain?: string;\n display_name?: string;\n status?: 'active' | 'inactive' | 'pending';\n}\n\n/**\n * Error thrown when property list operations fail\n */\nexport class PropertyListError extends Error {\n constructor(\n public code: string,\n message: string\n ) {\n super(message);\n this.name = 'PropertyListError';\n }\n}\n\n/**\n * Abstract interface for property list adapters.\n * Publishers implement this to provide their storage and resolution logic.\n */\nexport interface IPropertyListAdapter {\n /**\n * Check if property list management is supported by this server\n */\n isSupported(): boolean;\n\n /**\n * Create a new property list\n */\n createList(request: CreatePropertyListRequest): Promise<CreatePropertyListResponse>;\n\n /**\n * Update an existing property list\n */\n updateList(request: UpdatePropertyListRequest): Promise<UpdatePropertyListResponse>;\n\n /**\n * Get a property list with optional resolution\n */\n getList(request: GetPropertyListRequest): Promise<GetPropertyListResponse>;\n\n /**\n * List all property lists for an account\n */\n listLists(request: ListPropertyListsRequest): Promise<ListPropertyListsResponse>;\n\n /**\n * Delete a property list\n */\n deleteList(request: DeletePropertyListRequest): Promise<DeletePropertyListResponse>;\n\n /**\n * Check if a property identifier is in a property list.\n * Used by get_products and create_media_buy for filtering.\n */\n isPropertyInList(listId: string, identifierType: string, identifierValue: string): Promise<boolean>;\n\n /**\n * Resolve a property list to its constituent identifiers.\n * Applies filters and returns matching properties.\n */\n resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]>;\n}\n\n/**\n * Error codes for property list operations\n */\nexport const PropertyListErrorCodes = {\n NOT_SUPPORTED: 'UNSUPPORTED_FEATURE',\n LIST_NOT_FOUND: 'REFERENCE_NOT_FOUND',\n INVALID_LIST: 'INVALID_REQUEST',\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n} as const;\n\n/**\n * Stub implementation of PropertyListAdapter.\n * Uses in-memory storage for development and testing.\n *\n * Publishers should extend this class or provide their own implementation\n * that integrates with their property management systems.\n */\nexport class PropertyListAdapter implements IPropertyListAdapter {\n private lists: Map<string, PropertyList> = new Map();\n private authTokens: Map<string, string> = new Map();\n private nextId = 1;\n\n /**\n * Check if property list management is supported.\n * Override this to return true when implementing real logic.\n */\n isSupported(): boolean {\n return false;\n }\n\n async createList(request: CreatePropertyListRequest): Promise<CreatePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n // Generate ID and auth token\n const listId = `pl_${this.nextId++}_${Date.now()}`;\n const authToken = `plat_${this.generateToken()}`;\n const now = new Date().toISOString();\n\n const list: PropertyList = {\n list_id: listId,\n name: request.name,\n description: request.description,\n base_properties: request.base_properties,\n filters: request.filters,\n brand: request.brand,\n created_at: now,\n updated_at: now,\n property_count: 0,\n };\n\n this.lists.set(listId, list);\n this.authTokens.set(listId, authToken);\n\n return {\n status: 'completed',\n list,\n auth_token: authToken,\n };\n }\n\n async updateList(request: UpdatePropertyListRequest): Promise<UpdatePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n const existing = this.lists.get(request.list_id);\n if (!existing) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n // Update fields\n const updated: PropertyList = {\n ...existing,\n name: request.name ?? existing.name,\n description: request.description ?? existing.description,\n base_properties: request.base_properties ?? existing.base_properties,\n filters: request.filters ?? existing.filters,\n brand: request.brand ?? existing.brand,\n updated_at: new Date().toISOString(),\n };\n\n this.lists.set(request.list_id, updated);\n\n return { status: 'completed', list: updated };\n }\n\n async getList(request: GetPropertyListRequest): Promise<GetPropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n const list = this.lists.get(request.list_id);\n if (!list) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n // Resolve property identifiers if requested\n let identifiers: any[] | undefined;\n if (request.resolve !== false) {\n const resolved = await this.resolveList(\n request.list_id,\n request.pagination?.max_results ?? undefined,\n request.pagination?.cursor ?? undefined\n );\n identifiers = resolved.map(p => ({\n identifier_type: p.identifier_type,\n identifier_value: p.identifier_value,\n }));\n }\n\n return {\n status: 'completed',\n list: {\n ...list,\n property_count: identifiers?.length ?? list.property_count,\n },\n identifiers,\n pagination: {\n has_more: false,\n total_count: identifiers?.length,\n },\n resolved_at: new Date().toISOString(),\n };\n }\n\n async listLists(request: ListPropertyListsRequest): Promise<ListPropertyListsResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n // Filter by account if provided. AdCP 3.0 identifies list ownership via the\n // account primitive (either account_id or brand+operator). Match on whichever\n // key the request carries.\n let lists = Array.from(this.lists.values());\n if (request.account) {\n const filter = request.account;\n lists = lists.filter(l => {\n const acct = l.account;\n if (!acct) return false;\n if ('account_id' in filter && 'account_id' in acct) {\n return filter.account_id === acct.account_id;\n }\n if ('brand' in filter && 'brand' in acct) {\n return filter.brand.domain === acct.brand.domain && filter.operator === acct.operator;\n }\n return false;\n });\n }\n\n // Filter by name if provided\n if (request.name_contains) {\n const searchTerm = request.name_contains.toLowerCase();\n lists = lists.filter(l => l.name.toLowerCase().includes(searchTerm));\n }\n\n // Apply pagination\n const maxResults = request.pagination?.max_results ?? 100;\n const paginatedLists = lists.slice(0, maxResults);\n\n return {\n status: 'completed',\n lists: paginatedLists,\n pagination: {\n has_more: paginatedLists.length < lists.length,\n total_count: lists.length,\n },\n };\n }\n\n async deleteList(request: DeletePropertyListRequest): Promise<DeletePropertyListResponse> {\n if (!this.isSupported()) {\n throw new PropertyListError(\n PropertyListErrorCodes.NOT_SUPPORTED,\n 'Property list management is not supported by this server'\n );\n }\n\n if (!this.lists.has(request.list_id)) {\n throw new PropertyListError(PropertyListErrorCodes.LIST_NOT_FOUND, `Property list not found: ${request.list_id}`);\n }\n\n this.lists.delete(request.list_id);\n this.authTokens.delete(request.list_id);\n\n return {\n status: 'completed',\n deleted: true,\n list_id: request.list_id,\n };\n }\n\n /**\n * Check if a property is in a list.\n * Stub implementation returns false (property not found).\n */\n async isPropertyInList(listId: string, identifierType: string, identifierValue: string): Promise<boolean> {\n if (!this.isSupported()) {\n // When not supported, all properties are considered in-list (no filtering)\n return true;\n }\n\n // Override in subclass to implement actual lookup\n return false;\n }\n\n /**\n * Resolve a property list to identifiers.\n * Stub implementation returns empty array.\n */\n async resolveList(listId: string, maxResults?: number, cursor?: string): Promise<ResolvedProperty[]> {\n if (!this.isSupported()) {\n return [];\n }\n\n // Override in subclass to implement actual resolution\n return [];\n }\n\n /**\n * Generate a random token for auth.\n *\n * Returned to callers as `auth_token`, so it is a bearer credential and must\n * come from a CSPRNG — `Math.random` exposes recoverable PRNG state. 32 bytes\n * of base64url is 256 bits of entropy.\n */\n protected generateToken(): string {\n return randomBytes(32).toString('base64url');\n }\n}\n\n/**\n * Helper to check if an error is a PropertyListError\n */\nexport function isPropertyListError(error: unknown): error is PropertyListError {\n return error instanceof PropertyListError;\n}\n\n/**\n * Default singleton instance for servers that don't need property list management\n */\nexport const defaultPropertyListAdapter = new PropertyListAdapter();\n"],"mappings":"AAWA,SAAS,mBAAmB;AA8BrB,MAAM,0BAA0B,MAAM;AAAA,EAC3C,YACS,MACP,SACA;AACA,UAAM,OAAO;AAHN;AAIP,SAAK,OAAO;AAAA,EACd;AAAA,EALS;AAMX;AAqDO,MAAM,yBAAyB;AAAA,EACpC,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,gBAAgB;AAClB;AASO,MAAM,oBAAoD;AAAA,EACvD,QAAmC,oBAAI,IAAI;AAAA,EAC3C,aAAkC,oBAAI,IAAI;AAAA,EAC1C,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjB,cAAuB;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,SAAyE;AACxF,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAGA,UAAM,SAAS,MAAM,KAAK,QAAQ,IAAI,KAAK,IAAI,CAAC;AAChD,UAAM,YAAY,QAAQ,KAAK,cAAc,CAAC;AAC9C,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,UAAM,OAAqB;AAAA,MACzB,SAAS;AAAA,MACT,MAAM,QAAQ;AAAA,MACd,aAAa,QAAQ;AAAA,MACrB,iBAAiB,QAAQ;AAAA,MACzB,SAAS,QAAQ;AAAA,MACjB,OAAO,QAAQ;AAAA,MACf,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,gBAAgB;AAAA,IAClB;AAEA,SAAK,MAAM,IAAI,QAAQ,IAAI;AAC3B,SAAK,WAAW,IAAI,QAAQ,SAAS;AAErC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,SAAyE;AACxF,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,MAAM,IAAI,QAAQ,OAAO;AAC/C,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,kBAAkB,uBAAuB,gBAAgB,4BAA4B,QAAQ,OAAO,EAAE;AAAA,IAClH;AAGA,UAAM,UAAwB;AAAA,MAC5B,GAAG;AAAA,MACH,MAAM,QAAQ,QAAQ,SAAS;AAAA,MAC/B,aAAa,QAAQ,eAAe,SAAS;AAAA,MAC7C,iBAAiB,QAAQ,mBAAmB,SAAS;AAAA,MACrD,SAAS,QAAQ,WAAW,SAAS;AAAA,MACrC,OAAO,QAAQ,SAAS,SAAS;AAAA,MACjC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrC;AAEA,SAAK,MAAM,IAAI,QAAQ,SAAS,OAAO;AAEvC,WAAO,EAAE,QAAQ,aAAa,MAAM,QAAQ;AAAA,EAC9C;AAAA,EAEA,MAAM,QAAQ,SAAmE;AAC/E,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,MAAM,IAAI,QAAQ,OAAO;AAC3C,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,kBAAkB,uBAAuB,gBAAgB,4BAA4B,QAAQ,OAAO,EAAE;AAAA,IAClH;AAGA,QAAI;AACJ,QAAI,QAAQ,YAAY,OAAO;AAC7B,YAAM,WAAW,MAAM,KAAK;AAAA,QAC1B,QAAQ;AAAA,QACR,QAAQ,YAAY,eAAe;AAAA,QACnC,QAAQ,YAAY,UAAU;AAAA,MAChC;AACA,oBAAc,SAAS,IAAI,QAAM;AAAA,QAC/B,iBAAiB,EAAE;AAAA,QACnB,kBAAkB,EAAE;AAAA,MACtB,EAAE;AAAA,IACJ;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,GAAG;AAAA,QACH,gBAAgB,aAAa,UAAU,KAAK;AAAA,MAC9C;AAAA,MACA;AAAA,MACA,YAAY;AAAA,QACV,UAAU;AAAA,QACV,aAAa,aAAa;AAAA,MAC5B;AAAA,MACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,SAAuE;AACrF,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAKA,QAAI,QAAQ,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;AAC1C,QAAI,QAAQ,SAAS;AACnB,YAAM,SAAS,QAAQ;AACvB,cAAQ,MAAM,OAAO,OAAK;AACxB,cAAM,OAAO,EAAE;AACf,YAAI,CAAC,KAAM,QAAO;AAClB,YAAI,gBAAgB,UAAU,gBAAgB,MAAM;AAClD,iBAAO,OAAO,eAAe,KAAK;AAAA,QACpC;AACA,YAAI,WAAW,UAAU,WAAW,MAAM;AACxC,iBAAO,OAAO,MAAM,WAAW,KAAK,MAAM,UAAU,OAAO,aAAa,KAAK;AAAA,QAC/E;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAGA,QAAI,QAAQ,eAAe;AACzB,YAAM,aAAa,QAAQ,cAAc,YAAY;AACrD,cAAQ,MAAM,OAAO,OAAK,EAAE,KAAK,YAAY,EAAE,SAAS,UAAU,CAAC;AAAA,IACrE;AAGA,UAAM,aAAa,QAAQ,YAAY,eAAe;AACtD,UAAM,iBAAiB,MAAM,MAAM,GAAG,UAAU;AAEhD,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,YAAY;AAAA,QACV,UAAU,eAAe,SAAS,MAAM;AAAA,QACxC,aAAa,MAAM;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,SAAyE;AACxF,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,uBAAuB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,MAAM,IAAI,QAAQ,OAAO,GAAG;AACpC,YAAM,IAAI,kBAAkB,uBAAuB,gBAAgB,4BAA4B,QAAQ,OAAO,EAAE;AAAA,IAClH;AAEA,SAAK,MAAM,OAAO,QAAQ,OAAO;AACjC,SAAK,WAAW,OAAO,QAAQ,OAAO;AAEtC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS,QAAQ;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBAAiB,QAAgB,gBAAwB,iBAA2C;AACxG,QAAI,CAAC,KAAK,YAAY,GAAG;AAEvB,aAAO;AAAA,IACT;AAGA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,QAAgB,YAAqB,QAA8C;AACnG,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,aAAO,CAAC;AAAA,IACV;AAGA,WAAO,CAAC;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASU,gBAAwB;AAChC,WAAO,YAAY,EAAE,EAAE,SAAS,WAAW;AAAA,EAC7C;AACF;AAKO,SAAS,oBAAoB,OAA4C;AAC9E,SAAO,iBAAiB;AAC1B;AAKO,MAAM,6BAA6B,IAAI,oBAAoB;","names":[]}
@@ -30,7 +30,9 @@ export interface CLIFlowHandlerConfig {
30
30
  * @example
31
31
  * ```typescript
32
32
  * const flow = new CLIFlowHandler({ callbackPort: 8766 });
33
- * await flow.redirectToAuthorization(new URL('https://auth.example.com/authorize?...'));
33
+ * // The authorization URL MUST carry `state` — the callback is bound to it.
34
+ * // `MCPOAuthProvider` supplies one automatically via its `state()` method.
35
+ * await flow.redirectToAuthorization(new URL('https://auth.example.com/authorize?client_id=abc&state=<random>'));
34
36
  * const code = await flow.waitForCallback();
35
37
  * await flow.cleanup();
36
38
  * ```
@@ -41,14 +43,19 @@ export declare class CLIFlowHandler implements OAuthFlowHandler {
41
43
  private readonly callbackPath;
42
44
  private readonly quiet;
43
45
  private server;
46
+ private expectedState;
44
47
  private pendingCallback;
45
48
  constructor(config?: CLIFlowHandlerConfig);
46
49
  getRedirectUrl(): string;
47
50
  /**
48
51
  * Open URL in the user's default browser
49
52
  *
50
- * Uses spawn with separate arguments to avoid command injection risks.
51
- * The URL is passed as an argument, not interpolated into a shell command.
53
+ * Spawns the platform opener directly with the URL as a separate argument, so
54
+ * no command interpreter parses it. Windows uses `rundll32 url.dll` rather
55
+ * than `cmd /c start`: spawning `cmd` makes the interpreter itself the child,
56
+ * so `cmd` would parse `&`, `|`, and `^` in the URL even though `shell` is
57
+ * unset — and authorization URLs carry `&` by construction and derive partly
58
+ * from a discovered `authorization_endpoint`.
52
59
  */
53
60
  private openBrowser;
54
61
  redirectToAuthorization(authorizationUrl: URL): Promise<void>;
@@ -30,7 +30,9 @@ export interface CLIFlowHandlerConfig {
30
30
  * @example
31
31
  * ```typescript
32
32
  * const flow = new CLIFlowHandler({ callbackPort: 8766 });
33
- * await flow.redirectToAuthorization(new URL('https://auth.example.com/authorize?...'));
33
+ * // The authorization URL MUST carry `state` — the callback is bound to it.
34
+ * // `MCPOAuthProvider` supplies one automatically via its `state()` method.
35
+ * await flow.redirectToAuthorization(new URL('https://auth.example.com/authorize?client_id=abc&state=<random>'));
34
36
  * const code = await flow.waitForCallback();
35
37
  * await flow.cleanup();
36
38
  * ```
@@ -41,14 +43,19 @@ export declare class CLIFlowHandler implements OAuthFlowHandler {
41
43
  private readonly callbackPath;
42
44
  private readonly quiet;
43
45
  private server;
46
+ private expectedState;
44
47
  private pendingCallback;
45
48
  constructor(config?: CLIFlowHandlerConfig);
46
49
  getRedirectUrl(): string;
47
50
  /**
48
51
  * Open URL in the user's default browser
49
52
  *
50
- * Uses spawn with separate arguments to avoid command injection risks.
51
- * The URL is passed as an argument, not interpolated into a shell command.
53
+ * Spawns the platform opener directly with the URL as a separate argument, so
54
+ * no command interpreter parses it. Windows uses `rundll32 url.dll` rather
55
+ * than `cmd /c start`: spawning `cmd` makes the interpreter itself the child,
56
+ * so `cmd` would parse `&`, `|`, and `^` in the URL even though `shell` is
57
+ * unset — and authorization URLs carry `&` by construction and derive partly
58
+ * from a discovered `authorization_endpoint`.
52
59
  */
53
60
  private openBrowser;
54
61
  redirectToAuthorization(authorizationUrl: URL): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"CLIFlowHandler.d.ts","sourceRoot":"","sources":["../../../../src/lib/auth/oauth/CLIFlowHandler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGhD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,cAAe,YAAW,gBAAgB;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,eAAe,CAGP;gBAEJ,MAAM,GAAE,oBAAyB;IAO7C,cAAc,IAAI,MAAM;IAIxB;;;;;OAKG;YACW,WAAW;IAwCnB,uBAAuB,CAAC,gBAAgB,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAa7D,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAwCxC,OAAO,CAAC,aAAa;IAsDrB,OAAO,CAAC,mBAAmB;IA4C3B,OAAO,CAAC,iBAAiB;IA4CnB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAU/B"}
1
+ {"version":3,"file":"CLIFlowHandler.d.ts","sourceRoot":"","sources":["../../../../src/lib/auth/oauth/CLIFlowHandler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGhD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,cAAe,YAAW,gBAAgB;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,eAAe,CAGP;gBAEJ,MAAM,GAAE,oBAAyB;IAO7C,cAAc,IAAI,MAAM;IAIxB;;;;;;;;;OASG;YACW,WAAW;IAwCnB,uBAAuB,CAAC,gBAAgB,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC7D,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAwCxC,OAAO,CAAC,aAAa;IAwErB,OAAO,CAAC,mBAAmB;IA4C3B,OAAO,CAAC,iBAAiB;IA4CnB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAW/B"}
@@ -23,6 +23,7 @@ __export(CLIFlowHandler_exports, {
23
23
  module.exports = __toCommonJS(CLIFlowHandler_exports);
24
24
  var import_http = require("http");
25
25
  var import_child_process = require("child_process");
26
+ var import_crypto = require("crypto");
26
27
  var import_url = require("url");
27
28
  var import_os = require("os");
28
29
  var import_types = require('./types.js');
@@ -32,6 +33,7 @@ class CLIFlowHandler {
32
33
  callbackPath;
33
34
  quiet;
34
35
  server = null;
36
+ expectedState = null;
35
37
  pendingCallback = null;
36
38
  constructor(config = {}) {
37
39
  this.port = config.callbackPort ?? 8766;
@@ -45,8 +47,12 @@ class CLIFlowHandler {
45
47
  /**
46
48
  * Open URL in the user's default browser
47
49
  *
48
- * Uses spawn with separate arguments to avoid command injection risks.
49
- * The URL is passed as an argument, not interpolated into a shell command.
50
+ * Spawns the platform opener directly with the URL as a separate argument, so
51
+ * no command interpreter parses it. Windows uses `rundll32 url.dll` rather
52
+ * than `cmd /c start`: spawning `cmd` makes the interpreter itself the child,
53
+ * so `cmd` would parse `&`, `|`, and `^` in the URL even though `shell` is
54
+ * unset — and authorization URLs carry `&` by construction and derive partly
55
+ * from a discovered `authorization_endpoint`.
50
56
  */
51
57
  async openBrowser(url) {
52
58
  return new Promise((resolve) => {
@@ -58,8 +64,8 @@ class CLIFlowHandler {
58
64
  args = [url];
59
65
  break;
60
66
  case "win32":
61
- command = "cmd";
62
- args = ["/c", "start", "", url];
67
+ command = "rundll32";
68
+ args = ["url.dll,FileProtocolHandler", url];
63
69
  break;
64
70
  default:
65
71
  command = "xdg-open";
@@ -81,6 +87,18 @@ class CLIFlowHandler {
81
87
  });
82
88
  }
83
89
  async redirectToAuthorization(authorizationUrl) {
90
+ if (!["http:", "https:"].includes(authorizationUrl.protocol)) {
91
+ throw new Error(
92
+ `Refusing to open authorization URL with scheme "${authorizationUrl.protocol}" (only http/https)`
93
+ );
94
+ }
95
+ const state = authorizationUrl.searchParams.get("state");
96
+ if (!state) {
97
+ throw new Error(
98
+ 'Authorization URL is missing the "state" parameter; refusing to start a loopback OAuth flow that cannot bind its callback'
99
+ );
100
+ }
101
+ this.expectedState = state;
84
102
  const url = authorizationUrl.toString();
85
103
  if (!this.quiet) {
86
104
  console.log("");
@@ -135,12 +153,22 @@ class CLIFlowHandler {
135
153
  res.end("Not found");
136
154
  return;
137
155
  }
156
+ const state = url.searchParams.get("state");
157
+ if (!this.expectedState || !state || !statesMatch(state, this.expectedState)) {
158
+ const message = "OAuth state mismatch: callback does not correspond to the authorization request";
159
+ this.sendErrorResponse(res, message);
160
+ if (this.pendingCallback) {
161
+ this.pendingCallback.reject(new Error(message));
162
+ }
163
+ return;
164
+ }
138
165
  const code = url.searchParams.get("code");
139
166
  const error = url.searchParams.get("error");
140
167
  const errorDescription = url.searchParams.get("error_description");
141
168
  if (error) {
142
169
  const errorMsg = errorDescription || error;
143
170
  this.sendErrorResponse(res, errorMsg);
171
+ this.expectedState = null;
144
172
  if (this.pendingCallback) {
145
173
  if (error === "access_denied") {
146
174
  this.pendingCallback.reject(new import_types.OAuthCancelledError());
@@ -152,6 +180,7 @@ class CLIFlowHandler {
152
180
  }
153
181
  if (!code) {
154
182
  this.sendErrorResponse(res, "No authorization code received");
183
+ this.expectedState = null;
155
184
  if (this.pendingCallback) {
156
185
  this.pendingCallback.reject(new Error("No authorization code received"));
157
186
  }
@@ -250,6 +279,7 @@ class CLIFlowHandler {
250
279
  `);
251
280
  }
252
281
  async cleanup() {
282
+ this.expectedState = null;
253
283
  if (this.server) {
254
284
  return new Promise((resolve) => {
255
285
  this.server.close(() => {
@@ -260,6 +290,12 @@ class CLIFlowHandler {
260
290
  }
261
291
  }
262
292
  }
293
+ function statesMatch(received, expected) {
294
+ const a = Buffer.from(received, "utf8");
295
+ const b = Buffer.from(expected, "utf8");
296
+ if (a.length !== b.length) return false;
297
+ return (0, import_crypto.timingSafeEqual)(a, b);
298
+ }
263
299
  function escapeHtml(text) {
264
300
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
265
301
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/lib/auth/oauth/CLIFlowHandler.ts"],"sourcesContent":["/**\n * CLI OAuth flow handler\n *\n * Opens the user's browser for authorization and\n * starts a local HTTP server to receive the callback.\n */\n\nimport { createServer, Server, IncomingMessage, ServerResponse } from 'http';\nimport { spawn } from 'child_process';\nimport { URL } from 'url';\nimport { platform } from 'os';\nimport type { OAuthFlowHandler } from './types';\nimport { OAuthCancelledError, OAuthTimeoutError } from './types';\n\n/**\n * Configuration for CLI OAuth flow\n */\nexport interface CLIFlowHandlerConfig {\n /** Port for the callback server (default: 8766) */\n callbackPort?: number;\n /** Timeout in ms for waiting for callback (default: 300000 = 5 min) */\n timeout?: number;\n /** Custom callback path (default: /callback) */\n callbackPath?: string;\n /** Suppress console output */\n quiet?: boolean;\n}\n\n/**\n * CLI OAuth flow handler implementation\n *\n * Handles the OAuth flow for command-line tools by:\n * 1. Opening the authorization URL in the user's default browser\n * 2. Starting a local HTTP server to receive the callback\n * 3. Extracting and returning the authorization code\n *\n * @example\n * ```typescript\n * const flow = new CLIFlowHandler({ callbackPort: 8766 });\n * await flow.redirectToAuthorization(new URL('https://auth.example.com/authorize?...'));\n * const code = await flow.waitForCallback();\n * await flow.cleanup();\n * ```\n */\nexport class CLIFlowHandler implements OAuthFlowHandler {\n private readonly port: number;\n private readonly timeout: number;\n private readonly callbackPath: string;\n private readonly quiet: boolean;\n private server: Server | null = null;\n private pendingCallback: {\n resolve: (code: string) => void;\n reject: (error: Error) => void;\n } | null = null;\n\n constructor(config: CLIFlowHandlerConfig = {}) {\n this.port = config.callbackPort ?? 8766;\n this.timeout = config.timeout ?? 300000; // 5 minutes\n this.callbackPath = config.callbackPath ?? '/callback';\n this.quiet = config.quiet ?? false;\n }\n\n getRedirectUrl(): string {\n return `http://localhost:${this.port}${this.callbackPath}`;\n }\n\n /**\n * Open URL in the user's default browser\n *\n * Uses spawn with separate arguments to avoid command injection risks.\n * The URL is passed as an argument, not interpolated into a shell command.\n */\n private async openBrowser(url: string): Promise<void> {\n return new Promise(resolve => {\n let command: string;\n let args: string[];\n\n switch (platform()) {\n case 'darwin':\n command = 'open';\n args = [url];\n break;\n case 'win32':\n command = 'cmd';\n args = ['/c', 'start', '', url];\n break;\n default:\n // Linux and others - try xdg-open\n command = 'xdg-open';\n args = [url];\n }\n\n const child = spawn(command, args, {\n detached: true,\n stdio: 'ignore',\n });\n\n child.on('error', error => {\n if (!this.quiet) {\n console.error(`Failed to open browser: ${error.message}`);\n console.log(`Please manually open: ${url}`);\n }\n // Don't reject - user can manually open the URL\n resolve();\n });\n\n // Unref so the parent process can exit\n child.unref();\n resolve();\n });\n }\n\n async redirectToAuthorization(authorizationUrl: URL): Promise<void> {\n const url = authorizationUrl.toString();\n\n if (!this.quiet) {\n console.log('');\n console.log('Opening browser for authorization...');\n console.log(`URL: ${url}`);\n console.log('');\n }\n\n await this.openBrowser(url);\n }\n\n async waitForCallback(): Promise<string> {\n return new Promise((resolve, reject) => {\n // Set up timeout\n const timeoutId = setTimeout(() => {\n this.cleanup();\n reject(new OAuthTimeoutError(undefined, this.timeout));\n }, this.timeout);\n\n this.pendingCallback = {\n resolve: (code: string) => {\n clearTimeout(timeoutId);\n resolve(code);\n },\n reject: (error: Error) => {\n clearTimeout(timeoutId);\n reject(error);\n },\n };\n\n // Start the callback server\n this.server = createServer((req: IncomingMessage, res: ServerResponse) => {\n this.handleRequest(req, res);\n });\n\n this.server.on('error', error => {\n if (this.pendingCallback) {\n this.pendingCallback.reject(new Error(`Callback server error: ${error.message}`));\n }\n });\n\n this.server.listen(this.port, '127.0.0.1', () => {\n if (!this.quiet) {\n console.log(`Waiting for authorization callback on port ${this.port}...`);\n console.log('(Press Ctrl+C to cancel)');\n console.log('');\n }\n });\n });\n }\n\n private handleRequest(req: IncomingMessage, res: ServerResponse): void {\n const url = new URL(req.url || '/', `http://localhost:${this.port}`);\n\n // Ignore favicon requests\n if (url.pathname === '/favicon.ico') {\n res.writeHead(404);\n res.end();\n return;\n }\n\n // Only handle the callback path\n if (url.pathname !== this.callbackPath) {\n res.writeHead(404);\n res.end('Not found');\n return;\n }\n\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n const errorDescription = url.searchParams.get('error_description');\n\n if (error) {\n const errorMsg = errorDescription || error;\n this.sendErrorResponse(res, errorMsg);\n\n if (this.pendingCallback) {\n if (error === 'access_denied') {\n this.pendingCallback.reject(new OAuthCancelledError());\n } else {\n this.pendingCallback.reject(new Error(`OAuth error: ${errorMsg}`));\n }\n }\n return;\n }\n\n if (!code) {\n this.sendErrorResponse(res, 'No authorization code received');\n if (this.pendingCallback) {\n this.pendingCallback.reject(new Error('No authorization code received'));\n }\n return;\n }\n\n // Success!\n this.sendSuccessResponse(res);\n\n if (this.pendingCallback) {\n this.pendingCallback.resolve(code);\n }\n\n // Clean up after a short delay to ensure response is sent\n setTimeout(() => this.cleanup(), 1000);\n }\n\n private sendSuccessResponse(res: ServerResponse): void {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(`\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Authorization Successful</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n }\n .card {\n background: white;\n padding: 40px;\n border-radius: 12px;\n box-shadow: 0 10px 40px rgba(0,0,0,0.2);\n text-align: center;\n max-width: 400px;\n }\n h1 { color: #22c55e; margin-top: 0; }\n p { color: #666; line-height: 1.6; }\n .icon { font-size: 48px; margin-bottom: 20px; color: #22c55e; }\n </style>\n</head>\n<body>\n <div class=\"card\">\n <div class=\"icon\">&#10003;</div>\n <h1>Authorization Successful</h1>\n <p>You can close this window and return to your terminal.</p>\n <p><small>This window will close automatically in 3 seconds...</small></p>\n </div>\n <script>setTimeout(() => window.close(), 3000);</script>\n</body>\n</html>\n `);\n }\n\n private sendErrorResponse(res: ServerResponse, error: string): void {\n res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(`\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Authorization Failed</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #f87171 0%, #dc2626 100%);\n }\n .card {\n background: white;\n padding: 40px;\n border-radius: 12px;\n box-shadow: 0 10px 40px rgba(0,0,0,0.2);\n text-align: center;\n max-width: 400px;\n }\n h1 { color: #dc2626; margin-top: 0; }\n p { color: #666; line-height: 1.6; }\n .error { color: #dc2626; background: #fef2f2; padding: 10px; border-radius: 6px; }\n .icon { font-size: 48px; margin-bottom: 20px; color: #dc2626; }\n </style>\n</head>\n<body>\n <div class=\"card\">\n <div class=\"icon\">&#10007;</div>\n <h1>Authorization Failed</h1>\n <p class=\"error\">${escapeHtml(error)}</p>\n <p>Please close this window and try again.</p>\n </div>\n</body>\n</html>\n `);\n }\n\n async cleanup(): Promise<void> {\n if (this.server) {\n return new Promise(resolve => {\n this.server!.close(() => {\n this.server = null;\n resolve();\n });\n });\n }\n }\n}\n\n/**\n * Escape HTML entities to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/lib/auth/oauth/CLIFlowHandler.ts"],"sourcesContent":["/**\n * CLI OAuth flow handler\n *\n * Opens the user's browser for authorization and\n * starts a local HTTP server to receive the callback.\n */\n\nimport { createServer, Server, IncomingMessage, ServerResponse } from 'http';\nimport { spawn } from 'child_process';\nimport { timingSafeEqual } from 'crypto';\nimport { URL } from 'url';\nimport { platform } from 'os';\nimport type { OAuthFlowHandler } from './types';\nimport { OAuthCancelledError, OAuthTimeoutError } from './types';\n\n/**\n * Configuration for CLI OAuth flow\n */\nexport interface CLIFlowHandlerConfig {\n /** Port for the callback server (default: 8766) */\n callbackPort?: number;\n /** Timeout in ms for waiting for callback (default: 300000 = 5 min) */\n timeout?: number;\n /** Custom callback path (default: /callback) */\n callbackPath?: string;\n /** Suppress console output */\n quiet?: boolean;\n}\n\n/**\n * CLI OAuth flow handler implementation\n *\n * Handles the OAuth flow for command-line tools by:\n * 1. Opening the authorization URL in the user's default browser\n * 2. Starting a local HTTP server to receive the callback\n * 3. Extracting and returning the authorization code\n *\n * @example\n * ```typescript\n * const flow = new CLIFlowHandler({ callbackPort: 8766 });\n * // The authorization URL MUST carry `state` — the callback is bound to it.\n * // `MCPOAuthProvider` supplies one automatically via its `state()` method.\n * await flow.redirectToAuthorization(new URL('https://auth.example.com/authorize?client_id=abc&state=<random>'));\n * const code = await flow.waitForCallback();\n * await flow.cleanup();\n * ```\n */\nexport class CLIFlowHandler implements OAuthFlowHandler {\n private readonly port: number;\n private readonly timeout: number;\n private readonly callbackPath: string;\n private readonly quiet: boolean;\n private server: Server | null = null;\n private expectedState: string | null = null;\n private pendingCallback: {\n resolve: (code: string) => void;\n reject: (error: Error) => void;\n } | null = null;\n\n constructor(config: CLIFlowHandlerConfig = {}) {\n this.port = config.callbackPort ?? 8766;\n this.timeout = config.timeout ?? 300000; // 5 minutes\n this.callbackPath = config.callbackPath ?? '/callback';\n this.quiet = config.quiet ?? false;\n }\n\n getRedirectUrl(): string {\n return `http://localhost:${this.port}${this.callbackPath}`;\n }\n\n /**\n * Open URL in the user's default browser\n *\n * Spawns the platform opener directly with the URL as a separate argument, so\n * no command interpreter parses it. Windows uses `rundll32 url.dll` rather\n * than `cmd /c start`: spawning `cmd` makes the interpreter itself the child,\n * so `cmd` would parse `&`, `|`, and `^` in the URL even though `shell` is\n * unset — and authorization URLs carry `&` by construction and derive partly\n * from a discovered `authorization_endpoint`.\n */\n private async openBrowser(url: string): Promise<void> {\n return new Promise(resolve => {\n let command: string;\n let args: string[];\n\n switch (platform()) {\n case 'darwin':\n command = 'open';\n args = [url];\n break;\n case 'win32':\n command = 'rundll32';\n args = ['url.dll,FileProtocolHandler', url];\n break;\n default:\n // Linux and others - try xdg-open\n command = 'xdg-open';\n args = [url];\n }\n\n const child = spawn(command, args, {\n detached: true,\n stdio: 'ignore',\n });\n\n child.on('error', error => {\n if (!this.quiet) {\n console.error(`Failed to open browser: ${error.message}`);\n console.log(`Please manually open: ${url}`);\n }\n // Don't reject - user can manually open the URL\n resolve();\n });\n\n // Unref so the parent process can exit\n child.unref();\n resolve();\n });\n }\n\n async redirectToAuthorization(authorizationUrl: URL): Promise<void> {\n if (!['http:', 'https:'].includes(authorizationUrl.protocol)) {\n throw new Error(\n `Refusing to open authorization URL with scheme \"${authorizationUrl.protocol}\" (only http/https)`\n );\n }\n\n // Capture the state we are about to send so the callback can be bound to\n // this flow. The loopback callback server is reachable by any local process,\n // so without this check any of them could inject or cancel an authorization.\n //\n // Scope, stated honestly: this defeats a process that cannot see the\n // authorization URL, which is the ordinary case. It does NOT defeat a local\n // process running as the same user that reads the URL out of this process's\n // argv (`ps`, /proc/<pid>/cmdline) while the browser opens, or off stdout\n // when not `quiet`. PKCE is what protects the code exchange there.\n const state = authorizationUrl.searchParams.get('state');\n if (!state) {\n throw new Error(\n 'Authorization URL is missing the \"state\" parameter; refusing to start a loopback OAuth flow that cannot bind its callback'\n );\n }\n this.expectedState = state;\n\n const url = authorizationUrl.toString();\n\n if (!this.quiet) {\n console.log('');\n console.log('Opening browser for authorization...');\n console.log(`URL: ${url}`);\n console.log('');\n }\n\n await this.openBrowser(url);\n }\n\n async waitForCallback(): Promise<string> {\n return new Promise((resolve, reject) => {\n // Set up timeout\n const timeoutId = setTimeout(() => {\n this.cleanup();\n reject(new OAuthTimeoutError(undefined, this.timeout));\n }, this.timeout);\n\n this.pendingCallback = {\n resolve: (code: string) => {\n clearTimeout(timeoutId);\n resolve(code);\n },\n reject: (error: Error) => {\n clearTimeout(timeoutId);\n reject(error);\n },\n };\n\n // Start the callback server\n this.server = createServer((req: IncomingMessage, res: ServerResponse) => {\n this.handleRequest(req, res);\n });\n\n this.server.on('error', error => {\n if (this.pendingCallback) {\n this.pendingCallback.reject(new Error(`Callback server error: ${error.message}`));\n }\n });\n\n this.server.listen(this.port, '127.0.0.1', () => {\n if (!this.quiet) {\n console.log(`Waiting for authorization callback on port ${this.port}...`);\n console.log('(Press Ctrl+C to cancel)');\n console.log('');\n }\n });\n });\n }\n\n private handleRequest(req: IncomingMessage, res: ServerResponse): void {\n const url = new URL(req.url || '/', `http://localhost:${this.port}`);\n\n // Ignore favicon requests\n if (url.pathname === '/favicon.ico') {\n res.writeHead(404);\n res.end();\n return;\n }\n\n // Only handle the callback path\n if (url.pathname !== this.callbackPath) {\n res.writeHead(404);\n res.end('Not found');\n return;\n }\n\n // Bind the callback to the flow that started it BEFORE interpreting any of\n // its parameters. An unbound callback is not ours to act on — and that\n // includes its error parameters: reading `error=access_denied` first would\n // let any local process cancel the user's login with no knowledge of the\n // state at all. Code injection and cancellation are the same hole, so the\n // check has to precede both branches.\n const state = url.searchParams.get('state');\n if (!this.expectedState || !state || !statesMatch(state, this.expectedState)) {\n const message = 'OAuth state mismatch: callback does not correspond to the authorization request';\n this.sendErrorResponse(res, message);\n if (this.pendingCallback) {\n this.pendingCallback.reject(new Error(message));\n }\n return;\n }\n\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n const errorDescription = url.searchParams.get('error_description');\n\n if (error) {\n const errorMsg = errorDescription || error;\n this.sendErrorResponse(res, errorMsg);\n\n this.expectedState = null;\n if (this.pendingCallback) {\n if (error === 'access_denied') {\n this.pendingCallback.reject(new OAuthCancelledError());\n } else {\n this.pendingCallback.reject(new Error(`OAuth error: ${errorMsg}`));\n }\n }\n return;\n }\n\n if (!code) {\n this.sendErrorResponse(res, 'No authorization code received');\n this.expectedState = null;\n if (this.pendingCallback) {\n this.pendingCallback.reject(new Error('No authorization code received'));\n }\n return;\n }\n\n // Success!\n this.sendSuccessResponse(res);\n\n if (this.pendingCallback) {\n this.pendingCallback.resolve(code);\n }\n\n // Clean up after a short delay to ensure response is sent\n setTimeout(() => this.cleanup(), 1000);\n }\n\n private sendSuccessResponse(res: ServerResponse): void {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(`\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Authorization Successful</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n }\n .card {\n background: white;\n padding: 40px;\n border-radius: 12px;\n box-shadow: 0 10px 40px rgba(0,0,0,0.2);\n text-align: center;\n max-width: 400px;\n }\n h1 { color: #22c55e; margin-top: 0; }\n p { color: #666; line-height: 1.6; }\n .icon { font-size: 48px; margin-bottom: 20px; color: #22c55e; }\n </style>\n</head>\n<body>\n <div class=\"card\">\n <div class=\"icon\">&#10003;</div>\n <h1>Authorization Successful</h1>\n <p>You can close this window and return to your terminal.</p>\n <p><small>This window will close automatically in 3 seconds...</small></p>\n </div>\n <script>setTimeout(() => window.close(), 3000);</script>\n</body>\n</html>\n `);\n }\n\n private sendErrorResponse(res: ServerResponse, error: string): void {\n res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(`\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Authorization Failed</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #f87171 0%, #dc2626 100%);\n }\n .card {\n background: white;\n padding: 40px;\n border-radius: 12px;\n box-shadow: 0 10px 40px rgba(0,0,0,0.2);\n text-align: center;\n max-width: 400px;\n }\n h1 { color: #dc2626; margin-top: 0; }\n p { color: #666; line-height: 1.6; }\n .error { color: #dc2626; background: #fef2f2; padding: 10px; border-radius: 6px; }\n .icon { font-size: 48px; margin-bottom: 20px; color: #dc2626; }\n </style>\n</head>\n<body>\n <div class=\"card\">\n <div class=\"icon\">&#10007;</div>\n <h1>Authorization Failed</h1>\n <p class=\"error\">${escapeHtml(error)}</p>\n <p>Please close this window and try again.</p>\n </div>\n</body>\n</html>\n `);\n }\n\n async cleanup(): Promise<void> {\n this.expectedState = null;\n if (this.server) {\n return new Promise(resolve => {\n this.server!.close(() => {\n this.server = null;\n resolve();\n });\n });\n }\n }\n}\n\n/**\n * Compare two OAuth state values without leaking their contents through timing.\n */\nfunction statesMatch(received: string, expected: string): boolean {\n const a = Buffer.from(received, 'utf8');\n const b = Buffer.from(expected, 'utf8');\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n\n/**\n * Escape HTML entities to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,kBAAsE;AACtE,2BAAsB;AACtB,oBAAgC;AAChC,iBAAoB;AACpB,gBAAyB;AAEzB,mBAAuD;AAkChD,MAAM,eAA2C;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,SAAwB;AAAA,EACxB,gBAA+B;AAAA,EAC/B,kBAGG;AAAA,EAEX,YAAY,SAA+B,CAAC,GAAG;AAC7C,SAAK,OAAO,OAAO,gBAAgB;AACnC,SAAK,UAAU,OAAO,WAAW;AACjC,SAAK,eAAe,OAAO,gBAAgB;AAC3C,SAAK,QAAQ,OAAO,SAAS;AAAA,EAC/B;AAAA,EAEA,iBAAyB;AACvB,WAAO,oBAAoB,KAAK,IAAI,GAAG,KAAK,YAAY;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,YAAY,KAA4B;AACpD,WAAO,IAAI,QAAQ,aAAW;AAC5B,UAAI;AACJ,UAAI;AAEJ,kBAAQ,oBAAS,GAAG;AAAA,QAClB,KAAK;AACH,oBAAU;AACV,iBAAO,CAAC,GAAG;AACX;AAAA,QACF,KAAK;AACH,oBAAU;AACV,iBAAO,CAAC,+BAA+B,GAAG;AAC1C;AAAA,QACF;AAEE,oBAAU;AACV,iBAAO,CAAC,GAAG;AAAA,MACf;AAEA,YAAM,YAAQ,4BAAM,SAAS,MAAM;AAAA,QACjC,UAAU;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AAED,YAAM,GAAG,SAAS,WAAS;AACzB,YAAI,CAAC,KAAK,OAAO;AACf,kBAAQ,MAAM,2BAA2B,MAAM,OAAO,EAAE;AACxD,kBAAQ,IAAI,yBAAyB,GAAG,EAAE;AAAA,QAC5C;AAEA,gBAAQ;AAAA,MACV,CAAC;AAGD,YAAM,MAAM;AACZ,cAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,wBAAwB,kBAAsC;AAClE,QAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,SAAS,iBAAiB,QAAQ,GAAG;AAC5D,YAAM,IAAI;AAAA,QACR,mDAAmD,iBAAiB,QAAQ;AAAA,MAC9E;AAAA,IACF;AAWA,UAAM,QAAQ,iBAAiB,aAAa,IAAI,OAAO;AACvD,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,gBAAgB;AAErB,UAAM,MAAM,iBAAiB,SAAS;AAEtC,QAAI,CAAC,KAAK,OAAO;AACf,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,sCAAsC;AAClD,cAAQ,IAAI,QAAQ,GAAG,EAAE;AACzB,cAAQ,IAAI,EAAE;AAAA,IAChB;AAEA,UAAM,KAAK,YAAY,GAAG;AAAA,EAC5B;AAAA,EAEA,MAAM,kBAAmC;AACvC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAEtC,YAAM,YAAY,WAAW,MAAM;AACjC,aAAK,QAAQ;AACb,eAAO,IAAI,+BAAkB,QAAW,KAAK,OAAO,CAAC;AAAA,MACvD,GAAG,KAAK,OAAO;AAEf,WAAK,kBAAkB;AAAA,QACrB,SAAS,CAAC,SAAiB;AACzB,uBAAa,SAAS;AACtB,kBAAQ,IAAI;AAAA,QACd;AAAA,QACA,QAAQ,CAAC,UAAiB;AACxB,uBAAa,SAAS;AACtB,iBAAO,KAAK;AAAA,QACd;AAAA,MACF;AAGA,WAAK,aAAS,0BAAa,CAAC,KAAsB,QAAwB;AACxE,aAAK,cAAc,KAAK,GAAG;AAAA,MAC7B,CAAC;AAED,WAAK,OAAO,GAAG,SAAS,WAAS;AAC/B,YAAI,KAAK,iBAAiB;AACxB,eAAK,gBAAgB,OAAO,IAAI,MAAM,0BAA0B,MAAM,OAAO,EAAE,CAAC;AAAA,QAClF;AAAA,MACF,CAAC;AAED,WAAK,OAAO,OAAO,KAAK,MAAM,aAAa,MAAM;AAC/C,YAAI,CAAC,KAAK,OAAO;AACf,kBAAQ,IAAI,8CAA8C,KAAK,IAAI,KAAK;AACxE,kBAAQ,IAAI,0BAA0B;AACtC,kBAAQ,IAAI,EAAE;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,cAAc,KAAsB,KAA2B;AACrE,UAAM,MAAM,IAAI,eAAI,IAAI,OAAO,KAAK,oBAAoB,KAAK,IAAI,EAAE;AAGnE,QAAI,IAAI,aAAa,gBAAgB;AACnC,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI;AACR;AAAA,IACF;AAGA,QAAI,IAAI,aAAa,KAAK,cAAc;AACtC,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,WAAW;AACnB;AAAA,IACF;AAQA,UAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAC1C,QAAI,CAAC,KAAK,iBAAiB,CAAC,SAAS,CAAC,YAAY,OAAO,KAAK,aAAa,GAAG;AAC5E,YAAM,UAAU;AAChB,WAAK,kBAAkB,KAAK,OAAO;AACnC,UAAI,KAAK,iBAAiB;AACxB,aAAK,gBAAgB,OAAO,IAAI,MAAM,OAAO,CAAC;AAAA,MAChD;AACA;AAAA,IACF;AAEA,UAAM,OAAO,IAAI,aAAa,IAAI,MAAM;AACxC,UAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAC1C,UAAM,mBAAmB,IAAI,aAAa,IAAI,mBAAmB;AAEjE,QAAI,OAAO;AACT,YAAM,WAAW,oBAAoB;AACrC,WAAK,kBAAkB,KAAK,QAAQ;AAEpC,WAAK,gBAAgB;AACrB,UAAI,KAAK,iBAAiB;AACxB,YAAI,UAAU,iBAAiB;AAC7B,eAAK,gBAAgB,OAAO,IAAI,iCAAoB,CAAC;AAAA,QACvD,OAAO;AACL,eAAK,gBAAgB,OAAO,IAAI,MAAM,gBAAgB,QAAQ,EAAE,CAAC;AAAA,QACnE;AAAA,MACF;AACA;AAAA,IACF;AAEA,QAAI,CAAC,MAAM;AACT,WAAK,kBAAkB,KAAK,gCAAgC;AAC5D,WAAK,gBAAgB;AACrB,UAAI,KAAK,iBAAiB;AACxB,aAAK,gBAAgB,OAAO,IAAI,MAAM,gCAAgC,CAAC;AAAA,MACzE;AACA;AAAA,IACF;AAGA,SAAK,oBAAoB,GAAG;AAE5B,QAAI,KAAK,iBAAiB;AACxB,WAAK,gBAAgB,QAAQ,IAAI;AAAA,IACnC;AAGA,eAAW,MAAM,KAAK,QAAQ,GAAG,GAAI;AAAA,EACvC;AAAA,EAEQ,oBAAoB,KAA2B;AACrD,QAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,QAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAuCP;AAAA,EACH;AAAA,EAEQ,kBAAkB,KAAqB,OAAqB;AAClE,QAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,QAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAkCW,WAAW,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,KAKnC;AAAA,EACH;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,gBAAgB;AACrB,QAAI,KAAK,QAAQ;AACf,aAAO,IAAI,QAAQ,aAAW;AAC5B,aAAK,OAAQ,MAAM,MAAM;AACvB,eAAK,SAAS;AACd,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAKA,SAAS,YAAY,UAAkB,UAA2B;AAChE,QAAM,IAAI,OAAO,KAAK,UAAU,MAAM;AACtC,QAAM,IAAI,OAAO,KAAK,UAAU,MAAM;AACtC,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,aAAO,+BAAgB,GAAG,CAAC;AAC7B;AAKA,SAAS,WAAW,MAAsB;AACxC,SAAO,KACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;","names":[]}