@nevermined-io/payments 1.9.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.
Files changed (98) hide show
  1. package/dist/api/base-payments.d.ts +11 -0
  2. package/dist/api/base-payments.d.ts.map +1 -1
  3. package/dist/api/base-payments.js +28 -3
  4. package/dist/api/base-payments.js.map +1 -1
  5. package/dist/api/nvm-api.d.ts +4 -0
  6. package/dist/api/nvm-api.d.ts.map +1 -1
  7. package/dist/api/nvm-api.js +7 -0
  8. package/dist/api/nvm-api.js.map +1 -1
  9. package/dist/api/organizations-api/organizations-api.d.ts +19 -1
  10. package/dist/api/organizations-api/organizations-api.d.ts.map +1 -1
  11. package/dist/api/organizations-api/organizations-api.js +49 -0
  12. package/dist/api/organizations-api/organizations-api.js.map +1 -1
  13. package/dist/api/organizations-api/types.d.ts +27 -0
  14. package/dist/api/organizations-api/types.d.ts.map +1 -1
  15. package/dist/api/organizations-api/types.js.map +1 -1
  16. package/dist/common/api-version.d.ts +1 -1
  17. package/dist/common/api-version.js +1 -1
  18. package/dist/common/api-version.js.map +1 -1
  19. package/dist/common/types.d.ts +9 -4
  20. package/dist/common/types.d.ts.map +1 -1
  21. package/dist/common/types.js.map +1 -1
  22. package/dist/environments.d.ts +12 -0
  23. package/dist/environments.d.ts.map +1 -1
  24. package/dist/environments.js +31 -0
  25. package/dist/environments.js.map +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/mcp/core/server-manager.d.ts.map +1 -1
  31. package/dist/mcp/core/server-manager.js +3 -0
  32. package/dist/mcp/core/server-manager.js.map +1 -1
  33. package/dist/mcp/http/mcp-handler.d.ts +7 -0
  34. package/dist/mcp/http/mcp-handler.d.ts.map +1 -1
  35. package/dist/mcp/http/mcp-handler.js +8 -1
  36. package/dist/mcp/http/mcp-handler.js.map +1 -1
  37. package/dist/mcp/http/oauth-metadata.d.ts.map +1 -1
  38. package/dist/mcp/http/oauth-metadata.js +31 -6
  39. package/dist/mcp/http/oauth-metadata.js.map +1 -1
  40. package/dist/mcp/http/oauth-router.d.ts +17 -16
  41. package/dist/mcp/http/oauth-router.d.ts.map +1 -1
  42. package/dist/mcp/http/oauth-router.js +56 -28
  43. package/dist/mcp/http/oauth-router.js.map +1 -1
  44. package/dist/mpp/codec.d.ts +92 -0
  45. package/dist/mpp/codec.d.ts.map +1 -0
  46. package/dist/mpp/codec.js +405 -0
  47. package/dist/mpp/codec.js.map +1 -0
  48. package/dist/mpp/errors.d.ts +165 -0
  49. package/dist/mpp/errors.d.ts.map +1 -0
  50. package/dist/mpp/errors.js +177 -0
  51. package/dist/mpp/errors.js.map +1 -0
  52. package/dist/mpp/fetch.d.ts +196 -0
  53. package/dist/mpp/fetch.d.ts.map +1 -0
  54. package/dist/mpp/fetch.js +493 -0
  55. package/dist/mpp/fetch.js.map +1 -0
  56. package/dist/mpp/index.d.ts +11 -0
  57. package/dist/mpp/index.d.ts.map +1 -0
  58. package/dist/mpp/index.js +20 -0
  59. package/dist/mpp/index.js.map +1 -0
  60. package/dist/mpp/mpp-api.d.ts +153 -0
  61. package/dist/mpp/mpp-api.d.ts.map +1 -0
  62. package/dist/mpp/mpp-api.js +298 -0
  63. package/dist/mpp/mpp-api.js.map +1 -0
  64. package/dist/mpp/types.d.ts +43 -0
  65. package/dist/mpp/types.d.ts.map +1 -0
  66. package/dist/mpp/types.js +9 -0
  67. package/dist/mpp/types.js.map +1 -0
  68. package/dist/payments.d.ts +13 -0
  69. package/dist/payments.d.ts.map +1 -1
  70. package/dist/payments.js +3 -0
  71. package/dist/payments.js.map +1 -1
  72. package/dist/x402/delegation-api.d.ts +3 -1
  73. package/dist/x402/delegation-api.d.ts.map +1 -1
  74. package/dist/x402/delegation-api.js.map +1 -1
  75. package/dist/x402/express/index.d.ts +2 -0
  76. package/dist/x402/express/index.d.ts.map +1 -1
  77. package/dist/x402/express/index.js +2 -0
  78. package/dist/x402/express/index.js.map +1 -1
  79. package/dist/x402/express/middleware.d.ts +33 -1
  80. package/dist/x402/express/middleware.d.ts.map +1 -1
  81. package/dist/x402/express/middleware.js +831 -1
  82. package/dist/x402/express/middleware.js.map +1 -1
  83. package/dist/x402/express/mpp-support.d.ts +58 -0
  84. package/dist/x402/express/mpp-support.d.ts.map +1 -0
  85. package/dist/x402/express/mpp-support.js +67 -0
  86. package/dist/x402/express/mpp-support.js.map +1 -0
  87. package/dist/x402/express/raw-body.d.ts +24 -0
  88. package/dist/x402/express/raw-body.d.ts.map +1 -0
  89. package/dist/x402/express/raw-body.js +34 -0
  90. package/dist/x402/express/raw-body.js.map +1 -0
  91. package/dist/x402/token-request.d.ts +15 -0
  92. package/dist/x402/token-request.d.ts.map +1 -0
  93. package/dist/x402/token-request.js +66 -0
  94. package/dist/x402/token-request.js.map +1 -0
  95. package/dist/x402/token.d.ts.map +1 -1
  96. package/dist/x402/token.js +7 -51
  97. package/dist/x402/token.js.map +1 -1
  98. package/package.json +3 -1
@@ -77,16 +77,41 @@ export function getOAuthUrls(environment, overrides) {
77
77
  */
78
78
  export function buildProtectedResourceMetadata(config) {
79
79
  const scopes = config.scopes || [...DEFAULT_SCOPES];
80
- // oauthUrls calculated but not used in this metadata (kept for future use)
81
- void getOAuthUrls(config.environment, config.oauthUrls);
82
80
  return {
83
81
  resource: config.baseUrl,
84
- authorization_servers: [config.baseUrl],
82
+ authorization_servers: [resolveAuthorizationServer(config)],
85
83
  scopes_supported: scopes,
86
84
  bearer_methods_supported: ['header'],
87
85
  resource_documentation: `${config.baseUrl}/`,
88
86
  };
89
87
  }
88
+ /**
89
+ * Resolve the authorization-server identifier for `authorization_servers`.
90
+ *
91
+ * RFC 9728 §2 requires each entry to be an AS issuer identifier — a URL from
92
+ * which `/.well-known/oauth-authorization-server` (RFC 8414) actually resolves.
93
+ * For Nevermined that is the **backend** API origin (which serves the AS
94
+ * metadata and whose own published `issuer` is the backend URL), NOT:
95
+ * - `config.baseUrl` — that is this MCP server, the protected *resource*; and
96
+ * - `OAuthUrls.issuer` — that is the *frontend* (a client-routed SPA that 200s
97
+ * on every path, so an RFC 8414 fetch there silently returns HTML).
98
+ * The backend origin is derived from `tokenUri` (`${backend}/oauth/token`), which
99
+ * respects any `config.oauthUrls.tokenUri` override.
100
+ */
101
+ function resolveAuthorizationServer(config) {
102
+ const { tokenUri } = getOAuthUrls(config.environment, config.oauthUrls);
103
+ const envTokenUri = getOAuthUrls(config.environment).tokenUri;
104
+ // Guard both a falsy `{ tokenUri: undefined }` override (slips through the
105
+ // `{...baseUrls, ...overrides}` spread — exactOptionalPropertyTypes is off) AND
106
+ // a malformed non-falsy one — fall back to the environment default rather than
107
+ // crash the metadata endpoint with `new URL(...)`.
108
+ try {
109
+ return new URL(tokenUri || envTokenUri).origin;
110
+ }
111
+ catch {
112
+ return new URL(envTokenUri).origin;
113
+ }
114
+ }
90
115
  /**
91
116
  * Build MCP-specific Protected Resource Metadata.
92
117
  * Extends the base metadata with MCP capabilities information.
@@ -106,11 +131,11 @@ export function buildProtectedResourceMetadata(config) {
106
131
  */
107
132
  export function buildMcpProtectedResourceMetadata(config) {
108
133
  const scopes = config.scopes || [...DEFAULT_SCOPES];
109
- // oauthUrls calculated but not used in this metadata (kept for future use)
110
- void getOAuthUrls(config.environment, config.oauthUrls);
111
134
  return {
112
135
  resource: `${config.baseUrl}/mcp`,
113
- authorization_servers: [config.baseUrl],
136
+ // See resolveAuthorizationServer: the AS is the backend API origin, not this
137
+ // MCP server (baseUrl) nor the frontend SPA (OAuthUrls.issuer).
138
+ authorization_servers: [resolveAuthorizationServer(config)],
114
139
  scopes_supported: scopes,
115
140
  scopes_required: scopes,
116
141
  bearer_methods_supported: ['header'],
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-metadata.js","sourceRoot":"","sources":["../../../src/mcp/http/oauth-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure functions to generate OAuth 2.1 metadata responses.\n * These generators produce the JSON payloads for OAuth discovery endpoints\n * without any framework dependencies, making them reusable across different HTTP servers.\n */\nimport { Environments, type EnvironmentName } from '../../environments.js'\nimport type {\n OAuthUrls,\n OAuthConfig,\n ProtectedResourceMetadata,\n McpProtectedResourceMetadata,\n AuthorizationServerMetadata,\n OidcConfiguration,\n} from '../types/http.types.js'\n\n/**\n * Build OAuth URLs from frontend and backend URLs.\n * - issuer and authorizationUri use the frontend (user-facing)\n * - tokenUri, jwksUri, userinfoUri use the backend (API)\n *\n * @param frontendUrl - The frontend URL (e.g., https://nevermined.app)\n * @param backendUrl - The backend URL (e.g., https://api.sandbox.nevermined.app)\n * @returns OAuth URLs configuration\n */\nfunction buildOAuthUrls(frontendUrl: string, backendUrl: string): OAuthUrls {\n // Remove trailing slashes\n const frontend = frontendUrl.replace(/\\/$/, '')\n const backend = backendUrl.replace(/\\/$/, '')\n\n return {\n issuer: frontend,\n authorizationUri: `${frontend}/oauth/authorize`,\n tokenUri: `${backend}/oauth/token`,\n jwksUri: `${backend}/.well-known/jwks.json`,\n userinfoUri: `${backend}/oauth/userinfo`,\n }\n}\n\n/**\n * Get OAuth URLs for an environment.\n * Uses frontend and backend URLs from Environments configuration.\n *\n * @param environment - The Nevermined environment name\n * @returns OAuth URLs configuration\n */\nfunction getOAuthUrlsForEnvironment(environment: EnvironmentName): OAuthUrls {\n const envConfig = Environments[environment] || Environments.sandbox\n return buildOAuthUrls(envConfig.frontend, envConfig.backend)\n}\n\n/**\n * Default scopes supported by Nevermined MCP servers.\n */\nconst DEFAULT_SCOPES: readonly string[] = [\n 'openid',\n 'profile',\n 'credits',\n 'mcp:read',\n 'mcp:write',\n 'mcp:tools',\n]\n\n/**\n * Get OAuth URLs for a given environment with optional overrides.\n *\n * @param environment - The Nevermined environment name\n * @param overrides - Optional partial overrides for specific URLs\n * @returns Complete OAuth URLs configuration\n */\nexport function getOAuthUrls(\n environment: EnvironmentName,\n overrides?: Partial<OAuthUrls>,\n): OAuthUrls {\n const baseUrls = getOAuthUrlsForEnvironment(environment)\n return { ...baseUrls, ...overrides }\n}\n\n/**\n * Build Protected Resource Metadata (RFC 9728).\n * This metadata tells OAuth clients about the protected resource.\n *\n * @param config - OAuth configuration\n * @returns Protected Resource Metadata response object\n *\n * @example\n * ```typescript\n * const metadata = buildProtectedResourceMetadata({\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox'\n * })\n * // Returns: { resource: 'http://localhost:5001', authorization_servers: [...], ... }\n * ```\n */\nexport function buildProtectedResourceMetadata(config: OAuthConfig): ProtectedResourceMetadata {\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n // oauthUrls calculated but not used in this metadata (kept for future use)\n void getOAuthUrls(config.environment, config.oauthUrls)\n\n return {\n resource: config.baseUrl,\n authorization_servers: [config.baseUrl],\n scopes_supported: scopes,\n bearer_methods_supported: ['header'],\n resource_documentation: `${config.baseUrl}/`,\n }\n}\n\n/**\n * Build MCP-specific Protected Resource Metadata.\n * Extends the base metadata with MCP capabilities information.\n *\n * @param config - OAuth configuration\n * @returns MCP Protected Resource Metadata response object\n *\n * @example\n * ```typescript\n * const metadata = buildMcpProtectedResourceMetadata({\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox',\n * tools: ['hello_world', 'weather']\n * })\n * ```\n */\nexport function buildMcpProtectedResourceMetadata(\n config: OAuthConfig,\n): McpProtectedResourceMetadata {\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n // oauthUrls calculated but not used in this metadata (kept for future use)\n void getOAuthUrls(config.environment, config.oauthUrls)\n\n return {\n resource: `${config.baseUrl}/mcp`,\n authorization_servers: [config.baseUrl],\n scopes_supported: scopes,\n scopes_required: scopes,\n bearer_methods_supported: ['header'],\n resource_documentation: `${config.baseUrl}/`,\n mcp_capabilities: {\n tools: config.tools || [],\n protocol_version: config.protocolVersion || '2024-11-05',\n },\n }\n}\n\n/**\n * Build OAuth Authorization Server Metadata (RFC 8414).\n * This metadata describes the OAuth authorization server configuration.\n *\n * @param config - OAuth configuration\n * @returns Authorization Server Metadata response object\n *\n * @example\n * ```typescript\n * const metadata = buildAuthorizationServerMetadata({\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox'\n * })\n * ```\n */\nexport function buildAuthorizationServerMetadata(config: OAuthConfig): AuthorizationServerMetadata {\n const oauthUrls = getOAuthUrls(config.environment, config.oauthUrls)\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n\n return {\n issuer: oauthUrls.issuer,\n authorization_endpoint: oauthUrls.authorizationUri,\n token_endpoint: oauthUrls.tokenUri,\n registration_endpoint: `${config.baseUrl}/register`,\n jwks_uri: oauthUrls.jwksUri,\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n scopes_supported: scopes,\n token_endpoint_auth_methods_supported: ['client_secret_post'],\n subject_types_supported: ['public'],\n }\n}\n\n/**\n * Build OpenID Connect Discovery Metadata.\n * Provides OIDC-compatible configuration for clients that expect OpenID Connect.\n *\n * @param config - OAuth configuration\n * @returns OIDC Configuration response object\n *\n * @example\n * ```typescript\n * const metadata = buildOidcConfiguration({\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox'\n * })\n * ```\n */\nexport function buildOidcConfiguration(config: OAuthConfig): OidcConfiguration {\n const oauthUrls = getOAuthUrls(config.environment, config.oauthUrls)\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n const allScopes = scopes.includes('openid') ? scopes : ['openid', ...scopes]\n\n return {\n issuer: oauthUrls.issuer,\n authorization_endpoint: oauthUrls.authorizationUri,\n token_endpoint: oauthUrls.tokenUri,\n jwks_uri: oauthUrls.jwksUri,\n userinfo_endpoint: oauthUrls.userinfoUri,\n registration_endpoint: `${config.baseUrl}/register`,\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n token_endpoint_auth_methods_supported: ['none', 'client_secret_post'],\n subject_types_supported: ['public'],\n id_token_signing_alg_values_supported: ['RS256', 'HS256'],\n scopes_supported: allScopes,\n claims_supported: ['sub', 'iss', 'aud', 'exp', 'iat', 'name', 'email'],\n }\n}\n\n/**\n * Build server info response for the root endpoint.\n *\n * @param config - OAuth configuration\n * @param options - Additional options for the server info\n * @returns Server info response object\n */\nexport function buildServerInfoResponse(\n config: OAuthConfig,\n options?: {\n version?: string\n description?: string\n },\n): {\n name: string\n version: string\n description: string\n endpoints: Record<string, string>\n oauth: Record<string, any>\n tools: string[]\n resources: string[]\n prompts: string[]\n} {\n const oauthUrls = getOAuthUrls(config.environment, config.oauthUrls)\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n\n return {\n name: config.serverName || 'MCP Server',\n version: options?.version || '1.0.0',\n description:\n options?.description || 'MCP server with Nevermined OAuth integration via Streamable HTTP',\n endpoints: {\n mcp: `${config.baseUrl}/mcp`,\n health: `${config.baseUrl}/health`,\n register: `${config.baseUrl}/register`,\n },\n oauth: {\n authorization_server_metadata: `${config.baseUrl}/.well-known/oauth-authorization-server`,\n protected_resource_metadata: `${config.baseUrl}/.well-known/oauth-protected-resource`,\n openid_configuration: `${config.baseUrl}/.well-known/openid-configuration`,\n authorization_endpoint: oauthUrls.authorizationUri,\n token_endpoint: oauthUrls.tokenUri,\n jwks_uri: oauthUrls.jwksUri,\n registration_endpoint: `${config.baseUrl}/register`,\n // agentId is optional under the plan-centric model; omit client_id when absent.\n ...(config.agentId ? { client_id: config.agentId } : {}),\n scopes: scopes,\n },\n tools: config.tools || [],\n resources: config.resources || [],\n prompts: config.prompts || [],\n }\n}\n"]}
1
+ {"version":3,"file":"oauth-metadata.js","sourceRoot":"","sources":["../../../src/mcp/http/oauth-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure functions to generate OAuth 2.1 metadata responses.\n * These generators produce the JSON payloads for OAuth discovery endpoints\n * without any framework dependencies, making them reusable across different HTTP servers.\n */\nimport { Environments, type EnvironmentName } from '../../environments.js'\nimport type {\n OAuthUrls,\n OAuthConfig,\n ProtectedResourceMetadata,\n McpProtectedResourceMetadata,\n AuthorizationServerMetadata,\n OidcConfiguration,\n} from '../types/http.types.js'\n\n/**\n * Build OAuth URLs from frontend and backend URLs.\n * - issuer and authorizationUri use the frontend (user-facing)\n * - tokenUri, jwksUri, userinfoUri use the backend (API)\n *\n * @param frontendUrl - The frontend URL (e.g., https://nevermined.app)\n * @param backendUrl - The backend URL (e.g., https://api.sandbox.nevermined.app)\n * @returns OAuth URLs configuration\n */\nfunction buildOAuthUrls(frontendUrl: string, backendUrl: string): OAuthUrls {\n // Remove trailing slashes\n const frontend = frontendUrl.replace(/\\/$/, '')\n const backend = backendUrl.replace(/\\/$/, '')\n\n return {\n issuer: frontend,\n authorizationUri: `${frontend}/oauth/authorize`,\n tokenUri: `${backend}/oauth/token`,\n jwksUri: `${backend}/.well-known/jwks.json`,\n userinfoUri: `${backend}/oauth/userinfo`,\n }\n}\n\n/**\n * Get OAuth URLs for an environment.\n * Uses frontend and backend URLs from Environments configuration.\n *\n * @param environment - The Nevermined environment name\n * @returns OAuth URLs configuration\n */\nfunction getOAuthUrlsForEnvironment(environment: EnvironmentName): OAuthUrls {\n const envConfig = Environments[environment] || Environments.sandbox\n return buildOAuthUrls(envConfig.frontend, envConfig.backend)\n}\n\n/**\n * Default scopes supported by Nevermined MCP servers.\n */\nconst DEFAULT_SCOPES: readonly string[] = [\n 'openid',\n 'profile',\n 'credits',\n 'mcp:read',\n 'mcp:write',\n 'mcp:tools',\n]\n\n/**\n * Get OAuth URLs for a given environment with optional overrides.\n *\n * @param environment - The Nevermined environment name\n * @param overrides - Optional partial overrides for specific URLs\n * @returns Complete OAuth URLs configuration\n */\nexport function getOAuthUrls(\n environment: EnvironmentName,\n overrides?: Partial<OAuthUrls>,\n): OAuthUrls {\n const baseUrls = getOAuthUrlsForEnvironment(environment)\n return { ...baseUrls, ...overrides }\n}\n\n/**\n * Build Protected Resource Metadata (RFC 9728).\n * This metadata tells OAuth clients about the protected resource.\n *\n * @param config - OAuth configuration\n * @returns Protected Resource Metadata response object\n *\n * @example\n * ```typescript\n * const metadata = buildProtectedResourceMetadata({\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox'\n * })\n * // Returns: { resource: 'http://localhost:5001', authorization_servers: [...], ... }\n * ```\n */\nexport function buildProtectedResourceMetadata(config: OAuthConfig): ProtectedResourceMetadata {\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n\n return {\n resource: config.baseUrl,\n authorization_servers: [resolveAuthorizationServer(config)],\n scopes_supported: scopes,\n bearer_methods_supported: ['header'],\n resource_documentation: `${config.baseUrl}/`,\n }\n}\n\n/**\n * Resolve the authorization-server identifier for `authorization_servers`.\n *\n * RFC 9728 §2 requires each entry to be an AS issuer identifier — a URL from\n * which `/.well-known/oauth-authorization-server` (RFC 8414) actually resolves.\n * For Nevermined that is the **backend** API origin (which serves the AS\n * metadata and whose own published `issuer` is the backend URL), NOT:\n * - `config.baseUrl` — that is this MCP server, the protected *resource*; and\n * - `OAuthUrls.issuer` — that is the *frontend* (a client-routed SPA that 200s\n * on every path, so an RFC 8414 fetch there silently returns HTML).\n * The backend origin is derived from `tokenUri` (`${backend}/oauth/token`), which\n * respects any `config.oauthUrls.tokenUri` override.\n */\nfunction resolveAuthorizationServer(config: OAuthConfig): string {\n const { tokenUri } = getOAuthUrls(config.environment, config.oauthUrls)\n const envTokenUri = getOAuthUrls(config.environment).tokenUri\n // Guard both a falsy `{ tokenUri: undefined }` override (slips through the\n // `{...baseUrls, ...overrides}` spread — exactOptionalPropertyTypes is off) AND\n // a malformed non-falsy one — fall back to the environment default rather than\n // crash the metadata endpoint with `new URL(...)`.\n try {\n return new URL(tokenUri || envTokenUri).origin\n } catch {\n return new URL(envTokenUri).origin\n }\n}\n\n/**\n * Build MCP-specific Protected Resource Metadata.\n * Extends the base metadata with MCP capabilities information.\n *\n * @param config - OAuth configuration\n * @returns MCP Protected Resource Metadata response object\n *\n * @example\n * ```typescript\n * const metadata = buildMcpProtectedResourceMetadata({\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox',\n * tools: ['hello_world', 'weather']\n * })\n * ```\n */\nexport function buildMcpProtectedResourceMetadata(\n config: OAuthConfig,\n): McpProtectedResourceMetadata {\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n\n return {\n resource: `${config.baseUrl}/mcp`,\n // See resolveAuthorizationServer: the AS is the backend API origin, not this\n // MCP server (baseUrl) nor the frontend SPA (OAuthUrls.issuer).\n authorization_servers: [resolveAuthorizationServer(config)],\n scopes_supported: scopes,\n scopes_required: scopes,\n bearer_methods_supported: ['header'],\n resource_documentation: `${config.baseUrl}/`,\n mcp_capabilities: {\n tools: config.tools || [],\n protocol_version: config.protocolVersion || '2024-11-05',\n },\n }\n}\n\n/**\n * Build OAuth Authorization Server Metadata (RFC 8414).\n * This metadata describes the OAuth authorization server configuration.\n *\n * @param config - OAuth configuration\n * @returns Authorization Server Metadata response object\n *\n * @example\n * ```typescript\n * const metadata = buildAuthorizationServerMetadata({\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox'\n * })\n * ```\n */\nexport function buildAuthorizationServerMetadata(config: OAuthConfig): AuthorizationServerMetadata {\n const oauthUrls = getOAuthUrls(config.environment, config.oauthUrls)\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n\n return {\n issuer: oauthUrls.issuer,\n authorization_endpoint: oauthUrls.authorizationUri,\n token_endpoint: oauthUrls.tokenUri,\n registration_endpoint: `${config.baseUrl}/register`,\n jwks_uri: oauthUrls.jwksUri,\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n scopes_supported: scopes,\n token_endpoint_auth_methods_supported: ['client_secret_post'],\n subject_types_supported: ['public'],\n }\n}\n\n/**\n * Build OpenID Connect Discovery Metadata.\n * Provides OIDC-compatible configuration for clients that expect OpenID Connect.\n *\n * @param config - OAuth configuration\n * @returns OIDC Configuration response object\n *\n * @example\n * ```typescript\n * const metadata = buildOidcConfiguration({\n * baseUrl: 'http://localhost:5001',\n * agentId: 'agent_123',\n * environment: 'staging_sandbox'\n * })\n * ```\n */\nexport function buildOidcConfiguration(config: OAuthConfig): OidcConfiguration {\n const oauthUrls = getOAuthUrls(config.environment, config.oauthUrls)\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n const allScopes = scopes.includes('openid') ? scopes : ['openid', ...scopes]\n\n return {\n issuer: oauthUrls.issuer,\n authorization_endpoint: oauthUrls.authorizationUri,\n token_endpoint: oauthUrls.tokenUri,\n jwks_uri: oauthUrls.jwksUri,\n userinfo_endpoint: oauthUrls.userinfoUri,\n registration_endpoint: `${config.baseUrl}/register`,\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n token_endpoint_auth_methods_supported: ['none', 'client_secret_post'],\n subject_types_supported: ['public'],\n id_token_signing_alg_values_supported: ['RS256', 'HS256'],\n scopes_supported: allScopes,\n claims_supported: ['sub', 'iss', 'aud', 'exp', 'iat', 'name', 'email'],\n }\n}\n\n/**\n * Build server info response for the root endpoint.\n *\n * @param config - OAuth configuration\n * @param options - Additional options for the server info\n * @returns Server info response object\n */\nexport function buildServerInfoResponse(\n config: OAuthConfig,\n options?: {\n version?: string\n description?: string\n },\n): {\n name: string\n version: string\n description: string\n endpoints: Record<string, string>\n oauth: Record<string, any>\n tools: string[]\n resources: string[]\n prompts: string[]\n} {\n const oauthUrls = getOAuthUrls(config.environment, config.oauthUrls)\n const scopes = config.scopes || [...DEFAULT_SCOPES]\n\n return {\n name: config.serverName || 'MCP Server',\n version: options?.version || '1.0.0',\n description:\n options?.description || 'MCP server with Nevermined OAuth integration via Streamable HTTP',\n endpoints: {\n mcp: `${config.baseUrl}/mcp`,\n health: `${config.baseUrl}/health`,\n register: `${config.baseUrl}/register`,\n },\n oauth: {\n authorization_server_metadata: `${config.baseUrl}/.well-known/oauth-authorization-server`,\n protected_resource_metadata: `${config.baseUrl}/.well-known/oauth-protected-resource`,\n openid_configuration: `${config.baseUrl}/.well-known/openid-configuration`,\n authorization_endpoint: oauthUrls.authorizationUri,\n token_endpoint: oauthUrls.tokenUri,\n jwks_uri: oauthUrls.jwksUri,\n registration_endpoint: `${config.baseUrl}/register`,\n // agentId is optional under the plan-centric model; omit client_id when absent.\n ...(config.agentId ? { client_id: config.agentId } : {}),\n scopes: scopes,\n },\n tools: config.tools || [],\n resources: config.resources || [],\n prompts: config.prompts || [],\n }\n}\n"]}
@@ -65,25 +65,26 @@ export declare function createCorsMiddleware(origins?: string | string[]): (req:
65
65
  */
66
66
  export declare function createJsonMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
67
67
  /**
68
- * Create a middleware that requires a Bearer token in the Authorization header.
69
- * Returns HTTP 401 if the token is missing or malformed.
68
+ * Create a middleware that requires a Bearer token, emitting an RFC 9728 §5.1
69
+ * `WWW-Authenticate` challenge on 401 so a client can discover the PRM.
70
70
  *
71
- * This is a lightweight check that only verifies presence of a token.
72
- * Full validation (is the token valid? does the user have access?) is handled
73
- * by withPaywall() when a tool is actually called.
71
+ * `options.baseUrl` (optional) — operator-known public base URL of THIS MCP
72
+ * server. When set, the `resource_metadata` pointer is built from it (never from
73
+ * client input) — the correct choice behind a TLS-terminating proxy. When omitted,
74
+ * the pointer is derived from the request, but ONLY if the (forwarded) Host is a
75
+ * safe, well-formed value; otherwise the challenge header is omitted rather than
76
+ * emitting a broken/injectable one.
74
77
  *
75
- * @returns Express middleware for token requirement
76
- *
77
- * @example
78
- * ```typescript
79
- * const requireAuth = createRequireAuthMiddleware()
80
- *
81
- * // Protect MCP endpoints
82
- * app.post('/mcp', requireAuth, mcpHandler)
83
- * app.get('/mcp', requireAuth, sseHandler)
84
- * ```
78
+ * `options.resourceMetadataPath` (optional) — the PRM path to advertise; defaults
79
+ * to the root `/.well-known/oauth-protected-resource`. A caller mounting this on a
80
+ * sub-resource (e.g. `/mcp`) should pass the scoped document
81
+ * (`/.well-known/oauth-protected-resource/mcp`) so the client gets the correctly
82
+ * scoped `resource` (RFC 8707 audience) and the resource's capabilities.
85
83
  */
86
- export declare function createRequireAuthMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
84
+ export declare function createRequireAuthMiddleware(options?: {
85
+ baseUrl?: string;
86
+ resourceMetadataPath?: string;
87
+ }): (req: Request, res: Response, next: NextFunction) => void;
87
88
  /**
88
89
  * Create a middleware that logs all HTTP requests to endpoints.
89
90
  * Logs method, URL, IP address, and relevant headers.
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-router.d.ts","sourceRoot":"","sources":["../../../src/mcp/http/oauth-router.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAgB,EAAE,KAAK,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAc9D;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC1D,2CAA2C;IAC3C,QAAQ,EAAE,QAAQ,CAAA;IAClB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CA2KrE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,MAAM,GAAG,MAAM,EAAQ,IACpC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAkB/E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,KACH,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAwB/E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,KACH,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAgCtF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,IAOrC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAyCtF"}
1
+ {"version":3,"file":"oauth-router.d.ts","sourceRoot":"","sources":["../../../src/mcp/http/oauth-router.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAgB,EAAE,KAAK,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAc9D;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC1D,2CAA2C;IAC3C,QAAQ,EAAE,QAAQ,CAAA;IAClB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CA2KrE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,MAAM,GAAG,MAAM,EAAQ,IACpC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAkB/E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,KACH,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAwB/E;AASD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAAO,IAqB3B,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAkCtF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,IAOrC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAyCtF"}
@@ -224,47 +224,75 @@ export function createJsonMiddleware() {
224
224
  };
225
225
  }
226
226
  /**
227
- * Create a middleware that requires a Bearer token in the Authorization header.
228
- * Returns HTTP 401 if the token is missing or malformed.
229
- *
230
- * This is a lightweight check that only verifies presence of a token.
231
- * Full validation (is the token valid? does the user have access?) is handled
232
- * by withPaywall() when a tool is actually called.
227
+ * A well-formed HTTP Host value: `hostname[:port]` or `[ipv6][:port]`. Crucially
228
+ * it contains none of the characters (`"`, `\`, whitespace, `;`, `,`) that could
229
+ * break out of the `WWW-Authenticate` quoted-string and inject extra auth-params.
230
+ */
231
+ const SAFE_HOST_RE = /^(\[[0-9a-fA-F:]+\]|[A-Za-z0-9._-]+)(:\d{1,5})?$/;
232
+ /**
233
+ * Create a middleware that requires a Bearer token, emitting an RFC 9728 §5.1
234
+ * `WWW-Authenticate` challenge on 401 so a client can discover the PRM.
233
235
  *
234
- * @returns Express middleware for token requirement
236
+ * `options.baseUrl` (optional) — operator-known public base URL of THIS MCP
237
+ * server. When set, the `resource_metadata` pointer is built from it (never from
238
+ * client input) — the correct choice behind a TLS-terminating proxy. When omitted,
239
+ * the pointer is derived from the request, but ONLY if the (forwarded) Host is a
240
+ * safe, well-formed value; otherwise the challenge header is omitted rather than
241
+ * emitting a broken/injectable one.
235
242
  *
236
- * @example
237
- * ```typescript
238
- * const requireAuth = createRequireAuthMiddleware()
239
- *
240
- * // Protect MCP endpoints
241
- * app.post('/mcp', requireAuth, mcpHandler)
242
- * app.get('/mcp', requireAuth, sseHandler)
243
- * ```
243
+ * `options.resourceMetadataPath` (optional) — the PRM path to advertise; defaults
244
+ * to the root `/.well-known/oauth-protected-resource`. A caller mounting this on a
245
+ * sub-resource (e.g. `/mcp`) should pass the scoped document
246
+ * (`/.well-known/oauth-protected-resource/mcp`) so the client gets the correctly
247
+ * scoped `resource` (RFC 8707 audience) and the resource's capabilities.
244
248
  */
245
- export function createRequireAuthMiddleware() {
249
+ export function createRequireAuthMiddleware(options = {}) {
250
+ const pinnedBase = options.baseUrl?.replace(/\/+$/, '');
251
+ const prmPath = options.resourceMetadataPath ?? '/.well-known/oauth-protected-resource';
252
+ // Origin to advertise in the PRM pointer, or undefined if it can't be trusted.
253
+ const prmOrigin = (req) => {
254
+ if (pinnedBase)
255
+ return pinnedBase;
256
+ const fwdHost = req.headers['x-forwarded-host'];
257
+ const rawHost = (Array.isArray(fwdHost) ? fwdHost[0] : fwdHost) ?? req.get('host');
258
+ const host = typeof rawHost === 'string' ? rawHost.trim() : undefined;
259
+ if (!host || !SAFE_HOST_RE.test(host))
260
+ return undefined; // forged/broken → omit
261
+ const fwdProto = req.headers['x-forwarded-proto'];
262
+ const proto = (Array.isArray(fwdProto) ? fwdProto[0] : fwdProto)?.split(',')[0]?.trim() ||
263
+ req.protocol ||
264
+ 'https';
265
+ if (proto !== 'http' && proto !== 'https')
266
+ return undefined;
267
+ return `${proto}://${host}`;
268
+ };
246
269
  return function requireAuthMiddleware(req, res, next) {
270
+ // RFC 9728 §5.1: a 401 from a protected resource advertises where its PRM
271
+ // lives so a client can discover the authorization server. `error` (RFC 6750
272
+ // §3.1) is added only when a credential WAS presented but is malformed — a
273
+ // request with no credential at all omits it (§3).
274
+ const send401 = (error_description, error) => {
275
+ const origin = prmOrigin(req);
276
+ if (origin) {
277
+ const params = [`resource_metadata="${origin}${prmPath}"`];
278
+ if (error)
279
+ params.push(`error="${error}"`);
280
+ res.setHeader('WWW-Authenticate', `Bearer ${params.join(', ')}`);
281
+ }
282
+ res.status(401).json({ error: 'unauthorized', error_description });
283
+ };
247
284
  const authHeader = req.headers.authorization;
248
285
  if (!authHeader) {
249
- res.status(401).json({
250
- error: 'unauthorized',
251
- error_description: 'Authorization header required',
252
- });
286
+ send401('Authorization header required');
253
287
  return;
254
288
  }
255
289
  if (!authHeader.startsWith('Bearer ')) {
256
- res.status(401).json({
257
- error: 'unauthorized',
258
- error_description: 'Bearer token required',
259
- });
290
+ send401('Bearer token required', 'invalid_request');
260
291
  return;
261
292
  }
262
293
  const token = authHeader.slice(7).trim();
263
294
  if (!token) {
264
- res.status(401).json({
265
- error: 'unauthorized',
266
- error_description: 'Bearer token cannot be empty',
267
- });
295
+ send401('Bearer token cannot be empty', 'invalid_request');
268
296
  return;
269
297
  }
270
298
  // Token present and well-formed, continue
@@ -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"}