cleanups
This commit is contained in:
parent
ec0c50425e
commit
b16a415623
8 changed files with 25 additions and 52 deletions
|
|
@ -938,8 +938,7 @@ describe("OAuth Authorization", () => {
|
|||
|
||||
// Call the auth function with a resource that has a fragment
|
||||
const result = await auth(mockProvider, {
|
||||
serverUrl: "https://resource.example.com",
|
||||
resource: new URL("https://api.example.com/mcp-server#fragment"),
|
||||
serverUrl: "https://api.example.com/mcp-server#fragment",
|
||||
});
|
||||
|
||||
expect(result).toBe("REDIRECT");
|
||||
|
|
@ -987,8 +986,7 @@ describe("OAuth Authorization", () => {
|
|||
|
||||
// Call auth without authorization code (should trigger redirect)
|
||||
const result = await auth(mockProvider, {
|
||||
serverUrl: "https://resource.example.com",
|
||||
resource: new URL("https://api.example.com/mcp-server"),
|
||||
serverUrl: "https://api.example.com/mcp-server",
|
||||
});
|
||||
|
||||
expect(result).toBe("REDIRECT");
|
||||
|
|
@ -1048,9 +1046,8 @@ describe("OAuth Authorization", () => {
|
|||
|
||||
// Call auth with authorization code
|
||||
const result = await auth(mockProvider, {
|
||||
serverUrl: "https://resource.example.com",
|
||||
serverUrl: "https://api.example.com/mcp-server",
|
||||
authorizationCode: "auth-code-123",
|
||||
resource: new URL("https://api.example.com/mcp-server"),
|
||||
});
|
||||
|
||||
expect(result).toBe("AUTHORIZED");
|
||||
|
|
@ -1111,8 +1108,7 @@ describe("OAuth Authorization", () => {
|
|||
|
||||
// Call auth with existing tokens (should trigger refresh)
|
||||
const result = await auth(mockProvider, {
|
||||
serverUrl: "https://resource.example.com",
|
||||
resource: new URL("https://api.example.com/mcp-server"),
|
||||
serverUrl: "https://api.example.com/mcp-server",
|
||||
});
|
||||
|
||||
expect(result).toBe("AUTHORIZED");
|
||||
|
|
@ -1160,8 +1156,7 @@ describe("OAuth Authorization", () => {
|
|||
|
||||
// Call auth with empty resource parameter
|
||||
const result = await auth(mockProvider, {
|
||||
serverUrl: "https://resource.example.com",
|
||||
resource: undefined,
|
||||
serverUrl: "https://api.example.com/mcp-server",
|
||||
});
|
||||
|
||||
expect(result).toBe("REDIRECT");
|
||||
|
|
@ -1203,8 +1198,7 @@ describe("OAuth Authorization", () => {
|
|||
|
||||
// Call auth with resource containing multiple # symbols
|
||||
const result = await auth(mockProvider, {
|
||||
serverUrl: "https://resource.example.com",
|
||||
resource: new URL("https://api.example.com/mcp-server#fragment#another"),
|
||||
serverUrl: "https://api.example.com/mcp-server#fragment#another",
|
||||
});
|
||||
|
||||
expect(result).toBe("REDIRECT");
|
||||
|
|
@ -1248,8 +1242,7 @@ describe("OAuth Authorization", () => {
|
|||
// This tests the security fix that prevents token confusion between
|
||||
// multiple MCP servers on the same domain
|
||||
const result1 = await auth(mockProvider, {
|
||||
serverUrl: "https://api.example.com",
|
||||
resource: new URL("https://api.example.com/mcp-server-1/v1"),
|
||||
serverUrl: "https://api.example.com/mcp-server-1/v1",
|
||||
});
|
||||
|
||||
expect(result1).toBe("REDIRECT");
|
||||
|
|
@ -1263,8 +1256,7 @@ describe("OAuth Authorization", () => {
|
|||
|
||||
// Test with different path on same domain
|
||||
const result2 = await auth(mockProvider, {
|
||||
serverUrl: "https://api.example.com",
|
||||
resource: new URL("https://api.example.com/mcp-server-2/v1"),
|
||||
serverUrl: "https://api.example.com/mcp-server-2/v1",
|
||||
});
|
||||
|
||||
expect(result2).toBe("REDIRECT");
|
||||
|
|
@ -1308,8 +1300,7 @@ describe("OAuth Authorization", () => {
|
|||
|
||||
// Call auth with resource containing query parameters
|
||||
const result = await auth(mockProvider, {
|
||||
serverUrl: "https://resource.example.com",
|
||||
resource: new URL("https://api.example.com/mcp-server?param=value&another=test"),
|
||||
serverUrl: "https://api.example.com/mcp-server?param=value&another=test",
|
||||
});
|
||||
|
||||
expect(result).toBe("REDIRECT");
|
||||
|
|
|
|||
|
|
@ -94,19 +94,13 @@ export async function auth(
|
|||
authorizationCode,
|
||||
scope,
|
||||
resourceMetadataUrl,
|
||||
resource
|
||||
}: {
|
||||
serverUrl: string | URL;
|
||||
authorizationCode?: string;
|
||||
scope?: string;
|
||||
resourceMetadataUrl?: URL;
|
||||
resource?: URL }): Promise<AuthResult> {
|
||||
resourceMetadataUrl?: URL }): Promise<AuthResult> {
|
||||
|
||||
// Remove fragment from resource parameter if provided
|
||||
let canonicalResource: URL | undefined;
|
||||
if (resource) {
|
||||
canonicalResource = resourceUrlFromServerUrl(new URL(resource));
|
||||
}
|
||||
const resource = resourceUrlFromServerUrl(typeof serverUrl === "string" ? new URL(serverUrl) : serverUrl);
|
||||
|
||||
let authorizationServerUrl = serverUrl;
|
||||
try {
|
||||
|
|
@ -151,7 +145,7 @@ export async function auth(
|
|||
authorizationCode,
|
||||
codeVerifier,
|
||||
redirectUri: provider.redirectUrl,
|
||||
resource: canonicalResource,
|
||||
resource,
|
||||
});
|
||||
|
||||
await provider.saveTokens(tokens);
|
||||
|
|
@ -168,7 +162,7 @@ export async function auth(
|
|||
metadata,
|
||||
clientInformation,
|
||||
refreshToken: tokens.refresh_token,
|
||||
resource: canonicalResource,
|
||||
resource,
|
||||
});
|
||||
|
||||
await provider.saveTokens(newTokens);
|
||||
|
|
@ -187,7 +181,7 @@ export async function auth(
|
|||
state,
|
||||
redirectUrl: provider.redirectUrl,
|
||||
scope: scope || provider.clientMetadata.scope,
|
||||
resource: canonicalResource,
|
||||
resource,
|
||||
});
|
||||
|
||||
await provider.saveCodeVerifier(codeVerifier);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ export class SSEClientTransport implements Transport {
|
|||
result = await auth(this._authProvider, {
|
||||
serverUrl: this._url,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
resource: resourceUrlFromServerUrl(new URL(this._url))
|
||||
});
|
||||
} catch (error) {
|
||||
this.onerror?.(error as Error);
|
||||
|
|
@ -210,7 +209,6 @@ export class SSEClientTransport implements Transport {
|
|||
serverUrl: this._url,
|
||||
authorizationCode,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
resource: resourceUrlFromServerUrl(new URL(this._url))
|
||||
});
|
||||
if (result !== "AUTHORIZED") {
|
||||
throw new UnauthorizedError("Failed to authorize");
|
||||
|
|
@ -249,7 +247,6 @@ export class SSEClientTransport implements Transport {
|
|||
const result = await auth(this._authProvider, {
|
||||
serverUrl: this._url,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
resource: resourceUrlFromServerUrl(new URL(this._url))
|
||||
});
|
||||
if (result !== "AUTHORIZED") {
|
||||
throw new UnauthorizedError();
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ export class StreamableHTTPClientTransport implements Transport {
|
|||
result = await auth(this._authProvider, {
|
||||
serverUrl: this._url,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
resource: resourceUrlFromServerUrl(new URL(this._url))
|
||||
});
|
||||
} catch (error) {
|
||||
this.onerror?.(error as Error);
|
||||
|
|
@ -371,7 +370,6 @@ export class StreamableHTTPClientTransport implements Transport {
|
|||
serverUrl: this._url,
|
||||
authorizationCode,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
resource: resourceUrlFromServerUrl(new URL(this._url))
|
||||
});
|
||||
if (result !== "AUTHORIZED") {
|
||||
throw new UnauthorizedError("Failed to authorize");
|
||||
|
|
@ -423,7 +421,6 @@ export class StreamableHTTPClientTransport implements Transport {
|
|||
const result = await auth(this._authProvider, {
|
||||
serverUrl: this._url,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
resource: resourceUrlFromServerUrl(new URL(this._url))
|
||||
});
|
||||
if (result !== "AUTHORIZED") {
|
||||
throw new UnauthorizedError();
|
||||
|
|
|
|||
|
|
@ -21,6 +21,14 @@ export class DemoInMemoryClientsStore implements OAuthRegisteredClientsStore {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚨 DEMO ONLY - NOT FOR PRODUCTION
|
||||
*
|
||||
* This example demonstrates MCP OAuth flow but lacks some of the features required for production use,
|
||||
* for example:
|
||||
* - Persistent token storage
|
||||
* - Rate limiting
|
||||
*/
|
||||
export class DemoInMemoryAuthProvider implements OAuthServerProvider {
|
||||
clientsStore = new DemoInMemoryClientsStore();
|
||||
private codes = new Map<string, {
|
||||
|
|
@ -119,12 +127,12 @@ export class DemoInMemoryAuthProvider implements OAuthServerProvider {
|
|||
}
|
||||
|
||||
async exchangeRefreshToken(
|
||||
client: OAuthClientInformationFull,
|
||||
_client: OAuthClientInformationFull,
|
||||
_refreshToken: string,
|
||||
_scopes?: string[],
|
||||
resource?: URL
|
||||
): Promise<OAuthTokens> {
|
||||
throw new Error('Refresh tokens not implemented for example demo');
|
||||
throw new Error('Not implemented for example demo');
|
||||
}
|
||||
|
||||
async verifyAccessToken(token: string): Promise<AuthInfo> {
|
||||
|
|
|
|||
|
|
@ -282,12 +282,7 @@ if (useOAuth) {
|
|||
const mcpServerUrl = new URL(`http://localhost:${MCP_PORT}`);
|
||||
const authServerUrl = new URL(`http://localhost:${AUTH_PORT}`);
|
||||
|
||||
// Configure the demo auth provider to validate resources match this server
|
||||
const demoProviderConfig = {
|
||||
serverUrl: mcpServerUrl.href,
|
||||
validateResourceMatchesServer: false // Set to true to enable strict validation
|
||||
};
|
||||
const oauthMetadata: OAuthMetadata = setupAuthServer(authServerUrl, demoProviderConfig);
|
||||
const oauthMetadata: OAuthMetadata = setupAuthServer(authServerUrl, mcpServerUrl);
|
||||
|
||||
const tokenVerifier = {
|
||||
verifyAccessToken: async (token: string) => {
|
||||
|
|
|
|||
|
|
@ -119,9 +119,6 @@ export function authorizationHandler({ provider, rateLimit: rateLimitConfig }: A
|
|||
const { scope, code_challenge, resource } = parseResult.data;
|
||||
state = parseResult.data.state;
|
||||
|
||||
// Pass through the resource parameter to the provider
|
||||
// The provider can decide how to validate it
|
||||
|
||||
// Validate scopes
|
||||
let requestedScopes: string[] = [];
|
||||
if (scope !== undefined) {
|
||||
|
|
|
|||
|
|
@ -93,9 +93,6 @@ export function tokenHandler({ provider, rateLimit: rateLimitConfig }: TokenHand
|
|||
|
||||
const { code, code_verifier, redirect_uri, resource } = parseResult.data;
|
||||
|
||||
// Pass through the resource parameter to the provider
|
||||
// The provider can decide how to validate it
|
||||
|
||||
const skipLocalPkceValidation = provider.skipLocalPkceValidation;
|
||||
|
||||
// Perform local PKCE validation unless explicitly skipped
|
||||
|
|
@ -127,9 +124,6 @@ export function tokenHandler({ provider, rateLimit: rateLimitConfig }: TokenHand
|
|||
|
||||
const { refresh_token, scope, resource } = parseResult.data;
|
||||
|
||||
// Pass through the resource parameter to the provider
|
||||
// The provider can decide how to validate it
|
||||
|
||||
const scopes = scope?.split(" ");
|
||||
const tokens = await provider.exchangeRefreshToken(client, refresh_token, scopes, resource ? new URL(resource) : undefined);
|
||||
res.status(200).json(tokens);
|
||||
|
|
|
|||
Loading…
Reference in a new issue