@lwrjs/security 0.10.0-alpha.16

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/LICENSE ADDED
@@ -0,0 +1,10 @@
1
+ MIT LICENSE
2
+
3
+ Copyright (c) 2020, Salesforce.com, Inc.
4
+ All rights reserved.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,82 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {get: all[name], enumerable: true});
6
+ };
7
+
8
+ // packages/@lwrjs/security/src/headers/content-security-policy.ts
9
+ __markAsModule(exports);
10
+ __export(exports, {
11
+ default: () => contentSecurityPolicy
12
+ });
13
+ var DEFAULT_DIRECTIVES = {
14
+ "default-src": ["'self'"],
15
+ "base-uri": ["'self'"],
16
+ "font-src": ["'self'", "https:", "data:"],
17
+ "form-action": ["'self'"],
18
+ "frame-ancestors": ["'self'"],
19
+ "img-src": ["'self'", "data:"],
20
+ "object-src": ["'none'"],
21
+ "script-src": ["'self'"],
22
+ "script-src-attr": ["'none'"],
23
+ "style-src": ["'self'", "https:", "'unsafe-inline'"],
24
+ "upgrade-insecure-requests": []
25
+ };
26
+ function parseDirectives(str) {
27
+ const directives = {};
28
+ for (const directive of str.split(";")) {
29
+ const [key, ...value] = directive.trim().split(" ");
30
+ directives[key] = value;
31
+ }
32
+ return directives;
33
+ }
34
+ function stringifyDirectives(directives) {
35
+ const output = [];
36
+ for (const directive of directives.keys()) {
37
+ const values = directives.get(directive);
38
+ if (!values) {
39
+ continue;
40
+ }
41
+ output.push(`${directive} ${Array.from(values).join(" ")}`);
42
+ }
43
+ return output.join(";");
44
+ }
45
+ function normalizeDirectives(input) {
46
+ const output = new Map();
47
+ for (const directives of input) {
48
+ for (const [directive, values] of Object.entries(directives)) {
49
+ let set = output.get(directive);
50
+ if (!set) {
51
+ set = new Set();
52
+ output.set(directive, set);
53
+ }
54
+ for (const value of values) {
55
+ set.add(value);
56
+ }
57
+ }
58
+ }
59
+ return output;
60
+ }
61
+ function contentSecurityPolicy(header, options, hashes) {
62
+ const input = [];
63
+ if (options?.useDefault === void 0 || options?.useDefault === true) {
64
+ input.push(DEFAULT_DIRECTIVES);
65
+ }
66
+ if (options?.directives && typeof options.directives === "object") {
67
+ input.push(options.directives);
68
+ }
69
+ if (options?.directives && typeof options.directives === "string") {
70
+ input.push(parseDirectives(options.directives));
71
+ }
72
+ if (header?.length) {
73
+ input.push(parseDirectives(header));
74
+ }
75
+ if (hashes?.length) {
76
+ input.push({
77
+ "script-src": hashes
78
+ });
79
+ }
80
+ const normalizedDirectives = normalizeDirectives(input);
81
+ return stringifyDirectives(normalizedDirectives);
82
+ }
@@ -0,0 +1,21 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {get: all[name], enumerable: true});
6
+ };
7
+
8
+ // packages/@lwrjs/security/src/headers/referrer-policy.ts
9
+ __markAsModule(exports);
10
+ __export(exports, {
11
+ default: () => referrerPolicy
12
+ });
13
+ var DEFAULT_DIRECTIVE = "no-referrer";
14
+ function referrerPolicy(header, option) {
15
+ const value = header || option;
16
+ if (!value) {
17
+ return DEFAULT_DIRECTIVE;
18
+ }
19
+ const directives = typeof value === "string" ? [value] : value;
20
+ return directives.join(",");
21
+ }
@@ -0,0 +1,41 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {get: all[name], enumerable: true});
6
+ };
7
+
8
+ // packages/@lwrjs/security/src/headers/strict-transport-security.ts
9
+ __markAsModule(exports);
10
+ __export(exports, {
11
+ default: () => strictTransportSecurity
12
+ });
13
+ var DEFAULT_MAX_AGE = 180 * 24 * 60 * 60;
14
+ function parseDirectives(str) {
15
+ const policy = {};
16
+ for (const segment of str.split(";")) {
17
+ const directive = segment.trim();
18
+ if (directive.startsWith("maxage")) {
19
+ policy.maxAge = parseInt(directive.substring(directive.indexOf("=") + 1));
20
+ }
21
+ if (directive === "includeSubDomains") {
22
+ policy.includeSubDomains = true;
23
+ }
24
+ if (directive === "preload") {
25
+ policy.preload = true;
26
+ }
27
+ }
28
+ return policy;
29
+ }
30
+ function strictTransportSecurity(header, option) {
31
+ const policy = header !== void 0 ? parseDirectives(header) : option;
32
+ const maxAge = policy?.maxAge !== void 0 ? policy.maxAge : DEFAULT_MAX_AGE;
33
+ const directives = [`max-age=${maxAge}`];
34
+ if (policy?.includeSubDomains === void 0 || policy?.includeSubDomains) {
35
+ directives.push("includeSubDomains");
36
+ }
37
+ if (policy?.preload) {
38
+ directives.push("preload");
39
+ }
40
+ return directives.join("; ");
41
+ }
@@ -0,0 +1,127 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {get: all[name], enumerable: true});
11
+ };
12
+ var __exportStar = (target, module2, desc) => {
13
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
14
+ for (let key of __getOwnPropNames(module2))
15
+ if (!__hasOwnProp.call(target, key) && key !== "default")
16
+ __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
17
+ }
18
+ return target;
19
+ };
20
+ var __toModule = (module2) => {
21
+ return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
22
+ };
23
+
24
+ // packages/@lwrjs/security/src/headers.ts
25
+ __markAsModule(exports);
26
+ __export(exports, {
27
+ resolveHeaders: () => resolveHeaders
28
+ });
29
+ var import_crypto = __toModule(require("crypto"));
30
+ var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
31
+ var import_content_security_policy = __toModule(require("./headers/content-security-policy.cjs"));
32
+ var import_referrer_policy = __toModule(require("./headers/referrer-policy.cjs"));
33
+ var import_strict_transport_security = __toModule(require("./headers/strict-transport-security.cjs"));
34
+ function getResources(viewDefinition) {
35
+ const {viewRecord} = viewDefinition;
36
+ const resources = [];
37
+ if (viewRecord.resources) {
38
+ resources.push(...viewRecord.resources);
39
+ }
40
+ if (viewRecord.bootstrapModule?.resources) {
41
+ resources.push(...viewRecord.bootstrapModule.resources);
42
+ }
43
+ return resources;
44
+ }
45
+ function hash(source) {
46
+ return `'sha256-${(0, import_crypto.createHash)("sha256").update(source).digest("base64")}'`;
47
+ }
48
+ async function hashResources(resources) {
49
+ const hashes = [];
50
+ for (const resource of resources) {
51
+ if (!resource.inline || resource.type !== "application/javascript")
52
+ continue;
53
+ if (resource.content) {
54
+ hashes.push(hash(resource.content));
55
+ }
56
+ if (resource.stream) {
57
+ const content = await (0, import_shared_utils.streamToString)(resource.stream());
58
+ hashes.push(hash(content));
59
+ }
60
+ }
61
+ return hashes;
62
+ }
63
+ async function getResourceHashes(viewResponse) {
64
+ if (!viewResponse.metadata?.viewDefinition) {
65
+ return [];
66
+ }
67
+ const {viewDefinition} = viewResponse.metadata;
68
+ const resources = getResources(viewDefinition);
69
+ const hashes = await hashResources(resources);
70
+ return hashes;
71
+ }
72
+ function normalizeHeaders(headers = {}) {
73
+ return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
74
+ }
75
+ function normalizeOptions(options = {}) {
76
+ for (const [option, value] of Object.entries(options)) {
77
+ if (value === true) {
78
+ delete options[option];
79
+ }
80
+ }
81
+ return options;
82
+ }
83
+ async function resolveHeaders(viewResponse, options) {
84
+ options = normalizeOptions(options);
85
+ const headers = normalizeHeaders(viewResponse.headers);
86
+ if (options.contentSecurityPolicy === void 0 || typeof options.contentSecurityPolicy === "object") {
87
+ const headerName = options.contentSecurityPolicy?.reportOnly ? "content-security-policy-report-only" : "content-security-policy";
88
+ let hashes;
89
+ if (options.contentSecurityPolicy?.resourceHashing === void 0) {
90
+ hashes = await getResourceHashes(viewResponse);
91
+ }
92
+ headers[headerName] = (0, import_content_security_policy.default)(headers[headerName], options.contentSecurityPolicy, hashes);
93
+ }
94
+ if (options.referrerPolicy === void 0 || typeof options.referrerPolicy === "object") {
95
+ const headerName = "referrer-policy";
96
+ headers[headerName] = (0, import_referrer_policy.default)(headers[headerName], options.referrerPolicy);
97
+ }
98
+ if (options.strictTransportSecurity === void 0 || typeof options.strictTransportSecurity === "object") {
99
+ const headerName = "strict-transport-security";
100
+ headers[headerName] = (0, import_strict_transport_security.default)(headers[headerName], options.strictTransportSecurity);
101
+ }
102
+ if (options.crossOriginEmbedderPolicy === void 0 || typeof options.crossOriginEmbedderPolicy === "string") {
103
+ const headerName = "cross-origin-embedder-policy";
104
+ headers[headerName] = headers[headerName] || options?.crossOriginEmbedderPolicy || "require-corp";
105
+ }
106
+ if (options.crossOriginOpenerPolicy === void 0 || typeof options.crossOriginOpenerPolicy === "string") {
107
+ const headerName = "cross-origin-opener-policy";
108
+ headers[headerName] = headers[headerName] || options?.crossOriginOpenerPolicy || "same-origin";
109
+ }
110
+ if (options.crossOriginResourcePolicy === void 0 || typeof options.crossOriginResourcePolicy === "string") {
111
+ const headerName = "cross-origin-resource-policy";
112
+ headers[headerName] = headers[headerName] || options?.crossOriginResourcePolicy || "same-origin";
113
+ }
114
+ if (options.xContentTypeOptions === void 0) {
115
+ headers["x-content-type-options"] = "nosniff";
116
+ }
117
+ if (options.xFrameOptions === void 0) {
118
+ headers["x-frame-options"] = "sameorigin";
119
+ }
120
+ if (options.xPermittedCrossDomainPolicies === void 0) {
121
+ headers["x-permitted-cross-domain-policies"] = "none";
122
+ }
123
+ if (options.xXSSProtection === void 0) {
124
+ headers["x-xss-protection"] = "0";
125
+ }
126
+ return headers;
127
+ }
@@ -0,0 +1,32 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {get: all[name], enumerable: true});
11
+ };
12
+ var __exportStar = (target, module2, desc) => {
13
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
14
+ for (let key of __getOwnPropNames(module2))
15
+ if (!__hasOwnProp.call(target, key) && key !== "default")
16
+ __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
17
+ }
18
+ return target;
19
+ };
20
+ var __toModule = (module2) => {
21
+ return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
22
+ };
23
+
24
+ // packages/@lwrjs/security/src/index.ts
25
+ __markAsModule(exports);
26
+ __export(exports, {
27
+ default: () => import_route_handler.default,
28
+ secure: () => import_wrapper.secure
29
+ });
30
+ var import_route_handler = __toModule(require("./route-handler.cjs"));
31
+ var import_wrapper = __toModule(require("./wrapper.cjs"));
32
+ __exportStar(exports, __toModule(require("./options.cjs")));
@@ -0,0 +1,5 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
3
+
4
+ // packages/@lwrjs/security/src/options.ts
5
+ __markAsModule(exports);
@@ -0,0 +1,34 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {get: all[name], enumerable: true});
11
+ };
12
+ var __exportStar = (target, module2, desc) => {
13
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
14
+ for (let key of __getOwnPropNames(module2))
15
+ if (!__hasOwnProp.call(target, key) && key !== "default")
16
+ __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
17
+ }
18
+ return target;
19
+ };
20
+ var __toModule = (module2) => {
21
+ return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
22
+ };
23
+
24
+ // packages/@lwrjs/security/src/route-handler.ts
25
+ __markAsModule(exports);
26
+ __export(exports, {
27
+ default: () => securityRouteHandler
28
+ });
29
+ var import_headers = __toModule(require("./headers.cjs"));
30
+ async function securityRouteHandler(request, context, options) {
31
+ const viewResponse = await context.viewApi.getViewResponse(context.route);
32
+ viewResponse.headers = await (0, import_headers.resolveHeaders)(viewResponse, options);
33
+ return viewResponse;
34
+ }
@@ -0,0 +1,57 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {get: all[name], enumerable: true});
11
+ };
12
+ var __exportStar = (target, module2, desc) => {
13
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
14
+ for (let key of __getOwnPropNames(module2))
15
+ if (!__hasOwnProp.call(target, key) && key !== "default")
16
+ __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
17
+ }
18
+ return target;
19
+ };
20
+ var __toModule = (module2) => {
21
+ return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
22
+ };
23
+
24
+ // packages/@lwrjs/security/src/wrapper.ts
25
+ __markAsModule(exports);
26
+ __export(exports, {
27
+ secure: () => secure
28
+ });
29
+ var import_headers = __toModule(require("./headers.cjs"));
30
+ function isViewResponse(response) {
31
+ return response.body !== void 0;
32
+ }
33
+ function isViewDefinitionResponse(response) {
34
+ return response.view !== void 0;
35
+ }
36
+ function secure(routeHandler, options) {
37
+ return async (request, context) => {
38
+ const routeHandlerResponse = await routeHandler(request, context);
39
+ if (isViewResponse(routeHandlerResponse)) {
40
+ if (!routeHandlerResponse.headers || !routeHandlerResponse.headers["content-type"].startsWith("text/html")) {
41
+ return routeHandlerResponse;
42
+ }
43
+ routeHandlerResponse.headers = await (0, import_headers.resolveHeaders)(routeHandlerResponse, options);
44
+ return routeHandlerResponse;
45
+ }
46
+ if (isViewDefinitionResponse(routeHandlerResponse)) {
47
+ const viewResponse = await context.viewApi.getViewResponse(routeHandlerResponse.view, routeHandlerResponse.viewParams, routeHandlerResponse.renderOptions);
48
+ viewResponse.headers = {
49
+ ...viewResponse.headers,
50
+ ...routeHandlerResponse.headers
51
+ };
52
+ viewResponse.headers = await (0, import_headers.resolveHeaders)(viewResponse, options);
53
+ return viewResponse;
54
+ }
55
+ throw new Error("Unexpected route handler response");
56
+ };
57
+ }
@@ -0,0 +1,11 @@
1
+ export declare type Directives = {
2
+ [key: string]: string[];
3
+ };
4
+ export interface ContentSecurityPolicy {
5
+ useDefault?: boolean;
6
+ resourceHashing?: boolean;
7
+ reportOnly?: boolean;
8
+ directives?: Directives | string;
9
+ }
10
+ export default function contentSecurityPolicy(header?: string, options?: ContentSecurityPolicy, hashes?: string[]): string;
11
+ //# sourceMappingURL=content-security-policy.d.ts.map
@@ -0,0 +1,71 @@
1
+ const DEFAULT_DIRECTIVES = {
2
+ 'default-src': ["'self'"],
3
+ 'base-uri': ["'self'"],
4
+ 'font-src': ["'self'", 'https:', 'data:'],
5
+ 'form-action': ["'self'"],
6
+ 'frame-ancestors': ["'self'"],
7
+ 'img-src': ["'self'", 'data:'],
8
+ 'object-src': ["'none'"],
9
+ 'script-src': ["'self'"],
10
+ 'script-src-attr': ["'none'"],
11
+ 'style-src': ["'self'", 'https:', "'unsafe-inline'"],
12
+ 'upgrade-insecure-requests': [],
13
+ };
14
+ function parseDirectives(str) {
15
+ const directives = {};
16
+ for (const directive of str.split(';')) {
17
+ const [key, ...value] = directive.trim().split(' ');
18
+ directives[key] = value;
19
+ }
20
+ return directives;
21
+ }
22
+ function stringifyDirectives(directives) {
23
+ const output = [];
24
+ for (const directive of directives.keys()) {
25
+ const values = directives.get(directive);
26
+ if (!values) {
27
+ continue;
28
+ }
29
+ output.push(`${directive} ${Array.from(values).join(' ')}`);
30
+ }
31
+ return output.join(';');
32
+ }
33
+ function normalizeDirectives(input) {
34
+ const output = new Map();
35
+ for (const directives of input) {
36
+ for (const [directive, values] of Object.entries(directives)) {
37
+ let set = output.get(directive);
38
+ if (!set) {
39
+ set = new Set();
40
+ output.set(directive, set);
41
+ }
42
+ for (const value of values) {
43
+ set.add(value);
44
+ }
45
+ }
46
+ }
47
+ return output;
48
+ }
49
+ export default function contentSecurityPolicy(header, options, hashes) {
50
+ const input = [];
51
+ if (options?.useDefault === undefined || options?.useDefault === true) {
52
+ input.push(DEFAULT_DIRECTIVES);
53
+ }
54
+ if (options?.directives && typeof options.directives === 'object') {
55
+ input.push(options.directives);
56
+ }
57
+ if (options?.directives && typeof options.directives === 'string') {
58
+ input.push(parseDirectives(options.directives));
59
+ }
60
+ if (header?.length) {
61
+ input.push(parseDirectives(header));
62
+ }
63
+ if (hashes?.length) {
64
+ input.push({
65
+ 'script-src': hashes,
66
+ });
67
+ }
68
+ const normalizedDirectives = normalizeDirectives(input);
69
+ return stringifyDirectives(normalizedDirectives);
70
+ }
71
+ //# sourceMappingURL=content-security-policy.js.map
@@ -0,0 +1,3 @@
1
+ export declare type ReferrerPolicy = string | string[];
2
+ export default function referrerPolicy(header?: string, option?: ReferrerPolicy): string;
3
+ //# sourceMappingURL=referrer-policy.d.ts.map
@@ -0,0 +1,10 @@
1
+ const DEFAULT_DIRECTIVE = 'no-referrer';
2
+ export default function referrerPolicy(header, option) {
3
+ const value = header || option;
4
+ if (!value) {
5
+ return DEFAULT_DIRECTIVE;
6
+ }
7
+ const directives = typeof value === 'string' ? [value] : value;
8
+ return directives.join(',');
9
+ }
10
+ //# sourceMappingURL=referrer-policy.js.map
@@ -0,0 +1,7 @@
1
+ export interface StrictTransportSecurity {
2
+ maxAge?: number;
3
+ includeSubDomains?: boolean;
4
+ preload?: boolean;
5
+ }
6
+ export default function strictTransportSecurity(header?: string, option?: StrictTransportSecurity): string;
7
+ //# sourceMappingURL=strict-transport-security.d.ts.map
@@ -0,0 +1,30 @@
1
+ const DEFAULT_MAX_AGE = 180 * 24 * 60 * 60;
2
+ function parseDirectives(str) {
3
+ const policy = {};
4
+ for (const segment of str.split(';')) {
5
+ const directive = segment.trim();
6
+ if (directive.startsWith('maxage')) {
7
+ policy.maxAge = parseInt(directive.substring(directive.indexOf('=') + 1));
8
+ }
9
+ if (directive === 'includeSubDomains') {
10
+ policy.includeSubDomains = true;
11
+ }
12
+ if (directive === 'preload') {
13
+ policy.preload = true;
14
+ }
15
+ }
16
+ return policy;
17
+ }
18
+ export default function strictTransportSecurity(header, option) {
19
+ const policy = header !== undefined ? parseDirectives(header) : option;
20
+ const maxAge = policy?.maxAge !== undefined ? policy.maxAge : DEFAULT_MAX_AGE;
21
+ const directives = [`max-age=${maxAge}`];
22
+ if (policy?.includeSubDomains === undefined || policy?.includeSubDomains) {
23
+ directives.push('includeSubDomains');
24
+ }
25
+ if (policy?.preload) {
26
+ directives.push('preload');
27
+ }
28
+ return directives.join('; ');
29
+ }
30
+ //# sourceMappingURL=strict-transport-security.js.map
@@ -0,0 +1,4 @@
1
+ import type { ViewResponse } from '@lwrjs/types';
2
+ import type { SecurityOptions } from './options.js';
3
+ export declare function resolveHeaders(viewResponse: ViewResponse, options?: SecurityOptions): Promise<Record<string, string>>;
4
+ //# sourceMappingURL=headers.d.ts.map
@@ -0,0 +1,111 @@
1
+ import { createHash } from 'crypto';
2
+ import { streamToString } from '@lwrjs/shared-utils';
3
+ import contentSecurityPolicy from './headers/content-security-policy.js';
4
+ import referrerPolicy from './headers/referrer-policy.js';
5
+ import strictTransportSecurity from './headers/strict-transport-security.js';
6
+ function getResources(viewDefinition) {
7
+ const { viewRecord } = viewDefinition;
8
+ const resources = [];
9
+ if (viewRecord.resources) {
10
+ resources.push(...viewRecord.resources);
11
+ }
12
+ if (viewRecord.bootstrapModule?.resources) {
13
+ resources.push(...viewRecord.bootstrapModule.resources);
14
+ }
15
+ return resources;
16
+ }
17
+ function hash(source) {
18
+ return `'sha256-${createHash('sha256').update(source).digest('base64')}'`;
19
+ }
20
+ async function hashResources(resources) {
21
+ const hashes = [];
22
+ for (const resource of resources) {
23
+ // TODO: support other inlined resource types
24
+ if (!resource.inline || resource.type !== 'application/javascript')
25
+ continue;
26
+ if (resource.content) {
27
+ hashes.push(hash(resource.content));
28
+ }
29
+ if (resource.stream) {
30
+ // eslint-disable-next-line no-await-in-loop
31
+ const content = await streamToString(resource.stream());
32
+ hashes.push(hash(content));
33
+ }
34
+ }
35
+ return hashes;
36
+ }
37
+ async function getResourceHashes(viewResponse) {
38
+ if (!viewResponse.metadata?.viewDefinition) {
39
+ return [];
40
+ }
41
+ const { viewDefinition } = viewResponse.metadata;
42
+ const resources = getResources(viewDefinition);
43
+ const hashes = await hashResources(resources);
44
+ return hashes;
45
+ }
46
+ function normalizeHeaders(headers = {}) {
47
+ return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
48
+ }
49
+ function normalizeOptions(options = {}) {
50
+ for (const [option, value] of Object.entries(options)) {
51
+ // true values are the same as undefined
52
+ if (value === true) {
53
+ delete options[option];
54
+ }
55
+ }
56
+ return options;
57
+ }
58
+ export async function resolveHeaders(viewResponse, options) {
59
+ options = normalizeOptions(options);
60
+ const headers = normalizeHeaders(viewResponse.headers);
61
+ if (options.contentSecurityPolicy === undefined || typeof options.contentSecurityPolicy === 'object') {
62
+ const headerName = options.contentSecurityPolicy?.reportOnly
63
+ ? 'content-security-policy-report-only'
64
+ : 'content-security-policy';
65
+ let hashes;
66
+ if (options.contentSecurityPolicy?.resourceHashing === undefined) {
67
+ hashes = await getResourceHashes(viewResponse);
68
+ }
69
+ headers[headerName] = contentSecurityPolicy(headers[headerName], options.contentSecurityPolicy, hashes);
70
+ }
71
+ if (options.referrerPolicy === undefined || typeof options.referrerPolicy === 'object') {
72
+ const headerName = 'referrer-policy';
73
+ headers[headerName] = referrerPolicy(headers[headerName], options.referrerPolicy);
74
+ }
75
+ if (options.strictTransportSecurity === undefined ||
76
+ typeof options.strictTransportSecurity === 'object') {
77
+ const headerName = 'strict-transport-security';
78
+ headers[headerName] = strictTransportSecurity(headers[headerName], options.strictTransportSecurity);
79
+ }
80
+ if (options.crossOriginEmbedderPolicy === undefined ||
81
+ typeof options.crossOriginEmbedderPolicy === 'string') {
82
+ const headerName = 'cross-origin-embedder-policy';
83
+ headers[headerName] = headers[headerName] || options?.crossOriginEmbedderPolicy || 'require-corp';
84
+ }
85
+ if (options.crossOriginOpenerPolicy === undefined ||
86
+ typeof options.crossOriginOpenerPolicy === 'string') {
87
+ const headerName = 'cross-origin-opener-policy';
88
+ headers[headerName] = headers[headerName] || options?.crossOriginOpenerPolicy || 'same-origin';
89
+ }
90
+ if (options.crossOriginResourcePolicy === undefined ||
91
+ typeof options.crossOriginResourcePolicy === 'string') {
92
+ const headerName = 'cross-origin-resource-policy';
93
+ headers[headerName] = headers[headerName] || options?.crossOriginResourcePolicy || 'same-origin';
94
+ }
95
+ if (options.xContentTypeOptions === undefined) {
96
+ headers['x-content-type-options'] = 'nosniff';
97
+ }
98
+ if (options.xFrameOptions === undefined) {
99
+ headers['x-frame-options'] = 'sameorigin';
100
+ }
101
+ if (options.xPermittedCrossDomainPolicies === undefined) {
102
+ headers['x-permitted-cross-domain-policies'] = 'none';
103
+ }
104
+ if (options.xXSSProtection === undefined) {
105
+ // this header is deprecated, and the recommendation is to disable it
106
+ // https://owasp.org/www-project-secure-headers/#x-xss-protection
107
+ headers['x-xss-protection'] = '0';
108
+ }
109
+ return headers;
110
+ }
111
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1,4 @@
1
+ export { default } from './route-handler.js';
2
+ export { secure } from './wrapper.js';
3
+ export * from './options.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export { default } from './route-handler.js';
2
+ export { secure } from './wrapper.js';
3
+ export * from './options.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ import type { ContentSecurityPolicy } from './headers/content-security-policy.js';
2
+ import type { ReferrerPolicy } from './headers/referrer-policy.js';
3
+ import type { StrictTransportSecurity } from './headers/strict-transport-security.js';
4
+ export interface SecurityOptions extends Record<string, any> {
5
+ contentSecurityPolicy?: ContentSecurityPolicy | boolean;
6
+ referrerPolicy?: ReferrerPolicy | boolean;
7
+ strictTransportSecurity?: StrictTransportSecurity | boolean;
8
+ crossOriginEmbedderPolicy?: string | boolean;
9
+ crossOriginOpenerPolicy?: string | boolean;
10
+ crossOriginResourcePolicy?: string | boolean;
11
+ xContentTypeOptions?: boolean;
12
+ xFrameOptions?: boolean;
13
+ xPermittedCrossDomainPolicies?: boolean;
14
+ xXSSProtection?: boolean;
15
+ }
16
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1,11 @@
1
+ import type { HandlerContext, ViewRequest, ViewResponse } from '@lwrjs/types';
2
+ import type { SecurityOptions } from './options.js';
3
+ /**
4
+ * Resolve and secure the view
5
+ *
6
+ * @param request - inbound view request
7
+ * @param context - route handler context
8
+ * @returns a route handler
9
+ */
10
+ export default function securityRouteHandler(request: ViewRequest, context: HandlerContext, options?: SecurityOptions): Promise<ViewResponse>;
11
+ //# sourceMappingURL=route-handler.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { resolveHeaders } from './headers.js';
2
+ /**
3
+ * Resolve and secure the view
4
+ *
5
+ * @param request - inbound view request
6
+ * @param context - route handler context
7
+ * @returns a route handler
8
+ */
9
+ export default async function securityRouteHandler(request, context, options) {
10
+ const viewResponse = await context.viewApi.getViewResponse(context.route);
11
+ viewResponse.headers = await resolveHeaders(viewResponse, options);
12
+ return viewResponse;
13
+ }
14
+ //# sourceMappingURL=route-handler.js.map
@@ -0,0 +1,15 @@
1
+ import type { RouteHandlerFunction } from '@lwrjs/types';
2
+ import type { SecurityOptions } from './options.js';
3
+ /**
4
+ * Wrap existing route handlers to apply security headers and conditionally inline script hashing
5
+ *
6
+ * @remarks
7
+ * Script hashing is only applied when the provided route handler returns a `ViewDefinitionResponse`
8
+ *
9
+ * Security headers are not applied when the route handler returns a `ViewResponse` that is not HTML
10
+ *
11
+ * @param routeHandler - existing route handler
12
+ * @returns a route handler
13
+ */
14
+ export declare function secure(routeHandler: RouteHandlerFunction, options?: SecurityOptions): RouteHandlerFunction;
15
+ //# sourceMappingURL=wrapper.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { resolveHeaders } from './headers.js';
2
+ function isViewResponse(response) {
3
+ return response.body !== undefined;
4
+ }
5
+ function isViewDefinitionResponse(response) {
6
+ return response.view !== undefined;
7
+ }
8
+ /**
9
+ * Wrap existing route handlers to apply security headers and conditionally inline script hashing
10
+ *
11
+ * @remarks
12
+ * Script hashing is only applied when the provided route handler returns a `ViewDefinitionResponse`
13
+ *
14
+ * Security headers are not applied when the route handler returns a `ViewResponse` that is not HTML
15
+ *
16
+ * @param routeHandler - existing route handler
17
+ * @returns a route handler
18
+ */
19
+ export function secure(routeHandler, options) {
20
+ return async (request, context) => {
21
+ // TODO: handle sync response
22
+ const routeHandlerResponse = await routeHandler(request, context);
23
+ if (isViewResponse(routeHandlerResponse)) {
24
+ // only apply headers when the view response is html
25
+ if (!routeHandlerResponse.headers ||
26
+ !routeHandlerResponse.headers['content-type'].startsWith('text/html')) {
27
+ return routeHandlerResponse;
28
+ }
29
+ // merge custom security headers with defaults
30
+ routeHandlerResponse.headers = await resolveHeaders(routeHandlerResponse, options);
31
+ return routeHandlerResponse;
32
+ }
33
+ if (isViewDefinitionResponse(routeHandlerResponse)) {
34
+ // resolve the view based on the route handler response
35
+ const viewResponse = await context.viewApi.getViewResponse(routeHandlerResponse.view, routeHandlerResponse.viewParams, routeHandlerResponse.renderOptions);
36
+ viewResponse.headers = {
37
+ ...viewResponse.headers,
38
+ ...routeHandlerResponse.headers,
39
+ };
40
+ // set headers according to options
41
+ viewResponse.headers = await resolveHeaders(viewResponse, options);
42
+ return viewResponse;
43
+ }
44
+ throw new Error('Unexpected route handler response');
45
+ };
46
+ }
47
+ //# sourceMappingURL=wrapper.js.map
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@lwrjs/security",
3
+ "version": "0.10.0-alpha.16",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "types": "build/es/index.d.ts",
7
+ "module": "build/es/index.js",
8
+ "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/salesforce-experience-platform-emu/lwr.git",
12
+ "directory": "packages/@lwrjs/security"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/salesforce-experience-platform-emu/lwr/issues"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "import": "./build/es/index.js",
23
+ "require": "./build/cjs/index.cjs"
24
+ }
25
+ },
26
+ "files": [
27
+ "build/**/*.js",
28
+ "build/**/*.cjs",
29
+ "build/**/*.d.ts"
30
+ ],
31
+ "dependencies": {
32
+ "@lwrjs/shared-utils": "0.10.0-alpha.16"
33
+ },
34
+ "devDependencies": {
35
+ "@lwrjs/types": "0.10.0-alpha.16"
36
+ },
37
+ "engines": {
38
+ "node": ">=16.0.0 <20"
39
+ },
40
+ "gitHead": "c5a13d471330c0f738d0c783fd1b69f456c544bd"
41
+ }