@asenajs/ergenecore 3.2.1 → 5.0.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 +12 -5
- package/dist/lib/Ergenecore.d.ts +12 -1
- package/dist/lib/Ergenecore.js +320 -173
- package/dist/lib/Ergenecore.js.map +1 -1
- package/dist/lib/ErgenecoreContextWrapper.d.ts +45 -12
- package/dist/lib/ErgenecoreContextWrapper.js +200 -54
- package/dist/lib/ErgenecoreContextWrapper.js.map +1 -1
- package/dist/lib/defaults/index.d.ts +1 -0
- package/dist/lib/defaults/index.js +1 -0
- package/dist/lib/defaults/index.js.map +1 -1
- package/dist/lib/defineContext.d.ts +8 -0
- package/dist/lib/defineContext.js +10 -0
- package/dist/lib/defineContext.js.map +1 -0
- package/dist/lib/index.d.ts +8 -1
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/middlewares/CorsMiddleware.d.ts +2 -2
- package/dist/lib/middlewares/CorsMiddleware.js +5 -9
- package/dist/lib/middlewares/CorsMiddleware.js.map +1 -1
- package/dist/lib/middlewares/RateLimiterMiddleware.d.ts +73 -3
- package/dist/lib/middlewares/RateLimiterMiddleware.js +95 -7
- package/dist/lib/middlewares/RateLimiterMiddleware.js.map +1 -1
- package/dist/lib/middlewares/WebSocketOriginMiddleware.d.ts +82 -0
- package/dist/lib/middlewares/WebSocketOriginMiddleware.js +81 -0
- package/dist/lib/middlewares/WebSocketOriginMiddleware.js.map +1 -0
- package/dist/lib/stream/SSEStreamWriter.js +22 -13
- package/dist/lib/stream/SSEStreamWriter.js.map +1 -1
- package/dist/lib/utils/staticFileReader.d.ts +27 -0
- package/dist/lib/utils/staticFileReader.js +172 -0
- package/dist/lib/utils/staticFileReader.js.map +1 -0
- package/package.json +11 -7
|
@@ -91,10 +91,11 @@ export interface RateLimiterOptions {
|
|
|
91
91
|
/**
|
|
92
92
|
* Custom key generator function
|
|
93
93
|
*
|
|
94
|
-
* By default,
|
|
94
|
+
* By default, identifies the client by the connecting peer, or by the client hop of
|
|
95
|
+
* `X-Forwarded-For` when `trustProxy` trusts the peer.
|
|
95
96
|
* You can customize this to use user ID, API key, or any other identifier.
|
|
96
97
|
*
|
|
97
|
-
* @default (ctx) => ctx.
|
|
98
|
+
* @default (ctx) => ctx.getRequestIp() ?? 'unknown'
|
|
98
99
|
*
|
|
99
100
|
* @example
|
|
100
101
|
* ```typescript
|
|
@@ -106,6 +107,38 @@ export interface RateLimiterOptions {
|
|
|
106
107
|
* ```
|
|
107
108
|
*/
|
|
108
109
|
keyGenerator?: (context: Context) => string;
|
|
110
|
+
/**
|
|
111
|
+
* Which connecting peers are reverse proxies whose `X-Forwarded-For` may be believed.
|
|
112
|
+
*
|
|
113
|
+
* - `false` (default): the connecting peer (`getRequestIp()`) is the client; the header is ignored.
|
|
114
|
+
* - `true`: one proxy layer in front of the server. The header is read and its rightmost entry - the
|
|
115
|
+
* one the proxy appended - is the client. Also the right setting when the server listens on a unix
|
|
116
|
+
* socket (no peer address to check).
|
|
117
|
+
* - `string[]`: exact addresses of the trusted proxies. An IPv4 address matches whether it is written
|
|
118
|
+
* plain (`'10.0.0.1'`) or IPv4-mapped (`'::ffff:10.0.0.1'`, how Bun reports it on a dual-stack
|
|
119
|
+
* listener), in the list, the peer and the forwarded hops alike. Entries are walked from the right;
|
|
120
|
+
* the first one not in the list is the client.
|
|
121
|
+
* - `(address) => boolean`: same walk with a predicate (CIDR checks live here).
|
|
122
|
+
*
|
|
123
|
+
* A hop the client wrote itself (`X-Forwarded-For: <forged>, <real>`) is never selected: selection
|
|
124
|
+
* starts from the right and stops at the first untrusted address.
|
|
125
|
+
*
|
|
126
|
+
* Both misconfigurations below fail closed - every client lands in one bucket:
|
|
127
|
+
* - `true` behind two or more proxy layers keys on the outer proxy (the rightmost hop); list every
|
|
128
|
+
* proxy, or use a predicate, instead.
|
|
129
|
+
* - A trusted peer that sends no usable `X-Forwarded-For` keys on the peer itself; logged once.
|
|
130
|
+
*
|
|
131
|
+
* @default false
|
|
132
|
+
*/
|
|
133
|
+
trustProxy?: boolean | string[] | ((address: string) => boolean);
|
|
134
|
+
/**
|
|
135
|
+
* Upper bound on distinct client buckets held in memory. When a new client arrives at the cap, the
|
|
136
|
+
* least recently seen bucket is evicted - that client starts over with a full bucket.
|
|
137
|
+
* Must be a positive integer; the constructor throws otherwise.
|
|
138
|
+
*
|
|
139
|
+
* @default 10000
|
|
140
|
+
*/
|
|
141
|
+
maxBuckets?: number;
|
|
109
142
|
/**
|
|
110
143
|
* Custom message for rate limit exceeded response
|
|
111
144
|
*
|
|
@@ -232,6 +265,14 @@ export declare class RateLimiterMiddleware extends MiddlewareService {
|
|
|
232
265
|
* Client key generator function
|
|
233
266
|
*/
|
|
234
267
|
private readonly keyGenerator;
|
|
268
|
+
/**
|
|
269
|
+
* Trusted reverse proxies whose `X-Forwarded-For` may be believed
|
|
270
|
+
*/
|
|
271
|
+
private readonly trustProxy;
|
|
272
|
+
/**
|
|
273
|
+
* Upper bound on distinct client buckets held in memory
|
|
274
|
+
*/
|
|
275
|
+
private readonly maxBuckets;
|
|
235
276
|
/**
|
|
236
277
|
* Rate limit exceeded message
|
|
237
278
|
*/
|
|
@@ -256,6 +297,7 @@ export declare class RateLimiterMiddleware extends MiddlewareService {
|
|
|
256
297
|
* Bucket TTL in milliseconds
|
|
257
298
|
*/
|
|
258
299
|
private readonly bucketTTL;
|
|
300
|
+
private warnedNoForwardedFor;
|
|
259
301
|
/**
|
|
260
302
|
* Creates a new rate limiter middleware instance
|
|
261
303
|
*
|
|
@@ -321,12 +363,40 @@ export declare class RateLimiterMiddleware extends MiddlewareService {
|
|
|
321
363
|
*/
|
|
322
364
|
clearBuckets(): void;
|
|
323
365
|
/**
|
|
324
|
-
* Default key generator -
|
|
366
|
+
* Default key generator - the connecting peer, see `trustProxy`
|
|
325
367
|
*
|
|
326
368
|
* @param context - Ergenecore context
|
|
327
369
|
* @returns Client identifier
|
|
328
370
|
*/
|
|
329
371
|
private defaultKeyGenerator;
|
|
372
|
+
/**
|
|
373
|
+
* Resolve the client identifier: the connecting peer, or the client hop of `X-Forwarded-For`
|
|
374
|
+
* when the peer is a trusted proxy
|
|
375
|
+
*
|
|
376
|
+
* @param context - Ergenecore context
|
|
377
|
+
* @returns Client identifier
|
|
378
|
+
*/
|
|
379
|
+
private resolveClientKey;
|
|
380
|
+
/**
|
|
381
|
+
* Warn, once per instance, that a trusted peer forwarded no usable `X-Forwarded-For`
|
|
382
|
+
*
|
|
383
|
+
* @param peer - The trusted peer address
|
|
384
|
+
*/
|
|
385
|
+
private warnNoForwardedFor;
|
|
386
|
+
/**
|
|
387
|
+
* Whether an address is a reverse proxy whose forwarded hops may be believed
|
|
388
|
+
*
|
|
389
|
+
* @param address - Peer or forwarded hop address
|
|
390
|
+
* @returns True when the address is trusted
|
|
391
|
+
*/
|
|
392
|
+
private trusts;
|
|
393
|
+
/**
|
|
394
|
+
* Parse `X-Forwarded-For` into its hops, left to right
|
|
395
|
+
*
|
|
396
|
+
* @param context - Ergenecore context
|
|
397
|
+
* @returns The forwarded hops, or an empty list when the header is absent or over-long
|
|
398
|
+
*/
|
|
399
|
+
private forwardedHops;
|
|
330
400
|
/**
|
|
331
401
|
* Setup automatic cleanup of inactive buckets
|
|
332
402
|
*
|
|
@@ -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;
|
|
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 (
|
|
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.
|
|
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;
|
|
@@ -284,17 +307,82 @@ export class RateLimiterMiddleware extends MiddlewareService {
|
|
|
284
307
|
this.buckets.clear();
|
|
285
308
|
}
|
|
286
309
|
/**
|
|
287
|
-
* Default key generator -
|
|
310
|
+
* Default key generator - the connecting peer, see `trustProxy`
|
|
288
311
|
*
|
|
289
312
|
* @param context - Ergenecore context
|
|
290
313
|
* @returns Client identifier
|
|
291
314
|
*/
|
|
292
315
|
defaultKeyGenerator = (context) => {
|
|
293
|
-
return (context
|
|
294
|
-
context.req.headers.get('cf-connecting-ip') ||
|
|
295
|
-
context.getRequestIp?.() ||
|
|
296
|
-
'unknown');
|
|
316
|
+
return this.resolveClientKey(context);
|
|
297
317
|
};
|
|
318
|
+
/**
|
|
319
|
+
* Resolve the client identifier: the connecting peer, or the client hop of `X-Forwarded-For`
|
|
320
|
+
* when the peer is a trusted proxy
|
|
321
|
+
*
|
|
322
|
+
* @param context - Ergenecore context
|
|
323
|
+
* @returns Client identifier
|
|
324
|
+
*/
|
|
325
|
+
resolveClientKey(context) {
|
|
326
|
+
const peer = context.getRequestIp();
|
|
327
|
+
if (!this.trusts(peer)) {
|
|
328
|
+
return peer ?? 'unknown';
|
|
329
|
+
}
|
|
330
|
+
const hops = this.forwardedHops(context);
|
|
331
|
+
if (!hops.length) {
|
|
332
|
+
this.warnNoForwardedFor(peer);
|
|
333
|
+
return peer ?? 'unknown';
|
|
334
|
+
}
|
|
335
|
+
for (let i = hops.length - 1; i >= 0; i--) {
|
|
336
|
+
if (!this.trusts(hops[i])) {
|
|
337
|
+
return hops[i];
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return hops.at(-1);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Warn, once per instance, that a trusted peer forwarded no usable `X-Forwarded-For`
|
|
344
|
+
*
|
|
345
|
+
* @param peer - The trusted peer address
|
|
346
|
+
*/
|
|
347
|
+
warnNoForwardedFor(peer) {
|
|
348
|
+
if (this.warnedNoForwardedFor)
|
|
349
|
+
return;
|
|
350
|
+
this.warnedNoForwardedFor = true;
|
|
351
|
+
console.warn(`RateLimiterMiddleware: trusted proxy ${peer ?? '(no peer address)'} sent no usable X-Forwarded-For; ` +
|
|
352
|
+
'every client behind it shares one bucket. Make the proxy append X-Forwarded-For or narrow trustProxy.');
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Whether an address is a reverse proxy whose forwarded hops may be believed
|
|
356
|
+
*
|
|
357
|
+
* @param address - Peer or forwarded hop address
|
|
358
|
+
* @returns True when the address is trusted
|
|
359
|
+
*/
|
|
360
|
+
trusts(address) {
|
|
361
|
+
if (this.trustProxy === true)
|
|
362
|
+
return true;
|
|
363
|
+
if (!this.trustProxy || address === null)
|
|
364
|
+
return false;
|
|
365
|
+
if (!Array.isArray(this.trustProxy))
|
|
366
|
+
return this.trustProxy(address);
|
|
367
|
+
const normalized = unmapIPv4(address);
|
|
368
|
+
return this.trustProxy.some((entry) => unmapIPv4(entry) === normalized);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Parse `X-Forwarded-For` into its hops, left to right
|
|
372
|
+
*
|
|
373
|
+
* @param context - Ergenecore context
|
|
374
|
+
* @returns The forwarded hops, or an empty list when the header is absent or over-long
|
|
375
|
+
*/
|
|
376
|
+
forwardedHops(context) {
|
|
377
|
+
const header = context.req.headers.get('x-forwarded-for');
|
|
378
|
+
if (!header || header.length > MAX_FORWARDED_HEADER_LENGTH) {
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
return header
|
|
382
|
+
.split(',')
|
|
383
|
+
.map((hop) => hop.trim())
|
|
384
|
+
.filter(Boolean);
|
|
385
|
+
}
|
|
298
386
|
/**
|
|
299
387
|
* Setup automatic cleanup of inactive buckets
|
|
300
388
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimiterMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/RateLimiterMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;;;;;;;;;;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"RateLimiterMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/RateLimiterMiddleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Origin Middleware for Ergenecore
|
|
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
|
+
* - Zero external dependencies (uses Bun's native APIs)
|
|
8
|
+
*
|
|
9
|
+
* @module defaults/WebSocketOriginMiddleware
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { Middleware, WebSocket } from '@asenajs/asena/decorators';
|
|
14
|
+
* import { WebSocketOriginMiddleware } from '@asenajs/ergenecore';
|
|
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 } from '../ErgenecoreContextWrapper';
|
|
28
|
+
import { MiddlewareService } from '../defaults';
|
|
29
|
+
/**
|
|
30
|
+
* WebSocket origin policy options
|
|
31
|
+
*/
|
|
32
|
+
export interface WebSocketOriginOptions {
|
|
33
|
+
/** Origins allowed to open a WebSocket: exact `Origin` header values, or a predicate. */
|
|
34
|
+
allowedOrigins: string[] | ((origin: string) => boolean);
|
|
35
|
+
/**
|
|
36
|
+
* Accept handshakes that carry no `Origin` header (non-browser clients). Browsers always send one,
|
|
37
|
+
* so the default rejects: a missing header is not evidence of a first-party page.
|
|
38
|
+
*
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
allowMissingOrigin?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* WebSocket Origin Middleware
|
|
45
|
+
*
|
|
46
|
+
* Use it as a `@WebSocket({ middlewares: [...] })` middleware, listed **before** any cookie-based
|
|
47
|
+
* auth middleware: the browser attaches cookies to a cross-site handshake, so a session is already
|
|
48
|
+
* authenticated by the time an auth middleware looks at it.
|
|
49
|
+
*
|
|
50
|
+
* `CorsMiddleware` is not a WebSocket origin policy - it sets response headers a browser applies to
|
|
51
|
+
* `fetch`/XHR, and a disallowed origin still upgrades.
|
|
52
|
+
*
|
|
53
|
+
* The `Origin` header is compared exactly: `'https://app.example.com'`, scheme included, no trailing
|
|
54
|
+
* slash.
|
|
55
|
+
*
|
|
56
|
+
* It is an ordinary middleware, so on an HTTP route it would reject every request without an
|
|
57
|
+
* `Origin` - which is why the name says WebSocket.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* // A predicate covers wildcard subdomains; there is no '*' special value
|
|
62
|
+
* new WebSocketOriginMiddleware({ allowedOrigins: (origin) => origin.endsWith('.example.com') });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare class WebSocketOriginMiddleware extends MiddlewareService {
|
|
66
|
+
private readonly allowedOrigins;
|
|
67
|
+
private readonly allowMissingOrigin;
|
|
68
|
+
/**
|
|
69
|
+
* Creates a new WebSocket origin middleware instance
|
|
70
|
+
*
|
|
71
|
+
* @param options - Origin policy options
|
|
72
|
+
*/
|
|
73
|
+
constructor(options: WebSocketOriginOptions);
|
|
74
|
+
/**
|
|
75
|
+
* Reject the handshake unless its `Origin` is allowed
|
|
76
|
+
*
|
|
77
|
+
* @param context - Ergenecore context wrapper
|
|
78
|
+
* @param next - Function to call next middleware or handler
|
|
79
|
+
* @returns A 403 response, or the result of next() for an allowed origin
|
|
80
|
+
*/
|
|
81
|
+
handle(context: Context, next: () => Promise<void>): Promise<any>;
|
|
82
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Origin Middleware for Ergenecore
|
|
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
|
+
* - Zero external dependencies (uses Bun's native APIs)
|
|
8
|
+
*
|
|
9
|
+
* @module defaults/WebSocketOriginMiddleware
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { Middleware, WebSocket } from '@asenajs/asena/decorators';
|
|
14
|
+
* import { WebSocketOriginMiddleware } from '@asenajs/ergenecore';
|
|
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 - Ergenecore 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.headers.get('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;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAkBhD;;;;;;;;;;;;;;;;;;;;;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,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAEjD,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"}
|
|
@@ -5,19 +5,28 @@ import { StreamWriter } from './StreamWriter';
|
|
|
5
5
|
*/
|
|
6
6
|
export class SSEStreamWriter extends StreamWriter {
|
|
7
7
|
async writeSSE(message) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.
|
|
20
|
-
|
|
8
|
+
if (message.data === undefined && message.comment === undefined) {
|
|
9
|
+
throw new Error('writeSSE: message needs data or comment');
|
|
10
|
+
}
|
|
11
|
+
const lines = [];
|
|
12
|
+
for (const line of message.comment?.split(/\r\n|\r|\n/) ?? []) {
|
|
13
|
+
lines.push(`: ${line}`);
|
|
14
|
+
}
|
|
15
|
+
if (message.event) {
|
|
16
|
+
lines.push(`event: ${message.event}`);
|
|
17
|
+
}
|
|
18
|
+
if (message.data !== undefined) {
|
|
19
|
+
for (const line of message.data.split(/\r\n|\r|\n/)) {
|
|
20
|
+
lines.push(`data: ${line}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (message.id) {
|
|
24
|
+
lines.push(`id: ${message.id}`);
|
|
25
|
+
}
|
|
26
|
+
if (message.retry) {
|
|
27
|
+
lines.push(`retry: ${message.retry}`);
|
|
28
|
+
}
|
|
29
|
+
await this.write(lines.join('\n') + '\n\n');
|
|
21
30
|
}
|
|
22
31
|
}
|
|
23
32
|
//# sourceMappingURL=SSEStreamWriter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SSEStreamWriter.js","sourceRoot":"","sources":["../../../lib/stream/SSEStreamWriter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;;GAGG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY;IACxC,KAAK,CAAC,QAAQ,CAAC,OAAmB;QACvC,
|
|
1
|
+
{"version":3,"file":"SSEStreamWriter.js","sourceRoot":"","sources":["../../../lib/stream/SSEStreamWriter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;;GAGG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY;IACxC,KAAK,CAAC,QAAQ,CAAC,OAAmB;QACvC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC7D,CAAC;QAED,MAAM,KAAK,GAAa,EAAE,CAAC;QAE3B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9D,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QAC1B,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACxC,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;gBACpD,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;IAC9C,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Stats } from 'fs';
|
|
2
|
+
export declare function staticFileHeaders(type: string, etag: string, lastModified: string): Headers;
|
|
3
|
+
export interface StaticFileRead {
|
|
4
|
+
readonly bytes: Uint8Array<ArrayBuffer> | undefined;
|
|
5
|
+
readonly type: string;
|
|
6
|
+
readonly etag: string;
|
|
7
|
+
readonly lastModified: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function regularFileStats(filePath: string): Stats | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Reads small regular files from disk on every call through a bounded set of open descriptors.
|
|
12
|
+
* Nothing but the descriptor and derived metadata outlives a call; the bytes are always fresh.
|
|
13
|
+
*/
|
|
14
|
+
export declare class StaticFileReader {
|
|
15
|
+
private readonly maxFileBytes;
|
|
16
|
+
private readonly maxOpen;
|
|
17
|
+
private readonly files;
|
|
18
|
+
constructor(maxFileBytes?: number, maxOpen?: number);
|
|
19
|
+
read(filePath: string, stats: Stats, withBody: boolean): StaticFileRead;
|
|
20
|
+
respond(filePath: string, stats: Stats, withBody: boolean): Response;
|
|
21
|
+
private refresh;
|
|
22
|
+
release(filePath: string): void;
|
|
23
|
+
close(): void;
|
|
24
|
+
private readBytes;
|
|
25
|
+
private entry;
|
|
26
|
+
private forget;
|
|
27
|
+
}
|