@holo-js/security 0.3.27 → 0.3.28

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.
@@ -10,6 +10,7 @@ import {
10
10
  } from "./chunk-WRMZRO77.mjs";
11
11
 
12
12
  // src/csrf.ts
13
+ import { resolveRequestUrl } from "@holo-js/kernel";
13
14
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
14
15
 
15
16
  // src/runtime.ts
@@ -50,9 +51,22 @@ var securityRuntimeInternals = {
50
51
  };
51
52
 
52
53
  // src/proxy.ts
53
- function shouldTrustProxyHeaders() {
54
- const trustedProxy = typeof process !== "undefined" ? process.env.HOLO_SECURITY_TRUST_PROXY?.trim().toLowerCase() : void 0;
55
- return trustedProxy === "1" || trustedProxy === "true" || trustedProxy === "yes" || trustedProxy === "on";
54
+ import { isIP } from "net";
55
+ import { requestUrlInternals } from "@holo-js/kernel";
56
+ function bareClientAddress(value) {
57
+ if (!value) return void 0;
58
+ if (isIP(value)) return value;
59
+ const bracketed = /^\[([^\]]+)\](?::\d+)?$/u.exec(value)?.[1];
60
+ if (bracketed && isIP(bracketed) === 6) return bracketed;
61
+ const ipv4 = /^([\d.]+):\d+$/u.exec(value)?.[1];
62
+ return ipv4 && isIP(ipv4) === 4 ? ipv4 : void 0;
63
+ }
64
+ function resolveTrustedClientIp(request) {
65
+ const trustedProxyHeaders = requestUrlInternals.resolveTrustedProxyHeaders();
66
+ if (trustedProxyHeaders === "forwarded") {
67
+ return bareClientAddress(requestUrlInternals.readForwardedParameter(request.headers, "for")) ?? "unknown";
68
+ }
69
+ return ip(request, trustedProxyHeaders === "x-forwarded");
56
70
  }
57
71
 
58
72
  // src/rate-limit.ts
@@ -93,7 +107,7 @@ async function defaultRateLimitKey(request) {
93
107
  if (typeof runtimeDefaultKey !== "undefined" && runtimeDefaultKey !== null) {
94
108
  return normalizeResolvedLimiterKey(runtimeDefaultKey, "Default rate limiter key resolver");
95
109
  }
96
- return `ip:${ip(request, shouldTrustProxyHeaders())}`;
110
+ return `ip:${resolveTrustedClientIp(request)}`;
97
111
  }
98
112
  async function resolveLimiterKey(name, limiter, options) {
99
113
  if (typeof options.key === "string" && options.key.length > 0) {
@@ -224,28 +238,8 @@ function isExcludedPath(request) {
224
238
  const pathname = new URL(request.url).pathname;
225
239
  return pathname === "/broadcasting/auth" || except.some((pattern) => matchesPathPattern(pathname, pattern));
226
240
  }
227
- function normalizeForwardedValue(value) {
228
- return value.trim().replace(/^"|"$/g, "").toLowerCase();
229
- }
230
- function getForwardedProto(request) {
231
- const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",", 1)[0]?.trim();
232
- if (forwardedProto) {
233
- return normalizeForwardedValue(forwardedProto);
234
- }
235
- const forwarded = request.headers.get("forwarded")?.split(",", 1)[0];
236
- if (!forwarded) {
237
- return void 0;
238
- }
239
- for (const segment of forwarded.split(";")) {
240
- const [name, value] = segment.split("=", 2);
241
- if (name?.trim().toLowerCase() === "proto" && value) {
242
- return normalizeForwardedValue(value);
243
- }
244
- }
245
- return void 0;
246
- }
247
241
  function isSecureRequest(request) {
248
- return new URL(request.url).protocol === "https:" || shouldTrustProxyHeaders() && getForwardedProto(request) === "https";
242
+ return resolveRequestUrl(request).protocol === "https:";
249
243
  }
250
244
  function createCsrfToken() {
251
245
  return randomBytes(32).toString("base64url");
@@ -403,7 +397,6 @@ var csrf = Object.freeze({
403
397
  var csrfInternals = {
404
398
  createCsrfToken,
405
399
  generatedTokenCache,
406
- getForwardedProto,
407
400
  getCookieToken,
408
401
  getHeaderToken,
409
402
  isSecureRequest,
@@ -3,12 +3,15 @@ import {
3
3
  getSecurityRuntime,
4
4
  isSecureRequest,
5
5
  token
6
- } from "./chunk-5SCCUNBO.mjs";
6
+ } from "./chunk-26IYURJG.mjs";
7
7
  import {
8
8
  SECURITY_CLIENT_CONFIG_COOKIE,
9
9
  createSecurityClientConfig,
10
10
  serializeSecurityClientConfig
11
11
  } from "./chunk-PC2YCWDH.mjs";
12
+ import {
13
+ SecurityCsrfError
14
+ } from "./chunk-FQ4X7X6L.mjs";
12
15
 
13
16
  // src/cookie-issuance.ts
14
17
  async function prepareCsrfCookies(request, getCookie) {
@@ -40,6 +43,22 @@ async function prepareCsrfCookies(request, getCookie) {
40
43
  return cookies;
41
44
  }
42
45
 
46
+ // src/rejection-response.ts
47
+ import { HoloRequestUrlError } from "@holo-js/kernel";
48
+ function isSecurityRejection(error) {
49
+ return error instanceof SecurityCsrfError || error instanceof HoloRequestUrlError;
50
+ }
51
+ function createSecurityRejectionResponse(error) {
52
+ return new Response(error.message, {
53
+ status: error.status,
54
+ headers: {
55
+ "content-type": "text/plain; charset=utf-8"
56
+ }
57
+ });
58
+ }
59
+
43
60
  export {
44
- prepareCsrfCookies
61
+ prepareCsrfCookies,
62
+ isSecurityRejection,
63
+ createSecurityRejectionResponse
45
64
  };
package/dist/index.d.ts CHANGED
@@ -11,7 +11,6 @@ declare function serializeCookie(name: string, value: string, options?: {
11
11
  declare function isSafeMethod(method: string): boolean;
12
12
  declare function matchesPathPattern$1(pathname: string, pattern: string): boolean;
13
13
  declare function isExcludedPath(request: Request): boolean;
14
- declare function getForwardedProto(request: Request): string | undefined;
15
14
  declare function isSecureRequest(request: Request): boolean;
16
15
  declare function createCsrfToken(): string;
17
16
  declare function getCsrfSigningKey(): string;
@@ -40,7 +39,6 @@ declare const csrf: Readonly<{
40
39
  declare const csrfInternals: {
41
40
  createCsrfToken: typeof createCsrfToken;
42
41
  generatedTokenCache: WeakMap<Request, string>;
43
- getForwardedProto: typeof getForwardedProto;
44
42
  getCookieToken: typeof getCookieToken;
45
43
  getHeaderToken: typeof getHeaderToken;
46
44
  isSecureRequest: typeof isSecureRequest;
package/dist/index.mjs CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  token,
35
35
  verify,
36
36
  verifySignedToken
37
- } from "./chunk-5SCCUNBO.mjs";
37
+ } from "./chunk-26IYURJG.mjs";
38
38
  import {
39
39
  SecurityCsrfError,
40
40
  SecurityRateLimitError,
@@ -1,26 +1,18 @@
1
1
  import {
2
+ createSecurityRejectionResponse,
3
+ isSecurityRejection,
2
4
  prepareCsrfCookies
3
- } from "../chunk-F32JK536.mjs";
5
+ } from "../chunk-PHPA3C6L.mjs";
4
6
  import {
5
7
  cors,
6
8
  getSecurityRuntime,
7
9
  protect
8
- } from "../chunk-5SCCUNBO.mjs";
10
+ } from "../chunk-26IYURJG.mjs";
9
11
  import "../chunk-PC2YCWDH.mjs";
10
- import {
11
- SecurityCsrfError
12
- } from "../chunk-FQ4X7X6L.mjs";
12
+ import "../chunk-FQ4X7X6L.mjs";
13
13
  import "../chunk-WRMZRO77.mjs";
14
14
 
15
15
  // src/next/server.ts
16
- function createCsrfErrorResponse(error) {
17
- return new Response(error.message, {
18
- status: error.status,
19
- headers: {
20
- "content-type": "text/plain; charset=utf-8"
21
- }
22
- });
23
- }
24
16
  function getRequestCookie(request, name) {
25
17
  const cookie = request.cookies?.get(name);
26
18
  if (typeof cookie === "string") {
@@ -74,15 +66,16 @@ function csrfProtection() {
74
66
  headers: { Allow: "GET, HEAD, OPTIONS, POST, PUT, PATCH, DELETE" }
75
67
  });
76
68
  }
69
+ let response;
77
70
  try {
78
71
  await protect(await actionRequest(request));
72
+ response = await issueCsrfCookie(request);
79
73
  } catch (error) {
80
- if (error instanceof SecurityCsrfError) {
81
- return cors.apply(request, createCsrfErrorResponse(error));
74
+ if (isSecurityRejection(error)) {
75
+ return cors.apply(request, createSecurityRejectionResponse(error));
82
76
  }
83
77
  throw error;
84
78
  }
85
- const response = await issueCsrfCookie(request);
86
79
  if (response) return cors.apply(request, response);
87
80
  if ([...cors.headers(request)].length === 0) return void 0;
88
81
  const { NextResponse } = await import("next/server.js");
@@ -1,14 +1,13 @@
1
1
  import {
2
+ isSecurityRejection,
2
3
  prepareCsrfCookies
3
- } from "../chunk-F32JK536.mjs";
4
+ } from "../chunk-PHPA3C6L.mjs";
4
5
  import {
5
6
  cors,
6
7
  protect
7
- } from "../chunk-5SCCUNBO.mjs";
8
+ } from "../chunk-26IYURJG.mjs";
8
9
  import "../chunk-PC2YCWDH.mjs";
9
- import {
10
- SecurityCsrfError
11
- } from "../chunk-FQ4X7X6L.mjs";
10
+ import "../chunk-FQ4X7X6L.mjs";
12
11
  import "../chunk-WRMZRO77.mjs";
13
12
 
14
13
  // src/nuxt/server.ts
@@ -79,8 +78,9 @@ function csrfProtection() {
79
78
  cors.headers(request).forEach((value, name) => setResponseHeader(event, name, value));
80
79
  try {
81
80
  await protect(request);
81
+ await issueCsrfCookie(event, request);
82
82
  } catch (error) {
83
- if (error instanceof SecurityCsrfError) {
83
+ if (isSecurityRejection(error)) {
84
84
  throw createError({
85
85
  statusCode: error.status,
86
86
  statusMessage: error.message,
@@ -89,7 +89,6 @@ function csrfProtection() {
89
89
  }
90
90
  throw error;
91
91
  }
92
- await issueCsrfCookie(event, request);
93
92
  return void 0;
94
93
  });
95
94
  }
@@ -1,14 +1,14 @@
1
1
  import {
2
+ createSecurityRejectionResponse,
3
+ isSecurityRejection,
2
4
  prepareCsrfCookies
3
- } from "../chunk-F32JK536.mjs";
5
+ } from "../chunk-PHPA3C6L.mjs";
4
6
  import {
5
7
  cors,
6
8
  protect
7
- } from "../chunk-5SCCUNBO.mjs";
9
+ } from "../chunk-26IYURJG.mjs";
8
10
  import "../chunk-PC2YCWDH.mjs";
9
- import {
10
- SecurityCsrfError
11
- } from "../chunk-FQ4X7X6L.mjs";
11
+ import "../chunk-FQ4X7X6L.mjs";
12
12
  import "../chunk-WRMZRO77.mjs";
13
13
 
14
14
  // src/sveltekit/server.ts
@@ -18,14 +18,6 @@ async function issueCsrfCookie(event) {
18
18
  event.cookies.set(cookie.name, cookie.value, cookie.options);
19
19
  }
20
20
  }
21
- function createCsrfErrorResponse(error) {
22
- return new Response(error.message, {
23
- status: error.status,
24
- headers: {
25
- "content-type": "text/plain; charset=utf-8"
26
- }
27
- });
28
- }
29
21
  function csrfProtection() {
30
22
  return async ({ event, resolve }) => {
31
23
  const preflight = cors.preflight(event.request);
@@ -38,13 +30,13 @@ function csrfProtection() {
38
30
  }
39
31
  try {
40
32
  await protect(event.request);
33
+ await issueCsrfCookie(event);
41
34
  } catch (error) {
42
- if (error instanceof SecurityCsrfError) {
43
- return cors.apply(event.request, createCsrfErrorResponse(error));
35
+ if (isSecurityRejection(error)) {
36
+ return cors.apply(event.request, createSecurityRejectionResponse(error));
44
37
  }
45
38
  throw error;
46
39
  }
47
- await issueCsrfCookie(event);
48
40
  return cors.apply(event.request, await resolve(event));
49
41
  };
50
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/security",
3
- "version": "0.3.27",
3
+ "version": "0.3.28",
4
4
  "description": "Holo-JS Framework - CSRF and rate-limit contracts, config, and runtime seams",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -58,8 +58,8 @@
58
58
  "test": "vitest --run --coverage"
59
59
  },
60
60
  "dependencies": {
61
- "@holo-js/config": "^0.3.27",
62
- "@holo-js/kernel": "^0.3.27"
61
+ "@holo-js/config": "^0.3.28",
62
+ "@holo-js/kernel": "^0.3.28"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "h3": "^1.15.11",