nuxt-security-module 4.2.0 → 4.3.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/README.md +28 -2
- package/dist/index.d.ts +17 -0
- package/dist/index.js +105 -0
- package/dist/models/AllowedMethodsRestricterOptions.d.ts +5 -0
- package/dist/models/BasicAuthOptions.d.ts +9 -0
- package/dist/models/ContentSecurityPolicy.d.ts +3 -0
- package/dist/models/CorsHandlerOptions.d.ts +13 -0
- package/dist/models/CrossOriginEmbedderPolicy.d.ts +8 -0
- package/dist/models/CrossOriginEmbedderPolicy.js +8 -0
- package/dist/models/CsrfOptions.d.ts +6 -0
- package/dist/models/ModuleOptions.d.ts +27 -0
- package/dist/models/PermissionsPolicy.d.ts +3 -0
- package/dist/models/RateLimitWindow.d.ts +6 -0
- package/dist/models/RateLimiterOptions.d.ts +6 -0
- package/dist/models/RateLimiterStore.d.ts +6 -0
- package/dist/models/RequestSizeLimiterOptions.d.ts +6 -0
- package/dist/models/SecurityHeaders.d.ts +22 -0
- package/dist/models/SecurityRouteRules.d.ts +4 -0
- package/dist/models/SecurityRules.d.ts +4 -0
- package/dist/models/SsgOptions.d.ts +9 -0
- package/dist/models/StrictTransportSecurity.d.ts +7 -0
- package/dist/models/XFrameOptions.d.ts +7 -0
- package/dist/models/XFrameOptions.js +7 -0
- package/dist/runtime/constants.d.ts +24 -0
- package/dist/runtime/constants.js +52 -0
- package/dist/runtime/nitro/plugins/hidePoweredBy.d.ts +5 -0
- package/dist/runtime/nitro/plugins/hidePoweredBy.js +13 -0
- package/dist/runtime/nitro/plugins/nonce.d.ts +5 -0
- package/dist/runtime/nitro/plugins/nonce.js +27 -0
- package/dist/runtime/nitro/plugins/routeRules.d.ts +5 -0
- package/dist/runtime/nitro/plugins/routeRules.js +17 -0
- package/dist/runtime/nitro/plugins/securityHeaders.d.ts +5 -0
- package/dist/runtime/nitro/plugins/securityHeaders.js +25 -0
- package/dist/runtime/nitro/plugins/ssgHashes.d.ts +5 -0
- package/dist/runtime/nitro/plugins/ssgHashes.js +27 -0
- package/dist/runtime/nitro/plugins/subresourceIntegrity.d.ts +4 -0
- package/dist/runtime/nitro/plugins/subresourceIntegrity.js +19 -0
- package/dist/runtime/server/middleware/allowedMethodsRestricter.d.ts +5 -0
- package/dist/runtime/server/middleware/allowedMethodsRestricter.js +15 -0
- package/dist/runtime/server/middleware/basicAuth.d.ts +5 -0
- package/dist/runtime/server/middleware/basicAuth.js +23 -0
- package/dist/runtime/server/middleware/corsHandler.d.ts +5 -0
- package/dist/runtime/server/middleware/corsHandler.js +20 -0
- package/dist/runtime/server/middleware/csrf.d.ts +5 -0
- package/dist/runtime/server/middleware/csrf.js +28 -0
- package/dist/runtime/server/middleware/rateLimiter.d.ts +5 -0
- package/dist/runtime/server/middleware/rateLimiter.js +22 -0
- package/dist/runtime/server/middleware/requestSizeLimiter.d.ts +5 -0
- package/dist/runtime/server/middleware/requestSizeLimiter.js +17 -0
- package/dist/runtime/services/checkIsBasicAuthorized.d.ts +4 -0
- package/dist/runtime/services/checkIsBasicAuthorized.js +12 -0
- package/dist/runtime/services/checkIsChallenged.d.ts +4 -0
- package/dist/runtime/services/checkIsChallenged.js +8 -0
- package/dist/runtime/services/checkIsCsrfTokenValid.d.ts +3 -0
- package/dist/runtime/services/checkIsCsrfTokenValid.js +8 -0
- package/dist/runtime/services/checkIsEqualInConstantTime.d.ts +3 -0
- package/dist/runtime/services/checkIsEqualInConstantTime.js +11 -0
- package/dist/runtime/services/checkIsMethodAllowed.d.ts +3 -0
- package/dist/runtime/services/checkIsMethodAllowed.js +4 -0
- package/dist/runtime/services/checkIsRecord.d.ts +3 -0
- package/dist/runtime/services/checkIsRecord.js +4 -0
- package/dist/runtime/services/checkIsRequestTooLarge.d.ts +4 -0
- package/dist/runtime/services/checkIsRequestTooLarge.js +9 -0
- package/dist/runtime/services/collectSsgHashes.d.ts +5 -0
- package/dist/runtime/services/collectSsgHashes.js +41 -0
- package/dist/runtime/services/createInMemoryRateLimiterStore.d.ts +4 -0
- package/dist/runtime/services/createInMemoryRateLimiterStore.js +22 -0
- package/dist/runtime/services/generateHash.d.ts +3 -0
- package/dist/runtime/services/generateHash.js +9 -0
- package/dist/runtime/services/generateNonce.d.ts +3 -0
- package/dist/runtime/services/generateNonce.js +6 -0
- package/dist/runtime/services/generateRandomToken.d.ts +3 -0
- package/dist/runtime/services/generateRandomToken.js +8 -0
- package/dist/runtime/services/getBasicAuthCredentials.d.ts +6 -0
- package/dist/runtime/services/getBasicAuthCredentials.js +19 -0
- package/dist/runtime/services/getBasicAuthOptions.d.ts +4 -0
- package/dist/runtime/services/getBasicAuthOptions.js +17 -0
- package/dist/runtime/services/getRetryAfterSeconds.d.ts +5 -0
- package/dist/runtime/services/getRetryAfterSeconds.js +9 -0
- package/dist/runtime/services/getSecurityRules.d.ts +5 -0
- package/dist/runtime/services/getSecurityRules.js +15 -0
- package/dist/runtime/services/getSecurityState.d.ts +11 -0
- package/dist/runtime/services/getSecurityState.js +14 -0
- package/dist/runtime/services/injectNonce.d.ts +3 -0
- package/dist/runtime/services/injectNonce.js +7 -0
- package/dist/runtime/services/injectSubresourceIntegrity.d.ts +3 -0
- package/dist/runtime/services/injectSubresourceIntegrity.js +22 -0
- package/dist/runtime/services/insertContentSecurityPolicyMeta.d.ts +4 -0
- package/dist/runtime/services/insertContentSecurityPolicyMeta.js +17 -0
- package/dist/runtime/services/mergeSecurityRules.d.ts +4 -0
- package/dist/runtime/services/mergeSecurityRules.js +11 -0
- package/dist/runtime/services/readHtmlTagAttribute.d.ts +3 -0
- package/dist/runtime/services/readHtmlTagAttribute.js +8 -0
- package/dist/runtime/services/rewriteHtmlTags.d.ts +3 -0
- package/dist/runtime/services/rewriteHtmlTags.js +9 -0
- package/dist/runtime/services/securityRouteMatcher.d.ts +5 -0
- package/dist/runtime/services/securityRouteMatcher.js +11 -0
- package/dist/runtime/services/serializeBasicAuthChallenge.d.ts +3 -0
- package/dist/runtime/services/serializeBasicAuthChallenge.js +4 -0
- package/dist/runtime/services/serializeContentSecurityPolicy.d.ts +4 -0
- package/dist/runtime/services/serializeContentSecurityPolicy.js +7 -0
- package/dist/runtime/services/serializePermissionsPolicy.d.ts +4 -0
- package/dist/runtime/services/serializePermissionsPolicy.js +4 -0
- package/dist/runtime/services/serializeSecurityHeaders.d.ts +4 -0
- package/dist/runtime/services/serializeSecurityHeaders.js +19 -0
- package/dist/runtime/services/serializeStrictTransportSecurity.d.ts +4 -0
- package/dist/runtime/services/serializeStrictTransportSecurity.js +8 -0
- package/dist/runtime/services/stripHtmlTagAttribute.d.ts +3 -0
- package/dist/runtime/services/stripHtmlTagAttribute.js +5 -0
- package/dist/runtime/services/updateContentSecurityPolicy.d.ts +5 -0
- package/dist/runtime/services/updateContentSecurityPolicy.js +15 -0
- package/dist/schemas/allowedMethodsRestricterOptionsSchema.js +5 -0
- package/dist/schemas/basicAuthOptionsSchema.js +12 -0
- package/dist/schemas/contentSecurityPolicySchema.js +5 -0
- package/dist/schemas/corsHandlerOptionsSchema.js +14 -0
- package/dist/schemas/csrfOptionsSchema.js +9 -0
- package/dist/schemas/moduleOptionsSchema.js +29 -0
- package/dist/schemas/permissionsPolicySchema.js +5 -0
- package/dist/schemas/rateLimiterOptionsSchema.js +8 -0
- package/dist/schemas/requestSizeLimiterOptionsSchema.js +8 -0
- package/dist/schemas/securityHeadersSchema.js +25 -0
- package/dist/schemas/ssgOptionsSchema.js +11 -0
- package/dist/schemas/strictTransportSecurityOptionsSchema.js +9 -0
- package/dist/services/computeSubresourceIntegrityHashes.js +29 -0
- package/dist/services/formatZodIssues.js +16 -0
- package/dist/services/getDefaultModuleOptions.js +41 -0
- package/dist/services/getDefaultSecurityHeaders.js +85 -0
- package/dist/services/getPluginTemplate.js +10 -0
- package/dist/services/getRemoveLoggersViteConfig.js +4 -0
- package/dist/services/getRouteRulesTemplate.js +4 -0
- package/dist/services/getRouteSecurityRules.js +17 -0
- package/dist/services/getStaticResourceHeaders.js +12 -0
- package/dist/services/getTypeTemplate.js +25 -0
- package/dist/services/mergeModuleOptions.js +39 -0
- package/dist/services/parseModuleOptions.js +10 -0
- package/package.json +7 -14
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { HTML_ATTRIBUTE_REGEX } from "../constants.js";
|
|
2
|
+
//#region src/runtime/services/readHtmlTagAttribute.ts
|
|
3
|
+
const readHtmlTagAttribute = (attributes, attributeName) => {
|
|
4
|
+
for (const [, name, , value] of attributes.matchAll(HTML_ATTRIBUTE_REGEX)) if (name?.toLowerCase() === attributeName) return unquote(value ?? "");
|
|
5
|
+
};
|
|
6
|
+
const unquote = (value) => value.replace(/^(?<quote>["'])(?<inner>.*)\k<quote>$/su, "$<inner>");
|
|
7
|
+
//#endregion
|
|
8
|
+
export { readHtmlTagAttribute };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { HTML_TAG_REGEX } from "../constants.js";
|
|
2
|
+
//#region src/runtime/services/rewriteHtmlTags.ts
|
|
3
|
+
const rewriteHtmlTags = (html, rewriteAttributes) => html.replace(HTML_TAG_REGEX, (match, elementName, elementAttributes, elementTrailer, body, tagName, tagAttributes, tagTrailer) => {
|
|
4
|
+
if (elementName) return `<${elementName}${rewriteAttributes(elementName, elementAttributes ?? "") ?? elementAttributes}${elementTrailer}>${body}</${elementName}>`;
|
|
5
|
+
const attributes = rewriteAttributes(tagName ?? "", tagAttributes ?? "");
|
|
6
|
+
return attributes === void 0 ? match : `<${tagName}${attributes}${tagTrailer}>`;
|
|
7
|
+
});
|
|
8
|
+
//#endregion
|
|
9
|
+
export { rewriteHtmlTags };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { SecurityRules } from "../../models/SecurityRules.js";
|
|
2
|
+
//#region src/runtime/services/securityRouteMatcher.d.ts
|
|
3
|
+
export declare const setSecurityRouteMatcher: (routes: Record<string, SecurityRules>) => void;
|
|
4
|
+
export declare const findSecurityRules: (pathname: string) => SecurityRules[] | undefined;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { addRoute, createRouter, findAllRoutes } from "rou3";
|
|
2
|
+
//#region src/runtime/services/securityRouteMatcher.ts
|
|
3
|
+
let securityRouter;
|
|
4
|
+
const setSecurityRouteMatcher = (routes) => {
|
|
5
|
+
const router = createRouter();
|
|
6
|
+
for (const [pattern, rules] of Object.entries(routes)) addRoute(router, "", pattern, rules);
|
|
7
|
+
securityRouter = router;
|
|
8
|
+
};
|
|
9
|
+
const findSecurityRules = (pathname) => securityRouter && findAllRoutes(securityRouter, "", pathname).flatMap(({ data }) => data ? [data] : []);
|
|
10
|
+
//#endregion
|
|
11
|
+
export { findSecurityRules, setSecurityRouteMatcher };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/runtime/services/serializeContentSecurityPolicy.ts
|
|
2
|
+
const serializeContentSecurityPolicy = (policy) => Object.entries(policy).filter(([, value]) => value !== false).map(([directive, value]) => {
|
|
3
|
+
if (directive === "upgrade-insecure-requests") return "upgrade-insecure-requests;";
|
|
4
|
+
return `${directive} ${Array.isArray(value) ? value.map((source) => source.trim()).join(" ") : ""};`;
|
|
5
|
+
}).join(" ");
|
|
6
|
+
//#endregion
|
|
7
|
+
export { serializeContentSecurityPolicy };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region src/runtime/services/serializePermissionsPolicy.ts
|
|
2
|
+
const serializePermissionsPolicy = (policy) => Object.entries(policy).flatMap(([feature, sources]) => sources === false ? [] : [`${feature}=(${(sources ?? []).join(" ")})`]).join(", ");
|
|
3
|
+
//#endregion
|
|
4
|
+
export { serializePermissionsPolicy };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SECURITY_HEADER_NAMES } from "../constants.js";
|
|
2
|
+
import { serializeContentSecurityPolicy } from "./serializeContentSecurityPolicy.js";
|
|
3
|
+
import { serializePermissionsPolicy } from "./serializePermissionsPolicy.js";
|
|
4
|
+
import { serializeStrictTransportSecurity } from "./serializeStrictTransportSecurity.js";
|
|
5
|
+
//#region src/runtime/services/serializeSecurityHeaders.ts
|
|
6
|
+
const checkIsSecurityHeaderKey = (key) => Object.hasOwn(SECURITY_HEADER_NAMES, key);
|
|
7
|
+
const serializeSecurityHeaders = (headers) => Object.fromEntries(Object.keys(headers).flatMap((key) => {
|
|
8
|
+
if (!checkIsSecurityHeaderKey(key)) return [];
|
|
9
|
+
const value = headers[key];
|
|
10
|
+
return value === false || value === void 0 ? [] : [[SECURITY_HEADER_NAMES[key], serializeValue(key, value)]];
|
|
11
|
+
}));
|
|
12
|
+
const serializeValue = (key, value) => {
|
|
13
|
+
if (key === "contentSecurityPolicy") return serializeContentSecurityPolicy(value);
|
|
14
|
+
if (key === "permissionsPolicy") return serializePermissionsPolicy(value);
|
|
15
|
+
if (key === "strictTransportSecurity") return serializeStrictTransportSecurity(value);
|
|
16
|
+
return String(value);
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { serializeSecurityHeaders };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/runtime/services/serializeStrictTransportSecurity.ts
|
|
2
|
+
const serializeStrictTransportSecurity = (policy) => [
|
|
3
|
+
`max-age=${policy.maxAge}`,
|
|
4
|
+
policy.includeSubdomains && "includeSubDomains",
|
|
5
|
+
policy.preload && "preload"
|
|
6
|
+
].filter(Boolean).join("; ");
|
|
7
|
+
//#endregion
|
|
8
|
+
export { serializeStrictTransportSecurity };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { HTML_ATTRIBUTE_REGEX } from "../constants.js";
|
|
2
|
+
//#region src/runtime/services/stripHtmlTagAttribute.ts
|
|
3
|
+
const stripHtmlTagAttribute = (attributes, attributeName) => attributes.replace(HTML_ATTRIBUTE_REGEX, (token, name) => name.toLowerCase() === attributeName ? "" : token);
|
|
4
|
+
//#endregion
|
|
5
|
+
export { stripHtmlTagAttribute };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ContentSecurityPolicy } from "../../models/ContentSecurityPolicy.js";
|
|
2
|
+
import { SecurityState } from "./getSecurityState.js";
|
|
3
|
+
//#region src/runtime/services/updateContentSecurityPolicy.d.ts
|
|
4
|
+
export declare const updateContentSecurityPolicy: (policy: ContentSecurityPolicy, { hashes, nonce }: SecurityState) => ContentSecurityPolicy;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NONCE_PLACEHOLDER } from "../constants.js";
|
|
2
|
+
//#region src/runtime/services/updateContentSecurityPolicy.ts
|
|
3
|
+
const SCRIPT_DIRECTIVES = /* @__PURE__ */ new Set(["script-src", "script-src-elem"]);
|
|
4
|
+
const STYLE_DIRECTIVES = /* @__PURE__ */ new Set(["style-src", "style-src-elem"]);
|
|
5
|
+
const updateContentSecurityPolicy = (policy, { hashes, nonce }) => Object.fromEntries(Object.keys(policy).map((directive) => {
|
|
6
|
+
const value = policy[directive];
|
|
7
|
+
if (!Array.isArray(value)) return [directive, value];
|
|
8
|
+
const sources = value.filter((source) => !checkIsStaticNonce(source)).map((source) => source === "'nonce-{{nonce}}'" && nonce ? `'nonce-${nonce}'` : source).filter((source) => source !== NONCE_PLACEHOLDER);
|
|
9
|
+
if (SCRIPT_DIRECTIVES.has(directive)) sources.push(...hashes.script);
|
|
10
|
+
if (STYLE_DIRECTIVES.has(directive)) sources.push(...hashes.style);
|
|
11
|
+
return [directive, sources];
|
|
12
|
+
}));
|
|
13
|
+
const checkIsStaticNonce = (source) => source.startsWith("'nonce-") && source !== "'nonce-{{nonce}}'";
|
|
14
|
+
//#endregion
|
|
15
|
+
export { updateContentSecurityPolicy };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/basicAuthOptionsSchema.ts
|
|
3
|
+
const pathSchema = z.string().startsWith("/");
|
|
4
|
+
const basicAuthOptionsSchema = z.strictObject({
|
|
5
|
+
exclude: z.array(pathSchema).optional(),
|
|
6
|
+
include: z.array(pathSchema).optional(),
|
|
7
|
+
message: z.string().optional(),
|
|
8
|
+
name: z.string().min(1),
|
|
9
|
+
pass: z.string().min(1)
|
|
10
|
+
});
|
|
11
|
+
//#endregion
|
|
12
|
+
export { basicAuthOptionsSchema };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/corsHandlerOptionsSchema.ts
|
|
3
|
+
const headerNameSchema = z.string().regex(/^[\w-]+$/u);
|
|
4
|
+
const corsHandlerOptionsSchema = z.strictObject({
|
|
5
|
+
allowHeaders: z.array(headerNameSchema).optional(),
|
|
6
|
+
credentials: z.boolean().optional(),
|
|
7
|
+
exposeHeaders: z.array(headerNameSchema).optional(),
|
|
8
|
+
maxAge: z.string().regex(/^\d+$/u).optional(),
|
|
9
|
+
methods: z.array(z.string().min(1)).min(1),
|
|
10
|
+
origin: z.union([z.url(), z.array(z.url())]),
|
|
11
|
+
preflight: z.strictObject({ statusCode: z.int().min(200).max(299) }).optional()
|
|
12
|
+
});
|
|
13
|
+
//#endregion
|
|
14
|
+
export { corsHandlerOptionsSchema };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/csrfOptionsSchema.ts
|
|
3
|
+
const tokenNameSchema = z.string().regex(/^[\w-]+$/u, "must be letters, digits, underscores or hyphens");
|
|
4
|
+
const csrfOptionsSchema = z.strictObject({
|
|
5
|
+
cookieName: tokenNameSchema,
|
|
6
|
+
headerName: tokenNameSchema
|
|
7
|
+
});
|
|
8
|
+
//#endregion
|
|
9
|
+
export { csrfOptionsSchema };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { allowedMethodsRestricterOptionsSchema } from "./allowedMethodsRestricterOptionsSchema.js";
|
|
2
|
+
import { basicAuthOptionsSchema } from "./basicAuthOptionsSchema.js";
|
|
3
|
+
import { corsHandlerOptionsSchema } from "./corsHandlerOptionsSchema.js";
|
|
4
|
+
import { csrfOptionsSchema } from "./csrfOptionsSchema.js";
|
|
5
|
+
import { rateLimiterOptionsSchema } from "./rateLimiterOptionsSchema.js";
|
|
6
|
+
import { requestSizeLimiterOptionsSchema } from "./requestSizeLimiterOptionsSchema.js";
|
|
7
|
+
import { securityHeadersSchema } from "./securityHeadersSchema.js";
|
|
8
|
+
import { ssgOptionsSchema } from "./ssgOptionsSchema.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
//#region src/schemas/moduleOptionsSchema.ts
|
|
11
|
+
const moduleOptionsSchema = z.strictObject({
|
|
12
|
+
allowedMethodsRestricter: z.union([allowedMethodsRestricterOptionsSchema, z.literal(false)]),
|
|
13
|
+
basicAuth: z.union([basicAuthOptionsSchema, z.literal(false)]),
|
|
14
|
+
contentSecurityPolicyReportOnly: z.boolean(),
|
|
15
|
+
corsHandler: z.union([corsHandlerOptionsSchema, z.literal(false)]),
|
|
16
|
+
csrf: z.union([csrfOptionsSchema, z.literal(false)]),
|
|
17
|
+
enabled: z.boolean(),
|
|
18
|
+
headers: z.union([securityHeadersSchema, z.literal(false)]),
|
|
19
|
+
hidePoweredBy: z.boolean(),
|
|
20
|
+
nonce: z.boolean(),
|
|
21
|
+
rateLimiter: z.union([rateLimiterOptionsSchema, z.literal(false)]),
|
|
22
|
+
removeLoggers: z.boolean(),
|
|
23
|
+
requestSizeLimiter: z.union([requestSizeLimiterOptionsSchema, z.literal(false)]),
|
|
24
|
+
sri: z.boolean(),
|
|
25
|
+
ssg: z.union([ssgOptionsSchema, z.literal(false)]),
|
|
26
|
+
strict: z.boolean()
|
|
27
|
+
});
|
|
28
|
+
//#endregion
|
|
29
|
+
export { moduleOptionsSchema };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/rateLimiterOptionsSchema.ts
|
|
3
|
+
const rateLimiterOptionsSchema = z.strictObject({
|
|
4
|
+
intervalInMilliseconds: z.int("must be a whole number of milliseconds").positive("must be more than zero milliseconds"),
|
|
5
|
+
tokensPerInterval: z.int("must be a whole number of requests").positive("must allow at least one request")
|
|
6
|
+
});
|
|
7
|
+
//#endregion
|
|
8
|
+
export { rateLimiterOptionsSchema };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/requestSizeLimiterOptionsSchema.ts
|
|
3
|
+
const requestSizeLimiterOptionsSchema = z.strictObject({
|
|
4
|
+
maxRequestSizeInBytes: z.int("must be a whole number of bytes").positive("must be more than zero bytes"),
|
|
5
|
+
maxUploadFileRequestInBytes: z.int("must be a whole number of bytes").positive("must be more than zero bytes")
|
|
6
|
+
});
|
|
7
|
+
//#endregion
|
|
8
|
+
export { requestSizeLimiterOptionsSchema };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CrossOriginEmbedderPolicy } from "../models/CrossOriginEmbedderPolicy.js";
|
|
2
|
+
import { XFrameOptions } from "../models/XFrameOptions.js";
|
|
3
|
+
import { contentSecurityPolicySchema } from "./contentSecurityPolicySchema.js";
|
|
4
|
+
import { permissionsPolicySchema } from "./permissionsPolicySchema.js";
|
|
5
|
+
import { strictTransportSecurityOptionsSchema } from "./strictTransportSecurityOptionsSchema.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/schemas/securityHeadersSchema.ts
|
|
8
|
+
const headerValueSchema = z.string().min(1);
|
|
9
|
+
const securityHeadersSchema = z.strictObject({
|
|
10
|
+
contentSecurityPolicy: z.union([contentSecurityPolicySchema, z.literal(false)]).optional(),
|
|
11
|
+
crossOriginEmbedderPolicy: z.union([z.enum(CrossOriginEmbedderPolicy), z.literal(false)]).optional(),
|
|
12
|
+
crossOriginOpenerPolicy: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
13
|
+
crossOriginResourcePolicy: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
14
|
+
originAgentCluster: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
15
|
+
permissionsPolicy: z.union([permissionsPolicySchema, z.literal(false)]).optional(),
|
|
16
|
+
referrerPolicy: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
17
|
+
strictTransportSecurity: z.union([strictTransportSecurityOptionsSchema, z.literal(false)]).optional(),
|
|
18
|
+
xContentTypeOptions: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
19
|
+
xDNSPrefetchControl: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
20
|
+
xDownloadOptions: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
21
|
+
xFrameOptions: z.union([z.enum(XFrameOptions), z.literal(false)]).optional(),
|
|
22
|
+
xPermittedCrossDomainPolicies: z.union([headerValueSchema, z.literal(false)]).optional()
|
|
23
|
+
});
|
|
24
|
+
//#endregion
|
|
25
|
+
export { securityHeadersSchema };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/ssgOptionsSchema.ts
|
|
3
|
+
const ssgOptionsSchema = z.strictObject({
|
|
4
|
+
exportToPresets: z.boolean(),
|
|
5
|
+
hashScripts: z.boolean(),
|
|
6
|
+
hashStyles: z.boolean(),
|
|
7
|
+
meta: z.boolean(),
|
|
8
|
+
nitroHeaders: z.boolean()
|
|
9
|
+
});
|
|
10
|
+
//#endregion
|
|
11
|
+
export { ssgOptionsSchema };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/strictTransportSecurityOptionsSchema.ts
|
|
3
|
+
const strictTransportSecurityOptionsSchema = z.strictObject({
|
|
4
|
+
includeSubdomains: z.boolean(),
|
|
5
|
+
maxAge: z.int().nonnegative(),
|
|
6
|
+
preload: z.boolean()
|
|
7
|
+
});
|
|
8
|
+
//#endregion
|
|
9
|
+
export { strictTransportSecurityOptionsSchema };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SRI_HASH_ALGORITHM } from "../runtime/constants.js";
|
|
2
|
+
import { generateHash } from "../runtime/services/generateHash.js";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
5
|
+
import { join, posix, relative, sep } from "node:path";
|
|
6
|
+
//#region src/services/computeSubresourceIntegrityHashes.ts
|
|
7
|
+
const computeSubresourceIntegrityHashes = async (nitro) => {
|
|
8
|
+
const { baseURL: appBaseUrl, cdnURL: appCdnUrl } = nitro.options.runtimeConfig.app;
|
|
9
|
+
const existingPublicAssets = nitro.options.publicAssets.filter(({ dir: directory }) => existsSync(directory));
|
|
10
|
+
const filesByPublicAsset = await Promise.all(existingPublicAssets.map(async ({ baseURL = "", dir: directory }) => {
|
|
11
|
+
return (await readdir(directory, {
|
|
12
|
+
recursive: true,
|
|
13
|
+
withFileTypes: true
|
|
14
|
+
})).filter((entry) => entry.isFile()).map((entry) => {
|
|
15
|
+
const filePath = join(entry.parentPath, entry.name);
|
|
16
|
+
return {
|
|
17
|
+
filePath,
|
|
18
|
+
fullPath: posix.join(baseURL, ...relative(directory, filePath).split(sep))
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
}));
|
|
22
|
+
const hashEntries = await Promise.all(filesByPublicAsset.flat().map(async ({ filePath, fullPath }) => {
|
|
23
|
+
const hash = await generateHash(await readFile(filePath), SRI_HASH_ALGORITHM);
|
|
24
|
+
return [appCdnUrl ? new URL(posix.isAbsolute(fullPath) ? fullPath.slice(1) : fullPath, appCdnUrl.endsWith("/") ? appCdnUrl : `${appCdnUrl}/`).href : posix.join("/", appBaseUrl, fullPath), hash];
|
|
25
|
+
}));
|
|
26
|
+
return Object.fromEntries(hashEntries);
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { computeSubresourceIntegrityHashes };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/services/formatZodIssues.ts
|
|
2
|
+
const getDeepestPathLength = (leafIssues) => Math.max(-1, ...leafIssues.map(({ path }) => path.length));
|
|
3
|
+
const getLeafIssues = (issue, parentPath) => {
|
|
4
|
+
const path = [...parentPath, ...issue.path];
|
|
5
|
+
if (issue.code !== "invalid_union") return [{
|
|
6
|
+
issue,
|
|
7
|
+
path
|
|
8
|
+
}];
|
|
9
|
+
return issue.errors.map((branch) => branch.flatMap((branchIssue) => getLeafIssues(branchIssue, path))).reduce((deepest, branch) => getDeepestPathLength(branch) > getDeepestPathLength(deepest) ? branch : deepest);
|
|
10
|
+
};
|
|
11
|
+
const formatLeafIssue = ({ issue, path }) => {
|
|
12
|
+
return `${path.map(String).join(".") || "(options)"}: ${issue.message}`;
|
|
13
|
+
};
|
|
14
|
+
const formatZodIssues = (issues) => issues.flatMap((issue) => getLeafIssues(issue, [])).map((leafIssue) => formatLeafIssue(leafIssue)).join("; ");
|
|
15
|
+
//#endregion
|
|
16
|
+
export { formatZodIssues };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getDefaultSecurityHeaders } from "./getDefaultSecurityHeaders.js";
|
|
2
|
+
//#region src/services/getDefaultModuleOptions.ts
|
|
3
|
+
const getDefaultModuleOptions = (isDevelopment, devServerUrl, isStrict) => ({
|
|
4
|
+
allowedMethodsRestricter: false,
|
|
5
|
+
basicAuth: false,
|
|
6
|
+
contentSecurityPolicyReportOnly: false,
|
|
7
|
+
corsHandler: {
|
|
8
|
+
methods: [
|
|
9
|
+
"GET",
|
|
10
|
+
"HEAD",
|
|
11
|
+
"PUT",
|
|
12
|
+
"PATCH",
|
|
13
|
+
"POST",
|
|
14
|
+
"DELETE"
|
|
15
|
+
],
|
|
16
|
+
origin: devServerUrl,
|
|
17
|
+
preflight: { statusCode: 204 }
|
|
18
|
+
},
|
|
19
|
+
csrf: false,
|
|
20
|
+
enabled: true,
|
|
21
|
+
headers: getDefaultSecurityHeaders(isDevelopment, isStrict),
|
|
22
|
+
hidePoweredBy: true,
|
|
23
|
+
nonce: true,
|
|
24
|
+
rateLimiter: false,
|
|
25
|
+
removeLoggers: false,
|
|
26
|
+
requestSizeLimiter: {
|
|
27
|
+
maxRequestSizeInBytes: 2e6,
|
|
28
|
+
maxUploadFileRequestInBytes: 8e6
|
|
29
|
+
},
|
|
30
|
+
sri: true,
|
|
31
|
+
ssg: {
|
|
32
|
+
exportToPresets: true,
|
|
33
|
+
hashScripts: true,
|
|
34
|
+
hashStyles: false,
|
|
35
|
+
meta: true,
|
|
36
|
+
nitroHeaders: true
|
|
37
|
+
},
|
|
38
|
+
strict: isStrict
|
|
39
|
+
});
|
|
40
|
+
//#endregion
|
|
41
|
+
export { getDefaultModuleOptions };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { NONCE_PLACEHOLDER } from "../runtime/constants.js";
|
|
2
|
+
import { CrossOriginEmbedderPolicy } from "../models/CrossOriginEmbedderPolicy.js";
|
|
3
|
+
import { XFrameOptions } from "../models/XFrameOptions.js";
|
|
4
|
+
//#region src/services/getDefaultSecurityHeaders.ts
|
|
5
|
+
const getDefaultSecurityHeaders = (isDevelopment, isStrict) => ({
|
|
6
|
+
contentSecurityPolicy: {
|
|
7
|
+
"base-uri": ["'none'"],
|
|
8
|
+
"default-src": ["'self'"],
|
|
9
|
+
"font-src": [
|
|
10
|
+
"'self'",
|
|
11
|
+
"https:",
|
|
12
|
+
"data:"
|
|
13
|
+
],
|
|
14
|
+
"form-action": ["'self'"],
|
|
15
|
+
"frame-ancestors": ["'self'"],
|
|
16
|
+
"img-src": ["'self'", "data:"],
|
|
17
|
+
"object-src": ["'none'"],
|
|
18
|
+
"script-src": isStrict ? [
|
|
19
|
+
"'self'",
|
|
20
|
+
"'strict-dynamic'",
|
|
21
|
+
NONCE_PLACEHOLDER
|
|
22
|
+
] : [
|
|
23
|
+
"'self'",
|
|
24
|
+
"https:",
|
|
25
|
+
"'unsafe-inline'",
|
|
26
|
+
"'strict-dynamic'",
|
|
27
|
+
NONCE_PLACEHOLDER
|
|
28
|
+
],
|
|
29
|
+
"script-src-attr": ["'none'"],
|
|
30
|
+
"style-src": [
|
|
31
|
+
"'self'",
|
|
32
|
+
"https:",
|
|
33
|
+
"'unsafe-inline'"
|
|
34
|
+
],
|
|
35
|
+
"upgrade-insecure-requests": true
|
|
36
|
+
},
|
|
37
|
+
crossOriginEmbedderPolicy: isDevelopment ? CrossOriginEmbedderPolicy.UnsafeNone : CrossOriginEmbedderPolicy.Credentialless,
|
|
38
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
39
|
+
crossOriginResourcePolicy: "same-origin",
|
|
40
|
+
originAgentCluster: "?1",
|
|
41
|
+
permissionsPolicy: {
|
|
42
|
+
accelerometer: [],
|
|
43
|
+
autoplay: [],
|
|
44
|
+
camera: [],
|
|
45
|
+
"clipboard-read": [],
|
|
46
|
+
"clipboard-write": [],
|
|
47
|
+
"cross-origin-isolated": [],
|
|
48
|
+
"display-capture": [],
|
|
49
|
+
"encrypted-media": [],
|
|
50
|
+
fullscreen: [],
|
|
51
|
+
gamepad: [],
|
|
52
|
+
geolocation: [],
|
|
53
|
+
gyroscope: [],
|
|
54
|
+
hid: [],
|
|
55
|
+
"idle-detection": [],
|
|
56
|
+
"interest-cohort": [],
|
|
57
|
+
"keyboard-map": [],
|
|
58
|
+
magnetometer: [],
|
|
59
|
+
microphone: [],
|
|
60
|
+
midi: [],
|
|
61
|
+
payment: [],
|
|
62
|
+
"picture-in-picture": [],
|
|
63
|
+
"publickey-credentials-get": [],
|
|
64
|
+
"screen-wake-lock": [],
|
|
65
|
+
serial: [],
|
|
66
|
+
"sync-xhr": ["self"],
|
|
67
|
+
unload: [],
|
|
68
|
+
usb: [],
|
|
69
|
+
"web-share": [],
|
|
70
|
+
"xr-spatial-tracking": []
|
|
71
|
+
},
|
|
72
|
+
referrerPolicy: "no-referrer",
|
|
73
|
+
strictTransportSecurity: {
|
|
74
|
+
includeSubdomains: true,
|
|
75
|
+
maxAge: 63072e3,
|
|
76
|
+
preload: false
|
|
77
|
+
},
|
|
78
|
+
xContentTypeOptions: "nosniff",
|
|
79
|
+
xDNSPrefetchControl: "off",
|
|
80
|
+
xDownloadOptions: "noopen",
|
|
81
|
+
xFrameOptions: XFrameOptions.SameOrigin,
|
|
82
|
+
xPermittedCrossDomainPolicies: "none"
|
|
83
|
+
});
|
|
84
|
+
//#endregion
|
|
85
|
+
export { getDefaultSecurityHeaders };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/services/getPluginTemplate.ts
|
|
2
|
+
const getPluginTemplate = (factoryPath, factoryName, dataPath) => [
|
|
3
|
+
`import { ${factoryName} } from ${JSON.stringify(factoryPath)};`,
|
|
4
|
+
`import data from ${JSON.stringify(dataPath)};`,
|
|
5
|
+
"",
|
|
6
|
+
`export default ${factoryName}(data);`,
|
|
7
|
+
""
|
|
8
|
+
].join("\n");
|
|
9
|
+
//#endregion
|
|
10
|
+
export { getPluginTemplate };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { moduleOptionsSchema } from "../schemas/moduleOptionsSchema.js";
|
|
2
|
+
import { formatZodIssues } from "./formatZodIssues.js";
|
|
3
|
+
import { mergeModuleOptions } from "./mergeModuleOptions.js";
|
|
4
|
+
//#region src/services/getRouteSecurityRules.ts
|
|
5
|
+
const getRouteSecurityRules = (routeRules, options) => {
|
|
6
|
+
const routeSecurityRules = {};
|
|
7
|
+
for (const [route, { security }] of Object.entries(routeRules)) {
|
|
8
|
+
if (security === void 0) continue;
|
|
9
|
+
if (Object.hasOwn(security, "basicAuth")) throw new TypeError(`nuxt-security-module: route ${route}: basicAuth cannot be set in a route rule`);
|
|
10
|
+
const result = moduleOptionsSchema.safeParse(mergeModuleOptions(options, security));
|
|
11
|
+
if (!result.success) throw new TypeError(`nuxt-security-module: route ${route}: ${formatZodIssues(result.error.issues)}`);
|
|
12
|
+
routeSecurityRules[route] = security;
|
|
13
|
+
}
|
|
14
|
+
return routeSecurityRules;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { getRouteSecurityRules };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { serializeSecurityHeaders } from "../runtime/services/serializeSecurityHeaders.js";
|
|
2
|
+
//#region src/services/getStaticResourceHeaders.ts
|
|
3
|
+
const getStaticResourceHeaders = (headers) => serializeSecurityHeaders({
|
|
4
|
+
referrerPolicy: headers.referrerPolicy,
|
|
5
|
+
strictTransportSecurity: headers.strictTransportSecurity,
|
|
6
|
+
xContentTypeOptions: headers.xContentTypeOptions,
|
|
7
|
+
xDownloadOptions: headers.xDownloadOptions,
|
|
8
|
+
xFrameOptions: headers.xFrameOptions,
|
|
9
|
+
xPermittedCrossDomainPolicies: headers.xPermittedCrossDomainPolicies
|
|
10
|
+
});
|
|
11
|
+
//#endregion
|
|
12
|
+
export { getStaticResourceHeaders };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { MODULE_NAME, ROUTE_RULES_HOOK } from "../runtime/constants.js";
|
|
2
|
+
//#region src/services/getTypeTemplate.ts
|
|
3
|
+
const getTypeTemplate = () => `// Generated by ${MODULE_NAME}
|
|
4
|
+
import type { ModuleOptions } from '${MODULE_NAME}'
|
|
5
|
+
|
|
6
|
+
declare module '#${MODULE_NAME}/route-rules.mjs' {
|
|
7
|
+
const routeRules: Record<string, Partial<ModuleOptions>>
|
|
8
|
+
export default routeRules
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module '#${MODULE_NAME}/sri-hashes.mjs' {
|
|
12
|
+
const sriHashes: Record<string, string>
|
|
13
|
+
export default sriHashes
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module 'nitro/types' {
|
|
17
|
+
interface NitroRuntimeHooks {
|
|
18
|
+
'${ROUTE_RULES_HOOK}': (routeRules: Record<string, Partial<ModuleOptions>>) => void | Promise<void>
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {}
|
|
23
|
+
`;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { getTypeTemplate };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/services/mergeModuleOptions.ts
|
|
2
|
+
const mergeModuleOptions = (defaults, overrides) => ({
|
|
3
|
+
...defaults,
|
|
4
|
+
...overrides,
|
|
5
|
+
headers: mergeHeaders(defaults.headers, overrides.headers),
|
|
6
|
+
ssg: overrides.ssg === false ? false : {
|
|
7
|
+
...defaults.ssg,
|
|
8
|
+
...overrides.ssg
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const mergeHeaders = (defaults, overrides) => {
|
|
12
|
+
if (overrides === false || defaults === false) return overrides ?? defaults;
|
|
13
|
+
if (overrides === void 0) return defaults;
|
|
14
|
+
return {
|
|
15
|
+
...defaults,
|
|
16
|
+
...overrides,
|
|
17
|
+
contentSecurityPolicy: mergeDirectives(defaults.contentSecurityPolicy, overrides.contentSecurityPolicy),
|
|
18
|
+
permissionsPolicy: mergeFeatures(defaults.permissionsPolicy, overrides.permissionsPolicy)
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
const mergeDirectives = (defaults, overrides) => {
|
|
22
|
+
if (overrides === false || defaults === false) return overrides ?? defaults;
|
|
23
|
+
if (overrides === void 0) return defaults;
|
|
24
|
+
const merged = { ...defaults };
|
|
25
|
+
for (const [directive, value] of Object.entries(overrides)) if (value === false) delete merged[directive];
|
|
26
|
+
else if (Array.isArray(value)) merged[directive] = [.../* @__PURE__ */ new Set([...Array.isArray(merged[directive]) ? merged[directive] : [], ...value])];
|
|
27
|
+
else merged[directive] = value;
|
|
28
|
+
return merged;
|
|
29
|
+
};
|
|
30
|
+
const mergeFeatures = (defaults, overrides) => {
|
|
31
|
+
if (overrides === false || defaults === false) return overrides ?? defaults;
|
|
32
|
+
if (overrides === void 0) return defaults;
|
|
33
|
+
const merged = { ...defaults };
|
|
34
|
+
for (const [feature, sources] of Object.entries(overrides)) if (sources === false) delete merged[feature];
|
|
35
|
+
else merged[feature] = [.../* @__PURE__ */ new Set([...merged[feature] || [], ...sources || []])];
|
|
36
|
+
return merged;
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { mergeModuleOptions };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { moduleOptionsSchema } from "../schemas/moduleOptionsSchema.js";
|
|
2
|
+
import { formatZodIssues } from "./formatZodIssues.js";
|
|
3
|
+
//#region src/services/parseModuleOptions.ts
|
|
4
|
+
const parseModuleOptions = (options) => {
|
|
5
|
+
const result = moduleOptionsSchema.safeParse(options);
|
|
6
|
+
if (!result.success) throw new TypeError(`nuxt-security-module: ${formatZodIssues(result.error.issues)}`);
|
|
7
|
+
return result.data;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { parseModuleOptions };
|