mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-17 16:15:22 +00:00
Send redirectOnError param to getPublishedContent when token is pulled from cookie (#2626)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'@gitbook/react-contentkit': minor
|
||||||
|
'gitbook': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Send redirectOnError param to getPublishedContent when token is pulled from cookie
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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:*",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
Reference in New Issue
Block a user