mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 01:53:26 +00:00
Only advertise OAuth PRM doc for the auth MCP endpoint on non-VA sites (#4387)
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
createOAuthProtectedResourceUnauthResponse,
|
||||
handleUnauthedOAuthProtectedResourceRequest,
|
||||
isOAuthProtectedResourceMetadataRequest,
|
||||
isOAuthProtectedResourceMetadataRequestForAuthEndpoint,
|
||||
isOAuthProtectedResourceRequest,
|
||||
} from './oauth-protected';
|
||||
|
||||
@@ -250,4 +251,32 @@ describe('OAuth protected resources flow', () => {
|
||||
expect(isOAuthProtectedResourceMetadataRequest(url)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOAuthProtectedResourceMetadataRequestForAuthEndpoint', () => {
|
||||
it.each([
|
||||
{
|
||||
scenario: 'matches metadata doc for the authenticated resource',
|
||||
input: 'https://docs.acme.org/.well-known/oauth-protected-resource/~gitbook/mcp/auth',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scenario: 'does not match metadata doc for the public base resource',
|
||||
input: 'https://docs.acme.org/.well-known/oauth-protected-resource/~gitbook/mcp',
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scenario: 'does not match the resource itself',
|
||||
input: 'https://docs.acme.org/~gitbook/mcp/auth',
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scenario: 'does not match a non-protected path',
|
||||
input: 'https://docs.acme.org/.well-known/oauth-protected-resource/~gitbook/other',
|
||||
expected: false,
|
||||
},
|
||||
])('$scenario', ({ input, expected }) => {
|
||||
const url = new URL(input);
|
||||
expect(isOAuthProtectedResourceMetadataRequestForAuthEndpoint(url)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,13 @@ type OAuthProtectedResource = {
|
||||
endpoint: string;
|
||||
/** Authentication realm for this resource */
|
||||
realm?: string;
|
||||
/**
|
||||
* Whether this resource requires authentication regardless of visitor auth.
|
||||
* The base `~gitbook/mcp` endpoint is only protected when the site enforces
|
||||
* visitor auth; `~gitbook/mcp/auth` always advertises auth so clients can opt
|
||||
* into adaptive content on non-VA sites.
|
||||
*/
|
||||
authRequired?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -18,7 +25,7 @@ const OAUTH_PROTECTED_RESOURCE_METADATA_PATH = '/.well-known/oauth-protected-res
|
||||
* List of OAuth protected resources.
|
||||
*/
|
||||
const OAUTH_PROTECTED_RESOURCES: OAuthProtectedResource[] = [
|
||||
{ endpoint: '/~gitbook/mcp/auth', realm: 'mcp' },
|
||||
{ endpoint: '/~gitbook/mcp/auth', realm: 'mcp', authRequired: true },
|
||||
{ endpoint: '/~gitbook/mcp', realm: 'mcp' },
|
||||
];
|
||||
|
||||
@@ -138,6 +145,20 @@ export function isOAuthProtectedResourceMetadataRequest(
|
||||
return Boolean(getMatchedProtectedMetadataEndpoint(siteRequestURL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a metadata request targets a resource that requires authentication
|
||||
* regardless of visitor auth (e.g. `~gitbook/mcp/auth`).
|
||||
*
|
||||
* On non-VA sites the base `~gitbook/mcp` endpoint is public, so we must not
|
||||
* advertise a PRM document for it: clients that discover PRM proactively would
|
||||
* otherwise start an OAuth flow against an endpoint that never issues a challenge.
|
||||
*/
|
||||
export function isOAuthProtectedResourceMetadataRequestForAuthEndpoint(
|
||||
siteRequestURL: URL | NextRequest['nextUrl']
|
||||
) {
|
||||
return Boolean(getMatchedProtectedMetadataEndpoint(siteRequestURL)?.authRequired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the matched protected endpoint entry for a metadata doc request (if any).
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,7 @@ import { MiddlewareHeaders } from '@/lib/middleware';
|
||||
import {
|
||||
createOAuthProtectedResourceMetadataResponse,
|
||||
handleUnauthedOAuthProtectedResourceRequest,
|
||||
isOAuthProtectedResourceMetadataRequest,
|
||||
isOAuthProtectedResourceMetadataRequestForAuthEndpoint,
|
||||
isOAuthProtectedResourceRequest,
|
||||
} from '@/lib/oauth-protected';
|
||||
import { removeLeadingSlash, removeTrailingSlash } from '@/lib/paths';
|
||||
@@ -299,8 +299,10 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
||||
}
|
||||
|
||||
// Handles OAuth protected resource metadata for non-VA adaptive content sites.
|
||||
// If the requested URL resolved directly to a site, synthesize the metadata response immediately.
|
||||
if (isOAuthProtectedResourceMetadataRequest(siteRequestURL)) {
|
||||
// Only the `~gitbook/mcp/auth` endpoint advertises auth here; the base `~gitbook/mcp`
|
||||
// endpoint stays public so clients doing proactive PRM discovery don't start an OAuth
|
||||
// flow against an endpoint that never issues a challenge.
|
||||
if (isOAuthProtectedResourceMetadataRequestForAuthEndpoint(siteRequestURL)) {
|
||||
return createOAuthProtectedResourceMetadataResponse({
|
||||
siteRequestURL,
|
||||
siteId: siteURLData.site,
|
||||
|
||||
Reference in New Issue
Block a user