diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d3a31b..da79c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v1.0.3] - 2025-12-09 + +### Added +- Add methods to customize CSP + ## [v1.0.2] - 2025-12-09 ### Changed diff --git a/csp.js b/csp.js index 16ab4b5..1488f38 100644 --- a/csp.js +++ b/csp.js @@ -4,18 +4,6 @@ export const importmap = new Importmap(); await importmap.importLocalPackage(); export const integrity = await importmap.getIntegrity(); -export function useCSP(policy = { 'default-src': ['\'self\''] }) { - const policyStr = Object.entries(policy).map(([name, values]) => { - return `${name} ${Array.isArray(values) ? values.join(' ') : values}`; - }).join('; '); - - return function (response, { request }) { - if (request.destination === 'document' && ! response.headers.has('Content-Security-Policy')) { - response.headers.set('Content-Security-Policy', policyStr); - } - }; -} - const DEFAULT_SRC = ['\'self\'']; const SCRIPT_SRC = ['\'self\'', 'https://unpkg.com/@shgysk8zer0/', 'https://unpkg.com/@kernvalley/', 'https://unpkg.com/@aegisjsproject/', `'${integrity}'`]; const STYLE_SRC = ['\'self\'', 'https://unpkg.com/@agisjsproject/', 'blob:']; @@ -24,8 +12,63 @@ const MEDIA_SRC = ['\'self\'', 'blob:']; const CONNECT_SRC = ['\'self\'']; const FONT_SRC = ['\'self\'']; const FRAME_SRC = ['\'self\'', 'https://www.youtube-nocookie.com']; +const MANIFEST_SRC = ['\'self\'']; +const PREFETCH_SRC = ['\'self\'']; +const WORKER_SRC = ['\'selfs\'']; +const OBJECT_SRC = []; const TRUSTED_TYPES = ['aegis-sanitizer#html']; +export const lockCSP = () => { + Object.freeze(DEFAULT_SRC); + Object.freeze(SCRIPT_SRC); + Object.freeze(STYLE_SRC); + Object.freeze(IMAGE_SRC); + Object.freeze(MEDIA_SRC); + Object.freeze(CONNECT_SRC); + Object.freeze(FONT_SRC); + Object.freeze(FRAME_SRC); + Object.freeze(MANIFEST_SRC); + Object.freeze(PREFETCH_SRC); + Object.freeze(WORKER_SRC); + Object.freeze(OBJECT_SRC); + Object.freeze(TRUSTED_TYPES); +}; + +export const addDefaultSrc = (...srcs) => DEFAULT_SRC.push(...srcs); +export const addScriptSrc = (...srcs) => SCRIPT_SRC.push(...srcs); +export const addStyleSrc = (...srcs) => STYLE_SRC.push(...srcs); +export const addImageSrc = (...srcs) => IMAGE_SRC.push(...srcs); +export const addMediaSrc = (...srcs) => MEDIA_SRC.push(...srcs); +export const addConnectSrc = (...srcs) => CONNECT_SRC.push(...srcs); +export const addFontSrc = (...srcs) => FONT_SRC.push(...srcs); +export const addFrameSrc = (...srcs) => FRAME_SRC.push(...srcs); +export const addManifestSrc = (...srcs) => MANIFEST_SRC.push(...srcs); +export const addObjectSrc = (...srcs) => OBJECT_SRC.push(...srcs); +export const addPrefetchSrc = (...srcs) => PREFETCH_SRC.push(...srcs); +export const addWorkerSrc = (...srcs) => WORKER_SRC.push(...srcs); +export const addTrustedTypePolicy = (...policies) => TRUSTED_TYPES.push(...policies); + +export function useCSP(policy = { 'default-src': ['\'self\''] }) { + const policyStr = Object.entries(policy).map(([name, values]) => { + return `${name} ${Array.isArray(values) ? values.length === 0 ? '\'none\'' : values.join(' ') : values}`; + }).join('; '); + + /** + * @param {Response} response + * @param {object} config + * @param {Request} [config.request] + */ + return function (response, { request }) { + try { + if (request.destination === 'document' && ! response.headers.has('Content-Security-Policy')) { + response.headers.set('Content-Security-Policy', policyStr); + } + } catch(err) { + console.error(err); + } + }; +} + export const useDefaultCSP = ({ ...rest } = {}) => useCSP({ 'default-src': DEFAULT_SRC, 'script-src': SCRIPT_SRC, @@ -35,6 +78,10 @@ export const useDefaultCSP = ({ ...rest } = {}) => useCSP({ 'font-src': FONT_SRC, 'frame-src': FRAME_SRC, 'connect-src': CONNECT_SRC, + 'manifest-src': MANIFEST_SRC, + 'obejct-src': OBJECT_SRC, + 'prefetch-src': PREFETCH_SRC, + 'worker-src': WORKER_SRC, 'trusted-types': TRUSTED_TYPES, 'require-trusted-types-for': '\'script\'', ...rest diff --git a/http.config.js b/http.config.js index 4a12878..12b0bec 100644 --- a/http.config.js +++ b/http.config.js @@ -23,13 +23,13 @@ export default { ], responsePostprocessors: [ '@aegisjsproject/http-utils/compression.js', - setCacheItem, '@aegisjsproject/http-utils/cors.js', '@aegisjsproject/http-utils/csp.js', (response, { request }) => { if (request.destination === 'document') { response.headers.append('Link', `<${imports['@shgysk8zer0/polyfills']}>; rel="preload"; as="script"; fetchpriority="high"; crossorigin="anonymous"; referrerpolicy="no-referrer"`); } - } + }, + setCacheItem, ], }; diff --git a/package-lock.json b/package-lock.json index 0cfb6e1..4e872b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aegisjsproject/http-utils", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aegisjsproject/http-utils", - "version": "1.0.2", + "version": "1.0.3", "funding": [ { "type": "librepay", diff --git a/package.json b/package.json index 32eefcc..cd7e7bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aegisjsproject/http-utils", - "version": "1.0.2", + "version": "1.0.3", "description": "HTTP Utilities for @shgysk8zer0/http-server ", "keywords": [], "type": "module", @@ -8,6 +8,30 @@ "./*.js": { "import": "./*.js" }, + "./cache": { + "import": "./cache.js" + }, + "./compression": { + "import": "./compression.js" + }, + "./cors": { + "import": "./cors.js" + }, + "./csp": { + "import": "./csp.js" + }, + "./geo": { + "import": "./geo.js" + }, + "./logger": { + "import": "./logger.js" + }, + "./rate-limit": { + "import": "./rate-limit.js" + }, + "./request-id": { + "import": "./request-id.js" + }, "./*.mjs": { "import": "./*.js" },