Fix auth redirects with the new preview URL (#4111)

This commit is contained in:
Taran Vohra
2026-03-13 13:52:09 +05:30
committed by GitHub
parent 45d32b5084
commit 6c613d0df3
2 changed files with 68 additions and 25 deletions
+38
View File
@@ -709,6 +709,26 @@ const testCases: TestsCase[] = [
await expect(page.locator('[data-testid="print-button"]')).toBeVisible();
},
},
{
name: 'Show error when missing token',
url: async () => {
const data = await getSiteAPIToken(
'https://gitbook.gitbook.io/test-gitbook-open/'
);
// Intentionally not setting the token to test error handling when the token is missing
const searchParams = new URLSearchParams();
searchParams.set('limit', '10');
return `~space/${data.space}/~gitbook/pdf?${searchParams.toString()}`;
},
screenshot: false,
run: async (page, response) => {
expect(response).not.toBeNull();
expect(response?.status()).toBe(400);
await expect(page.getByText('Missing API token')).toBeVisible();
},
},
],
},
{
@@ -887,6 +907,24 @@ const testCases: TestsCase[] = [
).toBeVisible();
},
},
{
name: 'Redirect to app for authentication when missing token',
url: async () => {
const data = await getSiteAPIToken('https://gitbook.com/docs');
const searchParams = new URLSearchParams();
// Intentionally not setting the token to test redirection for authentication
return `url/${getGitBookPreviewURL(`${data.site}/?${searchParams.toString()}`)}`;
},
screenshot: false,
run: async (page) => {
await page.waitForURL(
(url) =>
url.host === 'app.gitbook.com' && url.pathname.includes('/preview/auth')
);
},
},
],
},
{
+30 -25
View File
@@ -473,12 +473,14 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
if (isPreviewRequest(siteRequestURL)) {
// Do not track page views for preview requests
request.headers.set('x-gitbook-disable-tracking', 'true');
return serveWithQueryAPIToken(
return serveWithQueryAPIToken({
// We scope the API token to the site ID.
['preview', getPreviewRequestIdentifier(siteRequestURL)].join('/'),
request,
withAPIToken
);
scopePath: ['preview', getPreviewRequestIdentifier(siteRequestURL)].join('/'),
// We keep the original request URL when using `url` mode
requestURL: mode === 'url' ? requestURL : siteRequestURL,
requestCookies: request.cookies,
serve: withAPIToken,
});
}
return withAPIToken(null);
@@ -493,18 +495,22 @@ async function serveSpacePDFRoutes(requestURL: URL, request: NextRequest) {
return null;
}
return serveWithQueryAPIToken(
pathnameParts.slice(0, 2).join('/'),
request,
async (apiToken) => {
return serveWithQueryAPIToken({
scopePath: pathnameParts.slice(0, 2).join('/'),
requestURL,
requestCookies: request.cookies,
serve: async (apiToken) => {
if (!apiToken) {
throw new DataFetcherError('Missing API token', 400);
}
// Handle the rest with the router default logic
return NextResponse.next({
headers: {
[MiddlewareHeaders.APIToken]: apiToken,
},
});
}
);
},
});
}
/**
@@ -522,23 +528,25 @@ function serveErrorResponse(error: Error) {
}
/**
* Server a response with an API token obtained from the query params.
* Serve a response with an API token obtained from the query params.
*/
async function serveWithQueryAPIToken(
scopePath: string,
request: NextRequest,
serve: (apiToken: string) => Promise<NextResponse>
) {
async function serveWithQueryAPIToken(input: {
scopePath: string;
requestURL: URL;
requestCookies: NextRequest['cookies'];
serve: (apiToken: string | null) => Promise<NextResponse>;
}) {
const { scopePath, requestURL, requestCookies, serve } = input;
// We store the API token in a cookie that is scoped to the specific route
// to avoid errors when multiple previews are opened in different tabs.
const cookieName = getPathScopedCookieName('gitbook-api-token', scopePath);
// Extract a potential GitBook API token passed in the request
// If found, we redirect to the same URL but with the token in the cookie
const queryAPIToken = request.nextUrl.searchParams.get('token');
const queryAPIToken = requestURL.searchParams.get('token');
if (queryAPIToken) {
request.nextUrl.searchParams.delete('token');
return writeResponseCookies(NextResponse.redirect(request.nextUrl.toString()), [
requestURL.searchParams.delete('token');
return writeResponseCookies(NextResponse.redirect(requestURL.toString()), [
{
name: cookieName,
value: queryAPIToken,
@@ -552,12 +560,9 @@ async function serveWithQueryAPIToken(
]);
}
const apiToken = request.cookies.get(cookieName)?.value;
if (!apiToken) {
throw new DataFetcherError('Missing API token', 400);
}
const apiToken = requestCookies.get(cookieName)?.value;
return serve(apiToken);
return serve(apiToken ?? null);
}
/**