@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.
- package/README.md +7 -1
- package/bin/adcp.js +7 -1
- package/dist/lib/adapters/property-list-adapter.d.mts +5 -1
- package/dist/lib/adapters/property-list-adapter.d.ts +5 -1
- package/dist/lib/adapters/property-list-adapter.d.ts.map +1 -1
- package/dist/lib/adapters/property-list-adapter.js +7 -7
- package/dist/lib/adapters/property-list-adapter.js.map +1 -1
- package/dist/lib/adapters/property-list-adapter.mjs +7 -7
- package/dist/lib/adapters/property-list-adapter.mjs.map +1 -1
- package/dist/lib/auth/oauth/CLIFlowHandler.d.mts +10 -3
- package/dist/lib/auth/oauth/CLIFlowHandler.d.ts +10 -3
- package/dist/lib/auth/oauth/CLIFlowHandler.d.ts.map +1 -1
- package/dist/lib/auth/oauth/CLIFlowHandler.js +40 -4
- package/dist/lib/auth/oauth/CLIFlowHandler.js.map +1 -1
- package/dist/lib/auth/oauth/CLIFlowHandler.mjs +40 -4
- package/dist/lib/auth/oauth/CLIFlowHandler.mjs.map +1 -1
- package/dist/lib/auth/oauth/index.d.mts +1 -1
- package/dist/lib/auth/oauth/index.d.ts +1 -1
- package/dist/lib/auth/oauth/index.d.ts.map +1 -1
- package/dist/lib/auth/oauth/index.js +2 -0
- package/dist/lib/auth/oauth/index.js.map +1 -1
- package/dist/lib/auth/oauth/index.mjs +2 -0
- package/dist/lib/auth/oauth/index.mjs.map +1 -1
- package/dist/lib/auth/oauth/web-flow.d.mts +12 -0
- package/dist/lib/auth/oauth/web-flow.d.ts +12 -0
- package/dist/lib/auth/oauth/web-flow.d.ts.map +1 -1
- package/dist/lib/auth/oauth/web-flow.js +20 -2
- package/dist/lib/auth/oauth/web-flow.js.map +1 -1
- package/dist/lib/auth/oauth/web-flow.mjs +19 -2
- package/dist/lib/auth/oauth/web-flow.mjs.map +1 -1
- package/dist/lib/core/AgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.d.mts +27 -1
- package/dist/lib/core/SingleAgentClient.d.ts +27 -1
- package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.js +46 -18
- package/dist/lib/core/SingleAgentClient.js.map +1 -1
- package/dist/lib/core/SingleAgentClient.mjs +46 -18
- package/dist/lib/core/SingleAgentClient.mjs.map +1 -1
- package/dist/lib/net/address-guards.d.ts.map +1 -1
- package/dist/lib/net/address-guards.js +4 -0
- package/dist/lib/net/address-guards.js.map +1 -1
- package/dist/lib/net/address-guards.mjs +4 -0
- package/dist/lib/net/address-guards.mjs.map +1 -1
- package/dist/lib/protocols/a2a.js +2 -2
- package/dist/lib/protocols/a2a.js.map +1 -1
- package/dist/lib/protocols/a2a.mjs +2 -2
- package/dist/lib/protocols/a2a.mjs.map +1 -1
- package/dist/lib/protocols/index.d.ts.map +1 -1
- package/dist/lib/protocols/index.js +28 -7
- package/dist/lib/protocols/index.js.map +1 -1
- package/dist/lib/protocols/index.mjs +28 -7
- package/dist/lib/protocols/index.mjs.map +1 -1
- package/dist/lib/protocols/mcp-modern.d.ts.map +1 -1
- package/dist/lib/protocols/mcp-modern.js +18 -5
- package/dist/lib/protocols/mcp-modern.js.map +1 -1
- package/dist/lib/protocols/mcp-modern.mjs +18 -5
- package/dist/lib/protocols/mcp-modern.mjs.map +1 -1
- package/dist/lib/protocols/mcp-tasks.d.ts.map +1 -1
- package/dist/lib/protocols/mcp-tasks.js +2 -12
- package/dist/lib/protocols/mcp-tasks.js.map +1 -1
- package/dist/lib/protocols/mcp-tasks.mjs +2 -12
- package/dist/lib/protocols/mcp-tasks.mjs.map +1 -1
- package/dist/lib/protocols/mcp.js +2 -2
- package/dist/lib/protocols/mcp.js.map +1 -1
- package/dist/lib/protocols/mcp.mjs +2 -2
- package/dist/lib/protocols/mcp.mjs.map +1 -1
- package/dist/lib/registry/types.generated.d.mts +547 -10
- package/dist/lib/registry/types.generated.d.ts +547 -10
- package/dist/lib/registry/types.generated.d.ts.map +1 -1
- package/dist/lib/registry/types.generated.js.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/decisioning/account.d.mts +6 -4
- package/dist/lib/server/decisioning/account.d.ts +6 -4
- package/dist/lib/server/decisioning/account.d.ts.map +1 -1
- package/dist/lib/server/decisioning/account.js.map +1 -1
- package/dist/lib/server/decisioning/account.mjs.map +1 -1
- package/dist/lib/server/decisioning/manifest-helpers.d.mts +6 -5
- package/dist/lib/server/decisioning/manifest-helpers.d.ts +6 -5
- package/dist/lib/server/decisioning/manifest-helpers.d.ts.map +1 -1
- package/dist/lib/server/decisioning/manifest-helpers.js.map +1 -1
- package/dist/lib/server/decisioning/manifest-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.js +4 -2
- package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +4 -2
- package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/tenant-store.d.mts +26 -14
- package/dist/lib/server/decisioning/tenant-store.d.ts +26 -14
- package/dist/lib/server/decisioning/tenant-store.d.ts.map +1 -1
- package/dist/lib/server/decisioning/tenant-store.js +6 -1
- package/dist/lib/server/decisioning/tenant-store.js.map +1 -1
- package/dist/lib/server/decisioning/tenant-store.mjs +6 -1
- package/dist/lib/server/decisioning/tenant-store.mjs.map +1 -1
- package/dist/lib/server/pin-and-bind-fetch.d.mts +2 -0
- package/dist/lib/server/pin-and-bind-fetch.d.ts +2 -0
- package/dist/lib/server/pin-and-bind-fetch.d.ts.map +1 -1
- package/dist/lib/server/pin-and-bind-fetch.js +2 -0
- package/dist/lib/server/pin-and-bind-fetch.js.map +1 -1
- package/dist/lib/server/pin-and-bind-fetch.mjs +2 -0
- package/dist/lib/server/pin-and-bind-fetch.mjs.map +1 -1
- package/dist/lib/server/structured-serialize.d.ts.map +1 -1
- package/dist/lib/server/structured-serialize.js +10 -2
- package/dist/lib/server/structured-serialize.js.map +1 -1
- package/dist/lib/server/structured-serialize.mjs +10 -2
- package/dist/lib/server/structured-serialize.mjs.map +1 -1
- package/dist/lib/server/webhook-emitter.d.ts.map +1 -1
- package/dist/lib/server/webhook-emitter.js +4 -2
- package/dist/lib/server/webhook-emitter.js.map +1 -1
- package/dist/lib/server/webhook-emitter.mjs +4 -2
- package/dist/lib/server/webhook-emitter.mjs.map +1 -1
- package/dist/lib/signing/middleware.d.mts +2 -1
- package/dist/lib/signing/middleware.d.ts +2 -1
- package/dist/lib/signing/middleware.d.ts.map +1 -1
- package/dist/lib/signing/middleware.js +26 -4
- package/dist/lib/signing/middleware.js.map +1 -1
- package/dist/lib/signing/middleware.mjs +26 -4
- package/dist/lib/signing/middleware.mjs.map +1 -1
- package/dist/lib/signing/webhook-verifier.d.ts.map +1 -1
- package/dist/lib/signing/webhook-verifier.js +4 -2
- package/dist/lib/signing/webhook-verifier.js.map +1 -1
- package/dist/lib/signing/webhook-verifier.mjs +4 -2
- package/dist/lib/signing/webhook-verifier.mjs.map +1 -1
- package/dist/lib/testing/agent-tester.d.ts.map +1 -1
- package/dist/lib/testing/agent-tester.js +46 -1
- package/dist/lib/testing/agent-tester.js.map +1 -1
- package/dist/lib/testing/agent-tester.mjs +46 -1
- package/dist/lib/testing/agent-tester.mjs.map +1 -1
- package/dist/lib/types/asset-instances.d.mts +8 -6
- package/dist/lib/types/asset-instances.d.ts +8 -6
- package/dist/lib/types/asset-instances.d.ts.map +1 -1
- package/dist/lib/types/asset-instances.js.map +1 -1
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/types/index.mjs.map +1 -1
- package/dist/lib/utils/probe-policy.d.mts +0 -16
- package/dist/lib/utils/probe-policy.d.ts +0 -16
- package/dist/lib/utils/probe-policy.d.ts.map +1 -1
- package/dist/lib/utils/probe-policy.js +15 -1
- package/dist/lib/utils/probe-policy.js.map +1 -1
- package/dist/lib/utils/probe-policy.mjs +15 -1
- package/dist/lib/utils/probe-policy.mjs.map +1 -1
- package/dist/lib/utils/redact-args.d.mts +36 -0
- package/dist/lib/utils/redact-args.d.ts +37 -0
- package/dist/lib/utils/redact-args.d.ts.map +1 -0
- package/dist/lib/utils/redact-args.js +46 -0
- package/dist/lib/utils/redact-args.js.map +1 -0
- package/dist/lib/utils/redact-args.mjs +22 -0
- package/dist/lib/utils/redact-args.mjs.map +1 -0
- package/dist/lib/v2/projection/creative-delivery.js +1 -5
- package/dist/lib/v2/projection/creative-delivery.js.map +1 -1
- package/dist/lib/v2/projection/creative-delivery.mjs +1 -5
- package/dist/lib/v2/projection/creative-delivery.mjs.map +1 -1
- package/dist/lib/validation/index.d.mts +15 -1
- package/dist/lib/validation/index.d.ts +15 -1
- package/dist/lib/validation/index.d.ts.map +1 -1
- package/dist/lib/validation/index.js +4 -8
- package/dist/lib/validation/index.js.map +1 -1
- package/dist/lib/validation/index.mjs +4 -8
- package/dist/lib/validation/index.mjs.map +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/version.mjs.map +1 -1
- package/examples/hello_seller_adapter_multi_tenant.ts +7 -0
- package/package.json +3 -2
- package/skills/build-holdco-agent/SKILL.md +19 -0
- 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
|
-
|
|
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":"AAAA;;;;;;;;;GASG;
|
|
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
|
-
|
|
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":"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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":"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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
|
-
|
|
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":"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","names":[]}
|
|
@@ -30,7 +30,9 @@ export interface CLIFlowHandlerConfig {
|
|
|
30
30
|
* @example
|
|
31
31
|
* ```typescript
|
|
32
32
|
* const flow = new CLIFlowHandler({ callbackPort: 8766 });
|
|
33
|
-
*
|
|
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
|
-
*
|
|
51
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
51
|
-
*
|
|
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;
|
|
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
|
-
*
|
|
49
|
-
*
|
|
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 = "
|
|
62
|
-
args = ["
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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\">✓</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\">✗</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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\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\">✓</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\">✗</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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n"],"mappings":"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","names":[]}
|