@sourceregistry/sveltekit-enhance 1.2.0 → 1.3.0

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 CHANGED
@@ -138,6 +138,39 @@ All helpers are available from `@sourceregistry/sveltekit-enhance` or `@sourcere
138
138
 
139
139
  ---
140
140
 
141
+ ### `CSRF`
142
+
143
+ Blocks cross-site form submissions on mutating methods (`POST`, `PUT`, `PATCH`, `DELETE`) with form content types. Checks the `Origin` header against the request origin. Absent `Origin` (server-side fetch, curl) is allowed through. Returns `403` — JSON body when `Accept: application/json`, SvelteKit `error()` otherwise.
144
+
145
+ ```ts
146
+ import { CSRF, CSRFChecker } from '@sourceregistry/sveltekit-enhance';
147
+
148
+ export const handle = enhance.handle(
149
+ myHandler,
150
+ CSRF.inspect(
151
+ CSRFChecker.list('/api/webhooks/stripe'), // bypass paths
152
+ myLogger, // optional, defaults to console
153
+ ),
154
+ );
155
+ ```
156
+
157
+ Built-in bypass checkers:
158
+
159
+ | Checker | Description |
160
+ |---------|-------------|
161
+ | `CSRFChecker.list(...paths)` | Exact pathname match |
162
+ | `CSRFChecker.regex(...patterns)` | RegExp match against pathname |
163
+
164
+ Custom checker — any `(input: EnhanceInput) => MaybePromise<boolean>`:
165
+ ```ts
166
+ // true = bypass CSRF check
167
+ CSRF.inspect((input) => input.url.pathname.startsWith('/api/public'))
168
+ ```
169
+
170
+ Returns `{ csrf_valid: true }` on pass. Locals set: none.
171
+
172
+ ---
173
+
141
174
  ### `Auth`
142
175
 
143
176
  Extracts and validates `Authorization: Bearer <token>` headers.
@@ -353,6 +386,7 @@ TraceOptions // { logger?: TraceLogger; record?: (entry) => any }
353
386
  RecordTraceMetricEntry // { method: string; path: string; status: number; durationMs: number }
354
387
  RequestTraceLocals // { trace?: { id: string; started_at: bigint } }
355
388
  RequestCorrelationLocals // { correlation_id?: string; request_started_at?: number }
389
+ CSRFChecker // { regex(...patterns): checker; list(...paths): checker }
356
390
  ```
357
391
 
358
392
  ---
@@ -0,0 +1,14 @@
1
+ import { type EnhanceFunction, type EnhanceInput } from "../index.js";
2
+ import type { MaybePromise } from "../index.js";
3
+ import type { Logger } from "./logger.js";
4
+ export declare const CSRF: {
5
+ inspect: (checker: (input: EnhanceInput) => MaybePromise<boolean>, logger?: Logger) => EnhanceFunction<"handle">;
6
+ };
7
+ export declare const CSRFChecker: {
8
+ regex: (...oneOf: RegExp[]) => (input: {
9
+ url: URL;
10
+ }) => boolean;
11
+ list: (...oneOf: string[]) => (input: {
12
+ url: URL;
13
+ }) => boolean;
14
+ };
@@ -0,0 +1,38 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import { error } from "../index.js";
3
+ function isContentType(request, ...types) {
4
+ const type = request.headers.get('content-type')?.split(';', 1)[0].trim() ?? '';
5
+ return types.includes(type.toLowerCase());
6
+ }
7
+ function isFormContentType(request) {
8
+ return isContentType(request, 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain');
9
+ }
10
+ const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
11
+ const SAFE_LOG_HEADERS = new Set(['content-type', 'origin', 'referer', 'user-agent', 'accept', 'host']);
12
+ export const CSRF = {
13
+ inspect: (checker, logger = console) => async (input) => {
14
+ const { request, url } = input;
15
+ const origin = request.headers.get('origin');
16
+ const forbidden = isFormContentType(request) &&
17
+ MUTATING_METHODS.has(request.method) &&
18
+ origin !== null &&
19
+ origin !== url.origin &&
20
+ !(await checker(input));
21
+ if (forbidden) {
22
+ const message = `Cross-site ${request.method} form submissions are forbidden`;
23
+ logger.warn('CSRF Violation detected', {
24
+ url: url.toString(),
25
+ headers: Object.fromEntries([...request.headers.entries()].filter(([k]) => SAFE_LOG_HEADERS.has(k.toLowerCase())))
26
+ });
27
+ if (request.headers.get('accept') === 'application/json') {
28
+ return json({ message }, { status: 403 });
29
+ }
30
+ return error(403, { message });
31
+ }
32
+ return { csrf_valid: true };
33
+ }
34
+ };
35
+ export const CSRFChecker = {
36
+ regex: (...oneOf) => (input) => oneOf.some((exp) => exp[Symbol.match](input.url.pathname)),
37
+ list: (...oneOf) => (input) => oneOf.includes(input.url.pathname)
38
+ };
@@ -4,3 +4,5 @@ export * from './featureflag.js';
4
4
  export * from './form.ts';
5
5
  export * from './request-correlation.ts';
6
6
  export * from './request-monitor.js';
7
+ export * from './CSRF.js';
8
+ export * from './logger.js';
@@ -4,3 +4,5 @@ export * from './featureflag.js';
4
4
  export * from "./form.js";
5
5
  export * from "./request-correlation.js";
6
6
  export * from './request-monitor.js';
7
+ export * from './CSRF.js';
8
+ export * from './logger.js';
@@ -0,0 +1,6 @@
1
+ export type Logger = {
2
+ debug: (...args: any[]) => any;
3
+ info: (...args: any[]) => any;
4
+ warn: (...args: any[]) => any;
5
+ error: (...args: any[]) => any;
6
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,5 @@
1
1
  import type { EnhanceFunction } from "../index.js";
2
+ import type { Logger } from "./logger.js";
2
3
  export type RequestTraceLocals = {
3
4
  trace?: {
4
5
  id: string;
@@ -11,14 +12,8 @@ export type RecordTraceMetricEntry = {
11
12
  status: number;
12
13
  durationMs: number;
13
14
  };
14
- export type TraceLogger = {
15
- debug: (...args: any[]) => any;
16
- info: (...args: any[]) => any;
17
- warn: (...args: any[]) => any;
18
- error: (...args: any[]) => any;
19
- };
20
15
  export type TraceOptions = {
21
- logger?: TraceLogger;
16
+ logger?: Logger;
22
17
  record?: (entry: RecordTraceMetricEntry) => any;
23
18
  };
24
19
  export declare const RequestMonitor: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sourceregistry/sveltekit-enhance",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Composable enhance and form utilities for SvelteKit actions, loads, methods, and hooks.",
5
5
  "author": "A.P.A. Slaa (a.p.a.slaa@projectsource.nl)",
6
6
  "scripts": {