Skip to content

Commit dd6ab60

Browse files
authored
Fix crash when using user mention or accessing invalid change requests (#3051)
1 parent d236bf0 commit dd6ab60

File tree

4 files changed

+48
-14
lines changed

4 files changed

+48
-14
lines changed

packages/gitbook-v2/src/lib/context.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import type {
1313
SiteStructure,
1414
Space,
1515
} from '@gitbook/api';
16-
import { type GitBookDataFetcher, createDataFetcher, throwIfDataError } from '@v2/lib/data';
16+
import {
17+
type GitBookDataFetcher,
18+
createDataFetcher,
19+
getDataOrNull,
20+
throwIfDataError,
21+
} from '@v2/lib/data';
22+
import { notFound } from 'next/navigation';
1723
import { assert } from 'ts-essentials';
1824
import { GITBOOK_URL } from './env';
1925
import { type ImageResizer, createImageResizer } from './images';
@@ -277,7 +283,7 @@ export async function fetchSpaceContextByIds(
277283
})
278284
),
279285
ids.changeRequest
280-
? throwIfDataError(
286+
? getDataOrNull(
281287
dataFetcher.getChangeRequest({
282288
spaceId: ids.space,
283289
changeRequestId: ids.changeRequest,
@@ -286,17 +292,30 @@ export async function fetchSpaceContextByIds(
286292
: null,
287293
]);
288294

289-
const revisionId = changeRequest?.revision ?? ids.revision ?? space.revision;
295+
if (ids.changeRequest && !changeRequest) {
296+
// When trying to render a change request with an invalid / non-existing ID,
297+
// we should return a 404.
298+
notFound();
299+
}
300+
301+
const revisionId = ids.revision ?? changeRequest?.revision ?? space.revision;
290302

291-
const pages = await throwIfDataError(
303+
const pages = await getDataOrNull(
292304
dataFetcher.getRevisionPages({
293305
spaceId: ids.space,
294306
revisionId,
295307
// We only care about the Git metadata when the Git sync is enabled,
296308
// otherwise we can optimize performance by not fetching it
297309
metadata: !!space.gitSync,
298-
})
310+
}),
311+
312+
// When trying to render a revision with an invalid / non-existing ID,
313+
// we should handle gracefully the 404 and throw notFound.
314+
ids.revision ? [404] : undefined
299315
);
316+
if (!pages) {
317+
notFound();
318+
}
300319

301320
return {
302321
...baseContext,

packages/gitbook-v2/src/lib/data/api.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,9 @@ export function createDataFetcher(
166166
})
167167
);
168168
},
169-
//
170-
// API that are not tied to the token
171-
// where the data is the same for all users
172-
//
169+
173170
getUserById(userId) {
174-
return trace('getUserById', () => getUserById({ apiToken: null }, { userId }));
171+
return trace('getUserById', () => getUserById(input, { userId }));
175172
},
176173
};
177174
}

packages/gitbook/e2e/internal.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
headerLinks,
2929
runTestCases,
3030
waitForCookiesDialog,
31+
waitForNotFound,
3132
} from './util';
3233

3334
const testCases: TestsCase[] = [
@@ -308,6 +309,18 @@ const testCases: TestsCase[] = [
308309
url: '~/revisions/S55pwsEr5UVoroaOiWnP/blocks/headings',
309310
run: waitForCookiesDialog,
310311
},
312+
{
313+
name: 'Invalid revision',
314+
url: '~/revisions/idnotfound/blocks/headings',
315+
run: waitForNotFound,
316+
screenshot: false,
317+
},
318+
{
319+
name: 'Invalid change request',
320+
url: '~/changes/idnotfound/blocks/headings',
321+
run: waitForNotFound,
322+
screenshot: false,
323+
},
311324
],
312325
},
313326
{

packages/gitbook/e2e/util.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
type CustomizationThemedColor,
1717
type SiteCustomizationSettings,
1818
} from '@gitbook/api';
19-
import { type BrowserContext, type Page, expect, test } from '@playwright/test';
19+
import { type BrowserContext, type Page, type Response, expect, test } from '@playwright/test';
2020
import deepMerge from 'deepmerge';
2121
import rison from 'rison';
2222
import type { DeepPartial } from 'ts-essentials';
@@ -33,7 +33,7 @@ export interface Test {
3333
/**
3434
* Test to run
3535
*/
36-
run?: (page: Page) => Promise<unknown>;
36+
run?: (page: Page, response: Response | null) => Promise<unknown>;
3737
/**
3838
* Whether the test should be fullscreened during testing.
3939
*/
@@ -138,6 +138,11 @@ export async function waitForCookiesDialog(page: Page) {
138138
await expect(dialog).toBeVisible();
139139
}
140140

141+
export async function waitForNotFound(_page: Page, response: Response | null) {
142+
expect(response).not.toBeNull();
143+
expect(response?.status()).toBe(404);
144+
}
145+
141146
/**
142147
* Transform test cases into Playwright tests and run it.
143148
*/
@@ -183,9 +188,9 @@ export function runTestCases(testCases: TestsCase[]) {
183188
}
184189
});
185190

186-
await page.goto(url);
191+
const response = await page.goto(url);
187192
if (testEntry.run) {
188-
await testEntry.run(page);
193+
await testEntry.run(page, response);
189194
}
190195
const screenshotOptions = testEntry.screenshot;
191196
if (screenshotOptions !== false) {

0 commit comments

Comments
 (0)