mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-17 16:15:22 +00:00
6bd6ddd031
* Test an assetPrefix * Fix condition * Use the env instead * Use it for CSP
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
import { SpaceIntegrationScript } from '@gitbook/api';
|
|
import { merge } from 'content-security-policy-merger';
|
|
import { headers } from 'next/headers';
|
|
|
|
import { filterOutNullable } from './typescript';
|
|
|
|
const assetsDomain = process.env.GITBOOK_ASSETS_PREFIX
|
|
? new URL(process.env.GITBOOK_ASSETS_PREFIX).host
|
|
: undefined;
|
|
|
|
/**
|
|
* Get the current nonce for the current request.
|
|
*/
|
|
export function getContentSecurityPolicyNonce(): string {
|
|
const headersList = headers();
|
|
const nonce = headersList.get('x-nonce');
|
|
if (!nonce) {
|
|
throw new Error('No nonce found in headers');
|
|
}
|
|
|
|
return nonce;
|
|
}
|
|
|
|
/**
|
|
* Create a nonce for a Content Security Policy.
|
|
*/
|
|
export function createContentSecurityPolicyNonce(): string {
|
|
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
|
|
return nonce;
|
|
}
|
|
|
|
/**
|
|
* Generate a Content Security Policy header for a space.
|
|
*/
|
|
export function getContentSecurityPolicy(scripts: SpaceIntegrationScript[], nonce: string): string {
|
|
// We need to allow loading any image or download any file
|
|
// to support image and OpenAPI blocks where the content reference could be external.
|
|
//
|
|
// Since I can't get the nonce to work for inline styles, we need to allow unsafe-inline
|
|
const defaultCSP = `
|
|
default-src 'self' ${assetsDomain};
|
|
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' ${assetsDomain} integrations.gitbook.com https://cdn.iframe.ly;
|
|
style-src 'self' ${assetsDomain} fonts.googleapis.com 'unsafe-inline';
|
|
img-src * 'self' blob: data: files.gitbook.com ${assetsDomain};
|
|
connect-src * 'self' integrations.gitbook.com app.gitbook.com ${assetsDomain};
|
|
font-src 'self' fonts.gstatic.com ${assetsDomain};
|
|
frame-src *;
|
|
object-src 'none';
|
|
base-uri 'self' ${assetsDomain};
|
|
form-action 'self' ${assetsDomain};
|
|
frame-ancestors 'none';
|
|
`;
|
|
|
|
const result = scripts
|
|
.map(({ contentSecurityPolicy }) => contentSecurityPolicy)
|
|
.filter(filterOutNullable)
|
|
.reduce((csp, policy) => merge(csp, policy), defaultCSP);
|
|
|
|
return result
|
|
.replace(/\n/g, ' ')
|
|
.replace(/\s{2,}/g, ' ')
|
|
.trim();
|
|
}
|