From afae4212e6faaf78b1ee4bc415f6ec230133e1e4 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 16 Feb 2025 20:07:30 +0000 Subject: [PATCH] Move OAuth definitions into shared, add more --- src/client/auth.test.ts | 1 - src/client/auth.ts | 81 +------------------------------ src/client/sse.test.ts | 3 +- src/shared/auth.ts | 104 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 81 deletions(-) create mode 100644 src/shared/auth.ts diff --git a/src/client/auth.test.ts b/src/client/auth.test.ts index c65a5f3a..97c9e14a 100644 --- a/src/client/auth.test.ts +++ b/src/client/auth.test.ts @@ -6,7 +6,6 @@ import { registerClient, } from "./auth.js"; - // Mock fetch globally const mockFetch = jest.fn(); global.fetch = mockFetch; diff --git a/src/client/auth.ts b/src/client/auth.ts index b30134b8..02ace32f 100644 --- a/src/client/auth.ts +++ b/src/client/auth.ts @@ -1,84 +1,7 @@ import pkceChallenge from "pkce-challenge"; -import { z } from "zod"; import { LATEST_PROTOCOL_VERSION } from "../types.js"; - -export const OAuthMetadataSchema = z - .object({ - issuer: z.string(), - authorization_endpoint: z.string(), - token_endpoint: z.string(), - registration_endpoint: z.string().optional(), - scopes_supported: z.array(z.string()).optional(), - response_types_supported: z.array(z.string()), - response_modes_supported: z.array(z.string()).optional(), - grant_types_supported: z.array(z.string()).optional(), - token_endpoint_auth_methods_supported: z.array(z.string()).optional(), - token_endpoint_auth_signing_alg_values_supported: z - .array(z.string()) - .optional(), - service_documentation: z.string().optional(), - revocation_endpoint: z.string().optional(), - revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(), - revocation_endpoint_auth_signing_alg_values_supported: z - .array(z.string()) - .optional(), - introspection_endpoint: z.string().optional(), - introspection_endpoint_auth_methods_supported: z - .array(z.string()) - .optional(), - introspection_endpoint_auth_signing_alg_values_supported: z - .array(z.string()) - .optional(), - code_challenge_methods_supported: z.array(z.string()).optional(), - }) - .passthrough(); - -export const OAuthTokensSchema = z - .object({ - access_token: z.string(), - token_type: z.string(), - expires_in: z.number().optional(), - scope: z.string().optional(), - refresh_token: z.string().optional(), - }) - .strip(); - -/** - * Client metadata schema according to RFC 7591 OAuth 2.0 Dynamic Client Registration - */ -export const OAuthClientMetadataSchema = z.object({ - redirect_uris: z.array(z.string()), - token_endpoint_auth_method: z.string().optional(), - grant_types: z.array(z.string()).optional(), - response_types: z.array(z.string()).optional(), - client_name: z.string().optional(), - client_uri: z.string().optional(), - logo_uri: z.string().optional(), - scope: z.string().optional(), - contacts: z.array(z.string()).optional(), - tos_uri: z.string().optional(), - policy_uri: z.string().optional(), - jwks_uri: z.string().optional(), - jwks: z.any().optional(), - software_id: z.string().optional(), - software_version: z.string().optional(), -}).passthrough(); - -/** - * Client information response schema according to RFC 7591 - */ -export const OAuthClientInformationSchema = z.object({ - client_id: z.string(), - client_secret: z.string().optional(), - client_id_issued_at: z.number().optional(), - client_secret_expires_at: z.number().optional(), -}).passthrough(); - -export type OAuthMetadata = z.infer; -export type OAuthTokens = z.infer; - -export type OAuthClientMetadata = z.infer; -export type OAuthClientInformation = z.infer; +import type { OAuthClientMetadata, OAuthClientInformation, OAuthTokens, OAuthMetadata } from "../shared/auth.js"; +import { OAuthClientInformationSchema, OAuthMetadataSchema, OAuthTokensSchema } from "../shared/auth.js"; /** * Implements an end-to-end OAuth client to be used with one MCP server. diff --git a/src/client/sse.test.ts b/src/client/sse.test.ts index 57497013..2b166530 100644 --- a/src/client/sse.test.ts +++ b/src/client/sse.test.ts @@ -2,7 +2,8 @@ import { createServer, type IncomingMessage, type Server } from "http"; import { AddressInfo } from "net"; import { JSONRPCMessage } from "../types.js"; import { SSEClientTransport } from "./sse.js"; -import { OAuthClientProvider, OAuthTokens, UnauthorizedError } from "./auth.js"; +import { OAuthClientProvider, UnauthorizedError } from "./auth.js"; +import { OAuthTokens } from "../shared/auth.js"; describe("SSEClientTransport", () => { let server: Server; diff --git a/src/shared/auth.ts b/src/shared/auth.ts new file mode 100644 index 00000000..1bfd9bd3 --- /dev/null +++ b/src/shared/auth.ts @@ -0,0 +1,104 @@ +import { z } from "zod"; + +/** + * RFC 8414 OAuth 2.0 Authorization Server Metadata + */ +export const OAuthMetadataSchema = z + .object({ + issuer: z.string(), + authorization_endpoint: z.string(), + token_endpoint: z.string(), + registration_endpoint: z.string().optional(), + scopes_supported: z.array(z.string()).optional(), + response_types_supported: z.array(z.string()), + response_modes_supported: z.array(z.string()).optional(), + grant_types_supported: z.array(z.string()).optional(), + token_endpoint_auth_methods_supported: z.array(z.string()).optional(), + token_endpoint_auth_signing_alg_values_supported: z + .array(z.string()) + .optional(), + service_documentation: z.string().optional(), + revocation_endpoint: z.string().optional(), + revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(), + revocation_endpoint_auth_signing_alg_values_supported: z + .array(z.string()) + .optional(), + introspection_endpoint: z.string().optional(), + introspection_endpoint_auth_methods_supported: z + .array(z.string()) + .optional(), + introspection_endpoint_auth_signing_alg_values_supported: z + .array(z.string()) + .optional(), + code_challenge_methods_supported: z.array(z.string()).optional(), + }) + .passthrough(); + +/** + * OAuth 2.1 token response + */ +export const OAuthTokensSchema = z + .object({ + access_token: z.string(), + token_type: z.string(), + expires_in: z.number().optional(), + scope: z.string().optional(), + refresh_token: z.string().optional(), + }) + .strip(); + +/** + * OAuth 2.1 error response + */ +export const OAuthErrorSchema = z + .object({ + error: z.string(), + error_description: z.string().optional(), + error_uri: z.string().optional(), + }); + +/** + * RFC 7591 OAuth 2.0 Dynamic Client Registration metadata + */ +export const OAuthClientMetadataSchema = z.object({ + redirect_uris: z.array(z.string()), + token_endpoint_auth_method: z.string().optional(), + grant_types: z.array(z.string()).optional(), + response_types: z.array(z.string()).optional(), + client_name: z.string().optional(), + client_uri: z.string().optional(), + logo_uri: z.string().optional(), + scope: z.string().optional(), + contacts: z.array(z.string()).optional(), + tos_uri: z.string().optional(), + policy_uri: z.string().optional(), + jwks_uri: z.string().optional(), + jwks: z.any().optional(), + software_id: z.string().optional(), + software_version: z.string().optional(), +}).passthrough(); + +/** + * RFC 7591 OAuth 2.0 Dynamic Client Registration response + */ +export const OAuthClientInformationSchema = OAuthClientMetadataSchema.extend({ + client_id: z.string(), + client_secret: z.string().optional(), + client_id_issued_at: z.number().optional(), + client_secret_expires_at: z.number().optional(), +}).passthrough(); + +/** + * RFC 7591 OAuth 2.0 Dynamic Client Registration error response + */ +export const OAuthClientRegistrationErrorSchema = z.object({ + error: z.string(), + error_description: z.string().optional(), +}).strip(); + +export type OAuthMetadata = z.infer; +export type OAuthTokens = z.infer; +export type OAuthError = z.infer; +export type OAuthClientMetadata = z.infer; +export type OAuthClientInformation = z.infer; +export type OAuthClientRegistrationError = z.infer; \ No newline at end of file