Send redirectOnError param to getPublishedContent when token is pulled from cookie (#2626)

This commit is contained in:
spastorelli
2024-12-19 11:16:06 +01:00
committed by GitHub
parent e8e64bf510
commit ea1468c892
8 changed files with 105 additions and 7 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/react-contentkit': minor
'gitbook': minor
---
Send redirectOnError param to getPublishedContent when token is pulled from cookie
BIN
View File
Binary file not shown.
+74 -2
View File
@@ -6,20 +6,25 @@ import {
CustomizationHeaderPreset, CustomizationHeaderPreset,
CustomizationIconsStyle, CustomizationIconsStyle,
CustomizationLocale, CustomizationLocale,
CustomizationSidebarBackgroundStyle,
CustomizationSidebarListStyle,
CustomizationThemeMode, CustomizationThemeMode,
SiteCustomizationSettings, SiteCustomizationSettings,
} from '@gitbook/api'; } from '@gitbook/api';
import { test, expect, Page } from '@playwright/test'; import { test, expect, Page, BrowserContext } from '@playwright/test';
import deepMerge from 'deepmerge'; import deepMerge from 'deepmerge';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import rison from 'rison'; import rison from 'rison';
import { DeepPartial } from 'ts-essentials'; import { DeepPartial } from 'ts-essentials';
import { getVisitorAuthCookieName, getVisitorAuthCookieValue } from '@/lib/visitor-auth';
import { getContentTestURL } from '../tests/utils'; import { getContentTestURL } from '../tests/utils';
interface Test { interface Test {
name: string; name: string;
url: string; // URL to visit for testing url: string; // URL to visit for testing
cookies?: Parameters<BrowserContext['addCookies']>[0];
run?: (page: Page) => Promise<unknown>; // The test to run run?: (page: Page) => Promise<unknown>; // The test to run
fullPage?: boolean; // Whether the test should be fullscreened during testing fullPage?: boolean; // Whether the test should be fullscreened during testing
screenshot?: false; // Should a screenshot be stored screenshot?: false; // Should a screenshot be stored
@@ -867,6 +872,60 @@ const testCases: TestsCase[] = [
}, },
], ],
}, },
{
name: 'Visitor Auth - Site (redirects to fallback/auth URL)',
baseUrl: `https://gitbook-open-e2e-sites.gitbook.io/va-site-redirects-fallback/`,
tests: [
{
name: 'Redirect to fallback on invalid token pulled from cookie',
url: '',
screenshot: false,
cookies: (() => {
const basePath = '/va-site-redirects-fallback/';
const invalidToken = jwt.sign(
{
name: 'gitbook-open-tests',
},
'invalidKey',
{
expiresIn: '24h',
},
);
return [
{
name: getVisitorAuthCookieName(basePath),
value: getVisitorAuthCookieValue(basePath, invalidToken),
httpOnly: true,
},
];
})(),
run: async (page) => {
await expect(page).toHaveURL(/https:\/\/www.google.com/);
},
},
{
name: 'Show error message when invalid token is passed to url',
screenshot: false,
url: (() => {
const token = jwt.sign(
{
name: 'gitbook-open-tests',
},
'invalidKey',
{
expiresIn: '24h',
},
);
return `?jwt_token=${token}`;
})(),
run: async (page) => {
await expect(page.locator('pre')).toContainText(
'Error while validating the JWT token. Reason: The token signature is invalid.',
);
},
},
],
},
{ {
name: 'Languages', name: 'Languages',
baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/', baseUrl: 'https://gitbook.gitbook.io/test-gitbook-open/',
@@ -1112,9 +1171,18 @@ for (const testCase of testCases) {
test.describe(testCase.name, () => { test.describe(testCase.name, () => {
for (const testEntry of testCase.tests) { for (const testEntry of testCase.tests) {
const testFn = testEntry.only ? test.only : test; const testFn = testEntry.only ? test.only : test;
testFn(testEntry.name, async ({ page, baseURL }) => { testFn(testEntry.name, async ({ page, baseURL, context }) => {
const contentUrl = new URL(testEntry.url, testCase.baseUrl); const contentUrl = new URL(testEntry.url, testCase.baseUrl);
const url = getContentTestURL(contentUrl.toString(), baseURL); const url = getContentTestURL(contentUrl.toString(), baseURL);
if (testEntry.cookies) {
await context.addCookies(
testEntry.cookies.map((cookie) => ({
...cookie,
domain: new URL(url).host,
path: '/',
})),
);
}
await page.goto(url); await page.goto(url);
if (testEntry.run) { if (testEntry.run) {
await testEntry.run(page); await testEntry.run(page);
@@ -1152,6 +1220,10 @@ function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettings>): s
font: CustomizationFont.Inter, font: CustomizationFont.Inter,
background: CustomizationBackground.Plain, background: CustomizationBackground.Plain,
icons: CustomizationIconsStyle.Regular, icons: CustomizationIconsStyle.Regular,
sidebar: {
background: CustomizationSidebarBackgroundStyle.Default,
list: CustomizationSidebarListStyle.Default,
},
}, },
internationalization: { internationalization: {
locale: CustomizationLocale.En, locale: CustomizationLocale.En,
+1 -1
View File
@@ -16,7 +16,7 @@
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static" "clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static"
}, },
"dependencies": { "dependencies": {
"@gitbook/api": "^0.81.0", "@gitbook/api": "^0.83.0",
"@gitbook/cache-do": "workspace:*", "@gitbook/cache-do": "workspace:*",
"@gitbook/emoji-codepoints": "workspace:*", "@gitbook/emoji-codepoints": "workspace:*",
"@gitbook/icons": "workspace:*", "@gitbook/icons": "workspace:*",
+2
View File
@@ -214,6 +214,7 @@ export const getPublishedContentByUrl = cache({
get: async ( get: async (
url: string, url: string,
visitorAuthToken: string | undefined, visitorAuthToken: string | undefined,
redirectOnError: boolean | undefined,
options: CacheFunctionOptions, options: CacheFunctionOptions,
) => { ) => {
try { try {
@@ -221,6 +222,7 @@ export const getPublishedContentByUrl = cache({
{ {
url, url,
visitorAuthToken, visitorAuthToken,
redirectOnError,
}, },
{ {
signal: options.signal, signal: options.signal,
+10
View File
@@ -12,6 +12,16 @@ export type VisitorAuthCookieValue = {
token: string; token: string;
}; };
export function isVisitorAuthTokenFromCookies(
visitorAuthToken: NonNullable<ReturnType<typeof getVisitorAuthToken>>,
) {
return (
typeof visitorAuthToken !== 'string' &&
'basePath' in visitorAuthToken &&
'token' in visitorAuthToken
);
}
/** /**
* Get the visitor authentication token for the request. This token can either be in the * Get the visitor authentication token for the request. This token can either be in the
* query parameters or stored as a cookie. * query parameters or stored as a cookie.
+11 -3
View File
@@ -27,6 +27,7 @@ import {
getVisitorAuthCookieName, getVisitorAuthCookieName,
getVisitorAuthCookieValue, getVisitorAuthCookieValue,
getVisitorAuthToken, getVisitorAuthToken,
isVisitorAuthTokenFromCookies,
normalizeVisitorAuthURL, normalizeVisitorAuthURL,
} from '@/lib/visitor-auth'; } from '@/lib/visitor-auth';
@@ -685,14 +686,21 @@ async function lookupSiteByAPI(
`lookup content for url "${url.toString()}", with ${lookup.urls.length} alternatives`, `lookup content for url "${url.toString()}", with ${lookup.urls.length} alternatives`,
); );
// When the visitor auth token is pulled from the cookie, set redirectOnError when calling getPublishedContentByUrl to allow
// redirecting when the token is invalid as we could be dealing with stale token stored in the cookie.
// For example when the VA backend signature has changed but the token stored in the cookie is not yet expired.
const redirectOnError =
typeof visitorAuthToken !== 'undefined' && isVisitorAuthTokenFromCookies(visitorAuthToken);
const result = await race(lookup.urls, async (alternative, { signal }) => { const result = await race(lookup.urls, async (alternative, { signal }) => {
const data = await getPublishedContentByUrl( const data = await getPublishedContentByUrl(
alternative.url, alternative.url,
typeof visitorAuthToken === 'undefined' typeof visitorAuthToken === 'undefined'
? undefined ? undefined
: typeof visitorAuthToken === 'string' : isVisitorAuthTokenFromCookies(visitorAuthToken)
? visitorAuthToken ? visitorAuthToken.token
: visitorAuthToken.token, : visitorAuthToken,
typeof visitorAuthToken === 'undefined' ? undefined : redirectOnError,
{ {
signal, signal,
}, },
+1 -1
View File
@@ -10,7 +10,7 @@
}, },
"dependencies": { "dependencies": {
"classnames": "^2.5.1", "classnames": "^2.5.1",
"@gitbook/api": "^0.77.0", "@gitbook/api": "^0.83.0",
"assert-never": "^1.2.1" "assert-never": "^1.2.1"
}, },
"peerDependencies": { "peerDependencies": {