@holo-js/security 0.1.4 → 0.1.5

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.
@@ -0,0 +1,16 @@
1
+ type NextCsrfRequest = Request & {
2
+ readonly nextUrl?: URL;
3
+ readonly cookies?: {
4
+ get(name: string): string | {
5
+ readonly value?: string;
6
+ } | undefined;
7
+ };
8
+ };
9
+ type NextCsrfMiddleware = (request: NextCsrfRequest) => Response | undefined | Promise<Response | undefined>;
10
+ declare function issueCsrfCookie(request: NextCsrfRequest): Promise<Response | undefined>;
11
+ declare function csrfProtection(): NextCsrfMiddleware;
12
+ declare const nextSecurityInternals: {
13
+ issueCsrfCookie: typeof issueCsrfCookie;
14
+ };
15
+
16
+ export { type NextCsrfMiddleware, csrfProtection, nextSecurityInternals };
@@ -0,0 +1,84 @@
1
+ import {
2
+ csrf,
3
+ csrfInternals,
4
+ getSecurityRuntime,
5
+ isSecureRequest,
6
+ protect
7
+ } from "../chunk-Q3A7RJ67.mjs";
8
+ import {
9
+ SECURITY_CLIENT_CONFIG_COOKIE,
10
+ createSecurityClientConfig,
11
+ serializeSecurityClientConfig
12
+ } from "../chunk-FUOZWKHK.mjs";
13
+ import {
14
+ SecurityCsrfError
15
+ } from "../chunk-EWQKJSFA.mjs";
16
+
17
+ // src/next/server.ts
18
+ function isSafeMethod(method) {
19
+ const normalized = method.trim().toUpperCase();
20
+ return normalized === "GET" || normalized === "HEAD";
21
+ }
22
+ function createCsrfErrorResponse(error) {
23
+ return new Response(error.message, {
24
+ status: error.status,
25
+ headers: {
26
+ "content-type": "text/plain; charset=utf-8"
27
+ }
28
+ });
29
+ }
30
+ function getRequestCookie(request, name) {
31
+ const cookie = request.cookies?.get(name);
32
+ if (typeof cookie === "string") {
33
+ return cookie;
34
+ }
35
+ return typeof cookie?.value === "string" ? cookie.value : void 0;
36
+ }
37
+ async function issueCsrfCookie(request) {
38
+ const { config } = getSecurityRuntime();
39
+ if (!config.csrf.enabled || !isSafeMethod(request.method)) {
40
+ return void 0;
41
+ }
42
+ const existingCsrfToken = getRequestCookie(request, config.csrf.cookie);
43
+ const shouldIssueCsrfToken = !existingCsrfToken || !csrfInternals.isValidSignedCsrfToken(existingCsrfToken);
44
+ const clientConfig = serializeSecurityClientConfig(createSecurityClientConfig(config));
45
+ const shouldIssueClientConfig = getRequestCookie(request, SECURITY_CLIENT_CONFIG_COOKIE) !== clientConfig;
46
+ if (!shouldIssueCsrfToken && !shouldIssueClientConfig) {
47
+ return void 0;
48
+ }
49
+ const { NextResponse } = await import("next/server");
50
+ const response = NextResponse.next();
51
+ const cookieOptions = {
52
+ httpOnly: false,
53
+ path: "/",
54
+ sameSite: "lax",
55
+ secure: isSecureRequest(request)
56
+ };
57
+ if (shouldIssueCsrfToken) {
58
+ response.cookies.set(config.csrf.cookie, await csrf.token(request), cookieOptions);
59
+ }
60
+ if (shouldIssueClientConfig) {
61
+ response.cookies.set(SECURITY_CLIENT_CONFIG_COOKIE, clientConfig, cookieOptions);
62
+ }
63
+ return response;
64
+ }
65
+ function csrfProtection() {
66
+ return async (request) => {
67
+ try {
68
+ await protect(request);
69
+ } catch (error) {
70
+ if (error instanceof SecurityCsrfError) {
71
+ return createCsrfErrorResponse(error);
72
+ }
73
+ throw error;
74
+ }
75
+ return await issueCsrfCookie(request);
76
+ };
77
+ }
78
+ var nextSecurityInternals = {
79
+ issueCsrfCookie
80
+ };
81
+ export {
82
+ csrfProtection,
83
+ nextSecurityInternals
84
+ };
@@ -0,0 +1,11 @@
1
+ import { defineEventHandler, H3Event } from 'h3';
2
+
3
+ declare function createRequest(event: H3Event): Promise<Request>;
4
+ declare function issueCsrfCookie(event: H3Event, request: Request): Promise<void>;
5
+ declare function csrfProtection(): ReturnType<typeof defineEventHandler>;
6
+ declare const nuxtSecurityInternals: {
7
+ createRequest: typeof createRequest;
8
+ issueCsrfCookie: typeof issueCsrfCookie;
9
+ };
10
+
11
+ export { csrfProtection, nuxtSecurityInternals };
@@ -0,0 +1,109 @@
1
+ import {
2
+ csrf,
3
+ csrfInternals,
4
+ getSecurityRuntime,
5
+ isSecureRequest,
6
+ protect
7
+ } from "../chunk-Q3A7RJ67.mjs";
8
+ import {
9
+ SECURITY_CLIENT_CONFIG_COOKIE,
10
+ createSecurityClientConfig,
11
+ serializeSecurityClientConfig
12
+ } from "../chunk-FUOZWKHK.mjs";
13
+ import {
14
+ SecurityCsrfError
15
+ } from "../chunk-EWQKJSFA.mjs";
16
+
17
+ // src/nuxt/server.ts
18
+ import {
19
+ createError,
20
+ defineEventHandler,
21
+ getCookie,
22
+ getMethod,
23
+ getRequestHeaders,
24
+ getRequestURL,
25
+ readRawBody,
26
+ setCookie
27
+ } from "h3";
28
+ function isSafeMethod(method) {
29
+ const normalized = method.trim().toUpperCase();
30
+ return normalized === "GET" || normalized === "HEAD";
31
+ }
32
+ function createHeaders(event) {
33
+ const headers = new Headers();
34
+ for (const [name, value] of Object.entries(getRequestHeaders(event))) {
35
+ if (typeof value === "string") {
36
+ headers.append(name, value);
37
+ }
38
+ }
39
+ return headers;
40
+ }
41
+ function createRequestBody(body) {
42
+ if (!body) {
43
+ return void 0;
44
+ }
45
+ const copy = new Uint8Array(body.byteLength);
46
+ copy.set(body);
47
+ return copy.buffer;
48
+ }
49
+ async function createRequest(event) {
50
+ const method = getMethod(event);
51
+ const headers = createHeaders(event);
52
+ const body = isSafeMethod(method) ? void 0 : await readRawBody(event, false);
53
+ return new Request(getRequestURL(event), {
54
+ method,
55
+ headers,
56
+ body: createRequestBody(body)
57
+ });
58
+ }
59
+ async function issueCsrfCookie(event, request) {
60
+ const { config } = getSecurityRuntime();
61
+ if (!config.csrf.enabled || !isSafeMethod(request.method)) {
62
+ return;
63
+ }
64
+ const existingCsrfToken = getCookie(event, config.csrf.cookie);
65
+ const shouldIssueCsrfToken = !existingCsrfToken || !csrfInternals.isValidSignedCsrfToken(existingCsrfToken);
66
+ const clientConfig = serializeSecurityClientConfig(createSecurityClientConfig(config));
67
+ const shouldIssueClientConfig = getCookie(event, SECURITY_CLIENT_CONFIG_COOKIE) !== clientConfig;
68
+ if (!shouldIssueCsrfToken && !shouldIssueClientConfig) {
69
+ return;
70
+ }
71
+ const cookieOptions = {
72
+ httpOnly: false,
73
+ path: "/",
74
+ sameSite: "lax",
75
+ secure: isSecureRequest(request)
76
+ };
77
+ if (shouldIssueCsrfToken) {
78
+ setCookie(event, config.csrf.cookie, await csrf.token(request), cookieOptions);
79
+ }
80
+ if (shouldIssueClientConfig) {
81
+ setCookie(event, SECURITY_CLIENT_CONFIG_COOKIE, clientConfig, cookieOptions);
82
+ }
83
+ }
84
+ function csrfProtection() {
85
+ return defineEventHandler(async (event) => {
86
+ const request = await createRequest(event);
87
+ try {
88
+ await protect(request);
89
+ } catch (error) {
90
+ if (error instanceof SecurityCsrfError) {
91
+ throw createError({
92
+ statusCode: error.status,
93
+ statusMessage: error.message,
94
+ message: error.message
95
+ });
96
+ }
97
+ throw error;
98
+ }
99
+ await issueCsrfCookie(event, request);
100
+ });
101
+ }
102
+ var nuxtSecurityInternals = {
103
+ createRequest,
104
+ issueCsrfCookie
105
+ };
106
+ export {
107
+ csrfProtection,
108
+ nuxtSecurityInternals
109
+ };
@@ -0,0 +1,37 @@
1
+ type SvelteKitCookieOptions = {
2
+ path: string;
3
+ secure?: boolean;
4
+ httpOnly?: boolean;
5
+ sameSite?: 'lax' | 'strict' | 'none';
6
+ };
7
+ type SvelteKitCsrfEvent = {
8
+ readonly url: URL;
9
+ readonly request: Request;
10
+ readonly cookies: {
11
+ get(name: string): string | undefined;
12
+ set(name: string, value: string, options: SvelteKitCookieOptions): void;
13
+ };
14
+ };
15
+ type SvelteKitResolveOptions = {
16
+ readonly transformPageChunk?: (input: {
17
+ readonly html: string;
18
+ readonly done: boolean;
19
+ }) => string | Promise<string>;
20
+ readonly filterSerializedResponseHeaders?: (name: string, value: string) => boolean;
21
+ readonly preload?: (input: {
22
+ readonly type: 'js' | 'css' | 'font' | 'asset';
23
+ readonly path: string;
24
+ }) => boolean;
25
+ };
26
+ type SvelteKitCsrfHandleInput<TEvent extends SvelteKitCsrfEvent = SvelteKitCsrfEvent> = {
27
+ readonly event: TEvent;
28
+ readonly resolve: (event: TEvent, options?: SvelteKitResolveOptions) => Response | Promise<Response>;
29
+ };
30
+ type SvelteKitCsrfHandle = <TEvent extends SvelteKitCsrfEvent>(input: SvelteKitCsrfHandleInput<TEvent>) => Response | Promise<Response>;
31
+ declare function issueCsrfCookie(event: SvelteKitCsrfEvent): Promise<void>;
32
+ declare function csrfProtection(): SvelteKitCsrfHandle;
33
+ declare const svelteKitSecurityInternals: {
34
+ issueCsrfCookie: typeof issueCsrfCookie;
35
+ };
36
+
37
+ export { type SvelteKitCsrfHandle, type SvelteKitCsrfHandleInput, csrfProtection, svelteKitSecurityInternals };
@@ -0,0 +1,68 @@
1
+ import {
2
+ csrf,
3
+ getSecurityRuntime,
4
+ isSecureRequest,
5
+ protect
6
+ } from "../chunk-Q3A7RJ67.mjs";
7
+ import {
8
+ SECURITY_CLIENT_CONFIG_COOKIE,
9
+ createSecurityClientConfig,
10
+ serializeSecurityClientConfig
11
+ } from "../chunk-FUOZWKHK.mjs";
12
+ import {
13
+ SecurityCsrfError
14
+ } from "../chunk-EWQKJSFA.mjs";
15
+
16
+ // src/sveltekit/server.ts
17
+ function isSafeMethod(method) {
18
+ const normalized = method.trim().toUpperCase();
19
+ return normalized === "GET" || normalized === "HEAD";
20
+ }
21
+ async function issueCsrfCookie(event) {
22
+ const runtime = getSecurityRuntime();
23
+ const { config } = runtime;
24
+ if (!config.csrf.enabled || !isSafeMethod(event.request.method)) {
25
+ return;
26
+ }
27
+ event.cookies.set(config.csrf.cookie, await csrf.token(event.request), {
28
+ httpOnly: false,
29
+ path: "/",
30
+ sameSite: "lax",
31
+ secure: isSecureRequest(event.request)
32
+ });
33
+ event.cookies.set(SECURITY_CLIENT_CONFIG_COOKIE, serializeSecurityClientConfig(createSecurityClientConfig(config)), {
34
+ httpOnly: false,
35
+ path: "/",
36
+ sameSite: "lax",
37
+ secure: isSecureRequest(event.request)
38
+ });
39
+ }
40
+ function createCsrfErrorResponse(error) {
41
+ return new Response(error.message, {
42
+ status: error.status,
43
+ headers: {
44
+ "content-type": "text/plain; charset=utf-8"
45
+ }
46
+ });
47
+ }
48
+ function csrfProtection() {
49
+ return async ({ event, resolve }) => {
50
+ try {
51
+ await protect(event.request);
52
+ } catch (error) {
53
+ if (error instanceof SecurityCsrfError) {
54
+ return createCsrfErrorResponse(error);
55
+ }
56
+ throw error;
57
+ }
58
+ await issueCsrfCookie(event);
59
+ return resolve(event);
60
+ };
61
+ }
62
+ var svelteKitSecurityInternals = {
63
+ issueCsrfCookie
64
+ };
65
+ export {
66
+ csrfProtection,
67
+ svelteKitSecurityInternals
68
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/security",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Holo-JS Framework - CSRF and rate-limit contracts, config, and runtime seams",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,6 +20,21 @@
20
20
  "import": "./dist/contracts.mjs",
21
21
  "default": "./dist/contracts.mjs"
22
22
  },
23
+ "./next/server": {
24
+ "types": "./dist/next/server.d.ts",
25
+ "import": "./dist/next/server.mjs",
26
+ "default": "./dist/next/server.mjs"
27
+ },
28
+ "./nuxt/server": {
29
+ "types": "./dist/nuxt/server.d.ts",
30
+ "import": "./dist/nuxt/server.mjs",
31
+ "default": "./dist/nuxt/server.mjs"
32
+ },
33
+ "./sveltekit/server": {
34
+ "types": "./dist/sveltekit/server.d.ts",
35
+ "import": "./dist/sveltekit/server.mjs",
36
+ "default": "./dist/sveltekit/server.mjs"
37
+ },
23
38
  "./drivers/redis-adapter": {
24
39
  "types": "./dist/drivers/redis-adapter.d.ts",
25
40
  "import": "./dist/drivers/redis-adapter.mjs",
@@ -35,24 +50,34 @@
35
50
  "build": "tsup",
36
51
  "stub": "tsup",
37
52
  "typecheck": "tsc -p tsconfig.json --noEmit",
38
- "test": "vitest --run"
53
+ "test": "vitest --run --coverage"
39
54
  },
40
55
  "dependencies": {
41
- "@holo-js/config": "^0.1.4"
56
+ "@holo-js/config": "^0.1.5"
42
57
  },
43
58
  "peerDependencies": {
44
- "ioredis": "catalog:"
59
+ "h3": "^1.15.11",
60
+ "next": "^16.2.4",
61
+ "ioredis": "^5.4.2"
45
62
  },
46
63
  "peerDependenciesMeta": {
64
+ "h3": {
65
+ "optional": true
66
+ },
67
+ "next": {
68
+ "optional": true
69
+ },
47
70
  "ioredis": {
48
71
  "optional": true
49
72
  }
50
73
  },
51
74
  "devDependencies": {
52
75
  "@types/node": "^22.10.2",
53
- "ioredis": "catalog:",
76
+ "h3": "^1.15.11",
77
+ "ioredis": "^5.4.2",
78
+ "next": "^16.2.4",
54
79
  "tsup": "^8.3.5",
55
80
  "typescript": "^5.7.2",
56
- "vitest": "^2.1.8"
81
+ "vitest": "^4.1.5"
57
82
  }
58
83
  }