nuxt-security-module 0.0.0-stage → 4.1.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.
Files changed (137) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +21 -2
  3. package/dist/index.d.ts +17 -0
  4. package/dist/index.js +105 -0
  5. package/dist/models/AllowedMethodsRestricterOptions.d.ts +5 -0
  6. package/dist/models/BasicAuthOptions.d.ts +9 -0
  7. package/dist/models/ContentSecurityPolicy.d.ts +3 -0
  8. package/dist/models/CorsHandlerOptions.d.ts +13 -0
  9. package/dist/models/CrossOriginEmbedderPolicy.d.ts +8 -0
  10. package/dist/models/CrossOriginEmbedderPolicy.js +8 -0
  11. package/dist/models/CsrfOptions.d.ts +6 -0
  12. package/dist/models/ModuleOptions.d.ts +27 -0
  13. package/dist/models/PermissionsPolicy.d.ts +3 -0
  14. package/dist/models/RateLimitWindow.d.ts +6 -0
  15. package/dist/models/RateLimiterOptions.d.ts +6 -0
  16. package/dist/models/RateLimiterStore.d.ts +6 -0
  17. package/dist/models/RequestSizeLimiterOptions.d.ts +6 -0
  18. package/dist/models/SecurityHeaders.d.ts +22 -0
  19. package/dist/models/SecurityRouteRules.d.ts +4 -0
  20. package/dist/models/SecurityRules.d.ts +4 -0
  21. package/dist/models/SsgOptions.d.ts +9 -0
  22. package/dist/models/StrictTransportSecurity.d.ts +7 -0
  23. package/dist/models/XFrameOptions.d.ts +7 -0
  24. package/dist/models/XFrameOptions.js +7 -0
  25. package/dist/runtime/constants.d.ts +24 -0
  26. package/dist/runtime/constants.js +52 -0
  27. package/dist/runtime/nitro/plugins/hidePoweredBy.d.ts +5 -0
  28. package/dist/runtime/nitro/plugins/hidePoweredBy.js +13 -0
  29. package/dist/runtime/nitro/plugins/nonce.d.ts +5 -0
  30. package/dist/runtime/nitro/plugins/nonce.js +27 -0
  31. package/dist/runtime/nitro/plugins/routeRules.d.ts +5 -0
  32. package/dist/runtime/nitro/plugins/routeRules.js +17 -0
  33. package/dist/runtime/nitro/plugins/securityHeaders.d.ts +5 -0
  34. package/dist/runtime/nitro/plugins/securityHeaders.js +25 -0
  35. package/dist/runtime/nitro/plugins/ssgHashes.d.ts +5 -0
  36. package/dist/runtime/nitro/plugins/ssgHashes.js +27 -0
  37. package/dist/runtime/nitro/plugins/subresourceIntegrity.d.ts +4 -0
  38. package/dist/runtime/nitro/plugins/subresourceIntegrity.js +19 -0
  39. package/dist/runtime/server/middleware/allowedMethodsRestricter.d.ts +5 -0
  40. package/dist/runtime/server/middleware/allowedMethodsRestricter.js +15 -0
  41. package/dist/runtime/server/middleware/basicAuth.d.ts +5 -0
  42. package/dist/runtime/server/middleware/basicAuth.js +23 -0
  43. package/dist/runtime/server/middleware/corsHandler.d.ts +5 -0
  44. package/dist/runtime/server/middleware/corsHandler.js +20 -0
  45. package/dist/runtime/server/middleware/csrf.d.ts +5 -0
  46. package/dist/runtime/server/middleware/csrf.js +28 -0
  47. package/dist/runtime/server/middleware/rateLimiter.d.ts +5 -0
  48. package/dist/runtime/server/middleware/rateLimiter.js +22 -0
  49. package/dist/runtime/server/middleware/requestSizeLimiter.d.ts +5 -0
  50. package/dist/runtime/server/middleware/requestSizeLimiter.js +17 -0
  51. package/dist/runtime/services/checkIsBasicAuthorized.d.ts +4 -0
  52. package/dist/runtime/services/checkIsBasicAuthorized.js +12 -0
  53. package/dist/runtime/services/checkIsChallenged.d.ts +4 -0
  54. package/dist/runtime/services/checkIsChallenged.js +8 -0
  55. package/dist/runtime/services/checkIsCsrfTokenValid.d.ts +3 -0
  56. package/dist/runtime/services/checkIsCsrfTokenValid.js +8 -0
  57. package/dist/runtime/services/checkIsEqualInConstantTime.d.ts +3 -0
  58. package/dist/runtime/services/checkIsEqualInConstantTime.js +11 -0
  59. package/dist/runtime/services/checkIsMethodAllowed.d.ts +3 -0
  60. package/dist/runtime/services/checkIsMethodAllowed.js +4 -0
  61. package/dist/runtime/services/checkIsRecord.d.ts +3 -0
  62. package/dist/runtime/services/checkIsRecord.js +4 -0
  63. package/dist/runtime/services/checkIsRequestTooLarge.d.ts +4 -0
  64. package/dist/runtime/services/checkIsRequestTooLarge.js +9 -0
  65. package/dist/runtime/services/collectSsgHashes.d.ts +5 -0
  66. package/dist/runtime/services/collectSsgHashes.js +41 -0
  67. package/dist/runtime/services/createInMemoryRateLimiterStore.d.ts +4 -0
  68. package/dist/runtime/services/createInMemoryRateLimiterStore.js +22 -0
  69. package/dist/runtime/services/generateHash.d.ts +3 -0
  70. package/dist/runtime/services/generateHash.js +9 -0
  71. package/dist/runtime/services/generateNonce.d.ts +3 -0
  72. package/dist/runtime/services/generateNonce.js +6 -0
  73. package/dist/runtime/services/generateRandomToken.d.ts +3 -0
  74. package/dist/runtime/services/generateRandomToken.js +8 -0
  75. package/dist/runtime/services/getBasicAuthCredentials.d.ts +6 -0
  76. package/dist/runtime/services/getBasicAuthCredentials.js +19 -0
  77. package/dist/runtime/services/getBasicAuthOptions.d.ts +4 -0
  78. package/dist/runtime/services/getBasicAuthOptions.js +17 -0
  79. package/dist/runtime/services/getRetryAfterSeconds.d.ts +5 -0
  80. package/dist/runtime/services/getRetryAfterSeconds.js +9 -0
  81. package/dist/runtime/services/getSecurityRules.d.ts +5 -0
  82. package/dist/runtime/services/getSecurityRules.js +15 -0
  83. package/dist/runtime/services/getSecurityState.d.ts +11 -0
  84. package/dist/runtime/services/getSecurityState.js +14 -0
  85. package/dist/runtime/services/injectNonce.d.ts +3 -0
  86. package/dist/runtime/services/injectNonce.js +7 -0
  87. package/dist/runtime/services/injectSubresourceIntegrity.d.ts +3 -0
  88. package/dist/runtime/services/injectSubresourceIntegrity.js +22 -0
  89. package/dist/runtime/services/insertContentSecurityPolicyMeta.d.ts +4 -0
  90. package/dist/runtime/services/insertContentSecurityPolicyMeta.js +17 -0
  91. package/dist/runtime/services/mergeSecurityRules.d.ts +4 -0
  92. package/dist/runtime/services/mergeSecurityRules.js +11 -0
  93. package/dist/runtime/services/readHtmlTagAttribute.d.ts +3 -0
  94. package/dist/runtime/services/readHtmlTagAttribute.js +8 -0
  95. package/dist/runtime/services/rewriteHtmlTags.d.ts +3 -0
  96. package/dist/runtime/services/rewriteHtmlTags.js +9 -0
  97. package/dist/runtime/services/securityRouteMatcher.d.ts +5 -0
  98. package/dist/runtime/services/securityRouteMatcher.js +11 -0
  99. package/dist/runtime/services/serializeBasicAuthChallenge.d.ts +3 -0
  100. package/dist/runtime/services/serializeBasicAuthChallenge.js +4 -0
  101. package/dist/runtime/services/serializeContentSecurityPolicy.d.ts +4 -0
  102. package/dist/runtime/services/serializeContentSecurityPolicy.js +7 -0
  103. package/dist/runtime/services/serializePermissionsPolicy.d.ts +4 -0
  104. package/dist/runtime/services/serializePermissionsPolicy.js +4 -0
  105. package/dist/runtime/services/serializeSecurityHeaders.d.ts +4 -0
  106. package/dist/runtime/services/serializeSecurityHeaders.js +19 -0
  107. package/dist/runtime/services/serializeStrictTransportSecurity.d.ts +4 -0
  108. package/dist/runtime/services/serializeStrictTransportSecurity.js +8 -0
  109. package/dist/runtime/services/stripHtmlTagAttribute.d.ts +3 -0
  110. package/dist/runtime/services/stripHtmlTagAttribute.js +5 -0
  111. package/dist/runtime/services/updateContentSecurityPolicy.d.ts +5 -0
  112. package/dist/runtime/services/updateContentSecurityPolicy.js +15 -0
  113. package/dist/schemas/allowedMethodsRestricterOptionsSchema.js +5 -0
  114. package/dist/schemas/basicAuthOptionsSchema.js +12 -0
  115. package/dist/schemas/contentSecurityPolicySchema.js +5 -0
  116. package/dist/schemas/corsHandlerOptionsSchema.js +14 -0
  117. package/dist/schemas/csrfOptionsSchema.js +9 -0
  118. package/dist/schemas/moduleOptionsSchema.js +29 -0
  119. package/dist/schemas/permissionsPolicySchema.js +5 -0
  120. package/dist/schemas/rateLimiterOptionsSchema.js +8 -0
  121. package/dist/schemas/requestSizeLimiterOptionsSchema.js +8 -0
  122. package/dist/schemas/securityHeadersSchema.js +25 -0
  123. package/dist/schemas/ssgOptionsSchema.js +11 -0
  124. package/dist/schemas/strictTransportSecurityOptionsSchema.js +9 -0
  125. package/dist/services/computeSubresourceIntegrityHashes.js +29 -0
  126. package/dist/services/formatZodIssues.js +16 -0
  127. package/dist/services/getDefaultModuleOptions.js +41 -0
  128. package/dist/services/getDefaultSecurityHeaders.js +85 -0
  129. package/dist/services/getPluginTemplate.js +10 -0
  130. package/dist/services/getRemoveLoggersViteConfig.js +4 -0
  131. package/dist/services/getRouteRulesTemplate.js +4 -0
  132. package/dist/services/getRouteSecurityRules.js +17 -0
  133. package/dist/services/getStaticResourceHeaders.js +12 -0
  134. package/dist/services/getTypeTemplate.js +25 -0
  135. package/dist/services/mergeModuleOptions.js +39 -0
  136. package/dist/services/parseModuleOptions.js +10 -0
  137. package/package.json +70 -4
@@ -0,0 +1,13 @@
1
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
2
+ import { definePlugin } from "nitro";
3
+ import { isEvent } from "nitro/h3";
4
+ //#region src/runtime/nitro/plugins/hidePoweredBy.ts
5
+ const hidePoweredBy = definePlugin((nitroApp) => {
6
+ nitroApp.hooks.hook("response", (_response, event) => {
7
+ if (!isEvent(event)) return;
8
+ const rules = getSecurityRules(event);
9
+ if (rules?.enabled && rules.hidePoweredBy) event.res.headers.delete("x-powered-by");
10
+ });
11
+ });
12
+ //#endregion
13
+ export { hidePoweredBy as default };
@@ -0,0 +1,5 @@
1
+ import { NitroAppPlugin } from "nitro/types";
2
+ //#region src/runtime/nitro/plugins/nonce.d.ts
3
+ declare const noncePlugin: NitroAppPlugin;
4
+ //#endregion
5
+ export { noncePlugin as default };
@@ -0,0 +1,27 @@
1
+ import { generateNonce } from "../../services/generateNonce.js";
2
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
3
+ import { getSecurityState } from "../../services/getSecurityState.js";
4
+ import { injectNonce } from "../../services/injectNonce.js";
5
+ import { definePlugin } from "nitro";
6
+ //#region src/runtime/nitro/plugins/nonce.ts
7
+ const HTML_SECTIONS = [
8
+ "body",
9
+ "bodyAppend",
10
+ "bodyPrepend",
11
+ "head"
12
+ ];
13
+ const noncePlugin = definePlugin((nitroApp) => {
14
+ if (import.meta.prerender) return;
15
+ nitroApp.hooks.hook("render:route", (_context, { event }) => {
16
+ const rules = getSecurityRules(event);
17
+ if (rules?.enabled && rules.nonce) getSecurityState(event).nonce = generateNonce();
18
+ });
19
+ nitroApp.hooks.hook("render:html", (html, { event }) => {
20
+ const rules = getSecurityRules(event);
21
+ const nonce = getSecurityState(event).nonce;
22
+ if (!rules?.enabled || !rules.nonce || !rules.headers || !rules.headers.contentSecurityPolicy || !nonce) return;
23
+ for (const section of HTML_SECTIONS) html[section] = html[section].map((element) => injectNonce(element, nonce));
24
+ });
25
+ });
26
+ //#endregion
27
+ export { noncePlugin as default };
@@ -0,0 +1,5 @@
1
+ import { SecurityRules } from "../../../models/SecurityRules.js";
2
+ import { NitroAppPlugin } from "nitro/types";
3
+ //#region src/runtime/nitro/plugins/routeRules.d.ts
4
+ export declare const createRouteRulesPlugin: (routeRules: Record<string, SecurityRules>) => NitroAppPlugin;
5
+ //#endregion
@@ -0,0 +1,17 @@
1
+ import { ROUTE_RULES_HOOK } from "../../constants.js";
2
+ import { setSecurityRouteMatcher } from "../../services/securityRouteMatcher.js";
3
+ import { definePlugin } from "nitro";
4
+ //#region src/runtime/nitro/plugins/routeRules.ts
5
+ const createRouteRulesPlugin = (routeRules) => definePlugin((nitroApp) => {
6
+ let routesResolved;
7
+ nitroApp.hooks.hook("request", () => {
8
+ routesResolved ??= (async () => {
9
+ const routes = structuredClone(routeRules);
10
+ await nitroApp.hooks.callHook(ROUTE_RULES_HOOK, routes);
11
+ setSecurityRouteMatcher(routes);
12
+ })();
13
+ return routesResolved;
14
+ });
15
+ });
16
+ //#endregion
17
+ export { createRouteRulesPlugin };
@@ -0,0 +1,5 @@
1
+ import { NitroAppPlugin } from "nitro/types";
2
+ //#region src/runtime/nitro/plugins/securityHeaders.d.ts
3
+ declare const securityHeaders: NitroAppPlugin;
4
+ //#endregion
5
+ export { securityHeaders as default };
@@ -0,0 +1,25 @@
1
+ import { CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER_NAME, SECURITY_HEADER_NAMES } from "../../constants.js";
2
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
3
+ import { getSecurityState } from "../../services/getSecurityState.js";
4
+ import { serializeSecurityHeaders } from "../../services/serializeSecurityHeaders.js";
5
+ import { updateContentSecurityPolicy } from "../../services/updateContentSecurityPolicy.js";
6
+ import { definePlugin } from "nitro";
7
+ //#region src/runtime/nitro/plugins/securityHeaders.ts
8
+ const CONTENT_SECURITY_POLICY_HEADER_NAME = SECURITY_HEADER_NAMES.contentSecurityPolicy;
9
+ const securityHeaders = definePlugin((nitroApp) => {
10
+ nitroApp.hooks.hook("render:route", (_context, { event }) => {
11
+ const rules = getSecurityRules(event);
12
+ if (!rules?.enabled || !rules.headers) return;
13
+ const { contentSecurityPolicy } = rules.headers;
14
+ const serializedHeaders = serializeSecurityHeaders({
15
+ ...rules.headers,
16
+ contentSecurityPolicy: contentSecurityPolicy ? updateContentSecurityPolicy(contentSecurityPolicy, getSecurityState(event)) : false
17
+ });
18
+ for (const [name, value] of Object.entries(serializedHeaders)) {
19
+ const isPolicy = name === CONTENT_SECURITY_POLICY_HEADER_NAME;
20
+ event.res.headers.set(isPolicy && rules.contentSecurityPolicyReportOnly ? CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER_NAME : name, value);
21
+ }
22
+ });
23
+ });
24
+ //#endregion
25
+ export { securityHeaders as default };
@@ -0,0 +1,5 @@
1
+ import { NitroAppPlugin } from "nitro/types";
2
+ //#region src/runtime/nitro/plugins/ssgHashes.d.ts
3
+ declare const ssgHashes: NitroAppPlugin;
4
+ //#endregion
5
+ export { ssgHashes as default };
@@ -0,0 +1,27 @@
1
+ import { collectSsgHashes } from "../../services/collectSsgHashes.js";
2
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
3
+ import { getSecurityState } from "../../services/getSecurityState.js";
4
+ import { insertContentSecurityPolicyMeta } from "../../services/insertContentSecurityPolicyMeta.js";
5
+ import { updateContentSecurityPolicy } from "../../services/updateContentSecurityPolicy.js";
6
+ import { definePlugin } from "nitro";
7
+ //#region src/runtime/nitro/plugins/ssgHashes.ts
8
+ const HTML_SECTIONS = [
9
+ "head",
10
+ "bodyPrepend",
11
+ "body",
12
+ "bodyAppend"
13
+ ];
14
+ const ssgHashes = definePlugin((nitroApp) => {
15
+ if (!import.meta.prerender) return;
16
+ nitroApp.hooks.hook("render:html", async (html, { event }) => {
17
+ const rules = getSecurityRules(event);
18
+ if (!rules?.enabled || !rules.ssg || !rules.headers || !rules.headers.contentSecurityPolicy) return;
19
+ const state = getSecurityState(event);
20
+ state.hashes = await collectSsgHashes(HTML_SECTIONS.map((section) => html[section].join("")).join(""), rules.ssg);
21
+ if (!rules.ssg.meta) return;
22
+ const policy = updateContentSecurityPolicy(rules.headers.contentSecurityPolicy, state);
23
+ html.head = [insertContentSecurityPolicyMeta(html.head.join(""), policy)];
24
+ });
25
+ });
26
+ //#endregion
27
+ export { ssgHashes as default };
@@ -0,0 +1,4 @@
1
+ import { NitroAppPlugin } from "nitro/types";
2
+ //#region src/runtime/nitro/plugins/subresourceIntegrity.d.ts
3
+ export declare const createSubresourceIntegrityPlugin: (sriHashes: Record<string, string>) => NitroAppPlugin;
4
+ //#endregion
@@ -0,0 +1,19 @@
1
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
2
+ import { injectSubresourceIntegrity } from "../../services/injectSubresourceIntegrity.js";
3
+ import { definePlugin } from "nitro";
4
+ //#region src/runtime/nitro/plugins/subresourceIntegrity.ts
5
+ const HTML_SECTIONS = [
6
+ "body",
7
+ "bodyAppend",
8
+ "bodyPrepend",
9
+ "head"
10
+ ];
11
+ const createSubresourceIntegrityPlugin = (sriHashes) => definePlugin((nitroApp) => {
12
+ nitroApp.hooks.hook("render:html", (html, { event }) => {
13
+ const rules = getSecurityRules(event);
14
+ if (!rules?.enabled || !rules.sri) return;
15
+ for (const section of HTML_SECTIONS) html[section] = html[section].map((element) => injectSubresourceIntegrity(element, sriHashes));
16
+ });
17
+ });
18
+ //#endregion
19
+ export { createSubresourceIntegrityPlugin };
@@ -0,0 +1,5 @@
1
+ import { Middleware } from "nitro/h3";
2
+ //#region src/runtime/server/middleware/allowedMethodsRestricter.d.ts
3
+ declare const allowedMethodsRestricter: Middleware;
4
+ //#endregion
5
+ export { allowedMethodsRestricter as default };
@@ -0,0 +1,15 @@
1
+ import { checkIsMethodAllowed } from "../../services/checkIsMethodAllowed.js";
2
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
3
+ import { defineMiddleware } from "nitro";
4
+ import { HTTPError } from "nitro/h3";
5
+ //#region src/runtime/server/middleware/allowedMethodsRestricter.ts
6
+ const allowedMethodsRestricter = defineMiddleware((event) => {
7
+ const rules = getSecurityRules(event);
8
+ if (!rules?.enabled || !rules.allowedMethodsRestricter) return;
9
+ if (!checkIsMethodAllowed(event.req.method, rules.allowedMethodsRestricter.methods)) throw new HTTPError({
10
+ status: 405,
11
+ statusText: "Method not allowed"
12
+ });
13
+ });
14
+ //#endregion
15
+ export { allowedMethodsRestricter as default };
@@ -0,0 +1,5 @@
1
+ import { Middleware } from "nitro/h3";
2
+ //#region src/runtime/server/middleware/basicAuth.d.ts
3
+ declare const basicAuthMiddleware: Middleware;
4
+ //#endregion
5
+ export { basicAuthMiddleware as default };
@@ -0,0 +1,23 @@
1
+ import "../../constants.js";
2
+ import { checkIsBasicAuthorized } from "../../services/checkIsBasicAuthorized.js";
3
+ import { checkIsChallenged } from "../../services/checkIsChallenged.js";
4
+ import { getBasicAuthOptions } from "../../services/getBasicAuthOptions.js";
5
+ import { serializeBasicAuthChallenge } from "../../services/serializeBasicAuthChallenge.js";
6
+ import { defineMiddleware } from "nitro";
7
+ import { HTTPError } from "nitro/h3";
8
+ import { useRuntimeConfig } from "nuxt/server";
9
+ //#region src/runtime/server/middleware/basicAuth.ts
10
+ const basicAuthMiddleware = defineMiddleware((event) => {
11
+ const basicAuth = getBasicAuthOptions(useRuntimeConfig().private);
12
+ if (!basicAuth) return;
13
+ const { pathname } = event.url;
14
+ if (!checkIsChallenged(pathname, basicAuth)) return;
15
+ if (checkIsBasicAuthorized(event.req.headers.get("authorization"), basicAuth)) return;
16
+ event.res.headers.set("WWW-Authenticate", serializeBasicAuthChallenge(basicAuth.message || "Please enter username and password"));
17
+ throw new HTTPError({
18
+ status: 401,
19
+ statusText: "Access denied"
20
+ });
21
+ });
22
+ //#endregion
23
+ export { basicAuthMiddleware as default };
@@ -0,0 +1,5 @@
1
+ import { Middleware } from "nitro/h3";
2
+ //#region src/runtime/server/middleware/corsHandler.d.ts
3
+ declare const corsHandler: Middleware;
4
+ //#endregion
5
+ export { corsHandler as default };
@@ -0,0 +1,20 @@
1
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
2
+ import { defineMiddleware } from "nitro";
3
+ import { handleCors } from "nitro/h3";
4
+ //#region src/runtime/server/middleware/corsHandler.ts
5
+ const corsHandler = defineMiddleware((event) => {
6
+ const rules = getSecurityRules(event);
7
+ if (!rules?.enabled || !rules.corsHandler) return;
8
+ const { allowHeaders, credentials, exposeHeaders, maxAge, methods, origin, preflight } = rules.corsHandler;
9
+ handleCors(event, {
10
+ allowHeaders,
11
+ credentials,
12
+ exposeHeaders,
13
+ maxAge,
14
+ methods,
15
+ origin: [origin].flat(),
16
+ preflight
17
+ });
18
+ });
19
+ //#endregion
20
+ export { corsHandler as default };
@@ -0,0 +1,5 @@
1
+ import { Middleware } from "nitro/h3";
2
+ //#region src/runtime/server/middleware/csrf.d.ts
3
+ declare const csrf: Middleware;
4
+ //#endregion
5
+ export { csrf as default };
@@ -0,0 +1,28 @@
1
+ import { CSRF_UNSAFE_METHODS } from "../../constants.js";
2
+ import { checkIsCsrfTokenValid } from "../../services/checkIsCsrfTokenValid.js";
3
+ import { generateRandomToken } from "../../services/generateRandomToken.js";
4
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
5
+ import { defineMiddleware } from "nitro";
6
+ import { HTTPError, getCookie, setCookie } from "nitro/h3";
7
+ //#region src/runtime/server/middleware/csrf.ts
8
+ const csrf = defineMiddleware((event) => {
9
+ const rules = getSecurityRules(event);
10
+ if (!rules?.enabled || !rules.csrf) return;
11
+ const { cookieName, headerName } = rules.csrf;
12
+ const cookieToken = getCookie(event, cookieName);
13
+ if (!CSRF_UNSAFE_METHODS.includes(event.req.method)) {
14
+ if (cookieToken === void 0) setCookie(event, cookieName, generateRandomToken(32), {
15
+ httpOnly: false,
16
+ path: "/",
17
+ sameSite: "strict",
18
+ secure: event.url.protocol === "https:"
19
+ });
20
+ return;
21
+ }
22
+ if (!checkIsCsrfTokenValid(cookieToken, event.req.headers.get(headerName))) throw new HTTPError({
23
+ status: 403,
24
+ statusText: "Forbidden"
25
+ });
26
+ });
27
+ //#endregion
28
+ export { csrf as default };
@@ -0,0 +1,5 @@
1
+ import { Middleware } from "nitro/h3";
2
+ //#region src/runtime/server/middleware/rateLimiter.d.ts
3
+ declare const rateLimiter: Middleware;
4
+ //#endregion
5
+ export { rateLimiter as default };
@@ -0,0 +1,22 @@
1
+ import { MAX_RATE_LIMIT_ENTRIES } from "../../constants.js";
2
+ import { createInMemoryRateLimiterStore } from "../../services/createInMemoryRateLimiterStore.js";
3
+ import { getRetryAfterSeconds } from "../../services/getRetryAfterSeconds.js";
4
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
5
+ import { defineMiddleware } from "nitro";
6
+ import { HTTPError, getRequestIP } from "nitro/h3";
7
+ //#region src/runtime/server/middleware/rateLimiter.ts
8
+ const rateLimiterStore = createInMemoryRateLimiterStore(MAX_RATE_LIMIT_ENTRIES);
9
+ const rateLimiter = defineMiddleware(async (event) => {
10
+ const rules = getSecurityRules(event);
11
+ if (!rules?.enabled || !rules.rateLimiter) return;
12
+ const key = getRequestIP(event, { xForwardedFor: false }) ?? "unknown";
13
+ const retryAfterSeconds = await getRetryAfterSeconds(rateLimiterStore, key, rules.rateLimiter);
14
+ if (retryAfterSeconds === void 0) return;
15
+ event.res.headers.set("Retry-After", String(retryAfterSeconds));
16
+ throw new HTTPError({
17
+ status: 429,
18
+ statusText: "Too Many Requests"
19
+ });
20
+ });
21
+ //#endregion
22
+ export { rateLimiter as default };
@@ -0,0 +1,5 @@
1
+ import { Middleware } from "nitro/h3";
2
+ //#region src/runtime/server/middleware/requestSizeLimiter.d.ts
3
+ declare const requestSizeLimiter: Middleware;
4
+ //#endregion
5
+ export { requestSizeLimiter as default };
@@ -0,0 +1,17 @@
1
+ import { checkIsRequestTooLarge } from "../../services/checkIsRequestTooLarge.js";
2
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
3
+ import { defineMiddleware } from "nitro";
4
+ import { HTTPError } from "nitro/h3";
5
+ //#region src/runtime/server/middleware/requestSizeLimiter.ts
6
+ const requestSizeLimiter = defineMiddleware((event) => {
7
+ const rules = getSecurityRules(event);
8
+ if (!rules?.enabled || !rules.requestSizeLimiter) return;
9
+ const contentLength = event.req.headers.get("content-length");
10
+ const contentType = event.req.headers.get("content-type");
11
+ if (checkIsRequestTooLarge(event.req.method, contentLength, contentType, rules.requestSizeLimiter)) throw new HTTPError({
12
+ status: 413,
13
+ statusText: "Payload Too Large"
14
+ });
15
+ });
16
+ //#endregion
17
+ export { requestSizeLimiter as default };
@@ -0,0 +1,4 @@
1
+ import { BasicAuthOptions } from "../../models/BasicAuthOptions.js";
2
+ //#region src/runtime/services/checkIsBasicAuthorized.d.ts
3
+ export declare const checkIsBasicAuthorized: (authorization: null | string, { name, pass }: BasicAuthOptions) => boolean;
4
+ //#endregion
@@ -0,0 +1,12 @@
1
+ import { checkIsEqualInConstantTime } from "./checkIsEqualInConstantTime.js";
2
+ import { getBasicAuthCredentials } from "./getBasicAuthCredentials.js";
3
+ //#region src/runtime/services/checkIsBasicAuthorized.ts
4
+ const checkIsBasicAuthorized = (authorization, { name, pass }) => {
5
+ const credentials = getBasicAuthCredentials(authorization);
6
+ if (!credentials) return false;
7
+ const isNameEqual = checkIsEqualInConstantTime(credentials.name, name);
8
+ const isPassEqual = checkIsEqualInConstantTime(credentials.pass, pass);
9
+ return isNameEqual && isPassEqual;
10
+ };
11
+ //#endregion
12
+ export { checkIsBasicAuthorized };
@@ -0,0 +1,4 @@
1
+ import { BasicAuthOptions } from "../../models/BasicAuthOptions.js";
2
+ //#region src/runtime/services/checkIsChallenged.d.ts
3
+ export declare const checkIsChallenged: (path: string, { exclude, include }: BasicAuthOptions) => boolean;
4
+ //#endregion
@@ -0,0 +1,8 @@
1
+ //#region src/runtime/services/checkIsChallenged.ts
2
+ const checkIsChallenged = (path, { exclude = [], include = [] }) => {
3
+ const isIncluded = include.some((prefix) => path.startsWith(prefix));
4
+ const isExcluded = exclude.some((prefix) => path.startsWith(prefix));
5
+ return isIncluded || !isExcluded;
6
+ };
7
+ //#endregion
8
+ export { checkIsChallenged };
@@ -0,0 +1,3 @@
1
+ //#region src/runtime/services/checkIsCsrfTokenValid.d.ts
2
+ export declare const checkIsCsrfTokenValid: (cookieToken: string | undefined, headerToken: null | string) => boolean;
3
+ //#endregion
@@ -0,0 +1,8 @@
1
+ import { checkIsEqualInConstantTime } from "./checkIsEqualInConstantTime.js";
2
+ //#region src/runtime/services/checkIsCsrfTokenValid.ts
3
+ const checkIsCsrfTokenValid = (cookieToken, headerToken) => {
4
+ if (!cookieToken || !headerToken) return false;
5
+ return checkIsEqualInConstantTime(cookieToken, headerToken);
6
+ };
7
+ //#endregion
8
+ export { checkIsCsrfTokenValid };
@@ -0,0 +1,3 @@
1
+ //#region src/runtime/services/checkIsEqualInConstantTime.d.ts
2
+ export declare const checkIsEqualInConstantTime: (first: string, second: string) => boolean;
3
+ //#endregion
@@ -0,0 +1,11 @@
1
+ //#region src/runtime/services/checkIsEqualInConstantTime.ts
2
+ const checkIsEqualInConstantTime = (first, second) => {
3
+ const firstBytes = new TextEncoder().encode(first);
4
+ const secondBytes = new TextEncoder().encode(second);
5
+ if (firstBytes.length !== secondBytes.length) return false;
6
+ let difference = 0;
7
+ for (const [index, byte] of firstBytes.entries()) difference |= byte ^ (secondBytes[index] ?? 0);
8
+ return difference === 0;
9
+ };
10
+ //#endregion
11
+ export { checkIsEqualInConstantTime };
@@ -0,0 +1,3 @@
1
+ //#region src/runtime/services/checkIsMethodAllowed.d.ts
2
+ export declare const checkIsMethodAllowed: (method: string, methods: string[]) => boolean;
3
+ //#endregion
@@ -0,0 +1,4 @@
1
+ //#region src/runtime/services/checkIsMethodAllowed.ts
2
+ const checkIsMethodAllowed = (method, methods) => methods.includes(method);
3
+ //#endregion
4
+ export { checkIsMethodAllowed };
@@ -0,0 +1,3 @@
1
+ //#region src/runtime/services/checkIsRecord.d.ts
2
+ export declare const checkIsRecord: (value: unknown) => value is Record<string, unknown>;
3
+ //#endregion
@@ -0,0 +1,4 @@
1
+ //#region src/runtime/services/checkIsRecord.ts
2
+ const checkIsRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3
+ //#endregion
4
+ export { checkIsRecord };
@@ -0,0 +1,4 @@
1
+ import { RequestSizeLimiterOptions } from "../../models/RequestSizeLimiterOptions.js";
2
+ //#region src/runtime/services/checkIsRequestTooLarge.d.ts
3
+ export declare const checkIsRequestTooLarge: (method: string, contentLength: null | string, contentType: null | string, { maxRequestSizeInBytes, maxUploadFileRequestInBytes }: RequestSizeLimiterOptions) => boolean;
4
+ //#endregion
@@ -0,0 +1,9 @@
1
+ import { SIZE_LIMITED_METHODS } from "../constants.js";
2
+ //#region src/runtime/services/checkIsRequestTooLarge.ts
3
+ const checkIsRequestTooLarge = (method, contentLength, contentType, { maxRequestSizeInBytes, maxUploadFileRequestInBytes }) => {
4
+ if (!SIZE_LIMITED_METHODS.includes(method) || contentLength === null) return false;
5
+ const limit = contentType?.includes("multipart/form-data") ?? false ? maxUploadFileRequestInBytes : maxRequestSizeInBytes;
6
+ return Math.trunc(Number(contentLength)) >= limit;
7
+ };
8
+ //#endregion
9
+ export { checkIsRequestTooLarge };
@@ -0,0 +1,5 @@
1
+ import { SsgOptions } from "../../models/SsgOptions.js";
2
+ import { SecurityState } from "./getSecurityState.js";
3
+ //#region src/runtime/services/collectSsgHashes.d.ts
4
+ export declare const collectSsgHashes: (html: string, options: SsgOptions) => Promise<SecurityState["hashes"]>;
5
+ //#endregion
@@ -0,0 +1,41 @@
1
+ import { CSP_HASH_ALGORITHM, HTML_TAG_REGEX } from "../constants.js";
2
+ import { generateHash } from "./generateHash.js";
3
+ import { readHtmlTagAttribute } from "./readHtmlTagAttribute.js";
4
+ //#region src/runtime/services/collectSsgHashes.ts
5
+ const SCRIPT_PRELOAD_TYPES = /* @__PURE__ */ new Set([
6
+ "audioworklet",
7
+ "paintworklet",
8
+ "script",
9
+ "xslt"
10
+ ]);
11
+ const getSsgHashSource = async ([name, attributes, body], { hashScripts, hashStyles }) => {
12
+ const integrity = readHtmlTagAttribute(attributes, "integrity");
13
+ if (name === "script" && hashScripts) {
14
+ const source = readHtmlTagAttribute(attributes, "src");
15
+ if (source !== void 0 && integrity !== void 0) return ["script", `'${integrity}'`];
16
+ if (source === void 0 && body) return ["script", `'${await generateHash(body, CSP_HASH_ALGORITHM)}'`];
17
+ return;
18
+ }
19
+ if (name === "style" && hashStyles && body) return ["style", `'${await generateHash(body, CSP_HASH_ALGORITHM)}'`];
20
+ if (name !== "link" || integrity === void 0) return void 0;
21
+ const rel = readHtmlTagAttribute(attributes, "rel");
22
+ const as = readHtmlTagAttribute(attributes, "as") ?? "";
23
+ if (rel === "stylesheet" && hashStyles) return ["style", `'${integrity}'`];
24
+ if (hashScripts && (rel === "modulepreload" || rel === "preload" && SCRIPT_PRELOAD_TYPES.has(as))) return ["script", `'${integrity}'`];
25
+ };
26
+ const collectSsgHashes = async (html, options) => {
27
+ const elements = Array.from(html.matchAll(HTML_TAG_REGEX), ([, elementName, elementAttributes, , body, tagName, tagAttributes]) => [
28
+ (elementName ?? tagName ?? "").toLowerCase(),
29
+ elementAttributes ?? tagAttributes ?? "",
30
+ body
31
+ ]);
32
+ const hashSources = await Promise.all(elements.map((element) => getSsgHashSource(element, options)));
33
+ const hashes = {
34
+ script: [],
35
+ style: []
36
+ };
37
+ for (const hashSource of hashSources) if (hashSource) hashes[hashSource[0]].push(hashSource[1]);
38
+ return hashes;
39
+ };
40
+ //#endregion
41
+ export { collectSsgHashes };
@@ -0,0 +1,4 @@
1
+ import { RateLimiterStore } from "../../models/RateLimiterStore.js";
2
+ //#region src/runtime/services/createInMemoryRateLimiterStore.d.ts
3
+ export declare const createInMemoryRateLimiterStore: (maxEntries: number) => RateLimiterStore;
4
+ //#endregion
@@ -0,0 +1,22 @@
1
+ //#region src/runtime/services/createInMemoryRateLimiterStore.ts
2
+ const createInMemoryRateLimiterStore = (maxEntries) => {
3
+ const windows = /* @__PURE__ */ new Map();
4
+ return { increment: (key, intervalInMilliseconds) => {
5
+ const now = Date.now();
6
+ const current = windows.get(key);
7
+ const window = current && current.resetAt > now ? {
8
+ count: current.count + 1,
9
+ resetAt: current.resetAt
10
+ } : {
11
+ count: 1,
12
+ resetAt: now + intervalInMilliseconds
13
+ };
14
+ windows.delete(key);
15
+ windows.set(key, window);
16
+ const [oldestKey] = windows.keys();
17
+ if (windows.size > maxEntries && oldestKey !== void 0) windows.delete(oldestKey);
18
+ return Promise.resolve(window);
19
+ } };
20
+ };
21
+ //#endregion
22
+ export { createInMemoryRateLimiterStore };
@@ -0,0 +1,3 @@
1
+ //#region src/runtime/services/generateHash.d.ts
2
+ export declare const generateHash: (content: string | Uint8Array, hashAlgorithm: string) => Promise<string>;
3
+ //#endregion
@@ -0,0 +1,9 @@
1
+ //#region src/runtime/services/generateHash.ts
2
+ const generateHash = async (content, hashAlgorithm) => {
3
+ const bytes = typeof content === "string" ? new TextEncoder().encode(content) : new Uint8Array(content);
4
+ const hashBuffer = await globalThis.crypto.subtle.digest(hashAlgorithm, bytes);
5
+ const base64 = btoa(String.fromCodePoint(...new Uint8Array(hashBuffer)));
6
+ return `${hashAlgorithm.replace("-", "").toLowerCase()}-${base64}`;
7
+ };
8
+ //#endregion
9
+ export { generateHash };
@@ -0,0 +1,3 @@
1
+ //#region src/runtime/services/generateNonce.d.ts
2
+ export declare const generateNonce: () => string;
3
+ //#endregion
@@ -0,0 +1,6 @@
1
+ import "../constants.js";
2
+ import { generateRandomToken } from "./generateRandomToken.js";
3
+ //#region src/runtime/services/generateNonce.ts
4
+ const generateNonce = () => generateRandomToken(18);
5
+ //#endregion
6
+ export { generateNonce };
@@ -0,0 +1,3 @@
1
+ //#region src/runtime/services/generateRandomToken.d.ts
2
+ export declare const generateRandomToken: (byteLength: number) => string;
3
+ //#endregion
@@ -0,0 +1,8 @@
1
+ //#region src/runtime/services/generateRandomToken.ts
2
+ const generateRandomToken = (byteLength) => {
3
+ const bytes = new Uint8Array(byteLength);
4
+ globalThis.crypto.getRandomValues(bytes);
5
+ return btoa(String.fromCodePoint(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
6
+ };
7
+ //#endregion
8
+ export { generateRandomToken };
@@ -0,0 +1,6 @@
1
+ //#region src/runtime/services/getBasicAuthCredentials.d.ts
2
+ export declare const getBasicAuthCredentials: (authorization: null | string) => undefined | {
3
+ name: string;
4
+ pass: string;
5
+ };
6
+ //#endregion
@@ -0,0 +1,19 @@
1
+ import { BASIC_AUTHORIZATION_SCHEME } from "../constants.js";
2
+ //#region src/runtime/services/getBasicAuthCredentials.ts
3
+ const BASE64_REGEX = /^[\d+/A-Za-z]*={0,2}$/u;
4
+ const BASE64_QUANTUM_LENGTH = 4;
5
+ const checkIsBase64 = (value) => BASE64_REGEX.test(value) && (value.endsWith("=") ? value.length % BASE64_QUANTUM_LENGTH === 0 : value.length % BASE64_QUANTUM_LENGTH !== 1);
6
+ const getBasicAuthCredentials = (authorization) => {
7
+ if (!authorization?.startsWith(`Basic `)) return void 0;
8
+ const encoded = authorization.slice(BASIC_AUTHORIZATION_SCHEME.length + 1).trim();
9
+ if (!checkIsBase64(encoded)) return void 0;
10
+ const decoded = new TextDecoder().decode(Uint8Array.fromBase64(encoded));
11
+ const separatorIndex = decoded.indexOf(":");
12
+ if (separatorIndex === -1) return void 0;
13
+ return {
14
+ name: decoded.slice(0, separatorIndex),
15
+ pass: decoded.slice(separatorIndex + 1)
16
+ };
17
+ };
18
+ //#endregion
19
+ export { getBasicAuthCredentials };
@@ -0,0 +1,4 @@
1
+ import { BasicAuthOptions } from "../../models/BasicAuthOptions.js";
2
+ //#region src/runtime/services/getBasicAuthOptions.d.ts
3
+ export declare const getBasicAuthOptions: (privateRuntimeConfig: unknown) => BasicAuthOptions | undefined;
4
+ //#endregion