@asenajs/hono-adapter 4.0.0 → 5.0.1

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.
@@ -57,6 +57,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
57
57
  };
58
58
  import { MiddlewareService } from '../defaults';
59
59
  import { OnStop } from '@asenajs/asena/decorators/ioc';
60
+ const MAX_FORWARDED_HEADER_LENGTH = 1024;
61
+ const IPV4_MAPPED_PREFIX = /^::ffff:(?=\d{1,3}(?:\.\d{1,3}){3}$)/i;
62
+ const unmapIPv4 = (address) => address.replace(IPV4_MAPPED_PREFIX, '');
60
63
  /**
61
64
  * Rate Limiter Middleware
62
65
  *
@@ -122,6 +125,14 @@ export class RateLimiterMiddleware extends MiddlewareService {
122
125
  * Client key generator function
123
126
  */
124
127
  keyGenerator;
128
+ /**
129
+ * Trusted reverse proxies whose `X-Forwarded-For` may be believed
130
+ */
131
+ trustProxy;
132
+ /**
133
+ * Upper bound on distinct client buckets held in memory
134
+ */
135
+ maxBuckets;
125
136
  /**
126
137
  * Rate limit exceeded message
127
138
  */
@@ -146,6 +157,7 @@ export class RateLimiterMiddleware extends MiddlewareService {
146
157
  * Bucket TTL in milliseconds
147
158
  */
148
159
  bucketTTL;
160
+ warnedNoForwardedFor = false;
149
161
  /**
150
162
  * Creates a new rate limiter middleware instance
151
163
  *
@@ -173,6 +185,11 @@ export class RateLimiterMiddleware extends MiddlewareService {
173
185
  this.capacity = options.capacity ?? 100;
174
186
  this.refillRate = options.refillRate ?? 10;
175
187
  this.keyGenerator = options.keyGenerator ?? this.defaultKeyGenerator.bind(this);
188
+ this.trustProxy = options.trustProxy ?? false;
189
+ this.maxBuckets = options.maxBuckets ?? 10_000;
190
+ if (!Number.isInteger(this.maxBuckets) || this.maxBuckets < 1) {
191
+ throw new Error(`RateLimiterMiddleware: maxBuckets must be a positive integer, got ${this.maxBuckets}`);
192
+ }
176
193
  this.message = options.message ?? 'Rate limit exceeded. Please try again later.';
177
194
  this.statusCode = options.statusCode ?? 429;
178
195
  this.cost = options.cost ?? 1;
@@ -208,13 +225,19 @@ export class RateLimiterMiddleware extends MiddlewareService {
208
225
  const key = this.keyGenerator(context);
209
226
  // Get or create bucket
210
227
  let bucket = this.buckets.get(key);
211
- if (!bucket) {
228
+ if (bucket) {
229
+ this.buckets.delete(key);
230
+ }
231
+ else {
212
232
  bucket = {
213
233
  tokens: this.capacity,
214
234
  lastRefill: Date.now(),
215
235
  };
216
- this.buckets.set(key, bucket);
236
+ if (this.buckets.size >= this.maxBuckets) {
237
+ this.buckets.delete(this.buckets.keys().next().value);
238
+ }
217
239
  }
240
+ this.buckets.set(key, bucket);
218
241
  // Refill tokens based on elapsed time
219
242
  const now = Date.now();
220
243
  const elapsedSeconds = (now - bucket.lastRefill) / 1000;
@@ -286,16 +309,81 @@ export class RateLimiterMiddleware extends MiddlewareService {
286
309
  this.buckets.clear();
287
310
  }
288
311
  /**
289
- * Default key generator - uses X-Forwarded-For or CF-Connecting-IP or falls back to 'unknown'
312
+ * Default key generator - the connecting peer, see `trustProxy`
290
313
  *
291
314
  * @param context - Hono context
292
315
  * @returns Client identifier
293
316
  */
294
317
  defaultKeyGenerator(context) {
295
- const req = context.req;
296
- const forwarded = typeof req.header === 'function' ? req.header('x-forwarded-for') : context.headers?.['x-forwarded-for'];
297
- const cfIp = typeof req.header === 'function' ? req.header('cf-connecting-ip') : context.headers?.['cf-connecting-ip'];
298
- return forwarded || cfIp || context.getRequestIp?.() || 'unknown';
318
+ return this.resolveClientKey(context);
319
+ }
320
+ /**
321
+ * Resolve the client identifier: the connecting peer, or the client hop of `X-Forwarded-For`
322
+ * when the peer is a trusted proxy
323
+ *
324
+ * @param context - Hono context
325
+ * @returns Client identifier
326
+ */
327
+ resolveClientKey(context) {
328
+ const peer = context.getRequestIp();
329
+ if (!this.trusts(peer)) {
330
+ return peer ?? 'unknown';
331
+ }
332
+ const hops = this.forwardedHops(context);
333
+ if (!hops.length) {
334
+ this.warnNoForwardedFor(peer);
335
+ return peer ?? 'unknown';
336
+ }
337
+ for (let i = hops.length - 1; i >= 0; i--) {
338
+ if (!this.trusts(hops[i])) {
339
+ return hops[i];
340
+ }
341
+ }
342
+ return hops.at(-1);
343
+ }
344
+ /**
345
+ * Warn, once per instance, that a trusted peer forwarded no usable `X-Forwarded-For`
346
+ *
347
+ * @param peer - The trusted peer address
348
+ */
349
+ warnNoForwardedFor(peer) {
350
+ if (this.warnedNoForwardedFor)
351
+ return;
352
+ this.warnedNoForwardedFor = true;
353
+ console.warn(`RateLimiterMiddleware: trusted proxy ${peer ?? '(no peer address)'} sent no usable X-Forwarded-For; ` +
354
+ 'every client behind it shares one bucket. Make the proxy append X-Forwarded-For or narrow trustProxy.');
355
+ }
356
+ /**
357
+ * Whether an address is a reverse proxy whose forwarded hops may be believed
358
+ *
359
+ * @param address - Peer or forwarded hop address
360
+ * @returns True when the address is trusted
361
+ */
362
+ trusts(address) {
363
+ if (this.trustProxy === true)
364
+ return true;
365
+ if (!this.trustProxy || address === null)
366
+ return false;
367
+ if (!Array.isArray(this.trustProxy))
368
+ return this.trustProxy(address);
369
+ const normalized = unmapIPv4(address);
370
+ return this.trustProxy.some((entry) => unmapIPv4(entry) === normalized);
371
+ }
372
+ /**
373
+ * Parse `X-Forwarded-For` into its hops, left to right
374
+ *
375
+ * @param context - Hono context
376
+ * @returns The forwarded hops, or an empty list when the header is absent or over-long
377
+ */
378
+ forwardedHops(context) {
379
+ const header = context.req.header('x-forwarded-for');
380
+ if (!header || header.length > MAX_FORWARDED_HEADER_LENGTH) {
381
+ return [];
382
+ }
383
+ return header
384
+ .split(',')
385
+ .map((hop) => hop.trim())
386
+ .filter(Boolean);
299
387
  }
300
388
  /**
301
389
  * Setup automatic cleanup of inactive buckets
@@ -1 +1 @@
1
- {"version":3,"file":"RateLimiterMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/RateLimiterMiddleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RateLimiterMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/RateLimiterMiddleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * WebSocket Origin Middleware for Hono Adapter
3
+ *
4
+ * Closes the cross-site WebSocket hijacking hole by:
5
+ * - Rejecting a handshake whose `Origin` is not allowed, before `server.upgrade()`
6
+ * - Deciding explicitly what happens when there is no `Origin` at all
7
+ * - Uses Hono's native header system
8
+ *
9
+ * @module defaults/WebSocketOriginMiddleware
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { Middleware, WebSocket } from '@asenajs/asena/decorators';
14
+ * import { WebSocketOriginMiddleware } from '@asenajs/hono-adapter';
15
+ *
16
+ * @Middleware()
17
+ * export class ChatOrigin extends WebSocketOriginMiddleware {
18
+ * constructor() {
19
+ * super({ allowedOrigins: ['https://app.example.com'] });
20
+ * }
21
+ * }
22
+ *
23
+ * @WebSocket({ path: 'chat', middlewares: [ChatOrigin, SessionAuth] })
24
+ * export class ChatSocket { ... }
25
+ * ```
26
+ */
27
+ import { type Context, MiddlewareService } from '../defaults';
28
+ /**
29
+ * WebSocket origin policy options
30
+ */
31
+ export interface WebSocketOriginOptions {
32
+ /** Origins allowed to open a WebSocket: exact `Origin` header values, or a predicate. */
33
+ allowedOrigins: string[] | ((origin: string) => boolean);
34
+ /**
35
+ * Accept handshakes that carry no `Origin` header (non-browser clients). Browsers always send one,
36
+ * so the default rejects: a missing header is not evidence of a first-party page.
37
+ *
38
+ * @default false
39
+ */
40
+ allowMissingOrigin?: boolean;
41
+ }
42
+ /**
43
+ * WebSocket Origin Middleware
44
+ *
45
+ * Use it as a `@WebSocket({ middlewares: [...] })` middleware, listed **before** any cookie-based
46
+ * auth middleware: the browser attaches cookies to a cross-site handshake, so a session is already
47
+ * authenticated by the time an auth middleware looks at it.
48
+ *
49
+ * `CorsMiddleware` is not a WebSocket origin policy - it sets response headers a browser applies to
50
+ * `fetch`/XHR, and a disallowed origin still upgrades.
51
+ *
52
+ * The `Origin` header is compared exactly: `'https://app.example.com'`, scheme included, no trailing
53
+ * slash.
54
+ *
55
+ * It is an ordinary middleware, so on an HTTP route it would reject every request without an
56
+ * `Origin` - which is why the name says WebSocket.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // A predicate covers wildcard subdomains; there is no '*' special value
61
+ * new WebSocketOriginMiddleware({ allowedOrigins: (origin) => origin.endsWith('.example.com') });
62
+ * ```
63
+ */
64
+ export declare class WebSocketOriginMiddleware extends MiddlewareService {
65
+ private readonly allowedOrigins;
66
+ private readonly allowMissingOrigin;
67
+ /**
68
+ * Creates a new WebSocket origin middleware instance
69
+ *
70
+ * @param options - Origin policy options
71
+ */
72
+ constructor(options: WebSocketOriginOptions);
73
+ /**
74
+ * Reject the handshake unless its `Origin` is allowed
75
+ *
76
+ * @param context - Hono context wrapper
77
+ * @param next - Function to call next middleware or handler
78
+ * @returns A 403 response, or the result of next() for an allowed origin
79
+ */
80
+ handle(context: Context, next: () => Promise<void>): Promise<any>;
81
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * WebSocket Origin Middleware for Hono Adapter
3
+ *
4
+ * Closes the cross-site WebSocket hijacking hole by:
5
+ * - Rejecting a handshake whose `Origin` is not allowed, before `server.upgrade()`
6
+ * - Deciding explicitly what happens when there is no `Origin` at all
7
+ * - Uses Hono's native header system
8
+ *
9
+ * @module defaults/WebSocketOriginMiddleware
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { Middleware, WebSocket } from '@asenajs/asena/decorators';
14
+ * import { WebSocketOriginMiddleware } from '@asenajs/hono-adapter';
15
+ *
16
+ * @Middleware()
17
+ * export class ChatOrigin extends WebSocketOriginMiddleware {
18
+ * constructor() {
19
+ * super({ allowedOrigins: ['https://app.example.com'] });
20
+ * }
21
+ * }
22
+ *
23
+ * @WebSocket({ path: 'chat', middlewares: [ChatOrigin, SessionAuth] })
24
+ * export class ChatSocket { ... }
25
+ * ```
26
+ */
27
+ import { MiddlewareService } from '../defaults';
28
+ /**
29
+ * WebSocket Origin Middleware
30
+ *
31
+ * Use it as a `@WebSocket({ middlewares: [...] })` middleware, listed **before** any cookie-based
32
+ * auth middleware: the browser attaches cookies to a cross-site handshake, so a session is already
33
+ * authenticated by the time an auth middleware looks at it.
34
+ *
35
+ * `CorsMiddleware` is not a WebSocket origin policy - it sets response headers a browser applies to
36
+ * `fetch`/XHR, and a disallowed origin still upgrades.
37
+ *
38
+ * The `Origin` header is compared exactly: `'https://app.example.com'`, scheme included, no trailing
39
+ * slash.
40
+ *
41
+ * It is an ordinary middleware, so on an HTTP route it would reject every request without an
42
+ * `Origin` - which is why the name says WebSocket.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // A predicate covers wildcard subdomains; there is no '*' special value
47
+ * new WebSocketOriginMiddleware({ allowedOrigins: (origin) => origin.endsWith('.example.com') });
48
+ * ```
49
+ */
50
+ export class WebSocketOriginMiddleware extends MiddlewareService {
51
+ allowedOrigins;
52
+ allowMissingOrigin;
53
+ /**
54
+ * Creates a new WebSocket origin middleware instance
55
+ *
56
+ * @param options - Origin policy options
57
+ */
58
+ constructor(options) {
59
+ super();
60
+ this.allowedOrigins = options.allowedOrigins;
61
+ this.allowMissingOrigin = options.allowMissingOrigin ?? false;
62
+ }
63
+ /**
64
+ * Reject the handshake unless its `Origin` is allowed
65
+ *
66
+ * @param context - Hono context wrapper
67
+ * @param next - Function to call next middleware or handler
68
+ * @returns A 403 response, or the result of next() for an allowed origin
69
+ */
70
+ async handle(context, next) {
71
+ const origin = context.req.header('Origin');
72
+ if (!origin) {
73
+ return this.allowMissingOrigin ? await next() : new Response('Forbidden', { status: 403 });
74
+ }
75
+ const allowed = Array.isArray(this.allowedOrigins)
76
+ ? this.allowedOrigins.includes(origin)
77
+ : this.allowedOrigins(origin);
78
+ return allowed ? await next() : new Response('Forbidden', { status: 403 });
79
+ }
80
+ }
81
+ //# sourceMappingURL=WebSocketOriginMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketOriginMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/WebSocketOriginMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAgB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAkB9D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,yBAA0B,SAAQ,iBAAiB;IAC7C,cAAc,CAA2C;IAEzD,kBAAkB,CAAU;IAE7C;;;;OAIG;IACH,YAAmB,OAA+B;QAChD,KAAK,EAAE,CAAC;QAER,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;QAC7C,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,KAAK,CAAC;IAChE,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,MAAM,CAAC,OAAgB,EAAE,IAAyB;QAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAE5C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7F,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC;YAChD,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC;YACtC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAEhC,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAC7E,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ import { type ServeStaticOptions } from 'hono/serve-static';
3
+ export declare const serveStatic: (options?: ServeStaticOptions) => MiddlewareHandler;
@@ -0,0 +1,17 @@
1
+ import { statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { serveStatic as baseServeStatic } from 'hono/serve-static';
4
+ const getContent = (async (path) => {
5
+ const file = Bun.file(path);
6
+ return (await file.exists()) ? file : null;
7
+ });
8
+ const isDir = (path) => {
9
+ try {
10
+ return statSync(path, { throwIfNoEntry: false })?.isDirectory();
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ };
16
+ export const serveStatic = (options = {}) => baseServeStatic({ ...options, getContent, join, isDir });
17
+ //# sourceMappingURL=serveStatic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serveStatic.js","sourceRoot":"","sources":["../../../lib/utils/serveStatic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,WAAW,IAAI,eAAe,EAA2B,MAAM,mBAAmB,CAAC;AAI5F,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;IACzC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE5B,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC,CAA6B,CAAC;AAE/B,MAAM,KAAK,GAAG,CAAC,IAAY,EAAuB,EAAE;IAClD,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,UAA8B,EAAE,EAAqB,EAAE,CACjF,eAAe,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC"}
package/package.json CHANGED
@@ -9,11 +9,11 @@
9
9
  "README.md",
10
10
  "LICENSE"
11
11
  ],
12
- "version": "4.0.0",
12
+ "version": "5.0.1",
13
13
  "author": "LibirSoft",
14
14
  "repository": {
15
15
  "type": "git",
16
- "url": "https://github.com/AsenaJs/hono-adapter.git"
16
+ "url": "git+https://github.com/AsenaJs/hono-adapter.git"
17
17
  },
18
18
  "license": "MIT",
19
19
  "engines": {
@@ -41,7 +41,7 @@
41
41
  "release": "bun run build && changeset publish"
42
42
  },
43
43
  "devDependencies": {
44
- "@asenajs/asena": "^0.11.0",
44
+ "@asenajs/asena": "^0.12.0",
45
45
  "@changesets/cli": "^2.31.1",
46
46
  "@types/bun": "latest",
47
47
  "@typescript-eslint/eslint-plugin": "^8.68.0",
@@ -55,7 +55,7 @@
55
55
  "zod": "^4.4.3"
56
56
  },
57
57
  "peerDependencies": {
58
- "@asenajs/asena": "^0.11.0",
58
+ "@asenajs/asena": "^0.12.0",
59
59
  "hono": ">=4.12.9",
60
60
  "typescript": "^5.8.2",
61
61
  "zod": "^4.3.6"