Skip to content

Commit

Permalink
Merge branch 'main' into improve-memory-cache
Browse files Browse the repository at this point in the history
  • Loading branch information
jpreynat committed Jun 24, 2024
2 parents 3e1a42e + e3f5f81 commit e1a6029
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 5 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-popover": "^1.0.7",
"@sentry/nextjs": "^7.94.1",
"@sindresorhus/fnv1a": "^3.1.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.10",
"@upstash/redis": "^1.27.1",
Expand Down
5 changes: 4 additions & 1 deletion src/app/(global)/~gitbook/image/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export const runtime = 'edge';
export async function GET(request: NextRequest) {
let urlParam = request.nextUrl.searchParams.get('url');
const signature = request.nextUrl.searchParams.get('sign');
// The current signature algorithm sets version as 1, but we need to support the older version as well
// for previously generated content. In this case, we default to version 0.
const signatureVersion = (request.nextUrl.searchParams.get('sv') as '1') || '0';
if (!urlParam || !signature) {
return new Response('Missing url/sign parameters', { status: 400 });
}
Expand All @@ -25,7 +28,7 @@ export async function GET(request: NextRequest) {
}

// Verify the signature
const verified = await verifyImageSignature(url, signature);
const verified = await verifyImageSignature(url, { signature, version: signatureVersion });
if (!verified) {
return new Response(`Invalid signature "${signature ?? ''}" for "${url}"`, { status: 400 });
}
Expand Down
33 changes: 29 additions & 4 deletions src/lib/images.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'server-only';

import fnv1a from '@sindresorhus/fnv1a';

import { noCacheFetchOptions } from '@/lib/cache/http';

import { rootUrl } from './links';
Expand Down Expand Up @@ -73,7 +75,7 @@ export async function getResizedImageURL(
return null;
}

const signature = await generateSignature(input);
const signature = await generateSignatureV1(input);

return (options) => {
const url = new URL('/~gitbook/image', rootUrl());
Expand All @@ -93,6 +95,7 @@ export async function getResizedImageURL(
}

url.searchParams.set('sign', signature);
url.searchParams.set('sv', '1');

return url.toString();
};
Expand All @@ -101,8 +104,12 @@ export async function getResizedImageURL(
/**
* Verify a signature of an image URL
*/
export async function verifyImageSignature(input: string, signature: string): Promise<boolean> {
const expectedSignature = await generateSignature(input);
export async function verifyImageSignature(
input: string,
{ signature, version }: { signature: string; version: '1' | '0' },
): Promise<boolean> {
const expectedSignature =
version === '1' ? await generateSignatureV1(input) : await generateSignatureV0(input);
return expectedSignature === signature;
}

Expand Down Expand Up @@ -201,7 +208,25 @@ function stringifyOptions(options: CloudflareImageOptions): string {
}, '');
}

async function generateSignature(input: string): Promise<string> {
// Reused buffer for FNV-1a hashing
const fnv1aUtf8Buffer = new Uint8Array(512);

/**
* New and faster algorithm to generate a signature for an image.
* When setting it in a URL, we use version '1' for the 'sv' querystring parameneter
* to know that it was the algorithm that was used.
*/
async function generateSignatureV1(input: string): Promise<string> {
const all = [input, process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY].filter(Boolean).join(':');
return fnv1a(all, { utf8Buffer: fnv1aUtf8Buffer }).toString(16);
}

/**
* Initial algorithm used to generate a signature for an image. It didn't use any versioning in the URL.
* We still need it to validate older signatures that were generated without versioning
* but still exist in previously generated and cached content.
*/
async function generateSignatureV0(input: string): Promise<string> {
const all = [input, process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY].filter(Boolean).join(':');
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(all));

Expand Down

0 comments on commit e1a6029

Please sign in to comment.