@holo-js/security 0.3.19 → 0.3.21

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.
@@ -293,28 +293,6 @@ function getHeaderToken(request) {
293
293
  const { header } = getSecurityRuntime().config.csrf;
294
294
  return request.headers.get(header)?.trim() || void 0;
295
295
  }
296
- function getRequestOrigin(request) {
297
- const origin = request.headers.get("origin")?.trim();
298
- if (origin) {
299
- return origin;
300
- }
301
- const referer = request.headers.get("referer")?.trim();
302
- if (!referer) {
303
- return void 0;
304
- }
305
- try {
306
- return new URL(referer).origin;
307
- } catch {
308
- return void 0;
309
- }
310
- }
311
- function isSameOriginRequest(request) {
312
- const requestOrigin = getRequestOrigin(request);
313
- if (!requestOrigin) {
314
- return false;
315
- }
316
- return requestOrigin === new URL(request.url).origin;
317
- }
318
296
  async function readFormToken(request) {
319
297
  const { field: field2 } = getSecurityRuntime().config.csrf;
320
298
  try {
@@ -343,9 +321,6 @@ async function verifyRequest(request, options) {
343
321
  }
344
322
  return;
345
323
  }
346
- if (isSameOriginRequest(request)) {
347
- return;
348
- }
349
324
  const requestToken = await readFormToken(request);
350
325
  if (requestToken !== cookieToken) {
351
326
  throw new SecurityCsrfError();
@@ -432,7 +407,6 @@ var csrfInternals = {
432
407
  getCookieToken,
433
408
  getHeaderToken,
434
409
  isSecureRequest,
435
- isSameOriginRequest,
436
410
  readFormToken,
437
411
  isExcludedPath,
438
412
  isSafeMethod,
@@ -3,12 +3,12 @@ import {
3
3
  getSecurityRuntime,
4
4
  isSecureRequest,
5
5
  token
6
- } from "./chunk-G3HBF4Q5.mjs";
6
+ } from "./chunk-5SCCUNBO.mjs";
7
7
  import {
8
8
  SECURITY_CLIENT_CONFIG_COOKIE,
9
9
  createSecurityClientConfig,
10
10
  serializeSecurityClientConfig
11
- } from "./chunk-FUOZWKHK.mjs";
11
+ } from "./chunk-PC2YCWDH.mjs";
12
12
 
13
13
  // src/cookie-issuance.ts
14
14
  async function prepareCsrfCookies(request, getCookie) {
@@ -38,15 +38,15 @@ function createSecurityClientConfig(config) {
38
38
  return Object.freeze({
39
39
  csrf: Object.freeze({
40
40
  field: config.csrf.field,
41
- cookie: config.csrf.cookie
41
+ cookie: config.csrf.cookie,
42
+ header: config.csrf.header
42
43
  })
43
44
  });
44
45
  }
45
46
  function serializeSecurityClientConfig(config) {
46
47
  return JSON.stringify(config);
47
48
  }
48
- function readSecurityClientConfigFromCookies(cookieHeader) {
49
- const raw = parseCookieHeader(cookieHeader)[SECURITY_CLIENT_CONFIG_COOKIE];
49
+ function parseSecurityClientConfig(raw) {
50
50
  if (!raw) {
51
51
  return void 0;
52
52
  }
@@ -55,18 +55,31 @@ function readSecurityClientConfigFromCookies(cookieHeader) {
55
55
  if (!isSecurityClientConfig(parsed)) {
56
56
  return void 0;
57
57
  }
58
- return Object.freeze({
59
- csrf: Object.freeze({
60
- field: parsed.csrf.field,
61
- cookie: parsed.csrf.cookie
62
- })
63
- });
58
+ return parsed;
64
59
  } catch {
65
60
  return void 0;
66
61
  }
67
62
  }
63
+ function readSecurityClientConfigFromCookies(cookieHeader) {
64
+ const parsed = parseSecurityClientConfig(parseCookieHeader(cookieHeader)[SECURITY_CLIENT_CONFIG_COOKIE]);
65
+ return parsed ? Object.freeze({
66
+ csrf: Object.freeze({
67
+ field: parsed.csrf.field,
68
+ cookie: parsed.csrf.cookie,
69
+ ..."header" in parsed.csrf && typeof parsed.csrf.header === "string" ? { header: parsed.csrf.header } : {}
70
+ })
71
+ }) : void 0;
72
+ }
73
+ function getCsrfRequestHeaders(cookieHeader) {
74
+ const cookies = parseCookieHeader(cookieHeader);
75
+ const config = parseSecurityClientConfig(cookies[SECURITY_CLIENT_CONFIG_COOKIE]) ?? DEFAULT_SECURITY_CLIENT_CONFIG;
76
+ const token = cookies[config.csrf.cookie];
77
+ const header = "header" in config.csrf && typeof config.csrf.header === "string" ? config.csrf.header : "X-CSRF-TOKEN";
78
+ return token ? { [header]: token } : {};
79
+ }
68
80
  var securityClientConfigInternals = {
69
- parseCookieHeader
81
+ parseCookieHeader,
82
+ getCsrfRequestHeaders
70
83
  };
71
84
 
72
85
  export {
package/dist/client.d.ts CHANGED
@@ -8,6 +8,7 @@ declare function readSecurityClientConfigFromCookies(cookieHeader: string | null
8
8
  declare function getSecurityClientConfig(): SecurityClientConfig;
9
9
  declare const securityClientInternals: {
10
10
  parseCookieHeader: (header: string | null | undefined) => Readonly<Record<string, string>>;
11
+ getCsrfRequestHeaders: (cookieHeader: string | undefined) => Readonly<Record<string, string>>;
11
12
  getDefaultSecurityClientConfig: typeof getDefaultSecurityClientConfig;
12
13
  readSecurityClientConfigFromCookies: typeof readSecurityClientConfigFromCookies;
13
14
  };
package/dist/client.mjs CHANGED
@@ -2,12 +2,13 @@ import {
2
2
  getDefaultSecurityClientConfig,
3
3
  readSecurityClientConfigFromCookies,
4
4
  securityClientConfigInternals
5
- } from "./chunk-FUOZWKHK.mjs";
5
+ } from "./chunk-PC2YCWDH.mjs";
6
6
 
7
7
  // src/client.ts
8
8
  function getSecurityClientConfig() {
9
9
  const runtime = globalThis;
10
- return readSecurityClientConfigFromCookies(runtime.document?.cookie) ?? getDefaultSecurityClientConfig();
10
+ const config = readSecurityClientConfigFromCookies(runtime.document?.cookie) ?? getDefaultSecurityClientConfig();
11
+ return Object.freeze({ csrf: Object.freeze({ field: config.csrf.field, cookie: config.csrf.cookie }) });
11
12
  }
12
13
  var securityClientInternals = {
13
14
  getDefaultSecurityClientConfig,
package/dist/index.d.ts CHANGED
@@ -23,7 +23,6 @@ declare function decodeCsrfToken(token: string): {
23
23
  declare function isValidSignedCsrfToken(token: string): boolean;
24
24
  declare function getCookieToken(request: Request): string | undefined;
25
25
  declare function getHeaderToken(request: Request): string | undefined;
26
- declare function isSameOriginRequest(request: Request): boolean;
27
26
  declare function readFormToken(request: Request): Promise<string | undefined>;
28
27
  declare function token(request: Request): Promise<string>;
29
28
  declare function field(request: Request): Promise<SecurityCsrfField>;
@@ -45,7 +44,6 @@ declare const csrfInternals: {
45
44
  getCookieToken: typeof getCookieToken;
46
45
  getHeaderToken: typeof getHeaderToken;
47
46
  isSecureRequest: typeof isSecureRequest;
48
- isSameOriginRequest: typeof isSameOriginRequest;
49
47
  readFormToken: typeof readFormToken;
50
48
  isExcludedPath: typeof isExcludedPath;
51
49
  isSafeMethod: typeof isSafeMethod;
package/dist/index.mjs CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  token,
35
35
  verify,
36
36
  verifySignedToken
37
- } from "./chunk-G3HBF4Q5.mjs";
37
+ } from "./chunk-5SCCUNBO.mjs";
38
38
  import {
39
39
  SecurityCsrfError,
40
40
  SecurityRateLimitError,
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  prepareCsrfCookies
3
- } from "../chunk-5M3RHQIG.mjs";
3
+ } from "../chunk-F32JK536.mjs";
4
4
  import {
5
+ cors,
6
+ getSecurityRuntime,
5
7
  protect
6
- } from "../chunk-G3HBF4Q5.mjs";
7
- import "../chunk-FUOZWKHK.mjs";
8
+ } from "../chunk-5SCCUNBO.mjs";
9
+ import "../chunk-PC2YCWDH.mjs";
8
10
  import {
9
11
  SecurityCsrfError
10
12
  } from "../chunk-FQ4X7X6L.mjs";
@@ -38,8 +40,34 @@ async function issueCsrfCookie(request) {
38
40
  }
39
41
  return response;
40
42
  }
43
+ async function actionRequest(request) {
44
+ const { field, header } = getSecurityRuntime().config.csrf;
45
+ if (!request.headers.has("next-action") || request.headers.get(header)?.trim() || !request.headers.get("content-type")?.startsWith("multipart/form-data")) return request;
46
+ try {
47
+ const data = await request.clone().formData();
48
+ const root = data.get("0");
49
+ if (typeof root !== "string") return request;
50
+ const args = JSON.parse(root);
51
+ if (!Array.isArray(args)) return request;
52
+ for (const argument of args) {
53
+ if (typeof argument !== "string") continue;
54
+ const part = /^\$K([0-9a-f]+)$/.exec(argument)?.[1];
55
+ if (!part) continue;
56
+ const token = data.get(`_${Number.parseInt(part, 16)}_${field}`);
57
+ if (typeof token !== "string") continue;
58
+ const headers = new Headers(request.headers);
59
+ headers.set(header, token);
60
+ return new Request(request.clone(), { headers });
61
+ }
62
+ } catch {
63
+ return request;
64
+ }
65
+ return request;
66
+ }
41
67
  function csrfProtection() {
42
68
  return async (request) => {
69
+ const preflight = cors.preflight(request);
70
+ if (preflight) return preflight;
43
71
  if (request.method.trim().toUpperCase() === "TRACE") {
44
72
  return new Response("Method Not Allowed", {
45
73
  status: 405,
@@ -47,14 +75,18 @@ function csrfProtection() {
47
75
  });
48
76
  }
49
77
  try {
50
- await protect(request);
78
+ await protect(await actionRequest(request));
51
79
  } catch (error) {
52
80
  if (error instanceof SecurityCsrfError) {
53
- return createCsrfErrorResponse(error);
81
+ return cors.apply(request, createCsrfErrorResponse(error));
54
82
  }
55
83
  throw error;
56
84
  }
57
- return await issueCsrfCookie(request);
85
+ const response = await issueCsrfCookie(request);
86
+ if (response) return cors.apply(request, response);
87
+ if ([...cors.headers(request)].length === 0) return void 0;
88
+ const { NextResponse } = await import("next/server.js");
89
+ return cors.apply(request, NextResponse.next());
58
90
  };
59
91
  }
60
92
  var nextSecurityInternals = {
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  prepareCsrfCookies
3
- } from "../chunk-5M3RHQIG.mjs";
3
+ } from "../chunk-F32JK536.mjs";
4
4
  import {
5
+ cors,
5
6
  protect
6
- } from "../chunk-G3HBF4Q5.mjs";
7
- import "../chunk-FUOZWKHK.mjs";
7
+ } from "../chunk-5SCCUNBO.mjs";
8
+ import "../chunk-PC2YCWDH.mjs";
8
9
  import {
9
10
  SecurityCsrfError
10
11
  } from "../chunk-FQ4X7X6L.mjs";
@@ -19,11 +20,12 @@ import {
19
20
  getRequestHeaders,
20
21
  getRequestURL,
21
22
  readRawBody,
22
- setCookie
23
+ setCookie,
24
+ setResponseHeader
23
25
  } from "h3";
24
26
  function isSafeMethod(method) {
25
27
  const normalized = method.trim().toUpperCase();
26
- return normalized === "GET" || normalized === "HEAD";
28
+ return normalized === "GET" || normalized === "HEAD" || normalized === "OPTIONS";
27
29
  }
28
30
  function createHeaders(event) {
29
31
  const headers = new Headers();
@@ -67,6 +69,9 @@ function csrfProtection() {
67
69
  });
68
70
  }
69
71
  const request = await createRequest(event);
72
+ const preflight = cors.preflight(request);
73
+ if (preflight) return preflight;
74
+ cors.headers(request).forEach((value, name) => setResponseHeader(event, name, value));
70
75
  try {
71
76
  await protect(request);
72
77
  } catch (error) {
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  prepareCsrfCookies
3
- } from "../chunk-5M3RHQIG.mjs";
3
+ } from "../chunk-F32JK536.mjs";
4
4
  import {
5
+ cors,
5
6
  protect
6
- } from "../chunk-G3HBF4Q5.mjs";
7
- import "../chunk-FUOZWKHK.mjs";
7
+ } from "../chunk-5SCCUNBO.mjs";
8
+ import "../chunk-PC2YCWDH.mjs";
8
9
  import {
9
10
  SecurityCsrfError
10
11
  } from "../chunk-FQ4X7X6L.mjs";
@@ -27,6 +28,8 @@ function createCsrfErrorResponse(error) {
27
28
  }
28
29
  function csrfProtection() {
29
30
  return async ({ event, resolve }) => {
31
+ const preflight = cors.preflight(event.request);
32
+ if (preflight) return preflight;
30
33
  if (event.request.method.trim().toUpperCase() === "TRACE") {
31
34
  return new Response("Method Not Allowed", {
32
35
  status: 405,
@@ -37,12 +40,12 @@ function csrfProtection() {
37
40
  await protect(event.request);
38
41
  } catch (error) {
39
42
  if (error instanceof SecurityCsrfError) {
40
- return createCsrfErrorResponse(error);
43
+ return cors.apply(event.request, createCsrfErrorResponse(error));
41
44
  }
42
45
  throw error;
43
46
  }
44
47
  await issueCsrfCookie(event);
45
- return resolve(event);
48
+ return cors.apply(event.request, await resolve(event));
46
49
  };
47
50
  }
48
51
  var svelteKitSecurityInternals = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/security",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
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.19",
62
- "@holo-js/kernel": "^0.3.19"
61
+ "@holo-js/config": "^0.3.21",
62
+ "@holo-js/kernel": "^0.3.21"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "h3": "^1.15.11",