skyguard-js 1.2.1 → 1.2.3

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 (64) hide show
  1. package/README.md +11 -680
  2. package/dist/app.d.ts +14 -9
  3. package/dist/app.js +37 -30
  4. package/dist/http/context.d.ts +115 -0
  5. package/dist/http/context.js +147 -0
  6. package/dist/http/httpAdapter.d.ts +4 -4
  7. package/dist/http/index.d.ts +3 -1
  8. package/dist/http/index.js +5 -1
  9. package/dist/http/logger.d.ts +9 -2
  10. package/dist/http/logger.js +49 -1
  11. package/dist/http/nodeNativeHttp.d.ts +4 -4
  12. package/dist/http/nodeNativeHttp.js +11 -4
  13. package/dist/http/request.d.ts +4 -0
  14. package/dist/http/request.js +8 -0
  15. package/dist/http/response.d.ts +21 -2
  16. package/dist/http/response.js +30 -2
  17. package/dist/http/webHttp.d.ts +19 -0
  18. package/dist/http/webHttp.js +95 -0
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +2 -2
  21. package/dist/middlewares/cors.d.ts +10 -4
  22. package/dist/middlewares/cors.js +35 -18
  23. package/dist/middlewares/csrf.js +33 -33
  24. package/dist/middlewares/index.d.ts +1 -1
  25. package/dist/middlewares/rateLimiter.d.ts +58 -6
  26. package/dist/middlewares/rateLimiter.js +149 -40
  27. package/dist/middlewares/session.js +4 -4
  28. package/dist/parsers/contentParserManager.d.ts +4 -2
  29. package/dist/parsers/contentParserManager.js +22 -3
  30. package/dist/routing/routeResolveFunc.d.ts +10 -0
  31. package/dist/routing/routeResolveFunc.js +21 -0
  32. package/dist/routing/router.d.ts +16 -10
  33. package/dist/routing/router.js +32 -25
  34. package/dist/routing/routerGroup.d.ts +10 -5
  35. package/dist/routing/routerGroup.js +11 -10
  36. package/dist/server/bunRuntimeServer.d.ts +7 -0
  37. package/dist/server/bunRuntimeServer.js +28 -0
  38. package/dist/server/createRuntimeServer.d.ts +4 -0
  39. package/dist/server/createRuntimeServer.js +23 -0
  40. package/dist/server/denoRuntimeServer.d.ts +7 -0
  41. package/dist/server/denoRuntimeServer.js +27 -0
  42. package/dist/server/index.d.ts +7 -0
  43. package/dist/server/index.js +19 -0
  44. package/dist/server/modulePath.d.ts +6 -0
  45. package/dist/server/modulePath.js +18 -0
  46. package/dist/server/nodeRuntimeServer.d.ts +7 -0
  47. package/dist/server/nodeRuntimeServer.js +21 -0
  48. package/dist/server/runtimeDetector.d.ts +4 -0
  49. package/dist/server/runtimeDetector.js +15 -0
  50. package/dist/server/types.d.ts +11 -0
  51. package/dist/server/types.js +2 -0
  52. package/dist/sessions/index.d.ts +2 -2
  53. package/dist/storage/storage.d.ts +3 -3
  54. package/dist/storage/storage.js +7 -7
  55. package/dist/storage/types.d.ts +5 -5
  56. package/dist/storage/uploader.d.ts +9 -9
  57. package/dist/storage/uploader.js +62 -62
  58. package/dist/types/index.d.ts +11 -10
  59. package/dist/validators/index.d.ts +2 -2
  60. package/dist/validators/rules/index.d.ts +5 -5
  61. package/dist/validators/validationSchema.js +8 -8
  62. package/package.json +2 -2
  63. package/dist/helpers/http.d.ts +0 -95
  64. package/dist/helpers/http.js +0 -112
@@ -29,6 +29,8 @@ export declare class Request {
29
29
  private _params;
30
30
  /** Session associated with the request */
31
31
  private _session;
32
+ /** Network peer address (adapter-provided). */
33
+ private _remoteAddress?;
32
34
  /**
33
35
  * Per-request shared state container.
34
36
  *
@@ -52,6 +54,8 @@ export declare class Request {
52
54
  setBody(body: Record<string, any>): void;
53
55
  get session(): Session;
54
56
  setSession(session: Session): void;
57
+ get remoteAddress(): string | undefined;
58
+ setRemoteAddress(remoteAddress: string): void;
55
59
  /**
56
60
  * Returns all request cookies as a key-value object.
57
61
  */
@@ -29,6 +29,8 @@ class Request {
29
29
  _params = Object.create(null);
30
30
  /** Session associated with the request */
31
31
  _session;
32
+ /** Network peer address (adapter-provided). */
33
+ _remoteAddress;
32
34
  /**
33
35
  * Per-request shared state container.
34
36
  *
@@ -80,6 +82,12 @@ class Request {
80
82
  setSession(session) {
81
83
  this._session = session;
82
84
  }
85
+ get remoteAddress() {
86
+ return this._remoteAddress;
87
+ }
88
+ setRemoteAddress(remoteAddress) {
89
+ this._remoteAddress = remoteAddress;
90
+ }
83
91
  /**
84
92
  * Returns all request cookies as a key-value object.
85
93
  */
@@ -1,5 +1,6 @@
1
1
  import { type CookieOptions } from "../sessions/cookies";
2
2
  import { IncomingHttpHeaders } from "node:http";
3
+ import { Readable } from "node:stream";
3
4
  /**
4
5
  * Represents an outgoing response sent to the client.
5
6
  *
@@ -56,8 +57,18 @@ export declare class Response {
56
57
  * .setContent("<h1>Hello</h1>");
57
58
  */
58
59
  setContentType(value: string): this;
59
- get content(): string | Buffer | null;
60
- setContent(content: string | Buffer): this;
60
+ get content(): string | Buffer | Readable | null;
61
+ setContent(content: string | Buffer | Readable): this;
62
+ /**
63
+ * Sets a readable stream as the response body.
64
+ *
65
+ * Use this when you need to send large payloads in chunks without
66
+ * buffering the full content in memory first.
67
+ *
68
+ * @param stream - Node.js readable stream
69
+ * @returns The current {@link Response} instance
70
+ */
71
+ stream(stream: Readable): this;
61
72
  /**
62
73
  * Prepares the response before being sent to the client.
63
74
  *
@@ -97,6 +108,14 @@ export declare class Response {
97
108
  * });
98
109
  */
99
110
  static text(data: string): Response;
111
+ /**
112
+ * Creates a response whose body is streamed.
113
+ *
114
+ * @param stream - Node.js readable stream
115
+ * @param headers - Optional headers to include in the response
116
+ * @returns A {@link Response} configured for streaming
117
+ */
118
+ static stream(stream: Readable, headers?: Record<string, string>): Response;
100
119
  /**
101
120
  * Creates an HTTP redirect response.
102
121
  *
@@ -11,6 +11,7 @@ const promises_1 = require("node:fs/promises");
11
11
  const node_path_1 = require("node:path");
12
12
  const mimeTypes_1 = require("../static/mimeTypes");
13
13
  const httpExceptions_1 = require("../exceptions/httpExceptions");
14
+ const node_stream_1 = require("node:stream");
14
15
  /**
15
16
  * Represents an outgoing response sent to the client.
16
17
  *
@@ -115,6 +116,18 @@ class Response {
115
116
  this._content = content;
116
117
  return this;
117
118
  }
119
+ /**
120
+ * Sets a readable stream as the response body.
121
+ *
122
+ * Use this when you need to send large payloads in chunks without
123
+ * buffering the full content in memory first.
124
+ *
125
+ * @param stream - Node.js readable stream
126
+ * @returns The current {@link Response} instance
127
+ */
128
+ stream(stream) {
129
+ return this.setContent(stream);
130
+ }
118
131
  /**
119
132
  * Prepares the response before being sent to the client.
120
133
  *
@@ -131,14 +144,16 @@ class Response {
131
144
  */
132
145
  prepare() {
133
146
  if (!this._headers["content-type"] && this._content) {
134
- if (Buffer.isBuffer(this._content)) {
147
+ if (Buffer.isBuffer(this._content) || this._content instanceof node_stream_1.Readable) {
135
148
  this._headers["content-type"] = "application/octet-stream";
136
149
  }
137
150
  else {
138
151
  this._headers["content-type"] = "text/plain";
139
152
  }
140
153
  }
141
- if (this._content && !this._headers["content-length"]) {
154
+ if (this._content &&
155
+ !(this._content instanceof node_stream_1.Readable) &&
156
+ !this._headers["content-length"]) {
142
157
  const length = Buffer.isBuffer(this._content)
143
158
  ? this._content.length
144
159
  : Buffer.byteLength(this._content, "utf-8");
@@ -175,6 +190,19 @@ class Response {
175
190
  static text(data) {
176
191
  return new this().setContentType("text/plain").setContent(data);
177
192
  }
193
+ /**
194
+ * Creates a response whose body is streamed.
195
+ *
196
+ * @param stream - Node.js readable stream
197
+ * @param headers - Optional headers to include in the response
198
+ * @returns A {@link Response} configured for streaming
199
+ */
200
+ static stream(stream, headers) {
201
+ const response = new this().stream(stream);
202
+ if (headers)
203
+ response.setHeaders(headers);
204
+ return response;
205
+ }
178
206
  /**
179
207
  * Creates an HTTP redirect response.
180
208
  *
@@ -0,0 +1,19 @@
1
+ import type { HttpAdapter } from "./httpAdapter";
2
+ import { Response } from "./response";
3
+ import { Context } from "./context";
4
+ import { type LoggerOptions } from "./logger";
5
+ type WebRequest = globalThis.Request;
6
+ type WebResponse = globalThis.Response;
7
+ export declare class WebHttpAdapter implements HttpAdapter {
8
+ private readonly req;
9
+ private contentParser;
10
+ private logger;
11
+ private startTimeMs;
12
+ private runtimeResponse;
13
+ constructor(req: WebRequest, loggerOptions?: LoggerOptions);
14
+ getContext(): Promise<Context>;
15
+ sendResponse(response: Response): void;
16
+ toWebResponse(): WebResponse;
17
+ private buildHeaders;
18
+ }
19
+ export {};
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebHttpAdapter = void 0;
4
+ const httpMethods_1 = require("./httpMethods");
5
+ const request_1 = require("./request");
6
+ const context_1 = require("./context");
7
+ const contentParserManager_1 = require("../parsers/contentParserManager");
8
+ const httpExceptions_1 = require("../exceptions/httpExceptions");
9
+ const node_stream_1 = require("node:stream");
10
+ const logger_1 = require("./logger");
11
+ class WebHttpAdapter {
12
+ req;
13
+ contentParser;
14
+ logger;
15
+ startTimeMs;
16
+ runtimeResponse = null;
17
+ constructor(req, loggerOptions = {}) {
18
+ this.req = req;
19
+ this.contentParser = new contentParserManager_1.ContentParserManager();
20
+ this.logger = new logger_1.Logger(loggerOptions);
21
+ this.startTimeMs = performance.now();
22
+ }
23
+ async getContext() {
24
+ const url = new URL(this.req.url);
25
+ const request = new request_1.Request(url.pathname);
26
+ request.setMethod((this.req.method || "GET").toUpperCase() ||
27
+ httpMethods_1.HttpMethods.get);
28
+ request.setQuery(Object.fromEntries(url.searchParams.entries()));
29
+ request.setHeaders(this.buildHeaders());
30
+ if (request.method === httpMethods_1.HttpMethods.post ||
31
+ request.method === httpMethods_1.HttpMethods.put ||
32
+ request.method === httpMethods_1.HttpMethods.patch) {
33
+ const parsedData = await this.contentParser.parse(this.req);
34
+ request.setBody(parsedData);
35
+ }
36
+ return new context_1.Context(request);
37
+ }
38
+ sendResponse(response) {
39
+ response.prepare();
40
+ const headers = new Headers();
41
+ for (const [header, value] of Object.entries(response.headers)) {
42
+ if (Array.isArray(value)) {
43
+ for (const entry of value) {
44
+ headers.append(header, entry);
45
+ }
46
+ continue;
47
+ }
48
+ if (typeof value !== "undefined") {
49
+ headers.set(header, String(value));
50
+ }
51
+ }
52
+ if (!response.content) {
53
+ headers.delete("content-type");
54
+ this.runtimeResponse = new globalThis.Response(null, {
55
+ status: response.statusCode,
56
+ headers,
57
+ });
58
+ this.logger.logWeb(this.req, this.runtimeResponse, this.startTimeMs);
59
+ return;
60
+ }
61
+ if (response.content instanceof node_stream_1.Readable) {
62
+ throw new httpExceptions_1.NotImplementedError("Node.js Readable stream responses are not supported in this runtime");
63
+ }
64
+ const body = typeof response.content === "string"
65
+ ? response.content
66
+ : new Uint8Array(response.content);
67
+ this.runtimeResponse = new globalThis.Response(body, {
68
+ status: response.statusCode,
69
+ headers,
70
+ });
71
+ this.logger.logWeb(this.req, this.runtimeResponse, this.startTimeMs);
72
+ }
73
+ toWebResponse() {
74
+ return (this.runtimeResponse ||
75
+ new globalThis.Response("No response generated", {
76
+ status: 500,
77
+ }));
78
+ }
79
+ buildHeaders() {
80
+ const headers = Object.create(null);
81
+ for (const [key, value] of this.req.headers.entries()) {
82
+ if (typeof headers[key] === "undefined") {
83
+ headers[key] = value;
84
+ continue;
85
+ }
86
+ if (Array.isArray(headers[key])) {
87
+ headers[key] = [...headers[key], value];
88
+ continue;
89
+ }
90
+ headers[key] = [headers[key], value];
91
+ }
92
+ return headers;
93
+ }
94
+ }
95
+ exports.WebHttpAdapter = WebHttpAdapter;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createApp } from "./app";
2
- export { Request, Response } from "./http";
2
+ export { Request, Response, Context } from "./http";
3
3
  export type { Middleware, RouteHandler } from "./types";
4
4
  export { RouterGroup } from "./routing";
5
5
  export { FileSessionStorage, MemorySessionStorage, DatabaseSessionStorage, type SessionDatabaseAdapter, } from "./sessions";
@@ -9,5 +9,5 @@ export { StorageType } from "./storage/types";
9
9
  export { v, schema, validateRequest } from "./validators/validationSchema";
10
10
  export { Hasher, JWT } from "./crypto";
11
11
  export { sessions, cors, csrf, rateLimit } from "./middlewares";
12
+ export type { RateLimitStore, RateLimitStoreEntry } from "./middlewares";
12
13
  export * from "./exceptions/httpExceptions";
13
- export * from "./helpers/http";
package/dist/index.js CHANGED
@@ -14,12 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.rateLimit = exports.csrf = exports.cors = exports.sessions = exports.JWT = exports.Hasher = exports.validateRequest = exports.schema = exports.v = exports.StorageType = exports.createUploader = exports.HttpMethods = exports.DatabaseSessionStorage = exports.MemorySessionStorage = exports.FileSessionStorage = exports.RouterGroup = exports.Response = exports.Request = exports.createApp = void 0;
17
+ exports.rateLimit = exports.csrf = exports.cors = exports.sessions = exports.JWT = exports.Hasher = exports.validateRequest = exports.schema = exports.v = exports.StorageType = exports.createUploader = exports.HttpMethods = exports.DatabaseSessionStorage = exports.MemorySessionStorage = exports.FileSessionStorage = exports.RouterGroup = exports.Context = exports.Response = exports.Request = exports.createApp = void 0;
18
18
  var app_1 = require("./app");
19
19
  Object.defineProperty(exports, "createApp", { enumerable: true, get: function () { return app_1.createApp; } });
20
20
  var http_1 = require("./http");
21
21
  Object.defineProperty(exports, "Request", { enumerable: true, get: function () { return http_1.Request; } });
22
22
  Object.defineProperty(exports, "Response", { enumerable: true, get: function () { return http_1.Response; } });
23
+ Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return http_1.Context; } });
23
24
  var routing_1 = require("./routing");
24
25
  Object.defineProperty(exports, "RouterGroup", { enumerable: true, get: function () { return routing_1.RouterGroup; } });
25
26
  var sessions_1 = require("./sessions");
@@ -45,4 +46,3 @@ Object.defineProperty(exports, "cors", { enumerable: true, get: function () { re
45
46
  Object.defineProperty(exports, "csrf", { enumerable: true, get: function () { return middlewares_1.csrf; } });
46
47
  Object.defineProperty(exports, "rateLimit", { enumerable: true, get: function () { return middlewares_1.rateLimit; } });
47
48
  __exportStar(require("./exceptions/httpExceptions"), exports);
48
- __exportStar(require("./helpers/http"), exports);
@@ -1,22 +1,28 @@
1
- import { HttpMethods } from "../http";
1
+ import { Context, HttpMethods } from "../http";
2
2
  import type { Middleware } from "../types";
3
+ /**
4
+ * Callback contract for dynamic CORS allow/deny decisions.
5
+ *
6
+ * Return `true` to allow the current request origin, `false` to deny it.
7
+ */
8
+ type CorsOriginResolver = (origin: string | undefined, context: Context) => boolean | Promise<boolean>;
3
9
  /**
4
10
  * CORS middleware configuration options.
5
11
  *
6
12
  * These options map to standard CORS response headers and preflight behavior.
7
13
  */
8
- interface CorsOptions {
14
+ export interface CorsOptions {
9
15
  /**
10
16
  * Allowed origin(s) for cross-origin requests.
11
17
  *
12
18
  * - `"*"` allows any origin (public).
13
19
  * - A specific origin (string) restricts access to that origin.
14
20
  * - An array provides a whitelist.
15
- * - A function can implement custom allow/deny logic.
21
+ * - A function implements custom allow/deny logic.
16
22
  *
17
23
  * Affects: `Access-Control-Allow-Origin`.
18
24
  */
19
- origin?: string | string[] | ((origin: string | undefined) => string);
25
+ origin?: string | string[] | CorsOriginResolver;
20
26
  /**
21
27
  * HTTP methods allowed for cross-origin requests.
22
28
  *
@@ -2,33 +2,50 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cors = void 0;
4
4
  const http_1 = require("../http");
5
+ /**
6
+ * Normalizes a header that may be represented as `string[]`.
7
+ *
8
+ * @param value - Header value.
9
+ * @returns A single header value or `undefined`.
10
+ */
11
+ const getHeaderValue = (value) => Array.isArray(value) ? value[0] : value;
5
12
  /**
6
13
  * Resolves the effective `Access-Control-Allow-Origin` value for a request.
7
14
  *
8
- * @param requestOrigin - The `Origin` header value from the incoming request.
9
- * @returns The origin to allow:
10
- * - `"*"` for public access when credentials are disabled
11
- * - the request origin when it is allowed (whitelist or custom resolver)
12
- * - `null` if the origin is not allowed (no CORS headers will be applied)
15
+ * @param context - Current HTTP context.
16
+ * @param requestOrigin - Incoming `Origin` header value.
17
+ * @param config - CORS config.
18
+ * @returns Allowed origin value or `null` when denied/not configured.
13
19
  */
14
- function resolveOrigin(requestOrigin, config) {
20
+ async function resolveOrigin(context, requestOrigin, config) {
15
21
  if (!requestOrigin)
16
22
  return null;
17
23
  const cleanOrigin = (origin) => origin.endsWith("/") ? origin.slice(0, -1) : origin;
18
24
  if (typeof config.origin === "function") {
19
- return config.origin(requestOrigin) ? requestOrigin : null;
25
+ const result = config.origin(requestOrigin, context);
26
+ const isAllowed = result instanceof Promise ? await result : result;
27
+ return isAllowed ? requestOrigin : null;
28
+ }
29
+ if (!config.origin) {
30
+ return null;
31
+ }
32
+ if (typeof config.origin !== "string" && !Array.isArray(config.origin)) {
33
+ return null;
34
+ }
35
+ if (typeof config.origin === "string") {
36
+ if (config.origin === "*") {
37
+ return config.credentials ? requestOrigin : "*";
38
+ }
39
+ return cleanOrigin(config.origin) === cleanOrigin(requestOrigin)
40
+ ? requestOrigin
41
+ : null;
20
42
  }
21
43
  if (Array.isArray(config.origin)) {
22
44
  return config.origin.map(cleanOrigin).includes(cleanOrigin(requestOrigin))
23
45
  ? requestOrigin
24
46
  : null;
25
47
  }
26
- if (config.origin === "*") {
27
- return config.credentials ? requestOrigin : "*";
28
- }
29
- return cleanOrigin(config.origin) === cleanOrigin(requestOrigin)
30
- ? requestOrigin
31
- : null;
48
+ return null;
32
49
  }
33
50
  /**
34
51
  * Native CORS middleware, generates a CORS configuration for the server.
@@ -50,7 +67,7 @@ function resolveOrigin(requestOrigin, config) {
50
67
  */
51
68
  const cors = (options = {}) => {
52
69
  const config = {
53
- origin: options.origin ?? "*",
70
+ origin: options.origin,
54
71
  methods: options.methods ?? [
55
72
  http_1.HttpMethods.get,
56
73
  http_1.HttpMethods.post,
@@ -65,8 +82,8 @@ const cors = (options = {}) => {
65
82
  maxAge: options.maxAge ?? 86400,
66
83
  preflightContinue: options.preflightContinue ?? false,
67
84
  };
68
- return async (request, next) => {
69
- const allowedOrigin = resolveOrigin(request.headers.origin, config);
85
+ return async (context, next) => {
86
+ const allowedOrigin = await resolveOrigin(context, getHeaderValue(context.headers.origin), config);
70
87
  const corsHeaders = {};
71
88
  if (allowedOrigin) {
72
89
  corsHeaders["Access-Control-Allow-Origin"] = allowedOrigin;
@@ -78,7 +95,7 @@ const cors = (options = {}) => {
78
95
  if (config.exposedHeaders.length)
79
96
  corsHeaders["Access-Control-Expose-Headers"] =
80
97
  config.exposedHeaders.join(", ");
81
- if (request.method === http_1.HttpMethods.options) {
98
+ if (context.req.method === http_1.HttpMethods.options) {
82
99
  corsHeaders["Access-Control-Allow-Methods"] = config.methods.join(", ");
83
100
  corsHeaders["Access-Control-Allow-Headers"] =
84
101
  config.allowedHeaders.join(", ");
@@ -89,7 +106,7 @@ const cors = (options = {}) => {
89
106
  .setContent(null)
90
107
  .setHeaders(corsHeaders);
91
108
  }
92
- const response = await next(request);
109
+ const response = await next(context);
93
110
  for (const [key, value] of Object.entries(corsHeaders)) {
94
111
  response.setHeader(key, value);
95
112
  }
@@ -16,7 +16,7 @@ const defaultTokenGenerator = () => (0, node_crypto_1.randomBytes)(32).toString(
16
16
  * Normalizes an origin-like string by removing trailing slashes.
17
17
  *
18
18
  * This helps make origin comparisons stable across minor formatting differences
19
- * (e.g. `https://example.com/` vs `https://example.com`).
19
+ * (e.g. `scheme://host/` vs `scheme://host`).
20
20
  *
21
21
  * @param value - Origin string to normalize.
22
22
  * @returns Normalized origin string without trailing slashes.
@@ -61,8 +61,8 @@ const buildConfig = (options) => ({
61
61
  * @param headerName - Header key to read (as stored in `request.headers`).
62
62
  * @returns The header value if exactly one is present, otherwise `null`.
63
63
  */
64
- function getSingleHeaderValue(request, headerName) {
65
- const value = request.headers[headerName];
64
+ function getSingleHeaderValue(context, headerName) {
65
+ const value = context.headers[headerName];
66
66
  if (!value)
67
67
  return null;
68
68
  if (Array.isArray(value)) {
@@ -86,8 +86,8 @@ function getSingleHeaderValue(request, headerName) {
86
86
  * @param headerName - Header key to inspect.
87
87
  * @returns `true` if duplicates/invalid array form exist, otherwise `false`.
88
88
  */
89
- const hasInvalidDuplicateHeader = (request, headerName) => Array.isArray(request.headers[headerName]) &&
90
- request.headers[headerName].length !== 1;
89
+ const hasInvalidDuplicateHeader = (context, headerName) => Array.isArray(context.headers[headerName]) &&
90
+ context.headers[headerName].length !== 1;
91
91
  /**
92
92
  * Extracts a CSRF token from the first matching header in `headerNames`.
93
93
  *
@@ -98,9 +98,9 @@ const hasInvalidDuplicateHeader = (request, headerName) => Array.isArray(request
98
98
  * @param headerNames - List of candidate header names to check.
99
99
  * @returns The token value if found, otherwise `null`.
100
100
  */
101
- function getHeaderToken(request, headerNames) {
101
+ function getHeaderToken(context, headerNames) {
102
102
  for (const headerName of headerNames) {
103
- const value = getSingleHeaderValue(request, headerName);
103
+ const value = getSingleHeaderValue(context, headerName);
104
104
  if (value)
105
105
  return value;
106
106
  }
@@ -135,11 +135,11 @@ function safeCompare(a, b) {
135
135
  * @param request - Incoming request.
136
136
  * @returns `true` if HTTPS is inferred, otherwise `false`.
137
137
  */
138
- function isHttpsRequest(request) {
139
- const forwardedProto = getSingleHeaderValue(request, "x-forwarded-proto");
138
+ function isHttpsRequest(context) {
139
+ const forwardedProto = getSingleHeaderValue(context, "x-forwarded-proto");
140
140
  if (forwardedProto?.split(",")[0]?.trim().toLowerCase() === "https")
141
141
  return true;
142
- const forwarded = getSingleHeaderValue(request, "forwarded");
142
+ const forwarded = getSingleHeaderValue(context, "forwarded");
143
143
  if (forwarded?.toLowerCase().includes("proto=https"))
144
144
  return true;
145
145
  return false;
@@ -152,10 +152,10 @@ function isHttpsRequest(request) {
152
152
  *
153
153
  * @param request - Incoming request.
154
154
  * @param isHttps - Whether the request is considered HTTPS.
155
- * @returns An origin string like `https://example.com`, or `null` if `Host` is missing.
155
+ * @returns An origin string like `scheme://host`, or `null` if `Host` is missing.
156
156
  */
157
- function inferRequestOrigin(request, isHttps) {
158
- const host = getSingleHeaderValue(request, "host");
157
+ function inferRequestOrigin(context, isHttps) {
158
+ const host = getSingleHeaderValue(context, "host");
159
159
  if (!host)
160
160
  return null;
161
161
  return `${isHttps ? "https" : "http"}://${host}`;
@@ -164,7 +164,7 @@ function inferRequestOrigin(request, isHttps) {
164
164
  * Extracts the origin (scheme + host + port) from a Referer header value.
165
165
  *
166
166
  * @param referer - The raw Referer header.
167
- * @returns The parsed origin (e.g., `https://example.com`) or `null` if invalid.
167
+ * @returns The parsed origin (e.g., `scheme://host`) or `null` if invalid.
168
168
  */
169
169
  function extractOriginFromReferer(referer) {
170
170
  try {
@@ -185,12 +185,12 @@ function extractOriginFromReferer(referer) {
185
185
  * @param headerNames - Token header names to validate for duplication.
186
186
  * @returns An error message string, or `null` if headers look safe.
187
187
  */
188
- function validateDuplicateSecurityHeaders(request, headerNames) {
189
- if (headerNames.some(headerName => hasInvalidDuplicateHeader(request, headerName))) {
188
+ function validateDuplicateSecurityHeaders(context, headerNames) {
189
+ if (headerNames.some(headerName => hasInvalidDuplicateHeader(context, headerName))) {
190
190
  return "Invalid CSRF token header format";
191
191
  }
192
- if (hasInvalidDuplicateHeader(request, "origin") ||
193
- hasInvalidDuplicateHeader(request, "referer")) {
192
+ if (hasInvalidDuplicateHeader(context, "origin") ||
193
+ hasInvalidDuplicateHeader(context, "referer")) {
194
194
  return "Invalid Origin/Referer headers";
195
195
  }
196
196
  return null;
@@ -202,15 +202,15 @@ function validateDuplicateSecurityHeaders(request, headerNames) {
202
202
  * @param config - Resolved CSRF configuration.
203
203
  * @returns An error message string, or `null` if origin is acceptable.
204
204
  */
205
- function validateOriginHeaders(request, config) {
205
+ function validateOriginHeaders(context, config) {
206
206
  if (!config.validateOrigin)
207
207
  return null;
208
- const httpsRequest = isHttpsRequest(request);
209
- const inferredOrigin = inferRequestOrigin(request, httpsRequest);
208
+ const httpsRequest = isHttpsRequest(context);
209
+ const inferredOrigin = inferRequestOrigin(context, httpsRequest);
210
210
  const allowedOrigins = config.allowedOrigins ??
211
211
  (inferredOrigin ? [normalizeOrigin(inferredOrigin)] : []);
212
- const originHeader = getSingleHeaderValue(request, "origin");
213
- const refererHeader = getSingleHeaderValue(request, "referer");
212
+ const originHeader = getSingleHeaderValue(context, "origin");
213
+ const refererHeader = getSingleHeaderValue(context, "referer");
214
214
  if (!originHeader &&
215
215
  httpsRequest &&
216
216
  config.requireRefererOnHttps &&
@@ -245,9 +245,9 @@ function validateOriginHeaders(request, config) {
245
245
  * @param expectedToken - The server-issued token (typically from cookie or generator).
246
246
  * @returns An error message string, or `null` if the token is valid.
247
247
  */
248
- function validateCsrfToken(request, config, expectedToken) {
249
- const tokenFromHeader = getHeaderToken(request, config.headerNames);
250
- const tokenFromBody = request.body[config.bodyField];
248
+ function validateCsrfToken(context, config, expectedToken) {
249
+ const tokenFromHeader = getHeaderToken(context, config.headerNames);
250
+ const tokenFromBody = context.body[config.bodyField];
251
251
  const providedToken = tokenFromHeader ?? tokenFromBody;
252
252
  if (typeof providedToken !== "string" ||
253
253
  !safeCompare(expectedToken, providedToken)) {
@@ -290,9 +290,9 @@ const buildForbiddenResponse = (message) => {
290
290
  */
291
291
  const csrf = (options = {}) => {
292
292
  const config = buildConfig(options);
293
- return async (request, next) => {
294
- const methodRequiresCheck = config.protectedMethods.includes(request.method);
295
- const tokenFromCookie = request.cookies[config.cookieName];
293
+ return async (context, next) => {
294
+ const methodRequiresCheck = config.protectedMethods.includes(context.req.method);
295
+ const tokenFromCookie = context.cookies[config.cookieName];
296
296
  const issuedToken = tokenFromCookie || config.tokenGenerator();
297
297
  const appendCsrfCookie = (response) => {
298
298
  if (!tokenFromCookie) {
@@ -301,14 +301,14 @@ const csrf = (options = {}) => {
301
301
  return response;
302
302
  };
303
303
  if (methodRequiresCheck) {
304
- const validationError = validateDuplicateSecurityHeaders(request, config.headerNames) ??
305
- validateOriginHeaders(request, config) ??
306
- validateCsrfToken(request, config, issuedToken);
304
+ const validationError = validateDuplicateSecurityHeaders(context, config.headerNames) ??
305
+ validateOriginHeaders(context, config) ??
306
+ validateCsrfToken(context, config, issuedToken);
307
307
  if (validationError) {
308
308
  return appendCsrfCookie(buildForbiddenResponse(validationError));
309
309
  }
310
310
  }
311
- const response = await next(request);
311
+ const response = await next(context);
312
312
  return appendCsrfCookie(response);
313
313
  };
314
314
  };
@@ -1,4 +1,4 @@
1
1
  export { cors } from "./cors";
2
2
  export { sessions } from "./session";
3
3
  export { csrf } from "./csrf";
4
- export { rateLimit } from "./rateLimiter";
4
+ export { rateLimit, type RateLimitStore, type RateLimitStoreEntry, } from "./rateLimiter";