Skip to content

Commit 434af90

Browse files
Fix image resizing when using proxy by passing signing identifier in site URL data (#3063)
Co-authored-by: Taran Vohra <[email protected]>
1 parent 0f41e19 commit 434af90

File tree

12 files changed

+75
-27
lines changed

12 files changed

+75
-27
lines changed

.changeset/friendly-oranges-clap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"gitbook": patch
3+
---
4+
5+
Fix image resizing when using the proxy feature in a site.

packages/gitbook-v2/src/app/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getVisitorAuthClaims, getVisitorAuthClaimsFromToken } from '@/lib/adaptive';
2-
import type { PublishedSiteContent, SiteAPIToken } from '@gitbook/api';
3-
import { fetchSiteContextByURLLookup, getBaseContext } from '@v2/lib/context';
2+
import type { SiteAPIToken } from '@gitbook/api';
3+
import { type SiteURLData, fetchSiteContextByURLLookup, getBaseContext } from '@v2/lib/context';
44
import { jwtDecode } from 'jwt-decode';
55
import { forbidden } from 'next/navigation';
66
import rison from 'rison';
@@ -98,7 +98,7 @@ function getModeFromParams(mode: string): RouteParamMode {
9898
/**
9999
* Get the decoded site data from the params.
100100
*/
101-
function getSiteURLDataFromParams(params: RouteLayoutParams): PublishedSiteContent {
101+
function getSiteURLDataFromParams(params: RouteLayoutParams): SiteURLData {
102102
const decoded = decodeURIComponent(params.siteData);
103103
return rison.decode(decoded);
104104
}

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ import { GITBOOK_URL } from './env';
2525
import { type ImageResizer, createImageResizer } from './images';
2626
import { type GitBookLinker, createLinker } from './links';
2727

28+
/**
29+
* Data about the site URL. Provided by the middleware.
30+
* These data are stable between pages in the same site space.
31+
*/
32+
export type SiteURLData = Pick<
33+
PublishedSiteContent,
34+
| 'organization'
35+
| 'apiToken'
36+
| 'site'
37+
| 'siteSpace'
38+
| 'space'
39+
| 'revision'
40+
| 'changeRequest'
41+
| 'shareKey'
42+
| 'siteSection'
43+
| 'siteBasePath'
44+
| 'basePath'
45+
> & {
46+
/**
47+
* Identifier used for image resizing.
48+
*/
49+
imagesContextId: string;
50+
};
51+
2852
/**
2953
* Generic context when rendering content.
3054
*/
@@ -111,7 +135,7 @@ export type GitBookPageContext = (GitBookSpaceContext | GitBookSiteContext) & {
111135
*/
112136
export function getBaseContext(input: {
113137
siteURL: URL | string;
114-
siteURLData: PublishedSiteContent;
138+
siteURLData: SiteURLData;
115139
urlMode: 'url' | 'url-host';
116140
}) {
117141
const { urlMode, siteURLData } = input;
@@ -145,7 +169,7 @@ export function getBaseContext(input: {
145169
}
146170

147171
const imageResizer = createImageResizer({
148-
host: siteURL.host,
172+
imagesContextId: siteURLData.imagesContextId,
149173
// To ensure image resizing work for proxied sites,
150174
// we serve images from the root of the site.
151175
linker: linker,
@@ -163,7 +187,7 @@ export function getBaseContext(input: {
163187
*/
164188
export async function fetchSiteContextByURLLookup(
165189
baseContext: GitBookBaseContext,
166-
data: PublishedSiteContent
190+
data: SiteURLData
167191
): Promise<GitBookSiteContext> {
168192
return await fetchSiteContextByIds(baseContext, {
169193
organization: data.organization,

packages/gitbook-v2/src/lib/images/createImageResizer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ export interface CloudflareImageOptions {
3333
* Create an image resizer for a rendering context.
3434
*/
3535
export function createImageResizer({
36-
host,
36+
imagesContextId,
3737
linker,
3838
}: {
3939
/** The linker to use to create URLs. */
4040
linker: GitBookLinker;
41-
/** The host name of the current site. */
42-
host: string;
41+
/** The site identifier to use for verifying the image signature. */
42+
imagesContextId: string;
4343
}): ImageResizer {
4444
if (!GITBOOK_IMAGE_RESIZE_URL || !GITBOOK_IMAGE_RESIZE_SIGNING_KEY) {
4545
return createNoopImageResizer();
@@ -58,7 +58,7 @@ export function createImageResizer({
5858

5959
return async (options) => {
6060
cachedSignature ??= await generateImageSignature({
61-
host,
61+
imagesContextId,
6262
url: urlInput,
6363
});
6464

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Get the site identifier to use for image resizing for an incoming request.
3+
* This identifier can be obtained before resolving the request URL.
4+
*/
5+
export function getImageResizingContextId(url: URL): string {
6+
if (url.host === 'proxy.gitbook.site' || url.host === 'proxy.gitbook-staging.site') {
7+
// For proxy requests, we extract the site ID from the pathname
8+
// e.g. https://proxy.gitbook.site/site/siteId/...
9+
const pathname = url.pathname.slice(1).split('/');
10+
return pathname.slice(0, 2).join('/');
11+
}
12+
13+
return url.host;
14+
}

packages/gitbook-v2/src/lib/images/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './types';
22
export * from './createImageResizer';
33
export * from './signatures';
44
export * from './utils';
5+
export * from './getImageResizingContextId';

packages/gitbook-v2/src/lib/images/signatures.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const CURRENT_SIGNATURE_VERSION: SignatureVersion = '2';
1818

1919
type SignFnInput = {
2020
url: string;
21-
host: string;
21+
imagesContextId: string;
2222
};
2323

2424
type SignFn = (input: SignFnInput) => MaybePromise<string>;
@@ -60,7 +60,7 @@ const generateSignatureV2: SignFn = async (input) => {
6060
assert(GITBOOK_IMAGE_RESIZE_SIGNING_KEY, 'GITBOOK_IMAGE_RESIZE_SIGNING_KEY is not set');
6161
const all = [
6262
input.url,
63-
input.host, // The hostname is used to avoid serving images from other sites on the same domain
63+
input.imagesContextId, // The hostname is used to avoid serving images from other sites on the same domain
6464
GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
6565
]
6666
.filter(Boolean)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { CustomizationThemeMode, type PublishedSiteContent } from '@gitbook/api';
1+
import { CustomizationThemeMode } from '@gitbook/api';
22
import { headers } from 'next/headers';
3+
import type { SiteURLData } from './context';
34

45
export enum MiddlewareHeaders {
56
/**
@@ -57,7 +58,7 @@ export async function getURLModeFromMiddleware(): Promise<'url' | 'url-host'> {
5758
* Get the site URL data from the middleware headers.
5859
* This function should only be called in a server action or a dynamic route.
5960
*/
60-
export async function getSiteURLDataFromMiddleware(): Promise<PublishedSiteContent> {
61+
export async function getSiteURLDataFromMiddleware(): Promise<SiteURLData> {
6162
const headersList = await headers();
6263
const siteURLData = headersList.get(MiddlewareHeaders.SiteURLData);
6364

packages/gitbook-v2/src/middleware.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import {
2121
throwIfDataError,
2222
} from '@v2/lib/data';
2323
import { isGitBookAssetsHostURL, isGitBookHostURL } from '@v2/lib/env';
24+
import { getImageResizingContextId } from '@v2/lib/images';
2425
import { MiddlewareHeaders } from '@v2/lib/middleware';
26+
import type { SiteURLData } from './lib/context';
2527

2628
export const config = {
2729
matcher: [
@@ -64,6 +66,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
6466
}
6567

6668
const { url: siteRequestURL, mode } = match;
69+
const imagesContextId = getImageResizingContextId(siteRequestURL);
6770

6871
/**
6972
* Serve image resizing requests (all requests containing `/~gitbook/image`).
@@ -75,7 +78,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
7578
*/
7679
if (siteRequestURL.pathname.endsWith('/~gitbook/image')) {
7780
return await serveResizedImage(request, {
78-
host: siteRequestURL.host,
81+
imagesContextId: imagesContextId,
7982
});
8083
}
8184

@@ -203,7 +206,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
203206

204207
// We pick only stable data from the siteURL data to prevent re-rendering of
205208
// the root layout when changing pages..
206-
const stableSiteURLData: Omit<typeof siteURLData, 'pathname' | 'canonicalUrl'> = {
209+
const stableSiteURLData: SiteURLData = {
207210
site: siteURLData.site,
208211
siteSection: siteURLData.siteSection,
209212
siteSpace: siteURLData.siteSpace,
@@ -215,8 +218,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
215218
revision: siteURLData.revision,
216219
shareKey: siteURLData.shareKey,
217220
apiToken: siteURLData.apiToken,
218-
complete: siteURLData.complete,
219-
contextId: siteURLData.contextId,
221+
imagesContextId: imagesContextId,
220222
};
221223

222224
const route = [

packages/gitbook/src/lib/adaptive.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { PublishedSiteContent, SiteAPIToken } from '@gitbook/api';
1+
import type { SiteAPIToken } from '@gitbook/api';
2+
import type { SiteURLData } from '@v2/lib/context';
23
import { jwtDecode } from 'jwt-decode';
34

45
/**
@@ -9,7 +10,7 @@ export type VisitorAuthClaims = Record<string, any>;
910
/**
1011
* Get the visitor auth claims from the API response obtained from `getPublishedContentByUrl`.
1112
*/
12-
export function getVisitorAuthClaims(siteData: PublishedSiteContent): VisitorAuthClaims {
13+
export function getVisitorAuthClaims(siteData: SiteURLData): VisitorAuthClaims {
1314
const { apiToken } = siteData;
1415

1516
return getVisitorAuthClaimsFromToken(jwtDecode<SiteAPIToken>(apiToken));

0 commit comments

Comments
 (0)