mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
fix(core): use RFC 9728 compliant path-based OAuth protected resource discovery (#15756)
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
This commit is contained in:
@@ -28,21 +28,8 @@ describe('OAuthUtils', () => {
|
||||
});
|
||||
|
||||
describe('buildWellKnownUrls', () => {
|
||||
it('should build standard root-based URLs by default', () => {
|
||||
it('should build RFC 9728 compliant path-based URLs by default', () => {
|
||||
const urls = OAuthUtils.buildWellKnownUrls('https://example.com/mcp');
|
||||
expect(urls.protectedResource).toBe(
|
||||
'https://example.com/.well-known/oauth-protected-resource',
|
||||
);
|
||||
expect(urls.authorizationServer).toBe(
|
||||
'https://example.com/.well-known/oauth-authorization-server',
|
||||
);
|
||||
});
|
||||
|
||||
it('should build path-based URLs when includePathSuffix is true', () => {
|
||||
const urls = OAuthUtils.buildWellKnownUrls(
|
||||
'https://example.com/mcp',
|
||||
true,
|
||||
);
|
||||
expect(urls.protectedResource).toBe(
|
||||
'https://example.com/.well-known/oauth-protected-resource/mcp',
|
||||
);
|
||||
@@ -51,8 +38,21 @@ describe('OAuthUtils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should build root-based URLs when useRootDiscovery is true', () => {
|
||||
const urls = OAuthUtils.buildWellKnownUrls(
|
||||
'https://example.com/mcp',
|
||||
true,
|
||||
);
|
||||
expect(urls.protectedResource).toBe(
|
||||
'https://example.com/.well-known/oauth-protected-resource',
|
||||
);
|
||||
expect(urls.authorizationServer).toBe(
|
||||
'https://example.com/.well-known/oauth-authorization-server',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle root path correctly', () => {
|
||||
const urls = OAuthUtils.buildWellKnownUrls('https://example.com', true);
|
||||
const urls = OAuthUtils.buildWellKnownUrls('https://example.com');
|
||||
expect(urls.protectedResource).toBe(
|
||||
'https://example.com/.well-known/oauth-protected-resource',
|
||||
);
|
||||
@@ -62,10 +62,7 @@ describe('OAuthUtils', () => {
|
||||
});
|
||||
|
||||
it('should handle trailing slash in path', () => {
|
||||
const urls = OAuthUtils.buildWellKnownUrls(
|
||||
'https://example.com/mcp/',
|
||||
true,
|
||||
);
|
||||
const urls = OAuthUtils.buildWellKnownUrls('https://example.com/mcp/');
|
||||
expect(urls.protectedResource).toBe(
|
||||
'https://example.com/.well-known/oauth-protected-resource/mcp',
|
||||
);
|
||||
@@ -73,6 +70,18 @@ describe('OAuthUtils', () => {
|
||||
'https://example.com/.well-known/oauth-authorization-server/mcp',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle deep paths per RFC 9728', () => {
|
||||
const urls = OAuthUtils.buildWellKnownUrls(
|
||||
'https://app.mintmcp.com/s/g_2lj2CNDoJdf3xnbFeeF6vx/mcp',
|
||||
);
|
||||
expect(urls.protectedResource).toBe(
|
||||
'https://app.mintmcp.com/.well-known/oauth-protected-resource/s/g_2lj2CNDoJdf3xnbFeeF6vx/mcp',
|
||||
);
|
||||
expect(urls.authorizationServer).toBe(
|
||||
'https://app.mintmcp.com/.well-known/oauth-authorization-server/s/g_2lj2CNDoJdf3xnbFeeF6vx/mcp',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchProtectedResourceMetadata', () => {
|
||||
|
||||
@@ -55,30 +55,26 @@ export const FIVE_MIN_BUFFER_MS = 5 * 60 * 1000;
|
||||
*/
|
||||
export class OAuthUtils {
|
||||
/**
|
||||
* Construct well-known OAuth endpoint URLs.
|
||||
* By default, uses standard root-based well-known URLs.
|
||||
* If includePathSuffix is true, appends any path from the base URL to the well-known endpoints.
|
||||
* Construct well-known OAuth endpoint URLs per RFC 9728 §3.1.
|
||||
*
|
||||
* The well-known URI is constructed by inserting /.well-known/oauth-protected-resource
|
||||
* between the host and any existing path component. This preserves the resource's
|
||||
* path structure in the metadata URL.
|
||||
*
|
||||
* Examples:
|
||||
* - https://example.com -> https://example.com/.well-known/oauth-protected-resource
|
||||
* - https://example.com/api/resource -> https://example.com/.well-known/oauth-protected-resource/api/resource
|
||||
*
|
||||
* @param baseUrl The resource URL
|
||||
* @param useRootDiscovery If true, ignores path and uses root-based discovery (for fallback compatibility)
|
||||
*/
|
||||
static buildWellKnownUrls(baseUrl: string, includePathSuffix = false) {
|
||||
static buildWellKnownUrls(baseUrl: string, useRootDiscovery = false) {
|
||||
const serverUrl = new URL(baseUrl);
|
||||
const base = `${serverUrl.protocol}//${serverUrl.host}`;
|
||||
const pathSuffix = useRootDiscovery
|
||||
? ''
|
||||
: serverUrl.pathname.replace(/\/$/, ''); // Remove trailing slash
|
||||
|
||||
if (!includePathSuffix) {
|
||||
// Standard discovery: use root-based well-known URLs
|
||||
return {
|
||||
protectedResource: new URL(
|
||||
'/.well-known/oauth-protected-resource',
|
||||
base,
|
||||
).toString(),
|
||||
authorizationServer: new URL(
|
||||
'/.well-known/oauth-authorization-server',
|
||||
base,
|
||||
).toString(),
|
||||
};
|
||||
}
|
||||
|
||||
// Path-based discovery: append path suffix to well-known URLs
|
||||
const pathSuffix = serverUrl.pathname.replace(/\/$/, ''); // Remove trailing slash
|
||||
return {
|
||||
protectedResource: new URL(
|
||||
`/.well-known/oauth-protected-resource${pathSuffix}`,
|
||||
@@ -234,21 +230,21 @@ export class OAuthUtils {
|
||||
serverUrl: string,
|
||||
): Promise<MCPOAuthConfig | null> {
|
||||
try {
|
||||
// First try standard root-based discovery
|
||||
const wellKnownUrls = this.buildWellKnownUrls(serverUrl, false);
|
||||
|
||||
// Try to get the protected resource metadata at root
|
||||
// RFC 9728 §3.1: Construct well-known URL by inserting /.well-known/oauth-protected-resource
|
||||
// between the host and path. This is the RFC-compliant approach.
|
||||
const wellKnownUrls = this.buildWellKnownUrls(serverUrl);
|
||||
let resourceMetadata = await this.fetchProtectedResourceMetadata(
|
||||
wellKnownUrls.protectedResource,
|
||||
);
|
||||
|
||||
// If root discovery fails and we have a path, try path-based discovery
|
||||
// Fallback: If path-based discovery fails and we have a path, try root-based discovery
|
||||
// for backwards compatibility with servers that don't implement RFC 9728 path handling
|
||||
if (!resourceMetadata) {
|
||||
const url = new URL(serverUrl);
|
||||
if (url.pathname && url.pathname !== '/') {
|
||||
const pathBasedUrls = this.buildWellKnownUrls(serverUrl, true);
|
||||
const rootBasedUrls = this.buildWellKnownUrls(serverUrl, true);
|
||||
resourceMetadata = await this.fetchProtectedResourceMetadata(
|
||||
pathBasedUrls.protectedResource,
|
||||
rootBasedUrls.protectedResource,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user