|
| 1 | +import helmet from 'helmet' |
| 2 | +import { cloneDeep } from 'lodash-es' |
| 3 | +import isArchivedVersion from '../lib/is-archived-version.js' |
| 4 | +import versionSatisfiesRange from '../lib/version-satisfies-range.js' |
| 5 | + |
| 6 | +const isDev = process.env.NODE_ENV === 'development' |
| 7 | +const AZURE_STORAGE_URL = 'githubdocs.azureedge.net' |
| 8 | +const GITHUB_DOMAINS = [ |
| 9 | + "'self'", |
| 10 | + 'github.com', |
| 11 | + '*.github.com', |
| 12 | + '*.githubusercontent.com', |
| 13 | + '*.githubassets.com', |
| 14 | +] |
| 15 | + |
| 16 | +const DEFAULT_OPTIONS = { |
| 17 | + crossOriginResourcePolicy: true, |
| 18 | + crossOriginEmbedderPolicy: false, // doesn't work with youtube |
| 19 | + referrerPolicy: { |
| 20 | + policy: 'strict-origin-when-cross-origin', |
| 21 | + }, |
| 22 | + // This module defines a Content Security Policy (CSP) to disallow |
| 23 | + // inline scripts and content from untrusted sources. |
| 24 | + contentSecurityPolicy: { |
| 25 | + directives: { |
| 26 | + defaultSrc: ["'none'"], |
| 27 | + prefetchSrc: ["'self'"], |
| 28 | + // When doing local dev, especially in Safari, you need to add `ws:` |
| 29 | + // which NextJS uses for the hot module reloading. |
| 30 | + connectSrc: ["'self'", isDev && 'ws:'].filter(Boolean), |
| 31 | + fontSrc: ["'self'", 'data:', AZURE_STORAGE_URL], |
| 32 | + imgSrc: [...GITHUB_DOMAINS, 'data:', AZURE_STORAGE_URL, 'placehold.it'], |
| 33 | + objectSrc: ["'self'"], |
| 34 | + // For use during development only! |
| 35 | + // `unsafe-eval` allows us to use a performant webpack devtool setting (eval) |
| 36 | + // https://webpack.js.org/configuration/devtool/#devtool |
| 37 | + scriptSrc: ["'self'", isDev && "'unsafe-eval'"].filter(Boolean), |
| 38 | + frameSrc: [ |
| 39 | + ...GITHUB_DOMAINS, |
| 40 | + isDev && 'http://localhost:3000', |
| 41 | + 'https://www.youtube-nocookie.com', |
| 42 | + ].filter(Boolean), |
| 43 | + frameAncestors: [...GITHUB_DOMAINS], |
| 44 | + styleSrc: ["'self'", "'unsafe-inline'"], |
| 45 | + childSrc: ["'self'"], // exception for search in deprecated GHE versions |
| 46 | + }, |
| 47 | + }, |
| 48 | +} |
| 49 | + |
| 50 | +const NODE_DEPRECATED_OPTIONS = cloneDeep(DEFAULT_OPTIONS) |
| 51 | +const { directives: ndDirs } = NODE_DEPRECATED_OPTIONS.contentSecurityPolicy |
| 52 | +ndDirs.scriptSrc.push( |
| 53 | + "'unsafe-eval'", |
| 54 | + "'unsafe-inline'", |
| 55 | + 'http://www.google-analytics.com', |
| 56 | + 'https://ssl.google-analytics.com' |
| 57 | +) |
| 58 | +ndDirs.connectSrc.push('https://www.google-analytics.com') |
| 59 | +ndDirs.imgSrc.push('http://www.google-analytics.com', 'https://ssl.google-analytics.com') |
| 60 | + |
| 61 | +const STATIC_DEPRECATED_OPTIONS = cloneDeep(DEFAULT_OPTIONS) |
| 62 | +STATIC_DEPRECATED_OPTIONS.contentSecurityPolicy.directives.scriptSrc.push("'unsafe-inline'") |
| 63 | + |
| 64 | +const defaultHelmet = helmet(DEFAULT_OPTIONS) |
| 65 | +const nodeDeprecatedHelmet = helmet(NODE_DEPRECATED_OPTIONS) |
| 66 | +const staticDeprecatedHelmet = helmet(STATIC_DEPRECATED_OPTIONS) |
| 67 | + |
| 68 | +export default function helmetMiddleware(req, res, next) { |
| 69 | + // Enable CORS |
| 70 | + if (['GET', 'OPTIONS'].includes(req.method)) { |
| 71 | + res.set('access-control-allow-origin', '*') |
| 72 | + } |
| 73 | + |
| 74 | + // Determine version for exceptions |
| 75 | + const { requestedVersion } = isArchivedVersion(req) |
| 76 | + |
| 77 | + // Exception for deprecated Enterprise docs (Node.js era) |
| 78 | + if ( |
| 79 | + versionSatisfiesRange(requestedVersion, '<=2.19') && |
| 80 | + versionSatisfiesRange(requestedVersion, '>2.12') |
| 81 | + ) { |
| 82 | + return nodeDeprecatedHelmet(req, res, next) |
| 83 | + } |
| 84 | + |
| 85 | + // Exception for search in deprecated Enterprise docs <=2.12 (static site era) |
| 86 | + if (versionSatisfiesRange(requestedVersion, '<=2.12')) { |
| 87 | + return staticDeprecatedHelmet(req, res, next) |
| 88 | + } |
| 89 | + |
| 90 | + return defaultHelmet(req, res, next) |
| 91 | +} |
0 commit comments