diff --git a/packages/gitbook/src/lib/visitors.test.ts b/packages/gitbook/src/lib/visitors.test.ts index d70ec19ad..b4ea12a7c 100644 --- a/packages/gitbook/src/lib/visitors.test.ts +++ b/packages/gitbook/src/lib/visitors.test.ts @@ -10,6 +10,7 @@ import { getVisitorToken, getVisitorType, getVisitorUnsignedClaims, + isRevalidationRequest, normalizeVisitorURL, } from './visitors'; @@ -564,3 +565,16 @@ describe('getVisitorType', () => { expect(getVisitorType(requestWith({ 'user-agent': '' }))).toBe('human'); }); }); + +describe('isRevalidationRequest', () => { + it('should detect the revalidation worker regardless of casing', () => { + expect( + isRevalidationRequest(new Headers({ 'User-Agent': 'GitBook-Open-Revalidation-Worker' })) + ).toBe(true); + }); + + it('should not detect a regular request', () => { + expect(isRevalidationRequest(new Headers({ 'User-Agent': 'Mozilla/5.0' }))).toBe(false); + expect(isRevalidationRequest(new Headers())).toBe(false); + }); +}); diff --git a/packages/gitbook/src/lib/visitors.ts b/packages/gitbook/src/lib/visitors.ts index 29d92e145..033aa13c8 100644 --- a/packages/gitbook/src/lib/visitors.ts +++ b/packages/gitbook/src/lib/visitors.ts @@ -132,6 +132,14 @@ export function getVisitorData({ }; } +/** + * Check if the request is coming from our revalidation worker. Such requests carry the visitor + * data they want to revalidate in the URL, so we must not redirect them to a normalized URL. + */ +export function isRevalidationRequest(headers: Headers): boolean { + return headers.get('user-agent')?.toLowerCase() === 'gitbook-open-revalidation-worker'; +} + /** * Get the visitor token for the request. This token can either be in the * query parameters or stored as a cookie. @@ -154,7 +162,7 @@ export function getVisitorToken({ // Allow the empty string to come through if (fromUrl !== null && fromUrl !== undefined) { - if (headers.get('user-agent')?.toLowerCase() === 'gitbook-open-revalidation-worker') { + if (isRevalidationRequest(headers)) { return { source: 'revalidation', token: fromUrl }; } diff --git a/packages/gitbook/src/middleware.ts b/packages/gitbook/src/middleware.ts index f1e491808..eaa2bf5d3 100644 --- a/packages/gitbook/src/middleware.ts +++ b/packages/gitbook/src/middleware.ts @@ -56,6 +56,7 @@ import { getResponseCookiesForVisitorAuth, getVisitorData, getVisitorType, + isRevalidationRequest, normalizeVisitorURL, serveVisitorClaimsDataRequest, } from '@/lib/visitors'; @@ -339,7 +340,9 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { // Make sure the URL is clean of any va token after a successful lookup, // and of any visitor.* params that may have been passed to the URL. // - // We only redirect if the visitor token is not coming from a revalidation request, as we don't want to redirect in that case. + // We only redirect if the request is not coming from the revalidation worker, as we don't + // want to redirect in that case. It can carry unsigned claims without any token, so we rely + // on the request headers rather than on the visitor token source. // // The token and the visitor.* params value are stored in cookies that are set // on the redirect response. @@ -347,7 +350,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { const normalizedVisitorURL = normalizeVisitorURL(incomingURL); if ( normalizedVisitorURL.toString() !== incomingURL.toString() && - visitorToken?.source !== 'revalidation' + !isRevalidationRequest(request.headers) ) { return writeResponseCookies( NextResponse.redirect(normalizedVisitorURL.toString()),