From 065d23584f51a83fd8089decda22211a913fd489 Mon Sep 17 00:00:00 2001 From: ChipGPT Date: Fri, 10 Oct 2025 12:13:44 -0500 Subject: [PATCH 1/5] rebase after prettier update --- src/client/auth.test.ts | 30 ++++++++++++++++-------- src/client/auth.ts | 26 ++++++++++++++------ src/client/sse.test.ts | 9 +++++-- src/client/streamableHttp.test.ts | 6 ++++- src/examples/client/simpleOAuthClient.ts | 4 ++-- 5 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/client/auth.test.ts b/src/client/auth.test.ts index f2dadbb15..a1f1e188d 100644 --- a/src/client/auth.test.ts +++ b/src/client/auth.test.ts @@ -1588,7 +1588,8 @@ describe('OAuth Authorization', () => { // Mock provider methods for authorization flow (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret' + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'] }); (mockProvider.tokens as jest.Mock).mockResolvedValue(undefined); (mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -1658,7 +1659,8 @@ describe('OAuth Authorization', () => { // Mock provider methods for token exchange (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret' + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'] }); (mockProvider.codeVerifier as jest.Mock).mockResolvedValue('test-verifier'); (mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined); @@ -1724,7 +1726,8 @@ describe('OAuth Authorization', () => { // Mock provider methods for token refresh (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret' + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'] }); (mockProvider.tokens as jest.Mock).mockResolvedValue({ access_token: 'old-access', @@ -1790,7 +1793,8 @@ describe('OAuth Authorization', () => { // Mock provider methods (providerWithCustomValidation.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret' + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'] }); (providerWithCustomValidation.tokens as jest.Mock).mockResolvedValue(undefined); (providerWithCustomValidation.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -1845,7 +1849,8 @@ describe('OAuth Authorization', () => { // Mock provider methods (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret' + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'] }); (mockProvider.tokens as jest.Mock).mockResolvedValue(undefined); (mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -1903,7 +1908,8 @@ describe('OAuth Authorization', () => { // Mock provider methods (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret' + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'] }); (mockProvider.tokens as jest.Mock).mockResolvedValue(undefined); (mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -1970,7 +1976,8 @@ describe('OAuth Authorization', () => { // Mock provider methods for token exchange (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret' + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'] }); (mockProvider.codeVerifier as jest.Mock).mockResolvedValue('test-verifier'); (mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined); @@ -2033,7 +2040,8 @@ describe('OAuth Authorization', () => { // Mock provider methods for token refresh (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret' + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'] }); (mockProvider.tokens as jest.Mock).mockResolvedValue({ access_token: 'old-access', @@ -2094,7 +2102,8 @@ describe('OAuth Authorization', () => { // Mock provider methods (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret' + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'] }); (mockProvider.tokens as jest.Mock).mockResolvedValue(undefined); (mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -2156,7 +2165,8 @@ describe('OAuth Authorization', () => { }, clientInformation: jest.fn().mockResolvedValue({ client_id: 'client123', - client_secret: 'secret123' + client_secret: 'secret123', + redirect_uris: ['http://localhost:3000/callback'] }), tokens: jest.fn().mockResolvedValue(undefined), saveTokens: jest.fn(), diff --git a/src/client/auth.ts b/src/client/auth.ts index 1e90f34ba..6df9dd385 100644 --- a/src/client/auth.ts +++ b/src/client/auth.ts @@ -2,7 +2,6 @@ import pkceChallenge from 'pkce-challenge'; import { LATEST_PROTOCOL_VERSION } from '../types.js'; import { OAuthClientMetadata, - OAuthClientInformation, OAuthTokens, OAuthMetadata, OAuthClientInformationFull, @@ -56,7 +55,7 @@ export interface OAuthClientProvider { * server, or returns `undefined` if the client is not registered with the * server. */ - clientInformation(): OAuthClientInformation | undefined | Promise; + clientInformation(): OAuthClientInformationFull | undefined | Promise; /** * If implemented, this permits the OAuth client to dynamically register with @@ -149,6 +148,10 @@ export class UnauthorizedError extends Error { type ClientAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'none'; +function isClientAuthMethod(method: string): method is ClientAuthMethod { + return ['client_secret_basic', 'client_secret_post', 'none'].includes(method); +} + const AUTHORIZATION_CODE_RESPONSE_TYPE = 'code'; const AUTHORIZATION_CODE_CHALLENGE_METHOD = 'S256'; @@ -164,7 +167,7 @@ const AUTHORIZATION_CODE_CHALLENGE_METHOD = 'S256'; * @param supportedMethods - Authentication methods supported by the authorization server * @returns The selected authentication method */ -function selectClientAuthMethod(clientInformation: OAuthClientInformation, supportedMethods: string[]): ClientAuthMethod { +function selectClientAuthMethod(clientInformation: OAuthClientInformationFull, supportedMethods: string[]): ClientAuthMethod { const hasClientSecret = clientInformation.client_secret !== undefined; // If server doesn't specify supported methods, use RFC 6749 defaults @@ -172,6 +175,15 @@ function selectClientAuthMethod(clientInformation: OAuthClientInformation, suppo return hasClientSecret ? 'client_secret_post' : 'none'; } + // Prefer the method returned by the server during client registration if valid and supported + if ( + clientInformation.token_endpoint_auth_method && + isClientAuthMethod(clientInformation.token_endpoint_auth_method) && + supportedMethods.includes(clientInformation.token_endpoint_auth_method) + ) { + return clientInformation.token_endpoint_auth_method; + } + // Try methods in priority order (most secure first) if (hasClientSecret && supportedMethods.includes('client_secret_basic')) { return 'client_secret_basic'; @@ -205,7 +217,7 @@ function selectClientAuthMethod(clientInformation: OAuthClientInformation, suppo */ function applyClientAuthentication( method: ClientAuthMethod, - clientInformation: OAuthClientInformation, + clientInformation: OAuthClientInformationFull, headers: Headers, params: URLSearchParams ): void { @@ -793,7 +805,7 @@ export async function startAuthorization( resource }: { metadata?: AuthorizationServerMetadata; - clientInformation: OAuthClientInformation; + clientInformation: OAuthClientInformationFull; redirectUrl: string | URL; scope?: string; state?: string; @@ -876,7 +888,7 @@ export async function exchangeAuthorization( fetchFn }: { metadata?: AuthorizationServerMetadata; - clientInformation: OAuthClientInformation; + clientInformation: OAuthClientInformationFull; authorizationCode: string; codeVerifier: string; redirectUri: string | URL; @@ -955,7 +967,7 @@ export async function refreshAuthorization( fetchFn }: { metadata?: AuthorizationServerMetadata; - clientInformation: OAuthClientInformation; + clientInformation: OAuthClientInformationFull; refreshToken: string; resource?: URL; addClientAuthentication?: OAuthClientProvider['addClientAuthentication']; diff --git a/src/client/sse.test.ts b/src/client/sse.test.ts index 9e4b73e92..dcc0cca1c 100644 --- a/src/client/sse.test.ts +++ b/src/client/sse.test.ts @@ -355,7 +355,11 @@ describe('SSEClientTransport', () => { get clientMetadata() { return { redirect_uris: ['http://localhost/callback'] }; }, - clientInformation: jest.fn(() => ({ client_id: 'test-client-id', client_secret: 'test-client-secret' })), + clientInformation: jest.fn(() => ({ + client_id: 'test-client-id', + client_secret: 'test-client-secret', + redirect_uris: ['http://localhost/callback'] + })), tokens: jest.fn(), saveTokens: jest.fn(), redirectToAuthorization: jest.fn(), @@ -1159,7 +1163,8 @@ describe('SSEClientTransport', () => { const clientInfo = config.clientRegistered ? { client_id: 'test-client-id', - client_secret: 'test-client-secret' + client_secret: 'test-client-secret', + redirect_uris: ['http://localhost/callback'] } : undefined; diff --git a/src/client/streamableHttp.test.ts b/src/client/streamableHttp.test.ts index 3c6a9ec4d..794119bb1 100644 --- a/src/client/streamableHttp.test.ts +++ b/src/client/streamableHttp.test.ts @@ -15,7 +15,11 @@ describe('StreamableHTTPClientTransport', () => { get clientMetadata() { return { redirect_uris: ['http://localhost/callback'] }; }, - clientInformation: jest.fn(() => ({ client_id: 'test-client-id', client_secret: 'test-client-secret' })), + clientInformation: jest.fn(() => ({ + client_id: 'test-client-id', + client_secret: 'test-client-secret', + redirect_uris: ['http://localhost/callback'] + })), tokens: jest.fn(), saveTokens: jest.fn(), redirectToAuthorization: jest.fn(), diff --git a/src/examples/client/simpleOAuthClient.ts b/src/examples/client/simpleOAuthClient.ts index 354886050..2289a4362 100644 --- a/src/examples/client/simpleOAuthClient.ts +++ b/src/examples/client/simpleOAuthClient.ts @@ -6,7 +6,7 @@ import { URL } from 'node:url'; import { exec } from 'node:child_process'; import { Client } from '../../client/index.js'; import { StreamableHTTPClientTransport } from '../../client/streamableHttp.js'; -import { OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '../../shared/auth.js'; +import { OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '../../shared/auth.js'; import { CallToolRequest, ListToolsRequest, CallToolResultSchema, ListToolsResultSchema } from '../../types.js'; import { OAuthClientProvider, UnauthorizedError } from '../../client/auth.js'; @@ -46,7 +46,7 @@ class InMemoryOAuthClientProvider implements OAuthClientProvider { return this._clientMetadata; } - clientInformation(): OAuthClientInformation | undefined { + clientInformation(): OAuthClientInformationFull | undefined { return this._clientInformation; } From 9bda5df6b05af5582e42c394b8f6e94905d8cc53 Mon Sep 17 00:00:00 2001 From: ChipGPT Date: Mon, 27 Oct 2025 20:46:06 -0500 Subject: [PATCH 2/5] apply OAuthClientInformationMixed type --- src/client/auth.test.ts | 30 ++++++++---------------- src/client/auth.ts | 17 ++++++++------ src/client/sse.test.ts | 6 ++--- src/client/streamableHttp.test.ts | 3 +-- src/examples/client/simpleOAuthClient.ts | 8 +++---- src/shared/auth.ts | 1 + 6 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/client/auth.test.ts b/src/client/auth.test.ts index a1f1e188d..f2dadbb15 100644 --- a/src/client/auth.test.ts +++ b/src/client/auth.test.ts @@ -1588,8 +1588,7 @@ describe('OAuth Authorization', () => { // Mock provider methods for authorization flow (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'test-secret' }); (mockProvider.tokens as jest.Mock).mockResolvedValue(undefined); (mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -1659,8 +1658,7 @@ describe('OAuth Authorization', () => { // Mock provider methods for token exchange (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'test-secret' }); (mockProvider.codeVerifier as jest.Mock).mockResolvedValue('test-verifier'); (mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined); @@ -1726,8 +1724,7 @@ describe('OAuth Authorization', () => { // Mock provider methods for token refresh (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'test-secret' }); (mockProvider.tokens as jest.Mock).mockResolvedValue({ access_token: 'old-access', @@ -1793,8 +1790,7 @@ describe('OAuth Authorization', () => { // Mock provider methods (providerWithCustomValidation.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'test-secret' }); (providerWithCustomValidation.tokens as jest.Mock).mockResolvedValue(undefined); (providerWithCustomValidation.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -1849,8 +1845,7 @@ describe('OAuth Authorization', () => { // Mock provider methods (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'test-secret' }); (mockProvider.tokens as jest.Mock).mockResolvedValue(undefined); (mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -1908,8 +1903,7 @@ describe('OAuth Authorization', () => { // Mock provider methods (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'test-secret' }); (mockProvider.tokens as jest.Mock).mockResolvedValue(undefined); (mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -1976,8 +1970,7 @@ describe('OAuth Authorization', () => { // Mock provider methods for token exchange (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'test-secret' }); (mockProvider.codeVerifier as jest.Mock).mockResolvedValue('test-verifier'); (mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined); @@ -2040,8 +2033,7 @@ describe('OAuth Authorization', () => { // Mock provider methods for token refresh (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'test-secret' }); (mockProvider.tokens as jest.Mock).mockResolvedValue({ access_token: 'old-access', @@ -2102,8 +2094,7 @@ describe('OAuth Authorization', () => { // Mock provider methods (mockProvider.clientInformation as jest.Mock).mockResolvedValue({ client_id: 'test-client', - client_secret: 'test-secret', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'test-secret' }); (mockProvider.tokens as jest.Mock).mockResolvedValue(undefined); (mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined); @@ -2165,8 +2156,7 @@ describe('OAuth Authorization', () => { }, clientInformation: jest.fn().mockResolvedValue({ client_id: 'client123', - client_secret: 'secret123', - redirect_uris: ['http://localhost:3000/callback'] + client_secret: 'secret123' }), tokens: jest.fn().mockResolvedValue(undefined), saveTokens: jest.fn(), diff --git a/src/client/auth.ts b/src/client/auth.ts index 6df9dd385..0e3a810e4 100644 --- a/src/client/auth.ts +++ b/src/client/auth.ts @@ -2,6 +2,8 @@ import pkceChallenge from 'pkce-challenge'; import { LATEST_PROTOCOL_VERSION } from '../types.js'; import { OAuthClientMetadata, + OAuthClientInformation, + OAuthClientInformationMixed, OAuthTokens, OAuthMetadata, OAuthClientInformationFull, @@ -55,7 +57,7 @@ export interface OAuthClientProvider { * server, or returns `undefined` if the client is not registered with the * server. */ - clientInformation(): OAuthClientInformationFull | undefined | Promise; + clientInformation(): OAuthClientInformationMixed | undefined | Promise; /** * If implemented, this permits the OAuth client to dynamically register with @@ -65,7 +67,7 @@ export interface OAuthClientProvider { * This method is not required to be implemented if client information is * statically known (e.g., pre-registered). */ - saveClientInformation?(clientInformation: OAuthClientInformationFull): void | Promise; + saveClientInformation?(clientInformation: OAuthClientInformationMixed): void | Promise; /** * Loads any existing OAuth tokens for the current session, or returns @@ -167,7 +169,7 @@ const AUTHORIZATION_CODE_CHALLENGE_METHOD = 'S256'; * @param supportedMethods - Authentication methods supported by the authorization server * @returns The selected authentication method */ -function selectClientAuthMethod(clientInformation: OAuthClientInformationFull, supportedMethods: string[]): ClientAuthMethod { +function selectClientAuthMethod(clientInformation: OAuthClientInformationMixed, supportedMethods: string[]): ClientAuthMethod { const hasClientSecret = clientInformation.client_secret !== undefined; // If server doesn't specify supported methods, use RFC 6749 defaults @@ -177,6 +179,7 @@ function selectClientAuthMethod(clientInformation: OAuthClientInformationFull, s // Prefer the method returned by the server during client registration if valid and supported if ( + 'token_endpoint_auth_method' in clientInformation && clientInformation.token_endpoint_auth_method && isClientAuthMethod(clientInformation.token_endpoint_auth_method) && supportedMethods.includes(clientInformation.token_endpoint_auth_method) @@ -217,7 +220,7 @@ function selectClientAuthMethod(clientInformation: OAuthClientInformationFull, s */ function applyClientAuthentication( method: ClientAuthMethod, - clientInformation: OAuthClientInformationFull, + clientInformation: OAuthClientInformation, headers: Headers, params: URLSearchParams ): void { @@ -805,7 +808,7 @@ export async function startAuthorization( resource }: { metadata?: AuthorizationServerMetadata; - clientInformation: OAuthClientInformationFull; + clientInformation: OAuthClientInformationMixed; redirectUrl: string | URL; scope?: string; state?: string; @@ -888,7 +891,7 @@ export async function exchangeAuthorization( fetchFn }: { metadata?: AuthorizationServerMetadata; - clientInformation: OAuthClientInformationFull; + clientInformation: OAuthClientInformationMixed; authorizationCode: string; codeVerifier: string; redirectUri: string | URL; @@ -967,7 +970,7 @@ export async function refreshAuthorization( fetchFn }: { metadata?: AuthorizationServerMetadata; - clientInformation: OAuthClientInformationFull; + clientInformation: OAuthClientInformationMixed; refreshToken: string; resource?: URL; addClientAuthentication?: OAuthClientProvider['addClientAuthentication']; diff --git a/src/client/sse.test.ts b/src/client/sse.test.ts index dcc0cca1c..f54dd7434 100644 --- a/src/client/sse.test.ts +++ b/src/client/sse.test.ts @@ -357,8 +357,7 @@ describe('SSEClientTransport', () => { }, clientInformation: jest.fn(() => ({ client_id: 'test-client-id', - client_secret: 'test-client-secret', - redirect_uris: ['http://localhost/callback'] + client_secret: 'test-client-secret' })), tokens: jest.fn(), saveTokens: jest.fn(), @@ -1163,8 +1162,7 @@ describe('SSEClientTransport', () => { const clientInfo = config.clientRegistered ? { client_id: 'test-client-id', - client_secret: 'test-client-secret', - redirect_uris: ['http://localhost/callback'] + client_secret: 'test-client-secret' } : undefined; diff --git a/src/client/streamableHttp.test.ts b/src/client/streamableHttp.test.ts index 794119bb1..24618fbc6 100644 --- a/src/client/streamableHttp.test.ts +++ b/src/client/streamableHttp.test.ts @@ -17,8 +17,7 @@ describe('StreamableHTTPClientTransport', () => { }, clientInformation: jest.fn(() => ({ client_id: 'test-client-id', - client_secret: 'test-client-secret', - redirect_uris: ['http://localhost/callback'] + client_secret: 'test-client-secret' })), tokens: jest.fn(), saveTokens: jest.fn(), diff --git a/src/examples/client/simpleOAuthClient.ts b/src/examples/client/simpleOAuthClient.ts index 2289a4362..fc296bc6a 100644 --- a/src/examples/client/simpleOAuthClient.ts +++ b/src/examples/client/simpleOAuthClient.ts @@ -6,7 +6,7 @@ import { URL } from 'node:url'; import { exec } from 'node:child_process'; import { Client } from '../../client/index.js'; import { StreamableHTTPClientTransport } from '../../client/streamableHttp.js'; -import { OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '../../shared/auth.js'; +import { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from '../../shared/auth.js'; import { CallToolRequest, ListToolsRequest, CallToolResultSchema, ListToolsResultSchema } from '../../types.js'; import { OAuthClientProvider, UnauthorizedError } from '../../client/auth.js'; @@ -20,7 +20,7 @@ const CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`; * In production, you should persist tokens securely */ class InMemoryOAuthClientProvider implements OAuthClientProvider { - private _clientInformation?: OAuthClientInformationFull; + private _clientInformation?: OAuthClientInformationMixed; private _tokens?: OAuthTokens; private _codeVerifier?: string; @@ -46,11 +46,11 @@ class InMemoryOAuthClientProvider implements OAuthClientProvider { return this._clientMetadata; } - clientInformation(): OAuthClientInformationFull | undefined { + clientInformation(): OAuthClientInformationMixed | undefined { return this._clientInformation; } - saveClientInformation(clientInformation: OAuthClientInformationFull): void { + saveClientInformation(clientInformation: OAuthClientInformationMixed): void { this._clientInformation = clientInformation; } diff --git a/src/shared/auth.ts b/src/shared/auth.ts index c5ddbda16..819b33086 100644 --- a/src/shared/auth.ts +++ b/src/shared/auth.ts @@ -226,6 +226,7 @@ export type OAuthErrorResponse = z.infer; export type OAuthClientMetadata = z.infer; export type OAuthClientInformation = z.infer; export type OAuthClientInformationFull = z.infer; +export type OAuthClientInformationMixed = OAuthClientInformation | OAuthClientInformationFull; export type OAuthClientRegistrationError = z.infer; export type OAuthTokenRevocationRequest = z.infer; export type OAuthProtectedResourceMetadata = z.infer; From fb487a70cade87b961a19835acf1e8035dc966a7 Mon Sep 17 00:00:00 2001 From: ChipGPT Date: Mon, 27 Oct 2025 20:49:03 -0500 Subject: [PATCH 3/5] remove whitespace changes --- src/client/sse.test.ts | 5 +---- src/client/streamableHttp.test.ts | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/client/sse.test.ts b/src/client/sse.test.ts index f54dd7434..9e4b73e92 100644 --- a/src/client/sse.test.ts +++ b/src/client/sse.test.ts @@ -355,10 +355,7 @@ describe('SSEClientTransport', () => { get clientMetadata() { return { redirect_uris: ['http://localhost/callback'] }; }, - clientInformation: jest.fn(() => ({ - client_id: 'test-client-id', - client_secret: 'test-client-secret' - })), + clientInformation: jest.fn(() => ({ client_id: 'test-client-id', client_secret: 'test-client-secret' })), tokens: jest.fn(), saveTokens: jest.fn(), redirectToAuthorization: jest.fn(), diff --git a/src/client/streamableHttp.test.ts b/src/client/streamableHttp.test.ts index 24618fbc6..3c6a9ec4d 100644 --- a/src/client/streamableHttp.test.ts +++ b/src/client/streamableHttp.test.ts @@ -15,10 +15,7 @@ describe('StreamableHTTPClientTransport', () => { get clientMetadata() { return { redirect_uris: ['http://localhost/callback'] }; }, - clientInformation: jest.fn(() => ({ - client_id: 'test-client-id', - client_secret: 'test-client-secret' - })), + clientInformation: jest.fn(() => ({ client_id: 'test-client-id', client_secret: 'test-client-secret' })), tokens: jest.fn(), saveTokens: jest.fn(), redirectToAuthorization: jest.fn(), From fd3665f4c30bb544ec61c887e3a4c646a65ada66 Mon Sep 17 00:00:00 2001 From: ChipGPT Date: Mon, 27 Oct 2025 21:04:20 -0500 Subject: [PATCH 4/5] add test for `selectClientAuthMethod` --- src/client/auth.test.ts | 18 +++++++++++++++++- src/client/auth.ts | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/client/auth.test.ts b/src/client/auth.test.ts index f2dadbb15..c4bf32f49 100644 --- a/src/client/auth.test.ts +++ b/src/client/auth.test.ts @@ -10,7 +10,8 @@ import { discoverOAuthProtectedResourceMetadata, extractResourceMetadataUrl, auth, - type OAuthClientProvider + type OAuthClientProvider, + selectClientAuthMethod } from './auth.js'; import { ServerError } from '../server/auth/errors.js'; import { AuthorizationServerMetadata } from '../shared/auth.js'; @@ -881,6 +882,21 @@ describe('OAuth Authorization', () => { }); }); + describe('selectClientAuthMethod', () => { + it('selects the correct client authentication method from client information', () => { + const clientInfo = { client_id: 'test-client-id', client_secret: 'test-client-secret', token_endpoint_auth_method: 'client_secret_basic' }; + const supportedMethods = ['client_secret_post', 'client_secret_basic', 'none']; + const authMethod = selectClientAuthMethod(clientInfo, supportedMethods); + expect(authMethod).toBe('client_secret_basic'); + }); + it('selects the correct client authentication method from supported methods', () => { + const clientInfo = { client_id: 'test-client-id' }; + const supportedMethods = ['client_secret_post', 'client_secret_basic', 'none']; + const authMethod = selectClientAuthMethod(clientInfo, supportedMethods); + expect(authMethod).toBe('none'); + }); + }); + describe('startAuthorization', () => { const validMetadata = { issuer: 'https://auth.example.com', diff --git a/src/client/auth.ts b/src/client/auth.ts index 0e3a810e4..5e48345a3 100644 --- a/src/client/auth.ts +++ b/src/client/auth.ts @@ -169,7 +169,7 @@ const AUTHORIZATION_CODE_CHALLENGE_METHOD = 'S256'; * @param supportedMethods - Authentication methods supported by the authorization server * @returns The selected authentication method */ -function selectClientAuthMethod(clientInformation: OAuthClientInformationMixed, supportedMethods: string[]): ClientAuthMethod { +export function selectClientAuthMethod(clientInformation: OAuthClientInformationMixed, supportedMethods: string[]): ClientAuthMethod { const hasClientSecret = clientInformation.client_secret !== undefined; // If server doesn't specify supported methods, use RFC 6749 defaults From 779f7e49575387fb5c0529761b30b4345e491e3a Mon Sep 17 00:00:00 2001 From: ChipGPT Date: Mon, 27 Oct 2025 21:09:55 -0500 Subject: [PATCH 5/5] delint --- src/client/auth.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client/auth.test.ts b/src/client/auth.test.ts index c4bf32f49..6c924898a 100644 --- a/src/client/auth.test.ts +++ b/src/client/auth.test.ts @@ -884,7 +884,11 @@ describe('OAuth Authorization', () => { describe('selectClientAuthMethod', () => { it('selects the correct client authentication method from client information', () => { - const clientInfo = { client_id: 'test-client-id', client_secret: 'test-client-secret', token_endpoint_auth_method: 'client_secret_basic' }; + const clientInfo = { + client_id: 'test-client-id', + client_secret: 'test-client-secret', + token_endpoint_auth_method: 'client_secret_basic' + }; const supportedMethods = ['client_secret_post', 'client_secret_basic', 'none']; const authMethod = selectClientAuthMethod(clientInfo, supportedMethods); expect(authMethod).toBe('client_secret_basic');