@salesforce/pwa-kit-runtime 3.2.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/pwa-kit-runtime",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "The PWAKit Runtime",
5
5
  "homepage": "https://github.com/SalesforceCommerceCloud/pwa-kit/tree/develop/packages/pwa-kit-runtime#readme",
6
6
  "bugs": {
@@ -46,11 +46,11 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@loadable/component": "^5.15.3",
49
- "@salesforce/pwa-kit-dev": "3.2.0",
49
+ "@salesforce/pwa-kit-dev": "3.2.1",
50
50
  "@serverless/event-mocks": "^1.1.1",
51
51
  "aws-lambda-mock-context": "^3.2.1",
52
52
  "fs-extra": "^11.1.1",
53
- "internal-lib-build": "3.2.0",
53
+ "internal-lib-build": "3.2.1",
54
54
  "nock": "^13.3.0",
55
55
  "nodemon": "^2.0.22",
56
56
  "sinon": "^13.0.2",
@@ -58,7 +58,7 @@
58
58
  "supertest": "^4.0.2"
59
59
  },
60
60
  "peerDependencies": {
61
- "@salesforce/pwa-kit-dev": "3.2.0"
61
+ "@salesforce/pwa-kit-dev": "3.2.1"
62
62
  },
63
63
  "peerDependenciesMeta": {
64
64
  "@salesforce/pwa-kit-dev": {
@@ -72,5 +72,5 @@
72
72
  "publishConfig": {
73
73
  "directory": "dist"
74
74
  },
75
- "gitHead": "5ec74f88e7bf9ba10a2f86f22a137311ee9bf1b9"
75
+ "gitHead": "2c8502478dc9f571f57042565d3d5938e46336a2"
76
76
  }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.once = exports.enforceSecurityHeaders = exports.RemoteServerFactory = exports.REMOTE_REQUIRED_ENV_VARS = void 0;
6
+ exports.once = exports.RemoteServerFactory = exports.REMOTE_REQUIRED_ENV_VARS = void 0;
7
7
  var _path = _interopRequireDefault(require("path"));
8
8
  var _constants = require("./constants");
9
9
  var _ssrServer = require("../../utils/ssr-server");
@@ -523,7 +523,6 @@ const RemoteServerFactory = {
523
523
 
524
524
  // Apply the SSR middleware to any subsequent routes that we expect users
525
525
  // to add in their projects, like in any regular Express app.
526
- app.use(enforceSecurityHeaders); // Must be AFTER prepNonProxyRequest, as they both modify setHeader.
527
526
  app.use(ssrMiddleware);
528
527
  app.use(errorHandlerMiddleware);
529
528
  applyPatches(options);
@@ -805,102 +804,6 @@ const RemoteServerFactory = {
805
804
  }
806
805
  };
807
806
 
808
- /**
809
- * Patches `res.setHeader` to ensure that the Content-Security-Policy header always includes the
810
- * directives required for PWA Kit to work.
811
- * @param {express.Request} req Express request object
812
- * @param {express.Response} res Express response object
813
- * @param {express.NextFunction} next Express next callback
814
- */
815
- exports.RemoteServerFactory = RemoteServerFactory;
816
- const enforceSecurityHeaders = (req, res, next) => {
817
- /** CSP-compatible origin for Runtime Admin. */
818
- // localhost doesn't include a protocol because different browsers behave differently :\
819
- const runtimeAdmin = (0, _ssrServer.isRemote)() ? 'https://runtime.commercecloud.com' : 'localhost:*';
820
- /**
821
- * Map of directive names/values that are required for PWA Kit to work. Array values will be
822
- * merged with user-provided values; boolean values will replace user-provided values.
823
- * @type Object.<string, string[] | boolean>
824
- */
825
- const directives = {
826
- 'connect-src': ["'self'", runtimeAdmin],
827
- 'frame-ancestors': [runtimeAdmin],
828
- 'img-src': ["'self'", 'data:'],
829
- 'script-src': ["'self'", "'unsafe-eval'", runtimeAdmin],
830
- // Always upgrade insecure requests when deployed, never upgrade on local dev server
831
- 'upgrade-insecure-requests': (0, _ssrServer.isRemote)()
832
- };
833
- const setHeader = res.setHeader;
834
- res.setHeader = (name, value) => {
835
- let modifiedValue = value;
836
- switch (name === null || name === void 0 ? void 0 : name.toLowerCase()) {
837
- case _constants.CONTENT_SECURITY_POLICY:
838
- {
839
- // If multiple Content-Security-Policy headers are provided, then the most restrictive
840
- // option is chosen for each directive. Therefore, we must modify *all* directives to
841
- // ensure that our required directives will work as expected.
842
- // Ref: https://w3c.github.io/webappsec-csp/#multiple-policies
843
- modifiedValue = Array.isArray(value) ? value.map(item => modifyDirectives(item, directives)) : modifyDirectives(value, directives);
844
- break;
845
- }
846
- case _constants.STRICT_TRANSPORT_SECURITY:
847
- {
848
- // Block setting this header on local development server - it will break things!
849
- if (!(0, _ssrServer.isRemote)()) return;
850
- break;
851
- }
852
- default:
853
- {
854
- break;
855
- }
856
- }
857
- return setHeader.call(res, name, modifiedValue);
858
- };
859
- // Provide an initial CSP (or patch the existing header)
860
- res.setHeader(_constants.CONTENT_SECURITY_POLICY, res.getHeader(_constants.CONTENT_SECURITY_POLICY) ?? '');
861
- // Provide an initial value for HSTS, if not already set - use default from `helmet`
862
- if (!res.hasHeader(_constants.STRICT_TRANSPORT_SECURITY)) {
863
- res.setHeader(_constants.STRICT_TRANSPORT_SECURITY, 'max-age=15552000; includeSubDomains');
864
- }
865
- next();
866
- };
867
-
868
- /**
869
- * Updates the given Content-Security-Policy header to include all directives required by PWA Kit.
870
- * @param {string} original Original Content-Security-Policy header
871
- * @returns {string} Modified Content-Security-Policy header
872
- * @private
873
- */
874
- exports.enforceSecurityHeaders = enforceSecurityHeaders;
875
- const modifyDirectives = (original, required) => {
876
- const directives = original.trim().split(';').reduce((acc, directive) => {
877
- const text = directive.trim();
878
- if (text) {
879
- const [name, ...values] = text.split(/ +/);
880
- acc[name] = values;
881
- }
882
- return acc;
883
- }, {});
884
-
885
- // Add missing required CSP directives
886
- for (const [name, value] of Object.entries(required)) {
887
- if (value === true) {
888
- // Boolean directive (required) - overwrite original value
889
- directives[name] = [];
890
- } else if (value === false) {
891
- // Boolean directive (disabled) - delete original value
892
- delete directives[name];
893
- } else {
894
- // Regular string[] directive - merge values
895
- // Wrapping with `[...new Set(array)]` removes duplicate entries
896
- directives[name] = [...new Set([...(directives[name] ?? []), ...value])];
897
- }
898
- }
899
-
900
- // Re-construct header string
901
- return Object.entries(directives).map(([name, values]) => [name, ...values].join(' ')).join(';');
902
- };
903
-
904
807
  /**
905
808
  * ExpressJS middleware that processes any non-proxy request passing
906
809
  * through the Express app.
@@ -917,6 +820,7 @@ const modifyDirectives = (original, required) => {
917
820
  *
918
821
  * @private
919
822
  */
823
+ exports.RemoteServerFactory = RemoteServerFactory;
920
824
  const prepNonProxyRequest = (req, res, next) => {
921
825
  const options = req.app.options;
922
826
  if (!options.allowCookies) {
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
 
3
3
  var _buildRemoteServer = require("./build-remote-server");
4
- var _constants = require("./constants");
5
4
  /*
6
5
  * Copyright (c) 2021, salesforce.com, inc.
7
6
  * All rights reserved.
@@ -22,99 +21,4 @@ describe('the once function', () => {
22
21
  expect(fn.mock.calls).toHaveLength(1);
23
22
  expect(v1).toBe(v2); // The exact same instance
24
23
  });
25
- });
26
-
27
- describe('Content-Security-Policy enforcement', () => {
28
- let res;
29
-
30
- /** Sets the correct values for `isRemote()` to return true */
31
- const mockProduction = () => {
32
- process.env.AWS_LAMBDA_FUNCTION_NAME = 'testEnforceSecurityHeaders';
33
- };
34
- /**
35
- * Helper to make expected CSP more readable. Asserts that the actual CSP header contains each
36
- * of the expected directives.
37
- * @param {string[]} expected Array of expected CSP directives
38
- */
39
- const expectDirectives = expected => {
40
- const actual = res.getHeader(_constants.CONTENT_SECURITY_POLICY).split(';');
41
- expect(actual).toEqual(expect.arrayContaining(expected));
42
- };
43
- beforeEach(() => {
44
- const headers = {};
45
- res = {
46
- hasHeader: key => Object.hasOwn(headers, key),
47
- getHeader: key => headers[key],
48
- setHeader: (key, val) => headers[key] = val
49
- };
50
- });
51
- // Revert state detected by `isRemote()`
52
- afterEach(() => delete process.env.AWS_LAMBDA_FUNCTION_NAME);
53
- test('adds required directives for development', () => {
54
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
55
- res.setHeader(_constants.CONTENT_SECURITY_POLICY, '');
56
- expectDirectives(["connect-src 'self' localhost:*", 'frame-ancestors localhost:*', "img-src 'self' data:", "script-src 'self' 'unsafe-eval' localhost:*"]);
57
- });
58
- test('adds required directives for production', () => {
59
- mockProduction();
60
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
61
- res.setHeader(_constants.CONTENT_SECURITY_POLICY, '');
62
- expectDirectives(["connect-src 'self' https://runtime.commercecloud.com", 'frame-ancestors https://runtime.commercecloud.com', "img-src 'self' data:", "script-src 'self' 'unsafe-eval' https://runtime.commercecloud.com", 'upgrade-insecure-requests']);
63
- });
64
- test('merges with existing CSP directives', () => {
65
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
66
- res.setHeader(_constants.CONTENT_SECURITY_POLICY, "connect-src test:* ; script-src 'unsafe-eval' test:*");
67
- expectDirectives(["connect-src test:* 'self' localhost:*", "script-src 'unsafe-eval' test:* 'self' localhost:*"]);
68
- });
69
- test('allows other CSP directives', () => {
70
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
71
- res.setHeader(_constants.CONTENT_SECURITY_POLICY, 'fake-directive test:*');
72
- expectDirectives(['fake-directive test:*']);
73
- });
74
- test('enforces upgrade-insecure-requests disabled on development', () => {
75
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
76
- res.setHeader(_constants.CONTENT_SECURITY_POLICY, 'upgrade-insecure-requests');
77
- expect(res.getHeader(_constants.CONTENT_SECURITY_POLICY)).not.toContain('upgrade-insecure-requests');
78
- });
79
- test('enforces upgrade-insecure-requests enabled on production', () => {
80
- mockProduction();
81
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
82
- res.setHeader(_constants.CONTENT_SECURITY_POLICY, 'connect-src localhost:*');
83
- expectDirectives(['upgrade-insecure-requests']);
84
- });
85
- test('adds directives even if setHeader is never called', () => {
86
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
87
- expectDirectives(["img-src 'self' data:"]);
88
- });
89
- test('handles multiple CSP headers', () => {
90
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
91
- res.setHeader(_constants.CONTENT_SECURITY_POLICY, ['connect-src first.header', 'script-src second.header']);
92
- const headers = res.getHeader(_constants.CONTENT_SECURITY_POLICY);
93
- expect(headers).toHaveLength(2);
94
- expect(headers[0]).toContain('connect-src first.header');
95
- expect(headers[1]).toContain('script-src second.header');
96
- });
97
- test('does not modify unrelated headers', () => {
98
- const header = 'Contentious-Secret-Police';
99
- const value = 'connect-src unmodified fake directive';
100
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
101
- res.setHeader(header, value);
102
- expect(res.getHeader(header)).toBe(value);
103
- });
104
- test('blocks Strict-Transport-Security header in development', () => {
105
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
106
- res.setHeader(_constants.STRICT_TRANSPORT_SECURITY, 'max-age=12345');
107
- expect(res.hasHeader(_constants.STRICT_TRANSPORT_SECURITY)).toBe(false);
108
- });
109
- test('allows Strict-Transport-Security header in production', () => {
110
- mockProduction();
111
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
112
- res.setHeader(_constants.STRICT_TRANSPORT_SECURITY, 'max-age=12345');
113
- expect(res.getHeader(_constants.STRICT_TRANSPORT_SECURITY)).toBe('max-age=12345');
114
- });
115
- test('provides default value for Strict-Transport-Security header in production', () => {
116
- mockProduction();
117
- (0, _buildRemoteServer.enforceSecurityHeaders)({}, res, () => {});
118
- expect(res.getHeader(_constants.STRICT_TRANSPORT_SECURITY)).toBe('max-age=15552000; includeSubDomains');
119
- });
120
24
  });
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ var _security = require("./security");
7
+ Object.keys(_security).forEach(function (key) {
8
+ if (key === "default" || key === "__esModule") return;
9
+ if (key in exports && exports[key] === _security[key]) return;
10
+ Object.defineProperty(exports, key, {
11
+ enumerable: true,
12
+ get: function () {
13
+ return _security[key];
14
+ }
15
+ });
16
+ });
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.defaultPwaKitSecurityHeaders = void 0;
7
+ var _constants = require("../../ssr/server/constants");
8
+ var _ssrServer = require("../ssr-server");
9
+ /*
10
+ * Copyright (c) 2023, Salesforce, Inc.
11
+ * All rights reserved.
12
+ * SPDX-License-Identifier: BSD-3-Clause
13
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
14
+ */
15
+
16
+ /**
17
+ * This express middleware sets the Content-Security-Policy and Strict-Transport-Security headers to
18
+ * default values that are required for PWA Kit to work. It also patches `res.setHeader` to allow
19
+ * additional CSP directives to be added without removing the required directives, and it prevents
20
+ * the Strict-Transport-Security header from being set on the local dev server.
21
+ * @param {express.Request} req Express request object
22
+ * @param {express.Response} res Express response object
23
+ * @param {express.NextFunction} next Express next callback
24
+ */
25
+ const defaultPwaKitSecurityHeaders = (req, res, next) => {
26
+ /** CSP-compatible origin for Runtime Admin. */
27
+ // localhost doesn't include a protocol because different browsers behave differently :\
28
+ const runtimeAdmin = (0, _ssrServer.isRemote)() ? 'https://runtime.commercecloud.com' : 'localhost:*';
29
+ /**
30
+ * Map of directive names/values that are required for PWA Kit to work. Array values will be
31
+ * merged with user-provided values; boolean values will replace user-provided values.
32
+ * @type Object.<string, string[] | boolean>
33
+ */
34
+ const directives = {
35
+ 'connect-src': ["'self'", runtimeAdmin],
36
+ 'frame-ancestors': [runtimeAdmin],
37
+ 'img-src': ["'self'", 'data:'],
38
+ 'script-src': ["'self'", "'unsafe-eval'", runtimeAdmin],
39
+ // Always upgrade insecure requests when deployed, never upgrade on local dev server
40
+ 'upgrade-insecure-requests': (0, _ssrServer.isRemote)()
41
+ };
42
+ const setHeader = res.setHeader;
43
+ res.setHeader = (name, value) => {
44
+ let modifiedValue = value;
45
+ switch (name === null || name === void 0 ? void 0 : name.toLowerCase()) {
46
+ case _constants.CONTENT_SECURITY_POLICY:
47
+ {
48
+ // If multiple Content-Security-Policy headers are provided, then the most restrictive
49
+ // option is chosen for each directive. Therefore, we must modify *all* directives to
50
+ // ensure that our required directives will work as expected.
51
+ // Ref: https://w3c.github.io/webappsec-csp/#multiple-policies
52
+ modifiedValue = Array.isArray(value) ? value.map(item => modifyDirectives(item, directives)) : modifyDirectives(value, directives);
53
+ break;
54
+ }
55
+ case _constants.STRICT_TRANSPORT_SECURITY:
56
+ {
57
+ // Block setting this header on local development server - it will break things!
58
+ if (!(0, _ssrServer.isRemote)()) return;
59
+ break;
60
+ }
61
+ default:
62
+ {
63
+ break;
64
+ }
65
+ }
66
+ return setHeader.call(res, name, modifiedValue);
67
+ };
68
+ // Provide an initial CSP (or patch the existing header)
69
+ res.setHeader(_constants.CONTENT_SECURITY_POLICY, res.getHeader(_constants.CONTENT_SECURITY_POLICY) ?? '');
70
+ // Provide an initial value for HSTS, if not already set - use default from `helmet`
71
+ if (!res.hasHeader(_constants.STRICT_TRANSPORT_SECURITY)) {
72
+ res.setHeader(_constants.STRICT_TRANSPORT_SECURITY, 'max-age=15552000; includeSubDomains');
73
+ }
74
+ next();
75
+ };
76
+
77
+ /**
78
+ * Updates the given Content-Security-Policy header to include all directives required by PWA Kit.
79
+ * @param {string} original Original Content-Security-Policy header
80
+ * @returns {string} Modified Content-Security-Policy header
81
+ * @private
82
+ */
83
+ exports.defaultPwaKitSecurityHeaders = defaultPwaKitSecurityHeaders;
84
+ const modifyDirectives = (original, required) => {
85
+ const directives = original.trim().split(';').reduce((acc, directive) => {
86
+ const text = directive.trim();
87
+ if (text) {
88
+ const [name, ...values] = text.split(/ +/);
89
+ acc[name] = values;
90
+ }
91
+ return acc;
92
+ }, {});
93
+
94
+ // Add missing required CSP directives
95
+ for (const [name, value] of Object.entries(required)) {
96
+ if (value === true) {
97
+ // Boolean directive (required) - overwrite original value
98
+ directives[name] = [];
99
+ } else if (value === false) {
100
+ // Boolean directive (disabled) - delete original value
101
+ delete directives[name];
102
+ } else {
103
+ // Regular string[] directive - merge values
104
+ // Wrapping with `[...new Set(array)]` removes duplicate entries
105
+ directives[name] = [...new Set([...(directives[name] ?? []), ...value])];
106
+ }
107
+ }
108
+
109
+ // Re-construct header string
110
+ return Object.entries(directives).map(([name, values]) => [name, ...values].join(' ')).join(';');
111
+ };
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+
3
+ var _constants = require("../../ssr/server/constants");
4
+ var _security = require("./security");
5
+ /*
6
+ * Copyright (c) 2023, Salesforce, Inc.
7
+ * All rights reserved.
8
+ * SPDX-License-Identifier: BSD-3-Clause
9
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
10
+ */
11
+
12
+ describe('Content-Security-Policy enforcement', () => {
13
+ let res;
14
+
15
+ /** Sets the correct values for `isRemote()` to return true */
16
+ const mockProduction = () => {
17
+ process.env.AWS_LAMBDA_FUNCTION_NAME = 'testEnforceSecurityHeaders';
18
+ };
19
+ /**
20
+ * Helper to make expected CSP more readable. Asserts that the actual CSP header contains each
21
+ * of the expected directives.
22
+ * @param {string[]} expected Array of expected CSP directives
23
+ */
24
+ const expectDirectives = expected => {
25
+ const actual = res.getHeader(_constants.CONTENT_SECURITY_POLICY).split(';');
26
+ expect(actual).toEqual(expect.arrayContaining(expected));
27
+ };
28
+ beforeEach(() => {
29
+ const headers = {};
30
+ res = {
31
+ hasHeader: key => Object.hasOwn(headers, key),
32
+ getHeader: key => headers[key],
33
+ setHeader: (key, val) => headers[key] = val
34
+ };
35
+ });
36
+ // Revert state detected by `isRemote()`
37
+ afterEach(() => delete process.env.AWS_LAMBDA_FUNCTION_NAME);
38
+ test('adds required directives for development', () => {
39
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
40
+ res.setHeader(_constants.CONTENT_SECURITY_POLICY, '');
41
+ expectDirectives(["connect-src 'self' localhost:*", 'frame-ancestors localhost:*', "img-src 'self' data:", "script-src 'self' 'unsafe-eval' localhost:*"]);
42
+ });
43
+ test('adds required directives for production', () => {
44
+ mockProduction();
45
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
46
+ res.setHeader(_constants.CONTENT_SECURITY_POLICY, '');
47
+ expectDirectives(["connect-src 'self' https://runtime.commercecloud.com", 'frame-ancestors https://runtime.commercecloud.com', "img-src 'self' data:", "script-src 'self' 'unsafe-eval' https://runtime.commercecloud.com", 'upgrade-insecure-requests']);
48
+ });
49
+ test('merges with existing CSP directives', () => {
50
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
51
+ res.setHeader(_constants.CONTENT_SECURITY_POLICY, "connect-src test:* ; script-src 'unsafe-eval' test:*");
52
+ expectDirectives(["connect-src test:* 'self' localhost:*", "script-src 'unsafe-eval' test:* 'self' localhost:*"]);
53
+ });
54
+ test('allows other CSP directives', () => {
55
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
56
+ res.setHeader(_constants.CONTENT_SECURITY_POLICY, 'fake-directive test:*');
57
+ expectDirectives(['fake-directive test:*']);
58
+ });
59
+ test('enforces upgrade-insecure-requests disabled on development', () => {
60
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
61
+ res.setHeader(_constants.CONTENT_SECURITY_POLICY, 'upgrade-insecure-requests');
62
+ expect(res.getHeader(_constants.CONTENT_SECURITY_POLICY)).not.toContain('upgrade-insecure-requests');
63
+ });
64
+ test('enforces upgrade-insecure-requests enabled on production', () => {
65
+ mockProduction();
66
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
67
+ res.setHeader(_constants.CONTENT_SECURITY_POLICY, 'connect-src localhost:*');
68
+ expectDirectives(['upgrade-insecure-requests']);
69
+ });
70
+ test('adds directives even if setHeader is never called', () => {
71
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
72
+ expectDirectives(["img-src 'self' data:"]);
73
+ });
74
+ test('handles multiple CSP headers', () => {
75
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
76
+ res.setHeader(_constants.CONTENT_SECURITY_POLICY, ['connect-src first.header', 'script-src second.header']);
77
+ const headers = res.getHeader(_constants.CONTENT_SECURITY_POLICY);
78
+ expect(headers).toHaveLength(2);
79
+ expect(headers[0]).toContain('connect-src first.header');
80
+ expect(headers[1]).toContain('script-src second.header');
81
+ });
82
+ test('does not modify unrelated headers', () => {
83
+ const header = 'Contentious-Secret-Police';
84
+ const value = 'connect-src unmodified fake directive';
85
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
86
+ res.setHeader(header, value);
87
+ expect(res.getHeader(header)).toBe(value);
88
+ });
89
+ test('blocks Strict-Transport-Security header in development', () => {
90
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
91
+ res.setHeader(_constants.STRICT_TRANSPORT_SECURITY, 'max-age=12345');
92
+ expect(res.hasHeader(_constants.STRICT_TRANSPORT_SECURITY)).toBe(false);
93
+ });
94
+ test('allows Strict-Transport-Security header in production', () => {
95
+ mockProduction();
96
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
97
+ res.setHeader(_constants.STRICT_TRANSPORT_SECURITY, 'max-age=12345');
98
+ expect(res.getHeader(_constants.STRICT_TRANSPORT_SECURITY)).toBe('max-age=12345');
99
+ });
100
+ test('provides default value for Strict-Transport-Security header in production', () => {
101
+ mockProduction();
102
+ (0, _security.defaultPwaKitSecurityHeaders)({}, res, () => {});
103
+ expect(res.getHeader(_constants.STRICT_TRANSPORT_SECURITY)).toBe('max-age=15552000; includeSubDomains');
104
+ });
105
+ });