Make authorizationUrl and tokenUrl mandatory
This commit is contained in:
parent
96f3d81aa3
commit
57f59c69a1
4 changed files with 36 additions and 83 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/sdk",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@modelcontextprotocol/sdk",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
|
|
|
|||
|
|
@ -298,6 +298,7 @@ describe('Token Handler', () => {
|
|||
|
||||
const proxyProvider = new ProxyOAuthServerProvider({
|
||||
endpoints: {
|
||||
authorizationUrl: 'https://example.com/authorize',
|
||||
tokenUrl: 'https://example.com/token'
|
||||
},
|
||||
verifyAccessToken: async (token) => ({
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ describe("Proxy OAuth Server Provider", () => {
|
|||
// Mock provider functions
|
||||
const mockVerifyToken = jest.fn();
|
||||
const mockGetClient = jest.fn();
|
||||
|
||||
|
||||
// Base provider options
|
||||
const baseOptions: ProxyOptions = {
|
||||
endpoints: {
|
||||
|
|
@ -66,7 +66,7 @@ describe("Proxy OAuth Server Provider", () => {
|
|||
|
||||
// Add helper function for failed responses
|
||||
const mockFailedResponse = () => {
|
||||
(global.fetch as jest.Mock).mockImplementation(() =>
|
||||
(global.fetch as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 400,
|
||||
|
|
@ -103,20 +103,6 @@ describe("Proxy OAuth Server Provider", () => {
|
|||
|
||||
expect(mockResponse.redirect).toHaveBeenCalledWith(expectedUrl.toString());
|
||||
});
|
||||
|
||||
it("throws error when authorization endpoint is not configured", async () => {
|
||||
const providerWithoutAuth = new ProxyOAuthServerProvider({
|
||||
...baseOptions,
|
||||
endpoints: { ...baseOptions.endpoints, authorizationUrl: undefined },
|
||||
});
|
||||
|
||||
await expect(
|
||||
providerWithoutAuth.authorize(validClient, {
|
||||
redirectUri: "https://example.com/callback",
|
||||
codeChallenge: "test-challenge",
|
||||
}, mockResponse)
|
||||
).rejects.toThrow("No authorization endpoint configured");
|
||||
});
|
||||
});
|
||||
|
||||
describe("token exchange", () => {
|
||||
|
|
@ -128,7 +114,7 @@ describe("Proxy OAuth Server Provider", () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(global.fetch as jest.Mock).mockImplementation(() =>
|
||||
(global.fetch as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockTokenResponse),
|
||||
|
|
@ -176,23 +162,6 @@ describe("Proxy OAuth Server Provider", () => {
|
|||
expect(tokens).toEqual(mockTokenResponse);
|
||||
});
|
||||
|
||||
it("throws error when token endpoint is not configured", async () => {
|
||||
const providerWithoutToken = new ProxyOAuthServerProvider({
|
||||
...baseOptions,
|
||||
endpoints: { ...baseOptions.endpoints, tokenUrl: undefined },
|
||||
});
|
||||
|
||||
await expect(
|
||||
providerWithoutToken.exchangeAuthorizationCode(validClient, "test-code")
|
||||
).rejects.toThrow("No token endpoint configured");
|
||||
});
|
||||
|
||||
it("handles token exchange failure", async () => {
|
||||
mockFailedResponse();
|
||||
await expect(
|
||||
provider.exchangeAuthorizationCode(validClient, "invalid-code")
|
||||
).rejects.toThrow(ServerError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("client registration", () => {
|
||||
|
|
@ -202,7 +171,7 @@ describe("Proxy OAuth Server Provider", () => {
|
|||
redirect_uris: ["https://new-client.com/callback"],
|
||||
};
|
||||
|
||||
(global.fetch as jest.Mock).mockImplementation(() =>
|
||||
(global.fetch as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(newClient),
|
||||
|
|
@ -239,7 +208,7 @@ describe("Proxy OAuth Server Provider", () => {
|
|||
|
||||
describe("token revocation", () => {
|
||||
it("revokes token", async () => {
|
||||
(global.fetch as jest.Mock).mockImplementation(() =>
|
||||
(global.fetch as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { Response } from "express";
|
||||
import { OAuthRegisteredClientsStore } from "./clients.js";
|
||||
import {
|
||||
OAuthClientInformationFull,
|
||||
OAuthClientInformationFullSchema,
|
||||
OAuthTokenRevocationRequest,
|
||||
import {
|
||||
OAuthClientInformationFull,
|
||||
OAuthClientInformationFullSchema,
|
||||
OAuthTokenRevocationRequest,
|
||||
OAuthTokens,
|
||||
OAuthTokensSchema,
|
||||
} from "./../../shared/auth.js";
|
||||
|
|
@ -12,8 +12,8 @@ import { AuthorizationParams, OAuthServerProvider } from "./provider.js";
|
|||
import { ServerError } from "./errors.js";
|
||||
|
||||
export type ProxyEndpoints = {
|
||||
authorizationUrl?: string;
|
||||
tokenUrl?: string;
|
||||
authorizationUrl: string;
|
||||
tokenUrl: string;
|
||||
revocationUrl?: string;
|
||||
registrationUrl?: string;
|
||||
};
|
||||
|
|
@ -24,14 +24,14 @@ export type ProxyOptions = {
|
|||
*/
|
||||
endpoints: ProxyEndpoints;
|
||||
|
||||
/**
|
||||
* Function to verify access tokens and return auth info
|
||||
*/
|
||||
verifyAccessToken: (token: string) => Promise<AuthInfo>;
|
||||
/**
|
||||
* Function to verify access tokens and return auth info
|
||||
*/
|
||||
verifyAccessToken: (token: string) => Promise<AuthInfo>;
|
||||
|
||||
/**
|
||||
* Function to fetch client information from the upstream server
|
||||
*/
|
||||
/**
|
||||
* Function to fetch client information from the upstream server
|
||||
*/
|
||||
getClient: (clientId: string) => Promise<OAuthClientInformationFull | undefined>;
|
||||
|
||||
};
|
||||
|
|
@ -45,7 +45,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
protected readonly _getClient: (clientId: string) => Promise<OAuthClientInformationFull | undefined>;
|
||||
|
||||
revokeToken?: (
|
||||
client: OAuthClientInformationFull,
|
||||
client: OAuthClientInformationFull,
|
||||
request: OAuthTokenRevocationRequest
|
||||
) => Promise<void>;
|
||||
|
||||
|
|
@ -55,15 +55,15 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
this._getClient = options.getClient;
|
||||
if (options.endpoints?.revocationUrl) {
|
||||
this.revokeToken = async (
|
||||
client: OAuthClientInformationFull,
|
||||
client: OAuthClientInformationFull,
|
||||
request: OAuthTokenRevocationRequest
|
||||
) => {
|
||||
const revocationUrl = this._endpoints.revocationUrl;
|
||||
|
||||
|
||||
if (!revocationUrl) {
|
||||
throw new Error("No revocation endpoint configured");
|
||||
}
|
||||
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.set("token", request.token);
|
||||
params.set("client_id", client.client_id);
|
||||
|
|
@ -73,7 +73,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
if (request.token_type_hint) {
|
||||
params.set("token_type_hint", request.token_type_hint);
|
||||
}
|
||||
|
||||
|
||||
const response = await fetch(revocationUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -81,7 +81,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
},
|
||||
body: params.toString(),
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ServerError(`Token revocation failed: ${response.status}`);
|
||||
}
|
||||
|
|
@ -115,18 +115,12 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
}
|
||||
|
||||
async authorize(
|
||||
client: OAuthClientInformationFull,
|
||||
params: AuthorizationParams,
|
||||
client: OAuthClientInformationFull,
|
||||
params: AuthorizationParams,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const authorizationUrl = this._endpoints.authorizationUrl;
|
||||
|
||||
if (!authorizationUrl) {
|
||||
throw new Error("No authorization endpoint configured");
|
||||
}
|
||||
|
||||
// Start with required OAuth parameters
|
||||
const targetUrl = new URL(authorizationUrl);
|
||||
const targetUrl = new URL(this._endpoints.authorizationUrl);
|
||||
const searchParams = new URLSearchParams({
|
||||
client_id: client.client_id,
|
||||
response_type: "code",
|
||||
|
|
@ -144,7 +138,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
}
|
||||
|
||||
async challengeForAuthorizationCode(
|
||||
_client: OAuthClientInformationFull,
|
||||
_client: OAuthClientInformationFull,
|
||||
_authorizationCode: string
|
||||
): Promise<string> {
|
||||
// In a proxy setup, we don't store the code challenge ourselves
|
||||
|
|
@ -153,16 +147,10 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
}
|
||||
|
||||
async exchangeAuthorizationCode(
|
||||
client: OAuthClientInformationFull,
|
||||
client: OAuthClientInformationFull,
|
||||
authorizationCode: string,
|
||||
codeVerifier?: string
|
||||
): Promise<OAuthTokens> {
|
||||
const tokenUrl = this._endpoints.tokenUrl;
|
||||
|
||||
if (!tokenUrl) {
|
||||
throw new Error("No token endpoint configured");
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
client_id: client.client_id,
|
||||
|
|
@ -177,7 +165,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
params.append("code_verifier", codeVerifier);
|
||||
}
|
||||
|
||||
const response = await fetch(tokenUrl, {
|
||||
const response = await fetch(this._endpoints.tokenUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
|
|
@ -195,15 +183,10 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
}
|
||||
|
||||
async exchangeRefreshToken(
|
||||
client: OAuthClientInformationFull,
|
||||
client: OAuthClientInformationFull,
|
||||
refreshToken: string,
|
||||
scopes?: string[]
|
||||
): Promise<OAuthTokens> {
|
||||
const tokenUrl = this._endpoints.tokenUrl;
|
||||
|
||||
if (!tokenUrl) {
|
||||
throw new Error("No token endpoint configured");
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
|
|
@ -219,7 +202,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
params.set("scope", scopes.join(" "));
|
||||
}
|
||||
|
||||
const response = await fetch(tokenUrl, {
|
||||
const response = await fetch(this._endpoints.tokenUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
|
|
@ -237,5 +220,5 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
|
|||
|
||||
async verifyAccessToken(token: string): Promise<AuthInfo> {
|
||||
return this._verifyAccessToken(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue