@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.
- package/dist/api/base-payments.d.ts +11 -0
- package/dist/api/base-payments.d.ts.map +1 -1
- package/dist/api/base-payments.js +28 -3
- package/dist/api/base-payments.js.map +1 -1
- 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 +9 -4
- package/dist/common/types.d.ts.map +1 -1
- package/dist/common/types.js.map +1 -1
- package/dist/environments.d.ts +12 -0
- package/dist/environments.d.ts.map +1 -1
- package/dist/environments.js +31 -0
- package/dist/environments.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 +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
|
|
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
|
-
|
|
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
|
|
69
|
-
*
|
|
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
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
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
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
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(
|
|
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;
|
|
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
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"}
|