@nevermined-io/payments 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/nvm-api.d.ts +4 -0
- package/dist/api/nvm-api.d.ts.map +1 -1
- package/dist/api/nvm-api.js +7 -0
- package/dist/api/nvm-api.js.map +1 -1
- package/dist/api/organizations-api/organizations-api.d.ts +19 -1
- package/dist/api/organizations-api/organizations-api.d.ts.map +1 -1
- package/dist/api/organizations-api/organizations-api.js +49 -0
- package/dist/api/organizations-api/organizations-api.js.map +1 -1
- package/dist/api/organizations-api/types.d.ts +27 -0
- package/dist/api/organizations-api/types.d.ts.map +1 -1
- package/dist/api/organizations-api/types.js.map +1 -1
- package/dist/common/api-version.d.ts +1 -1
- package/dist/common/api-version.js +1 -1
- package/dist/common/api-version.js.map +1 -1
- package/dist/common/types.d.ts +1 -1
- package/dist/common/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/core/server-manager.d.ts.map +1 -1
- package/dist/mcp/core/server-manager.js +3 -0
- package/dist/mcp/core/server-manager.js.map +1 -1
- package/dist/mcp/http/mcp-handler.d.ts +7 -0
- package/dist/mcp/http/mcp-handler.d.ts.map +1 -1
- package/dist/mcp/http/mcp-handler.js +8 -1
- package/dist/mcp/http/mcp-handler.js.map +1 -1
- package/dist/mcp/http/oauth-metadata.d.ts.map +1 -1
- package/dist/mcp/http/oauth-metadata.js +31 -6
- package/dist/mcp/http/oauth-metadata.js.map +1 -1
- package/dist/mcp/http/oauth-router.d.ts +17 -16
- package/dist/mcp/http/oauth-router.d.ts.map +1 -1
- package/dist/mcp/http/oauth-router.js +56 -28
- package/dist/mcp/http/oauth-router.js.map +1 -1
- package/dist/mpp/codec.d.ts +92 -0
- package/dist/mpp/codec.d.ts.map +1 -0
- package/dist/mpp/codec.js +405 -0
- package/dist/mpp/codec.js.map +1 -0
- package/dist/mpp/errors.d.ts +165 -0
- package/dist/mpp/errors.d.ts.map +1 -0
- package/dist/mpp/errors.js +177 -0
- package/dist/mpp/errors.js.map +1 -0
- package/dist/mpp/fetch.d.ts +196 -0
- package/dist/mpp/fetch.d.ts.map +1 -0
- package/dist/mpp/fetch.js +493 -0
- package/dist/mpp/fetch.js.map +1 -0
- package/dist/mpp/index.d.ts +11 -0
- package/dist/mpp/index.d.ts.map +1 -0
- package/dist/mpp/index.js +20 -0
- package/dist/mpp/index.js.map +1 -0
- package/dist/mpp/mpp-api.d.ts +153 -0
- package/dist/mpp/mpp-api.d.ts.map +1 -0
- package/dist/mpp/mpp-api.js +298 -0
- package/dist/mpp/mpp-api.js.map +1 -0
- package/dist/mpp/types.d.ts +43 -0
- package/dist/mpp/types.d.ts.map +1 -0
- package/dist/mpp/types.js +9 -0
- package/dist/mpp/types.js.map +1 -0
- package/dist/payments.d.ts +13 -0
- package/dist/payments.d.ts.map +1 -1
- package/dist/payments.js +3 -0
- package/dist/payments.js.map +1 -1
- package/dist/x402/delegation-api.d.ts +3 -1
- package/dist/x402/delegation-api.d.ts.map +1 -1
- package/dist/x402/delegation-api.js.map +1 -1
- package/dist/x402/express/index.d.ts +2 -0
- package/dist/x402/express/index.d.ts.map +1 -1
- package/dist/x402/express/index.js +2 -0
- package/dist/x402/express/index.js.map +1 -1
- package/dist/x402/express/middleware.d.ts +33 -1
- package/dist/x402/express/middleware.d.ts.map +1 -1
- package/dist/x402/express/middleware.js +831 -1
- package/dist/x402/express/middleware.js.map +1 -1
- package/dist/x402/express/mpp-support.d.ts +58 -0
- package/dist/x402/express/mpp-support.d.ts.map +1 -0
- package/dist/x402/express/mpp-support.js +67 -0
- package/dist/x402/express/mpp-support.js.map +1 -0
- package/dist/x402/express/raw-body.d.ts +24 -0
- package/dist/x402/express/raw-body.d.ts.map +1 -0
- package/dist/x402/express/raw-body.js +34 -0
- package/dist/x402/express/raw-body.js.map +1 -0
- package/dist/x402/token-request.d.ts +15 -0
- package/dist/x402/token-request.d.ts.map +1 -0
- package/dist/x402/token-request.js +66 -0
- package/dist/x402/token-request.js.map +1 -0
- package/dist/x402/token.d.ts.map +1 -1
- package/dist/x402/token.js +7 -51
- package/dist/x402/token.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-router.js","sourceRoot":"","sources":["../../../src/mcp/http/oauth-router.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Express Router for OAuth 2.1 endpoints.\n * Provides a pre-configured router with all OAuth discovery and registration endpoints\n * that can be mounted on any Express application.\n */\nimport express, { type Router, type Request, type Response, type NextFunction } from 'express'\nimport type { Payments } from '../../payments.js'\nimport type { HttpRouterConfig } from '../types/http.types.js'\nimport {\n buildProtectedResourceMetadata,\n buildMcpProtectedResourceMetadata,\n buildAuthorizationServerMetadata,\n buildOidcConfiguration,\n buildServerInfoResponse,\n} from './oauth-metadata.js'\nimport {\n isClientRegistrationRequest,\n processClientRegistration,\n ClientRegistrationError,\n} from './client-registration.js'\n\n/**\n * Options for creating the OAuth router.\n */\nexport interface OAuthRouterOptions extends HttpRouterConfig {\n /** Payments instance for authentication */\n payments: Payments\n /** Server version for info endpoint */\n version?: string\n /** Server description for info endpoint */\n description?: string\n /** Optional logging callback (no logs by default) */\n onLog?: (message: string) => void\n}\n\n/**\n * Create an Express router with OAuth 2.1 discovery and registration endpoints.\n * This router can be mounted on any Express application to add OAuth support.\n *\n * @param options - Router configuration options\n * @returns Express Router with OAuth endpoints\n *\n * @example\n * ```typescript\n * import express from 'express'\n * import { Payments } from '@nevermined-io/payments'\n *\n * const app = express()\n * const payments = Payments.getInstance({ nvmApiKey: '...', environment: 'staging_sandbox' })\n *\n * // Create and mount the OAuth router\n * const oauthRouter = createOAuthRouter({\n * payments,\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox',\n * serverName: 'my-mcp-server',\n * tools: ['hello_world']\n * })\n *\n * app.use(oauthRouter)\n * app.listen(5001)\n * ```\n */\nexport function createOAuthRouter(options: OAuthRouterOptions): Router {\n const router: Router = express.Router()\n\n const {\n baseUrl,\n agentId,\n environment,\n serverName = 'mcp-server',\n tools = [],\n resources = [],\n prompts = [],\n scopes,\n oauthUrls,\n protocolVersion,\n enableOAuthDiscovery = true,\n enableClientRegistration = true,\n enableHealthCheck = true,\n enableServerInfo = true,\n version = '1.0.0',\n description,\n onLog,\n } = options\n\n // Optional logging helper (no-op if onLog not provided)\n const log =\n onLog ||\n (() => {\n // Intentionally empty - no logging when onLog not provided\n })\n\n // Build config object for metadata generators\n const config = {\n baseUrl,\n agentId,\n environment,\n serverName,\n tools,\n resources,\n prompts,\n scopes,\n oauthUrls,\n protocolVersion,\n }\n\n // --- OAuth Discovery Endpoints ---\n\n if (enableOAuthDiscovery) {\n /**\n * Protected Resource Metadata (RFC 9728)\n * GET /.well-known/oauth-protected-resource\n */\n router.get('/.well-known/oauth-protected-resource', (req: Request, res: Response) => {\n log(`GET /.well-known/oauth-protected-resource`)\n const metadata = buildProtectedResourceMetadata(config)\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Cache-Control', 'public, max-age=3600')\n res.json(metadata)\n })\n\n /**\n * MCP-specific Protected Resource Metadata\n * GET /.well-known/oauth-protected-resource/mcp\n */\n router.get('/.well-known/oauth-protected-resource/mcp', (req: Request, res: Response) => {\n log(`GET /.well-known/oauth-protected-resource/mcp`)\n const metadata = buildMcpProtectedResourceMetadata(config)\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Cache-Control', 'public, max-age=3600')\n res.json(metadata)\n })\n\n /**\n * OAuth Authorization Server Metadata (RFC 8414)\n * GET /.well-known/oauth-authorization-server\n */\n router.get('/.well-known/oauth-authorization-server', (req: Request, res: Response) => {\n log(`GET /.well-known/oauth-authorization-server`)\n const metadata = buildAuthorizationServerMetadata(config)\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Cache-Control', 'public, max-age=3600')\n res.json(metadata)\n })\n\n /**\n * OpenID Connect Discovery (for OIDC compatibility)\n * GET /.well-known/openid-configuration\n */\n router.get('/.well-known/openid-configuration', (req: Request, res: Response) => {\n log(`GET /.well-known/openid-configuration`)\n const metadata = buildOidcConfiguration(config)\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Cache-Control', 'public, max-age=3600')\n res.json(metadata)\n })\n }\n\n // --- Dynamic Client Registration ---\n\n if (enableClientRegistration) {\n /**\n * OAuth Dynamic Client Registration (RFC 7591)\n * POST /register\n */\n router.post('/register', async (req: Request, res: Response) => {\n log(`POST /register`)\n try {\n // Check if this is an OAuth registration request\n if (!isClientRegistrationRequest(req.body)) {\n log(`Invalid registration request`)\n // Not a registration request - return error\n res.status(400).json({\n error: 'invalid_request',\n error_description: 'Request body is not a valid client registration request',\n })\n return\n }\n\n // Process registration\n const response = await processClientRegistration(req.body, config)\n log(`Client registered: ${response.client_id}`)\n\n // Return successful registration (201 Created)\n res.status(201).json(response)\n } catch (error: any) {\n log(`Registration error: ${error?.message || error}`)\n if (error instanceof ClientRegistrationError) {\n res.status(error.statusCode).json(error.toJSON())\n return\n }\n\n // Unexpected error\n res.status(500).json({\n error: 'server_error',\n error_description: error?.message || 'Internal server error during client registration',\n })\n }\n })\n }\n\n // --- Health Check ---\n\n if (enableHealthCheck) {\n /**\n * Health check endpoint\n * GET /health\n */\n router.get('/health', (req: Request, res: Response) => {\n log(`GET /health`)\n res.json({\n status: 'ok',\n service: serverName,\n timestamp: new Date().toISOString(),\n })\n })\n }\n\n // --- Server Info ---\n\n if (enableServerInfo) {\n /**\n * Server info endpoint\n * GET /\n */\n router.get('/', (req: Request, res: Response) => {\n log(`GET /`)\n const info = buildServerInfoResponse(config, { version, description })\n res.json(info)\n })\n }\n\n return router\n}\n\n/**\n * Apply CORS headers to an Express application.\n * This is a utility function for setting up CORS in MCP servers.\n *\n * @param origins - Allowed origins (default: '*')\n * @returns Express middleware for CORS\n */\nexport function createCorsMiddleware(origins: string | string[] = '*') {\n return function corsMiddleware(req: Request, res: Response, next: NextFunction) {\n const origin = Array.isArray(origins) ? origins.join(', ') : origins\n\n res.setHeader('Access-Control-Allow-Origin', origin)\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')\n res.setHeader(\n 'Access-Control-Allow-Headers',\n 'Origin, X-Requested-With, Content-Type, Accept, Authorization, mcp-session-id, mcp-protocol-version',\n )\n res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id')\n\n if (req.method === 'OPTIONS') {\n res.status(200).end()\n return\n }\n\n next()\n }\n}\n\n/**\n * Create a JSON body parser middleware that ensures JSON responses.\n * This wraps express.json() and adds response formatting.\n *\n * @returns Express middleware for JSON parsing\n */\nexport function createJsonMiddleware() {\n return function jsonMiddleware(req: Request, res: Response, next: NextFunction) {\n // Ensure JSON content type on responses\n const originalJson = res.json.bind(res)\n res.json = function (body: any) {\n res.setHeader('Content-Type', 'application/json')\n return originalJson(body)\n }\n\n // Parse JSON body\n express.json()(req, res, (err: any) => {\n if (err) {\n res.status(400).json({\n jsonrpc: '2.0',\n id: null,\n error: {\n code: -32700,\n message: 'Parse error: Invalid JSON',\n },\n })\n return\n }\n next()\n })\n }\n}\n\n/**\n * Create a middleware that requires a Bearer token in the Authorization header.\n * Returns HTTP 401 if the token is missing or malformed.\n *\n * This is a lightweight check that only verifies presence of a token.\n * Full validation (is the token valid? does the user have access?) is handled\n * by withPaywall() when a tool is actually called.\n *\n * @returns Express middleware for token requirement\n *\n * @example\n * ```typescript\n * const requireAuth = createRequireAuthMiddleware()\n *\n * // Protect MCP endpoints\n * app.post('/mcp', requireAuth, mcpHandler)\n * app.get('/mcp', requireAuth, sseHandler)\n * ```\n */\nexport function createRequireAuthMiddleware() {\n return function requireAuthMiddleware(req: Request, res: Response, next: NextFunction) {\n const authHeader = req.headers.authorization\n\n if (!authHeader) {\n res.status(401).json({\n error: 'unauthorized',\n error_description: 'Authorization header required',\n })\n return\n }\n\n if (!authHeader.startsWith('Bearer ')) {\n res.status(401).json({\n error: 'unauthorized',\n error_description: 'Bearer token required',\n })\n return\n }\n\n const token = authHeader.slice(7).trim()\n if (!token) {\n res.status(401).json({\n error: 'unauthorized',\n error_description: 'Bearer token cannot be empty',\n })\n return\n }\n\n // Token present and well-formed, continue\n // Full validation happens in withPaywall() when tools are called\n next()\n }\n}\n\n/**\n * Create a middleware that logs all HTTP requests to endpoints.\n * Logs method, URL, IP address, and relevant headers.\n *\n * @param onLog - Optional logging callback. If not provided, does nothing.\n * @returns Express middleware for HTTP request logging\n *\n * @example\n * ```typescript\n * const logMiddleware = createHttpLoggingMiddleware((msg) => console.log(msg))\n * app.use(logMiddleware)\n * ```\n */\nexport function createHttpLoggingMiddleware(onLog?: (message: string) => void) {\n const log =\n onLog ||\n ((_message: string) => {\n // Intentionally empty - no logging when onLog not provided\n })\n\n return function httpLoggingMiddleware(req: Request, res: Response, next: NextFunction) {\n const method = req.method\n const url = req.originalUrl || req.url\n const ip = req.ip || req.socket.remoteAddress || 'unknown'\n const timestamp = new Date().toISOString()\n\n // Extract relevant headers\n const authHeader = req.headers.authorization\n const sessionId = req.headers['mcp-session-id']\n const protocolVersion = req.headers['mcp-protocol-version']\n const userAgent = req.headers['user-agent']\n\n // Build log message\n const logParts: string[] = [`[${timestamp}]`, `${method}`, url, `IP: ${ip}`]\n\n if (authHeader) {\n const tokenPreview = authHeader.startsWith('Bearer ')\n ? `Bearer ${authHeader.slice(7, 20)}...`\n : 'Bearer [present]'\n logParts.push(`Auth: ${tokenPreview}`)\n }\n\n if (sessionId) {\n const sessionValue = Array.isArray(sessionId) ? sessionId[0] : sessionId\n logParts.push(`Session: ${sessionValue}`)\n }\n\n if (protocolVersion) {\n const versionValue = Array.isArray(protocolVersion) ? protocolVersion[0] : protocolVersion\n logParts.push(`MCP-Version: ${versionValue}`)\n }\n\n if (userAgent) {\n const uaValue = Array.isArray(userAgent) ? userAgent[0] : userAgent\n logParts.push(`User-Agent: ${uaValue}`)\n }\n\n log(`HTTP Request: ${logParts.join(' | ')}`)\n\n next()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"oauth-router.js","sourceRoot":"","sources":["../../../src/mcp/http/oauth-router.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Express Router for OAuth 2.1 endpoints.\n * Provides a pre-configured router with all OAuth discovery and registration endpoints\n * that can be mounted on any Express application.\n */\nimport express, { type Router, type Request, type Response, type NextFunction } from 'express'\nimport type { Payments } from '../../payments.js'\nimport type { HttpRouterConfig } from '../types/http.types.js'\nimport {\n buildProtectedResourceMetadata,\n buildMcpProtectedResourceMetadata,\n buildAuthorizationServerMetadata,\n buildOidcConfiguration,\n buildServerInfoResponse,\n} from './oauth-metadata.js'\nimport {\n isClientRegistrationRequest,\n processClientRegistration,\n ClientRegistrationError,\n} from './client-registration.js'\n\n/**\n * Options for creating the OAuth router.\n */\nexport interface OAuthRouterOptions extends HttpRouterConfig {\n /** Payments instance for authentication */\n payments: Payments\n /** Server version for info endpoint */\n version?: string\n /** Server description for info endpoint */\n description?: string\n /** Optional logging callback (no logs by default) */\n onLog?: (message: string) => void\n}\n\n/**\n * Create an Express router with OAuth 2.1 discovery and registration endpoints.\n * This router can be mounted on any Express application to add OAuth support.\n *\n * @param options - Router configuration options\n * @returns Express Router with OAuth endpoints\n *\n * @example\n * ```typescript\n * import express from 'express'\n * import { Payments } from '@nevermined-io/payments'\n *\n * const app = express()\n * const payments = Payments.getInstance({ nvmApiKey: '...', environment: 'staging_sandbox' })\n *\n * // Create and mount the OAuth router\n * const oauthRouter = createOAuthRouter({\n * payments,\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox',\n * serverName: 'my-mcp-server',\n * tools: ['hello_world']\n * })\n *\n * app.use(oauthRouter)\n * app.listen(5001)\n * ```\n */\nexport function createOAuthRouter(options: OAuthRouterOptions): Router {\n const router: Router = express.Router()\n\n const {\n baseUrl,\n agentId,\n environment,\n serverName = 'mcp-server',\n tools = [],\n resources = [],\n prompts = [],\n scopes,\n oauthUrls,\n protocolVersion,\n enableOAuthDiscovery = true,\n enableClientRegistration = true,\n enableHealthCheck = true,\n enableServerInfo = true,\n version = '1.0.0',\n description,\n onLog,\n } = options\n\n // Optional logging helper (no-op if onLog not provided)\n const log =\n onLog ||\n (() => {\n // Intentionally empty - no logging when onLog not provided\n })\n\n // Build config object for metadata generators\n const config = {\n baseUrl,\n agentId,\n environment,\n serverName,\n tools,\n resources,\n prompts,\n scopes,\n oauthUrls,\n protocolVersion,\n }\n\n // --- OAuth Discovery Endpoints ---\n\n if (enableOAuthDiscovery) {\n /**\n * Protected Resource Metadata (RFC 9728)\n * GET /.well-known/oauth-protected-resource\n */\n router.get('/.well-known/oauth-protected-resource', (req: Request, res: Response) => {\n log(`GET /.well-known/oauth-protected-resource`)\n const metadata = buildProtectedResourceMetadata(config)\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Cache-Control', 'public, max-age=3600')\n res.json(metadata)\n })\n\n /**\n * MCP-specific Protected Resource Metadata\n * GET /.well-known/oauth-protected-resource/mcp\n */\n router.get('/.well-known/oauth-protected-resource/mcp', (req: Request, res: Response) => {\n log(`GET /.well-known/oauth-protected-resource/mcp`)\n const metadata = buildMcpProtectedResourceMetadata(config)\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Cache-Control', 'public, max-age=3600')\n res.json(metadata)\n })\n\n /**\n * OAuth Authorization Server Metadata (RFC 8414)\n * GET /.well-known/oauth-authorization-server\n */\n router.get('/.well-known/oauth-authorization-server', (req: Request, res: Response) => {\n log(`GET /.well-known/oauth-authorization-server`)\n const metadata = buildAuthorizationServerMetadata(config)\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Cache-Control', 'public, max-age=3600')\n res.json(metadata)\n })\n\n /**\n * OpenID Connect Discovery (for OIDC compatibility)\n * GET /.well-known/openid-configuration\n */\n router.get('/.well-known/openid-configuration', (req: Request, res: Response) => {\n log(`GET /.well-known/openid-configuration`)\n const metadata = buildOidcConfiguration(config)\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Cache-Control', 'public, max-age=3600')\n res.json(metadata)\n })\n }\n\n // --- Dynamic Client Registration ---\n\n if (enableClientRegistration) {\n /**\n * OAuth Dynamic Client Registration (RFC 7591)\n * POST /register\n */\n router.post('/register', async (req: Request, res: Response) => {\n log(`POST /register`)\n try {\n // Check if this is an OAuth registration request\n if (!isClientRegistrationRequest(req.body)) {\n log(`Invalid registration request`)\n // Not a registration request - return error\n res.status(400).json({\n error: 'invalid_request',\n error_description: 'Request body is not a valid client registration request',\n })\n return\n }\n\n // Process registration\n const response = await processClientRegistration(req.body, config)\n log(`Client registered: ${response.client_id}`)\n\n // Return successful registration (201 Created)\n res.status(201).json(response)\n } catch (error: any) {\n log(`Registration error: ${error?.message || error}`)\n if (error instanceof ClientRegistrationError) {\n res.status(error.statusCode).json(error.toJSON())\n return\n }\n\n // Unexpected error\n res.status(500).json({\n error: 'server_error',\n error_description: error?.message || 'Internal server error during client registration',\n })\n }\n })\n }\n\n // --- Health Check ---\n\n if (enableHealthCheck) {\n /**\n * Health check endpoint\n * GET /health\n */\n router.get('/health', (req: Request, res: Response) => {\n log(`GET /health`)\n res.json({\n status: 'ok',\n service: serverName,\n timestamp: new Date().toISOString(),\n })\n })\n }\n\n // --- Server Info ---\n\n if (enableServerInfo) {\n /**\n * Server info endpoint\n * GET /\n */\n router.get('/', (req: Request, res: Response) => {\n log(`GET /`)\n const info = buildServerInfoResponse(config, { version, description })\n res.json(info)\n })\n }\n\n return router\n}\n\n/**\n * Apply CORS headers to an Express application.\n * This is a utility function for setting up CORS in MCP servers.\n *\n * @param origins - Allowed origins (default: '*')\n * @returns Express middleware for CORS\n */\nexport function createCorsMiddleware(origins: string | string[] = '*') {\n return function corsMiddleware(req: Request, res: Response, next: NextFunction) {\n const origin = Array.isArray(origins) ? origins.join(', ') : origins\n\n res.setHeader('Access-Control-Allow-Origin', origin)\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')\n res.setHeader(\n 'Access-Control-Allow-Headers',\n 'Origin, X-Requested-With, Content-Type, Accept, Authorization, mcp-session-id, mcp-protocol-version',\n )\n res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id')\n\n if (req.method === 'OPTIONS') {\n res.status(200).end()\n return\n }\n\n next()\n }\n}\n\n/**\n * Create a JSON body parser middleware that ensures JSON responses.\n * This wraps express.json() and adds response formatting.\n *\n * @returns Express middleware for JSON parsing\n */\nexport function createJsonMiddleware() {\n return function jsonMiddleware(req: Request, res: Response, next: NextFunction) {\n // Ensure JSON content type on responses\n const originalJson = res.json.bind(res)\n res.json = function (body: any) {\n res.setHeader('Content-Type', 'application/json')\n return originalJson(body)\n }\n\n // Parse JSON body\n express.json()(req, res, (err: any) => {\n if (err) {\n res.status(400).json({\n jsonrpc: '2.0',\n id: null,\n error: {\n code: -32700,\n message: 'Parse error: Invalid JSON',\n },\n })\n return\n }\n next()\n })\n }\n}\n\n/**\n * A well-formed HTTP Host value: `hostname[:port]` or `[ipv6][:port]`. Crucially\n * it contains none of the characters (`\"`, `\\`, whitespace, `;`, `,`) that could\n * break out of the `WWW-Authenticate` quoted-string and inject extra auth-params.\n */\nconst SAFE_HOST_RE = /^(\\[[0-9a-fA-F:]+\\]|[A-Za-z0-9._-]+)(:\\d{1,5})?$/\n\n/**\n * Create a middleware that requires a Bearer token, emitting an RFC 9728 §5.1\n * `WWW-Authenticate` challenge on 401 so a client can discover the PRM.\n *\n * `options.baseUrl` (optional) — operator-known public base URL of THIS MCP\n * server. When set, the `resource_metadata` pointer is built from it (never from\n * client input) — the correct choice behind a TLS-terminating proxy. When omitted,\n * the pointer is derived from the request, but ONLY if the (forwarded) Host is a\n * safe, well-formed value; otherwise the challenge header is omitted rather than\n * emitting a broken/injectable one.\n *\n * `options.resourceMetadataPath` (optional) — the PRM path to advertise; defaults\n * to the root `/.well-known/oauth-protected-resource`. A caller mounting this on a\n * sub-resource (e.g. `/mcp`) should pass the scoped document\n * (`/.well-known/oauth-protected-resource/mcp`) so the client gets the correctly\n * scoped `resource` (RFC 8707 audience) and the resource's capabilities.\n */\nexport function createRequireAuthMiddleware(\n options: { baseUrl?: string; resourceMetadataPath?: string } = {},\n) {\n const pinnedBase = options.baseUrl?.replace(/\\/+$/, '')\n const prmPath = options.resourceMetadataPath ?? '/.well-known/oauth-protected-resource'\n\n // Origin to advertise in the PRM pointer, or undefined if it can't be trusted.\n const prmOrigin = (req: Request): string | undefined => {\n if (pinnedBase) return pinnedBase\n const fwdHost = req.headers['x-forwarded-host']\n const rawHost = (Array.isArray(fwdHost) ? fwdHost[0] : fwdHost) ?? req.get('host')\n const host = typeof rawHost === 'string' ? rawHost.trim() : undefined\n if (!host || !SAFE_HOST_RE.test(host)) return undefined // forged/broken → omit\n const fwdProto = req.headers['x-forwarded-proto']\n const proto =\n (Array.isArray(fwdProto) ? fwdProto[0] : fwdProto)?.split(',')[0]?.trim() ||\n req.protocol ||\n 'https'\n if (proto !== 'http' && proto !== 'https') return undefined\n return `${proto}://${host}`\n }\n\n return function requireAuthMiddleware(req: Request, res: Response, next: NextFunction) {\n // RFC 9728 §5.1: a 401 from a protected resource advertises where its PRM\n // lives so a client can discover the authorization server. `error` (RFC 6750\n // §3.1) is added only when a credential WAS presented but is malformed — a\n // request with no credential at all omits it (§3).\n const send401 = (error_description: string, error?: string): void => {\n const origin = prmOrigin(req)\n if (origin) {\n const params = [`resource_metadata=\"${origin}${prmPath}\"`]\n if (error) params.push(`error=\"${error}\"`)\n res.setHeader('WWW-Authenticate', `Bearer ${params.join(', ')}`)\n }\n res.status(401).json({ error: 'unauthorized', error_description })\n }\n\n const authHeader = req.headers.authorization\n if (!authHeader) {\n send401('Authorization header required')\n return\n }\n if (!authHeader.startsWith('Bearer ')) {\n send401('Bearer token required', 'invalid_request')\n return\n }\n const token = authHeader.slice(7).trim()\n if (!token) {\n send401('Bearer token cannot be empty', 'invalid_request')\n return\n }\n\n // Token present and well-formed, continue\n // Full validation happens in withPaywall() when tools are called\n next()\n }\n}\n\n/**\n * Create a middleware that logs all HTTP requests to endpoints.\n * Logs method, URL, IP address, and relevant headers.\n *\n * @param onLog - Optional logging callback. If not provided, does nothing.\n * @returns Express middleware for HTTP request logging\n *\n * @example\n * ```typescript\n * const logMiddleware = createHttpLoggingMiddleware((msg) => console.log(msg))\n * app.use(logMiddleware)\n * ```\n */\nexport function createHttpLoggingMiddleware(onLog?: (message: string) => void) {\n const log =\n onLog ||\n ((_message: string) => {\n // Intentionally empty - no logging when onLog not provided\n })\n\n return function httpLoggingMiddleware(req: Request, res: Response, next: NextFunction) {\n const method = req.method\n const url = req.originalUrl || req.url\n const ip = req.ip || req.socket.remoteAddress || 'unknown'\n const timestamp = new Date().toISOString()\n\n // Extract relevant headers\n const authHeader = req.headers.authorization\n const sessionId = req.headers['mcp-session-id']\n const protocolVersion = req.headers['mcp-protocol-version']\n const userAgent = req.headers['user-agent']\n\n // Build log message\n const logParts: string[] = [`[${timestamp}]`, `${method}`, url, `IP: ${ip}`]\n\n if (authHeader) {\n const tokenPreview = authHeader.startsWith('Bearer ')\n ? `Bearer ${authHeader.slice(7, 20)}...`\n : 'Bearer [present]'\n logParts.push(`Auth: ${tokenPreview}`)\n }\n\n if (sessionId) {\n const sessionValue = Array.isArray(sessionId) ? sessionId[0] : sessionId\n logParts.push(`Session: ${sessionValue}`)\n }\n\n if (protocolVersion) {\n const versionValue = Array.isArray(protocolVersion) ? protocolVersion[0] : protocolVersion\n logParts.push(`MCP-Version: ${versionValue}`)\n }\n\n if (userAgent) {\n const uaValue = Array.isArray(userAgent) ? userAgent[0] : userAgent\n logParts.push(`User-Agent: ${uaValue}`)\n }\n\n log(`HTTP Request: ${logParts.join(' | ')}`)\n\n next()\n }\n}\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MPP wire format, hand-written so the SDK takes no protocol dependency.
|
|
3
|
+
*
|
|
4
|
+
* The one rule that matters: the backend re-derives the challenge id as
|
|
5
|
+
* `HMAC(secret, realm|method|intent|canonicalize(request)|expires|digest|opaque)`
|
|
6
|
+
* from the fields carried inside the credential. `request` and `opaque` are
|
|
7
|
+
* therefore passed through as the exact base64url strings received — this file
|
|
8
|
+
* never re-encodes them.
|
|
9
|
+
*/
|
|
10
|
+
import type { MppChallenge, MppReceipt } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Extracts the `Payment` scheme from a header value that may carry several
|
|
13
|
+
* schemes comma-separated (RFC 9110 §11.6.1).
|
|
14
|
+
*
|
|
15
|
+
* The `Payment` scheme takes one of two shapes on our wire: a bare token68
|
|
16
|
+
* credential (`Payment <base64url>`, which cannot itself contain a comma or
|
|
17
|
+
* a quote) or a structured challenge (`Payment id="...", realm="...", ...`,
|
|
18
|
+
* comma-separated `key="value"` auth-params, where a value MAY contain a
|
|
19
|
+
* comma). Bounding the match at the next literal "Payment " occurrence — or
|
|
20
|
+
* at end-of-string otherwise — corrupts the first shape whenever a
|
|
21
|
+
* *different* trailing scheme follows: e.g. `Payment <token>, Bearer <jwt>`
|
|
22
|
+
* used to extract the whole remainder including the trailing scheme.
|
|
23
|
+
* Bounding a structured challenge with a naive, quote-unaware comma split
|
|
24
|
+
* corrupts the second shape whenever a value contains a comma: it truncates
|
|
25
|
+
* mid-quote and silently drops every param after it. Instead: a bare
|
|
26
|
+
* token68 is bounded by its first top-level comma; a structured challenge is
|
|
27
|
+
* bounded by {@link findStructuredChallengeEnd}'s quote-aware scan.
|
|
28
|
+
*/
|
|
29
|
+
export declare function extractPaymentScheme(headerValue: string): string | null;
|
|
30
|
+
/**
|
|
31
|
+
* The stable identity of a credential: the challenge id it carries.
|
|
32
|
+
*
|
|
33
|
+
* The header bytes are NOT that identity. `PAYMENT_SCHEME_BOUNDARY` is
|
|
34
|
+
* case-insensitive and {@link extractPaymentScheme} returns the matched slice
|
|
35
|
+
* verbatim, so `Payment x`, `payment x` and `Payment x` are three different
|
|
36
|
+
* strings for one credential — and the body is base64url of JSON the BUYER
|
|
37
|
+
* assembles, so re-ordering its keys yields more. The backend collapses every
|
|
38
|
+
* one of them onto a single burn, because its own idempotency key is the
|
|
39
|
+
* decoded challenge id. Anything at the seller edge enforcing single-use has
|
|
40
|
+
* to key on the same thing, or a buyer flips one byte of case and buys the
|
|
41
|
+
* response again.
|
|
42
|
+
*
|
|
43
|
+
* @returns the `challenge.id` a token68 credential decodes to, or `null` when
|
|
44
|
+
* the credential cannot be decoded into one — a structured `key="value"`
|
|
45
|
+
* scheme (that shape is a challenge, never a credential), undecodable
|
|
46
|
+
* base64url JSON, or JSON without a non-empty string `challenge.id`. The
|
|
47
|
+
* backend rejects all of those anyway; returning `null` lets the caller refuse
|
|
48
|
+
* them without a round-trip rather than fall back to a key it cannot trust.
|
|
49
|
+
*/
|
|
50
|
+
export declare function extractCredentialChallengeId(credential: string): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Parses a `WWW-Authenticate` header into a challenge.
|
|
53
|
+
*
|
|
54
|
+
* @returns `null` when there is no usable `Payment` challenge to parse: the
|
|
55
|
+
* header carries no `Payment` scheme at all, OR the scheme is present but
|
|
56
|
+
* missing one of the structural auth-params (`id`/`realm`/`method`/`intent`/
|
|
57
|
+
* `request`) needed to even attempt decoding it. Both cases mean the same
|
|
58
|
+
* thing to a caller deciding whether to retry: there is nothing here to pay.
|
|
59
|
+
*
|
|
60
|
+
* A `Payment` scheme that has all of those structural params present, but
|
|
61
|
+
* whose `request` value fails to decode as JSON or decodes to something
|
|
62
|
+
* that is not a usable `{ planId, credits }` object, raises a typed
|
|
63
|
+
* {@link MppError} instead — that is a seller who tried to speak MPP and
|
|
64
|
+
* sent something broken, a distinct failure from "there is no challenge
|
|
65
|
+
* here to parse".
|
|
66
|
+
*/
|
|
67
|
+
export declare function parseChallengeHeader(headerValue: string): MppChallenge | null;
|
|
68
|
+
/**
|
|
69
|
+
* Builds the `Authorization: Payment …` value for a challenge.
|
|
70
|
+
*
|
|
71
|
+
* Key order in the JSON is irrelevant — the server parses it — but the two
|
|
72
|
+
* base64url strings are emitted untouched, which is what keeps the HMAC valid.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildCredentialHeader(challenge: MppChallenge, payload: {
|
|
75
|
+
accessToken: string;
|
|
76
|
+
}): string;
|
|
77
|
+
/**
|
|
78
|
+
* Decodes a `Payment-Receipt` header value.
|
|
79
|
+
*
|
|
80
|
+
* Raises a typed {@link MppError} on malformed input — undecodable base64url
|
|
81
|
+
* JSON, or JSON that decodes to something that is not a usable
|
|
82
|
+
* `{ method, reference, status, timestamp }` object — rather than a raw
|
|
83
|
+
* `SyntaxError` or an untyped receipt. This function does not decide whether
|
|
84
|
+
* that failure is fatal for its caller (the receipt is "unsigned by design,
|
|
85
|
+
* and carries no balance", so a caller may reasonably treat a decode
|
|
86
|
+
* failure as non-fatal and simply omit the receipt); it only guarantees the
|
|
87
|
+
* failure is typed and raised at the boundary, not as a later `TypeError`
|
|
88
|
+
* on a field access with nothing pointing back at the header that caused it
|
|
89
|
+
* — the same asymmetry {@link parseChallengeHeader} closes for `request=`.
|
|
90
|
+
*/
|
|
91
|
+
export declare function parseReceiptHeader(headerValue: string): MppReceipt;
|
|
92
|
+
//# sourceMappingURL=codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../../src/mpp/codec.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAuB,UAAU,EAAE,MAAM,YAAY,CAAA;AAgF/E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBvE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiB9E;AAiJD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA4B7E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAC/B,MAAM,CAgBR;AAmBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,CASlE"}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MPP wire format, hand-written so the SDK takes no protocol dependency.
|
|
3
|
+
*
|
|
4
|
+
* The one rule that matters: the backend re-derives the challenge id as
|
|
5
|
+
* `HMAC(secret, realm|method|intent|canonicalize(request)|expires|digest|opaque)`
|
|
6
|
+
* from the fields carried inside the credential. `request` and `opaque` are
|
|
7
|
+
* therefore passed through as the exact base64url strings received — this file
|
|
8
|
+
* never re-encodes them.
|
|
9
|
+
*/
|
|
10
|
+
import { MppError } from './errors.js';
|
|
11
|
+
/** A bare token (RFC 9110 §5.6.2, used for auth-scheme names and auth-param keys). */
|
|
12
|
+
const TOKEN = "[!#$%&'*+\\-.^_`|~0-9A-Za-z]+";
|
|
13
|
+
/** Matches the start of a `key=` auth-param, i.e. a Payment challenge continuing. */
|
|
14
|
+
const AUTH_PARAM_START = new RegExp(`^${TOKEN}\\s*=`);
|
|
15
|
+
/**
|
|
16
|
+
* Finds where a structured challenge's auth-param list ends within `rest`
|
|
17
|
+
* (the content right after "Payment "), scanning quote- and escape-aware so
|
|
18
|
+
* neither a comma nor a `\"` inside a `"…"` value is mistaken for a
|
|
19
|
+
* boundary. mppx serializes quoted values with
|
|
20
|
+
* `value.replace(/\\/g,'\\\\').replace(/"/g,'\\"')` (`Challenge.ts:316-319`),
|
|
21
|
+
* so a seller-supplied `description` like `5" screen replacement plan` wire-
|
|
22
|
+
* encodes as `description="5\" screen replacement plan"`. A scanner that
|
|
23
|
+
* toggles quote state on every literal `"` — escaped or not — desyncs on
|
|
24
|
+
* that `\"`, stays stuck "inside" a quote for the rest of the header, and
|
|
25
|
+
* swallows a genuine trailing scheme. This mirrors mppx's own reference
|
|
26
|
+
* parser's `escaped` flag (`Challenge.ts:362-379`) rather than inventing new
|
|
27
|
+
* semantics.
|
|
28
|
+
*
|
|
29
|
+
* A top-level (non-quoted, non-escaped) comma only ends the scheme if what
|
|
30
|
+
* follows it does NOT itself look like a continuing `key=value` auth-param —
|
|
31
|
+
* which is also how a following literal "Payment " (the merged-challenge
|
|
32
|
+
* case) is recognized as a new scheme: "Payment" followed by whitespace
|
|
33
|
+
* never matches `AUTH_PARAM_START`, since that requires "=" immediately
|
|
34
|
+
* (modulo optional whitespace) after the leading token.
|
|
35
|
+
*
|
|
36
|
+
* An unterminated quote is not an error here: the loop simply runs out of
|
|
37
|
+
* input without finding a boundary and falls back to treating the whole
|
|
38
|
+
* remainder as this scheme — a safe, total (never-throwing) O(n) fallback.
|
|
39
|
+
*/
|
|
40
|
+
function findStructuredChallengeEnd(rest) {
|
|
41
|
+
let inQuotes = false;
|
|
42
|
+
let escaped = false;
|
|
43
|
+
for (let i = 0; i < rest.length; i++) {
|
|
44
|
+
const ch = rest[i];
|
|
45
|
+
if (inQuotes) {
|
|
46
|
+
if (escaped) {
|
|
47
|
+
escaped = false;
|
|
48
|
+
}
|
|
49
|
+
else if (ch === '\\') {
|
|
50
|
+
escaped = true;
|
|
51
|
+
}
|
|
52
|
+
else if (ch === '"') {
|
|
53
|
+
inQuotes = false;
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === '"') {
|
|
58
|
+
inQuotes = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (ch === ',' && !AUTH_PARAM_START.test(rest.slice(i + 1).trimStart())) {
|
|
62
|
+
return i;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return rest.length;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Matches the `Payment` scheme name at a genuine scheme boundary: start of
|
|
69
|
+
* the header value, or right after a top-level comma (RFC 9110 §11.6.1
|
|
70
|
+
* separates comma-separated challenges/credentials that way). Unanchored,
|
|
71
|
+
* `/Payment\s+/i` matches mid-token — `XPayment abc`, `NotPayment abc`,
|
|
72
|
+
* `Bearer prepayment xyz` — and also matches "Payment" text embedded inside
|
|
73
|
+
* a *different*, preceding scheme's quoted value (e.g.
|
|
74
|
+
* `Digest username="my payment plan", …`), since plain whitespace alone is
|
|
75
|
+
* not a scheme boundary. Anchoring on comma-or-start (not bare whitespace)
|
|
76
|
+
* closes both: a scheme name is never preceded by an arbitrary space, only
|
|
77
|
+
* by the start of the header or the comma that separates it from what came
|
|
78
|
+
* before.
|
|
79
|
+
*
|
|
80
|
+
* This decides which protocol handles a request:
|
|
81
|
+
* `extractCredential` → `extractPaymentScheme` feeds the MPP-vs-x402 routing
|
|
82
|
+
* predicate in `middleware.ts`, so an unanchored match would divert an
|
|
83
|
+
* x402 buyer carrying a perfectly valid `payment-signature` token, plus an
|
|
84
|
+
* unrelated `Authorization` header that happens to contain "payment" text,
|
|
85
|
+
* onto the MPP path and challenge them instead of serving the request.
|
|
86
|
+
*/
|
|
87
|
+
const PAYMENT_SCHEME_BOUNDARY = /(?:^|,)\s*(Payment\s+)/i;
|
|
88
|
+
/**
|
|
89
|
+
* Extracts the `Payment` scheme from a header value that may carry several
|
|
90
|
+
* schemes comma-separated (RFC 9110 §11.6.1).
|
|
91
|
+
*
|
|
92
|
+
* The `Payment` scheme takes one of two shapes on our wire: a bare token68
|
|
93
|
+
* credential (`Payment <base64url>`, which cannot itself contain a comma or
|
|
94
|
+
* a quote) or a structured challenge (`Payment id="...", realm="...", ...`,
|
|
95
|
+
* comma-separated `key="value"` auth-params, where a value MAY contain a
|
|
96
|
+
* comma). Bounding the match at the next literal "Payment " occurrence — or
|
|
97
|
+
* at end-of-string otherwise — corrupts the first shape whenever a
|
|
98
|
+
* *different* trailing scheme follows: e.g. `Payment <token>, Bearer <jwt>`
|
|
99
|
+
* used to extract the whole remainder including the trailing scheme.
|
|
100
|
+
* Bounding a structured challenge with a naive, quote-unaware comma split
|
|
101
|
+
* corrupts the second shape whenever a value contains a comma: it truncates
|
|
102
|
+
* mid-quote and silently drops every param after it. Instead: a bare
|
|
103
|
+
* token68 is bounded by its first top-level comma; a structured challenge is
|
|
104
|
+
* bounded by {@link findStructuredChallengeEnd}'s quote-aware scan.
|
|
105
|
+
*/
|
|
106
|
+
export function extractPaymentScheme(headerValue) {
|
|
107
|
+
const schemeMatch = PAYMENT_SCHEME_BOUNDARY.exec(headerValue);
|
|
108
|
+
if (!schemeMatch || schemeMatch.index === undefined)
|
|
109
|
+
return null;
|
|
110
|
+
// Group 1 ("Payment\s+") is the tail of the whole match, so the full
|
|
111
|
+
// match's end position is also where the captured keyword ends; its start
|
|
112
|
+
// is offset back by the keyword's own length, which skips the leading
|
|
113
|
+
// comma/whitespace the boundary alternation consumed.
|
|
114
|
+
const keyword = schemeMatch[1];
|
|
115
|
+
const contentStart = schemeMatch.index + schemeMatch[0].length;
|
|
116
|
+
const start = contentStart - keyword.length;
|
|
117
|
+
const rest = headerValue.slice(contentStart);
|
|
118
|
+
if (!AUTH_PARAM_START.test(rest)) {
|
|
119
|
+
// Bare token68 credential: bounded by its first top-level comma, since a
|
|
120
|
+
// token68 cannot itself contain one (or a quote, so no quote-tracking
|
|
121
|
+
// is needed here).
|
|
122
|
+
const commaIndex = rest.indexOf(',');
|
|
123
|
+
const end = commaIndex === -1 ? headerValue.length : contentStart + commaIndex;
|
|
124
|
+
return headerValue.slice(start, end).trim();
|
|
125
|
+
}
|
|
126
|
+
const end = contentStart + findStructuredChallengeEnd(rest);
|
|
127
|
+
return headerValue.slice(start, end).replace(/,\s*$/, '').trim();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The stable identity of a credential: the challenge id it carries.
|
|
131
|
+
*
|
|
132
|
+
* The header bytes are NOT that identity. `PAYMENT_SCHEME_BOUNDARY` is
|
|
133
|
+
* case-insensitive and {@link extractPaymentScheme} returns the matched slice
|
|
134
|
+
* verbatim, so `Payment x`, `payment x` and `Payment x` are three different
|
|
135
|
+
* strings for one credential — and the body is base64url of JSON the BUYER
|
|
136
|
+
* assembles, so re-ordering its keys yields more. The backend collapses every
|
|
137
|
+
* one of them onto a single burn, because its own idempotency key is the
|
|
138
|
+
* decoded challenge id. Anything at the seller edge enforcing single-use has
|
|
139
|
+
* to key on the same thing, or a buyer flips one byte of case and buys the
|
|
140
|
+
* response again.
|
|
141
|
+
*
|
|
142
|
+
* @returns the `challenge.id` a token68 credential decodes to, or `null` when
|
|
143
|
+
* the credential cannot be decoded into one — a structured `key="value"`
|
|
144
|
+
* scheme (that shape is a challenge, never a credential), undecodable
|
|
145
|
+
* base64url JSON, or JSON without a non-empty string `challenge.id`. The
|
|
146
|
+
* backend rejects all of those anyway; returning `null` lets the caller refuse
|
|
147
|
+
* them without a round-trip rather than fall back to a key it cannot trust.
|
|
148
|
+
*/
|
|
149
|
+
export function extractCredentialChallengeId(credential) {
|
|
150
|
+
const token68 = credential.replace(/^Payment\s+/i, '').trim();
|
|
151
|
+
if (!token68 || AUTH_PARAM_START.test(token68))
|
|
152
|
+
return null;
|
|
153
|
+
let decoded;
|
|
154
|
+
try {
|
|
155
|
+
decoded = JSON.parse(Buffer.from(token68, 'base64url').toString('utf8'));
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded))
|
|
161
|
+
return null;
|
|
162
|
+
const { challenge } = decoded;
|
|
163
|
+
if (typeof challenge !== 'object' || challenge === null || Array.isArray(challenge))
|
|
164
|
+
return null;
|
|
165
|
+
const { id } = challenge;
|
|
166
|
+
return typeof id === 'string' && id !== '' ? id : null;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Reads a quoted-string value starting right after its opening `"`,
|
|
170
|
+
* unescaping `\"` to a literal `"` and `\\` to a literal `\` (mirrors
|
|
171
|
+
* mppx's `readQuotedAuthParamValue`, `Challenge.ts:461-465`).
|
|
172
|
+
*
|
|
173
|
+
* An unterminated quote is not an error here (unlike mppx's reference
|
|
174
|
+
* parser, which throws): the loop runs out of input and returns whatever was
|
|
175
|
+
* accumulated, matching {@link findStructuredChallengeEnd}'s equally
|
|
176
|
+
* permissive fallback for the same input shape.
|
|
177
|
+
*/
|
|
178
|
+
function readQuotedValue(input, start) {
|
|
179
|
+
let i = start;
|
|
180
|
+
let value = '';
|
|
181
|
+
let escaped = false;
|
|
182
|
+
while (i < input.length) {
|
|
183
|
+
const ch = input[i];
|
|
184
|
+
i++;
|
|
185
|
+
if (escaped) {
|
|
186
|
+
value += ch;
|
|
187
|
+
escaped = false;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (ch === '\\') {
|
|
191
|
+
escaped = true;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (ch === '"')
|
|
195
|
+
return [value, i];
|
|
196
|
+
value += ch;
|
|
197
|
+
}
|
|
198
|
+
return [value, i];
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Splits `key="value", key2="value2"` into a map. Quoted values are read
|
|
202
|
+
* escape-aware and unescaped ({@link readQuotedValue}), so a value may
|
|
203
|
+
* itself contain a comma, a quote (`\"`) or a backslash (`\\`) without
|
|
204
|
+
* corrupting the param that follows it. Unquoted values are tolerated too.
|
|
205
|
+
*/
|
|
206
|
+
function parseAuthParams(input) {
|
|
207
|
+
const params = {};
|
|
208
|
+
let i = 0;
|
|
209
|
+
while (i < input.length) {
|
|
210
|
+
while (i < input.length && /[\s,]/.test(input[i]))
|
|
211
|
+
i++;
|
|
212
|
+
if (i >= input.length)
|
|
213
|
+
break;
|
|
214
|
+
const keyStart = i;
|
|
215
|
+
while (i < input.length && /[a-zA-Z0-9_-]/.test(input[i]))
|
|
216
|
+
i++;
|
|
217
|
+
const key = input.slice(keyStart, i);
|
|
218
|
+
if (!key)
|
|
219
|
+
break;
|
|
220
|
+
while (i < input.length && /\s/.test(input[i]))
|
|
221
|
+
i++;
|
|
222
|
+
if (input[i] !== '=')
|
|
223
|
+
break;
|
|
224
|
+
i++;
|
|
225
|
+
while (i < input.length && /\s/.test(input[i]))
|
|
226
|
+
i++;
|
|
227
|
+
let value;
|
|
228
|
+
if (input[i] === '"') {
|
|
229
|
+
const [decoded, nextIndex] = readQuotedValue(input, i + 1);
|
|
230
|
+
value = decoded;
|
|
231
|
+
i = nextIndex;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const valueStart = i;
|
|
235
|
+
while (i < input.length && input[i] !== ',')
|
|
236
|
+
i++;
|
|
237
|
+
value = input.slice(valueStart, i).trim();
|
|
238
|
+
}
|
|
239
|
+
params[key] = value;
|
|
240
|
+
}
|
|
241
|
+
return params;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Decodes a base64url string as JSON, raising a typed {@link MppError}
|
|
245
|
+
* rather than a raw `SyntaxError` on malformed input.
|
|
246
|
+
*
|
|
247
|
+
* `Buffer.from(x, 'base64url')` never throws — it silently drops invalid
|
|
248
|
+
* characters — so garbage would otherwise reach `JSON.parse` and escape as a
|
|
249
|
+
* bare `SyntaxError` that names neither MPP nor payment, invisible to a
|
|
250
|
+
* caller writing `catch (e) { if (e instanceof MppError) … }` exactly as our
|
|
251
|
+
* own docs tell them to.
|
|
252
|
+
*/
|
|
253
|
+
function decodeBase64UrlJson(encoded, context) {
|
|
254
|
+
try {
|
|
255
|
+
return JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8'));
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
throw new MppError(`Could not decode the ${context}: ${error instanceof Error ? error.message : String(error)}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Validates a decoded `request` param's shape before it is trusted.
|
|
263
|
+
*
|
|
264
|
+
* The raw decode only guarantees valid JSON, not the right shape: a remote
|
|
265
|
+
* challenge's `request=` can decode to `null`, an array, or `{}`, all of
|
|
266
|
+
* which would otherwise sail through a bare type cast and reach
|
|
267
|
+
* `payments.mpp.fetch` with an unusable or `undefined` `planId`.
|
|
268
|
+
*
|
|
269
|
+
* `planId` is the field with the documented failure mode (an unusable or
|
|
270
|
+
* `undefined` value reaching the mint) and is checked strictly: a non-empty
|
|
271
|
+
* string, full stop. `agentId` is unchecked structurally but IS load-bearing
|
|
272
|
+
* once present — the buyer helper forwards it straight into the token mint
|
|
273
|
+
* (`options.agentId ?? challenge.request.agentId`) — so a wrong-typed value
|
|
274
|
+
* is rejected here rather than reaching that spend path. `credits`, by
|
|
275
|
+
* contrast, is not what anything spends: the amount the backend re-derives
|
|
276
|
+
* comes from `requestEncoded`, forwarded byte-verbatim, so rejecting a
|
|
277
|
+
* perfectly reasonable JSON-number encoding of "credits" would make a valid
|
|
278
|
+
* third-party seller wholly unpayable over a field this SDK does not itself
|
|
279
|
+
* act on — it is coerced by {@link toChallengeRequest} instead.
|
|
280
|
+
*/
|
|
281
|
+
function isValidChallengeRequestShape(value) {
|
|
282
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
283
|
+
return false;
|
|
284
|
+
const { planId, credits, agentId } = value;
|
|
285
|
+
if (typeof planId !== 'string' || planId === '')
|
|
286
|
+
return false;
|
|
287
|
+
if (typeof credits !== 'string' && typeof credits !== 'number')
|
|
288
|
+
return false;
|
|
289
|
+
if (agentId !== undefined && typeof agentId !== 'string')
|
|
290
|
+
return false;
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
/** Normalizes a validated decoded request into {@link MppChallengeRequest}'s
|
|
294
|
+
* declared shape, coercing a numeric `credits` to the decimal string the
|
|
295
|
+
* type promises. */
|
|
296
|
+
function toChallengeRequest(shape) {
|
|
297
|
+
return {
|
|
298
|
+
planId: shape.planId,
|
|
299
|
+
credits: String(shape.credits),
|
|
300
|
+
...(shape.agentId !== undefined && { agentId: shape.agentId }),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Parses a `WWW-Authenticate` header into a challenge.
|
|
305
|
+
*
|
|
306
|
+
* @returns `null` when there is no usable `Payment` challenge to parse: the
|
|
307
|
+
* header carries no `Payment` scheme at all, OR the scheme is present but
|
|
308
|
+
* missing one of the structural auth-params (`id`/`realm`/`method`/`intent`/
|
|
309
|
+
* `request`) needed to even attempt decoding it. Both cases mean the same
|
|
310
|
+
* thing to a caller deciding whether to retry: there is nothing here to pay.
|
|
311
|
+
*
|
|
312
|
+
* A `Payment` scheme that has all of those structural params present, but
|
|
313
|
+
* whose `request` value fails to decode as JSON or decodes to something
|
|
314
|
+
* that is not a usable `{ planId, credits }` object, raises a typed
|
|
315
|
+
* {@link MppError} instead — that is a seller who tried to speak MPP and
|
|
316
|
+
* sent something broken, a distinct failure from "there is no challenge
|
|
317
|
+
* here to parse".
|
|
318
|
+
*/
|
|
319
|
+
export function parseChallengeHeader(headerValue) {
|
|
320
|
+
const scheme = extractPaymentScheme(headerValue);
|
|
321
|
+
if (!scheme)
|
|
322
|
+
return null;
|
|
323
|
+
const params = parseAuthParams(scheme.replace(/^Payment\s+/i, ''));
|
|
324
|
+
const { id, realm, method, intent, request, expires, digest, opaque, description } = params;
|
|
325
|
+
if (!id || !realm || !method || !intent || !request)
|
|
326
|
+
return null;
|
|
327
|
+
const decodedRequest = decodeBase64UrlJson(request, 'MPP challenge request parameter');
|
|
328
|
+
if (!isValidChallengeRequestShape(decodedRequest)) {
|
|
329
|
+
throw new MppError('The MPP challenge names a request parameter that is not a valid ' +
|
|
330
|
+
'{ planId: string, credits: string | number, agentId?: string } object.');
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
id,
|
|
334
|
+
realm,
|
|
335
|
+
method,
|
|
336
|
+
intent,
|
|
337
|
+
request: toChallengeRequest(decodedRequest),
|
|
338
|
+
requestEncoded: request,
|
|
339
|
+
...(expires && { expires }),
|
|
340
|
+
...(digest && { digest }),
|
|
341
|
+
...(opaque && { opaque }),
|
|
342
|
+
...(description && { description }),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Builds the `Authorization: Payment …` value for a challenge.
|
|
347
|
+
*
|
|
348
|
+
* Key order in the JSON is irrelevant — the server parses it — but the two
|
|
349
|
+
* base64url strings are emitted untouched, which is what keeps the HMAC valid.
|
|
350
|
+
*/
|
|
351
|
+
export function buildCredentialHeader(challenge, payload) {
|
|
352
|
+
const wire = {
|
|
353
|
+
challenge: {
|
|
354
|
+
id: challenge.id,
|
|
355
|
+
realm: challenge.realm,
|
|
356
|
+
method: challenge.method,
|
|
357
|
+
intent: challenge.intent,
|
|
358
|
+
...(challenge.expires && { expires: challenge.expires }),
|
|
359
|
+
...(challenge.digest && { digest: challenge.digest }),
|
|
360
|
+
...(challenge.description && { description: challenge.description }),
|
|
361
|
+
...(challenge.opaque && { opaque: challenge.opaque }),
|
|
362
|
+
request: challenge.requestEncoded,
|
|
363
|
+
},
|
|
364
|
+
payload,
|
|
365
|
+
};
|
|
366
|
+
return `Payment ${Buffer.from(JSON.stringify(wire), 'utf8').toString('base64url')}`;
|
|
367
|
+
}
|
|
368
|
+
/** Validates a decoded `Payment-Receipt` body against {@link MppReceipt}'s
|
|
369
|
+
* shape — mirrors {@link isValidChallengeRequestShape}'s strictness for the
|
|
370
|
+
* same reason: without it, `null`, an array, or `{}` all sail through a
|
|
371
|
+
* bare type cast and surface later as an untyped `TypeError` on a field
|
|
372
|
+
* access, with nothing pointing back at the header that caused it. */
|
|
373
|
+
function isValidReceipt(value) {
|
|
374
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
375
|
+
return false;
|
|
376
|
+
const { method, reference, status, timestamp } = value;
|
|
377
|
+
return (typeof method === 'string' &&
|
|
378
|
+
typeof reference === 'string' &&
|
|
379
|
+
reference !== '' &&
|
|
380
|
+
typeof status === 'string' &&
|
|
381
|
+
typeof timestamp === 'string');
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Decodes a `Payment-Receipt` header value.
|
|
385
|
+
*
|
|
386
|
+
* Raises a typed {@link MppError} on malformed input — undecodable base64url
|
|
387
|
+
* JSON, or JSON that decodes to something that is not a usable
|
|
388
|
+
* `{ method, reference, status, timestamp }` object — rather than a raw
|
|
389
|
+
* `SyntaxError` or an untyped receipt. This function does not decide whether
|
|
390
|
+
* that failure is fatal for its caller (the receipt is "unsigned by design,
|
|
391
|
+
* and carries no balance", so a caller may reasonably treat a decode
|
|
392
|
+
* failure as non-fatal and simply omit the receipt); it only guarantees the
|
|
393
|
+
* failure is typed and raised at the boundary, not as a later `TypeError`
|
|
394
|
+
* on a field access with nothing pointing back at the header that caused it
|
|
395
|
+
* — the same asymmetry {@link parseChallengeHeader} closes for `request=`.
|
|
396
|
+
*/
|
|
397
|
+
export function parseReceiptHeader(headerValue) {
|
|
398
|
+
const decoded = decodeBase64UrlJson(headerValue.trim(), 'MPP receipt');
|
|
399
|
+
if (!isValidReceipt(decoded)) {
|
|
400
|
+
throw new MppError('The Payment-Receipt header is not a valid ' +
|
|
401
|
+
'{ method: string, reference: string, status: string, timestamp: string } object.');
|
|
402
|
+
}
|
|
403
|
+
return decoded;
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=codec.js.map
|