nestjs-advanced-rate-limiter 0.1.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.
Files changed (38) hide show
  1. package/README.md +347 -0
  2. package/dist/decorators/index.d.ts +3 -0
  3. package/dist/decorators/index.d.ts.map +1 -0
  4. package/dist/decorators/index.js +3 -0
  5. package/dist/decorators/index.js.map +1 -0
  6. package/dist/decorators/skip-throttle.decorator.d.ts +12 -0
  7. package/dist/decorators/skip-throttle.decorator.d.ts.map +1 -0
  8. package/dist/decorators/skip-throttle.decorator.js +14 -0
  9. package/dist/decorators/skip-throttle.decorator.js.map +1 -0
  10. package/dist/decorators/throttle.decorator.d.ts +13 -0
  11. package/dist/decorators/throttle.decorator.d.ts.map +1 -0
  12. package/dist/decorators/throttle.decorator.js +14 -0
  13. package/dist/decorators/throttle.decorator.js.map +1 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +12 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/throttler.constants.d.ts +4 -0
  19. package/dist/throttler.constants.d.ts.map +1 -0
  20. package/dist/throttler.constants.js +4 -0
  21. package/dist/throttler.constants.js.map +1 -0
  22. package/dist/throttler.guard.d.ts +18 -0
  23. package/dist/throttler.guard.d.ts.map +1 -0
  24. package/dist/throttler.guard.js +118 -0
  25. package/dist/throttler.guard.js.map +1 -0
  26. package/dist/throttler.interfaces.d.ts +21 -0
  27. package/dist/throttler.interfaces.d.ts.map +1 -0
  28. package/dist/throttler.interfaces.js +2 -0
  29. package/dist/throttler.interfaces.js.map +1 -0
  30. package/dist/throttler.module.d.ts +9 -0
  31. package/dist/throttler.module.d.ts.map +1 -0
  32. package/dist/throttler.module.js +65 -0
  33. package/dist/throttler.module.js.map +1 -0
  34. package/dist/trackers.d.ts +32 -0
  35. package/dist/trackers.d.ts.map +1 -0
  36. package/dist/trackers.js +84 -0
  37. package/dist/trackers.js.map +1 -0
  38. package/package.json +78 -0
package/README.md ADDED
@@ -0,0 +1,347 @@
1
+ # nestjs-advanced-rate-limiter
2
+
3
+ Advanced rate limiter for [NestJS](https://nestjs.com). Drop-in replacement for [`@nestjs/throttler`](https://docs.nestjs.com/security/rate-limiting) with real algorithms: token bucket (GCRA), sliding window, and fixed window. In-memory or Redis.
4
+
5
+ Built on [elysia-advanced-rate-limiter](https://github.com/mrtcmn/elysia-advanced-rate-limitter) core -- the same algorithms Stripe and Cloudflare run in production.
6
+
7
+ ## Why not `@nestjs/throttler`?
8
+
9
+ | | `@nestjs/throttler` | `nestjs-advanced-rate-limiter` |
10
+ |---|---|---|
11
+ | Algorithms | TTL-based counter | Token bucket (GCRA), sliding window, fixed window |
12
+ | Burst control | None | GCRA allows controlled bursts while enforcing steady rate |
13
+ | Boundary accuracy | Resets at TTL expiry | Sliding window eliminates 2x burst at boundaries |
14
+ | Storage efficiency | Stores timestamps | 21-64 bytes per key depending on algorithm |
15
+ | Redis | Via separate adapter | Built-in `RedisStore` with circuit breaker (`ResilientStore`) |
16
+ | Per-route override | Multiple throttler configs | `@Throttle()` decorator with any algorithm |
17
+ | User tracking | Manual | Built-in JWT/session `userTracker` |
18
+ | IP resolution | Basic | Cloudflare, nginx, proxy-depth-aware |
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install nestjs-advanced-rate-limiter
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```typescript
29
+ import { Module } from "@nestjs/common";
30
+ import { APP_GUARD } from "@nestjs/core";
31
+ import { ThrottlerModule, ThrottlerGuard } from "nestjs-advanced-rate-limiter";
32
+
33
+ @Module({
34
+ imports: [
35
+ ThrottlerModule.forRoot({
36
+ algorithm: { algorithm: "token-bucket", capacity: 100, refillRate: 10 },
37
+ }),
38
+ ],
39
+ providers: [
40
+ {
41
+ provide: APP_GUARD,
42
+ useExisting: ThrottlerGuard,
43
+ },
44
+ ],
45
+ })
46
+ export class AppModule {}
47
+ ```
48
+
49
+ Works out of the box. In-memory, token bucket, 100 capacity, 10 tokens/sec. Every route is protected.
50
+
51
+ ## Algorithms
52
+
53
+ Three algorithms. All O(1) time and space per request.
54
+
55
+ ### Token Bucket (GCRA) -- Default
56
+
57
+ Allows bursts up to `capacity` while enforcing a steady `refillRate` per second. Uses GCRA (Generic Cell Rate Algorithm) internally -- no refill loops, no locks, one number comparison.
58
+
59
+ ```typescript
60
+ ThrottlerModule.forRoot({
61
+ algorithm: { algorithm: "token-bucket", capacity: 100, refillRate: 10 },
62
+ })
63
+ ```
64
+
65
+ ```
66
+ capacity = 5, refillRate = 1/sec
67
+
68
+ t=0ms Req #1 ALLOWED (5 tokens -> 4)
69
+ t=0ms Req #2 ALLOWED (4 tokens -> 3)
70
+ t=0ms Req #3 ALLOWED (3 tokens -> 2)
71
+ t=0ms Req #4 ALLOWED (2 tokens -> 1)
72
+ t=0ms Req #5 ALLOWED (1 tokens -> 0)
73
+ t=0ms Req #6 DENIED retryAfter=1s
74
+ t=1000 Req #7 ALLOWED (1 token refilled)
75
+ ```
76
+
77
+ Storage: **21 bytes per key**
78
+
79
+ ### Fixed Window
80
+
81
+ Simplest option. Divides time into equal blocks, counts requests per block. Counter resets at each boundary.
82
+
83
+ ```typescript
84
+ ThrottlerModule.forRoot({
85
+ algorithm: { algorithm: "fixed-window", limit: 100, windowMs: 60_000 },
86
+ })
87
+ ```
88
+
89
+ Storage: **39 bytes per key**
90
+
91
+ > **Note:** Clients can send `limit` requests at the end of one window and `limit` at the start of the next, getting 2x the limit in a short span. If this matters, use sliding window.
92
+
93
+ ### Sliding Window
94
+
95
+ Blends current and previous window counts to eliminate the boundary burst. Uses the two-counter approximation.
96
+
97
+ ```typescript
98
+ ThrottlerModule.forRoot({
99
+ algorithm: { algorithm: "sliding-window", limit: 100, windowMs: 60_000 },
100
+ })
101
+ ```
102
+
103
+ Storage: **64 bytes per key**
104
+
105
+ ### Algorithm Comparison
106
+
107
+ ```
108
+ Fixed Window Sliding Window Token Bucket (GCRA)
109
+ +----------------+------------------+--------------------+
110
+ Time | O(1) | O(1) | O(1) |
111
+ Space/key | 39 bytes | 64 bytes | 21 bytes |
112
+ Redis cmds | 1 INCR | INCR + GET | 1 EVAL (Lua) |
113
+ Burst | 2x at edges | Smoothed | Controlled |
114
+ Precision | Exact | Approximate | Exact |
115
+ Best for | Simplicity | Smooth limiting | APIs / billing |
116
+ +----------------+------------------+--------------------+
117
+ ```
118
+
119
+ ## Storage
120
+
121
+ ### In-Memory (default)
122
+
123
+ No dependencies. Good for single-process deployments.
124
+
125
+ ```typescript
126
+ import { MemoryStore } from "nestjs-advanced-rate-limiter";
127
+
128
+ ThrottlerModule.forRoot({
129
+ store: new MemoryStore({ maxKeys: 100_000, cleanupIntervalMs: 60_000 }),
130
+ })
131
+ ```
132
+
133
+ ### Redis
134
+
135
+ For multi-instance deployments. Works with ioredis or any compatible client.
136
+
137
+ ```typescript
138
+ import Redis from "ioredis";
139
+ import { RedisStore } from "nestjs-advanced-rate-limiter";
140
+
141
+ ThrottlerModule.forRoot({
142
+ store: new RedisStore(new Redis()),
143
+ })
144
+ ```
145
+
146
+ ### Resilient Store
147
+
148
+ Wraps any store with circuit breaker. If Redis goes down, your app keeps running.
149
+
150
+ ```typescript
151
+ import { ResilientStore, RedisStore } from "nestjs-advanced-rate-limiter";
152
+
153
+ ThrottlerModule.forRoot({
154
+ store: new ResilientStore(new RedisStore(redis), {
155
+ failMode: "open", // allow traffic when store is down (default)
156
+ threshold: 5, // open circuit after 5 consecutive failures
157
+ cooldownMs: 30_000, // retry after 30s
158
+ onError: (err) => console.error(err),
159
+ }),
160
+ })
161
+ ```
162
+
163
+ ## Decorators
164
+
165
+ ### `@Throttle()` -- Per-Route Override
166
+
167
+ Override the default algorithm for specific routes or controllers.
168
+
169
+ ```typescript
170
+ import { Controller, Post, Get } from "@nestjs/common";
171
+ import { Throttle } from "nestjs-advanced-rate-limiter";
172
+
173
+ @Controller("auth")
174
+ export class AuthController {
175
+ @Throttle({ algorithm: "fixed-window", limit: 5, windowMs: 60_000 })
176
+ @Post("login")
177
+ login() {
178
+ // strict: 5 attempts per minute
179
+ }
180
+
181
+ @Get("profile")
182
+ profile() {
183
+ // uses the global default
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### `@SkipThrottle()` -- Skip Rate Limiting
189
+
190
+ Disable rate limiting for specific routes or entire controllers.
191
+
192
+ ```typescript
193
+ import { Controller, Get } from "@nestjs/common";
194
+ import { SkipThrottle } from "nestjs-advanced-rate-limiter";
195
+
196
+ @SkipThrottle()
197
+ @Controller("health")
198
+ export class HealthController {
199
+ @Get()
200
+ check() {
201
+ return "ok";
202
+ }
203
+ }
204
+ ```
205
+
206
+ ## User Tracking
207
+
208
+ Built-in JWT/session-based tracker. Identifies users from `Authorization: Bearer <token>` or session cookies. Falls back to IP.
209
+
210
+ ```typescript
211
+ import { userTracker } from "nestjs-advanced-rate-limiter";
212
+
213
+ ThrottlerModule.forRoot({
214
+ getTracker: userTracker(),
215
+ })
216
+
217
+ // Custom cookie name
218
+ ThrottlerModule.forRoot({
219
+ getTracker: userTracker({ cookieName: "session" }),
220
+ })
221
+
222
+ // Custom JWT parser
223
+ ThrottlerModule.forRoot({
224
+ getTracker: userTracker({
225
+ parseJwt: (token) => {
226
+ const payload = JSON.parse(atob(token.split(".")[1]));
227
+ return payload.sub;
228
+ },
229
+ }),
230
+ })
231
+
232
+ // Disable IP fallback
233
+ ThrottlerModule.forRoot({
234
+ getTracker: userTracker({ fallbackToIp: false }),
235
+ })
236
+ ```
237
+
238
+ ### IP Resolution
239
+
240
+ The default tracker (and `userTracker` fallback) checks headers in this order:
241
+
242
+ | Priority | Header | Set by |
243
+ |---|---|---|
244
+ | 1 | `cf-connecting-ip` | Cloudflare |
245
+ | 2 | `x-real-ip` | nginx / load balancer |
246
+ | 3 | `x-forwarded-for` (first IP) | Any proxy |
247
+ | 4 | `req.ip` / `req.socket.remoteAddress` | Express / Fastify |
248
+
249
+ Works with both Express and Fastify.
250
+
251
+ ## Async Configuration
252
+
253
+ Use `forRootAsync` when you need to inject dependencies (e.g., `ConfigService`).
254
+
255
+ ```typescript
256
+ import { ThrottlerModule } from "nestjs-advanced-rate-limiter";
257
+ import { ConfigModule, ConfigService } from "@nestjs/config";
258
+
259
+ @Module({
260
+ imports: [
261
+ ThrottlerModule.forRootAsync({
262
+ imports: [ConfigModule],
263
+ inject: [ConfigService],
264
+ useFactory: (config: ConfigService) => ({
265
+ algorithm: {
266
+ algorithm: "token-bucket",
267
+ capacity: config.get("RATE_LIMIT_CAPACITY", 100),
268
+ refillRate: config.get("RATE_LIMIT_REFILL", 10),
269
+ },
270
+ }),
271
+ }),
272
+ ],
273
+ })
274
+ export class AppModule {}
275
+ ```
276
+
277
+ ## All Options
278
+
279
+ ```typescript
280
+ ThrottlerModule.forRoot({
281
+ // Algorithm (default: token-bucket, 100 capacity, 10/sec)
282
+ algorithm: { algorithm: "token-bucket", capacity: 100, refillRate: 10 },
283
+
284
+ // Storage backend (default: in-memory)
285
+ store: new MemoryStore(),
286
+
287
+ // Key prefix for storage (default: "rl:")
288
+ prefix: "rl:",
289
+
290
+ // Custom tracker function (default: IP-based)
291
+ getTracker: (req) => req.ip,
292
+
293
+ // Programmatic skip (default: none)
294
+ skipIf: (req) => req.url.includes("/health"),
295
+
296
+ // Ignore specific user agents (default: [])
297
+ ignoreUserAgents: [/googlebot/i, /bingbot/i],
298
+
299
+ // Custom error message or response factory (default: "Too Many Requests")
300
+ errorMessage: "Rate limit exceeded",
301
+ // or
302
+ errorMessage: (result) => ({
303
+ error: "rate_limited",
304
+ retryAfter: Math.ceil(result.retryAfterMs / 1000),
305
+ }),
306
+ })
307
+ ```
308
+
309
+ ## Response Headers
310
+
311
+ Every response includes rate limit headers:
312
+
313
+ | Header | When | Example |
314
+ |---|---|---|
315
+ | `X-RateLimit-Limit` | Always | `100` |
316
+ | `X-RateLimit-Remaining` | Always | `95` |
317
+ | `X-RateLimit-Reset` | Always | `1712678460` (Unix timestamp) |
318
+ | `Retry-After` | 429 only | `5` (seconds) |
319
+
320
+ ## Migrating from `@nestjs/throttler`
321
+
322
+ ```diff
323
+ - import { ThrottlerModule, ThrottlerGuard } from "@nestjs/throttler";
324
+ + import { ThrottlerModule, ThrottlerGuard } from "nestjs-advanced-rate-limiter";
325
+
326
+ @Module({
327
+ imports: [
328
+ - ThrottlerModule.forRoot({ ttl: 60000, limit: 10 }),
329
+ + ThrottlerModule.forRoot({
330
+ + algorithm: { algorithm: "fixed-window", limit: 10, windowMs: 60_000 },
331
+ + }),
332
+ ],
333
+ providers: [{ provide: APP_GUARD, useExisting: ThrottlerGuard }],
334
+ })
335
+ export class AppModule {}
336
+ ```
337
+
338
+ The `@SkipThrottle()` decorator works the same way. Replace `@Throttle()` with the new config-based syntax:
339
+
340
+ ```diff
341
+ - @Throttle({ default: { limit: 5, ttl: 60000 } })
342
+ + @Throttle({ algorithm: "fixed-window", limit: 5, windowMs: 60_000 })
343
+ ```
344
+
345
+ ## License
346
+
347
+ MIT
@@ -0,0 +1,3 @@
1
+ export { SkipThrottle } from "./skip-throttle.decorator";
2
+ export { Throttle } from "./throttle.decorator";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { SkipThrottle } from "./skip-throttle.decorator";
2
+ export { Throttle } from "./throttle.decorator";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Skip rate limiting for a specific controller or route.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * @SkipThrottle()
7
+ * @Get("health")
8
+ * health() { return "ok"; }
9
+ * ```
10
+ */
11
+ export declare const SkipThrottle: () => import("@nestjs/common").CustomDecorator<string>;
12
+ //# sourceMappingURL=skip-throttle.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip-throttle.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/skip-throttle.decorator.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,wDAA0C,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { SetMetadata } from "@nestjs/common";
2
+ import { THROTTLER_SKIP } from "../throttler.constants";
3
+ /**
4
+ * Skip rate limiting for a specific controller or route.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * @SkipThrottle()
9
+ * @Get("health")
10
+ * health() { return "ok"; }
11
+ * ```
12
+ */
13
+ export const SkipThrottle = () => SetMetadata(THROTTLER_SKIP, true);
14
+ //# sourceMappingURL=skip-throttle.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip-throttle.decorator.js","sourceRoot":"","sources":["../../src/decorators/skip-throttle.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { AlgorithmConfig } from "elysia-advanced-rate-limiter";
2
+ /**
3
+ * Override the default rate limit algorithm for a specific controller or route.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * @Throttle({ algorithm: "fixed-window", limit: 5, windowMs: 60_000 })
8
+ * @Post("login")
9
+ * login() { ... }
10
+ * ```
11
+ */
12
+ export declare const Throttle: (config: AlgorithmConfig) => import("@nestjs/common").CustomDecorator<string>;
13
+ //# sourceMappingURL=throttle.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/throttle.decorator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAGpE;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,QAAQ,eAAe,qDACN,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { SetMetadata } from "@nestjs/common";
2
+ import { THROTTLER_ALGORITHM } from "../throttler.constants";
3
+ /**
4
+ * Override the default rate limit algorithm for a specific controller or route.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * @Throttle({ algorithm: "fixed-window", limit: 5, windowMs: 60_000 })
9
+ * @Post("login")
10
+ * login() { ... }
11
+ * ```
12
+ */
13
+ export const Throttle = (config) => SetMetadata(THROTTLER_ALGORITHM, config);
14
+ //# sourceMappingURL=throttle.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.decorator.js","sourceRoot":"","sources":["../../src/decorators/throttle.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAuB,EAAE,EAAE,CAClD,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export { SkipThrottle, Throttle } from "./decorators";
2
+ export { ThrottlerGuard } from "./throttler.guard";
3
+ export { ThrottlerModule } from "./throttler.module";
4
+ export { userTracker } from "./trackers";
5
+ export type { UserTrackerOptions } from "./trackers";
6
+ export { THROTTLER_OPTIONS } from "./throttler.constants";
7
+ export type { ThrottlerAsyncOptions, ThrottlerModuleOptions, ThrottlerOptionsFactory, } from "./throttler.interfaces";
8
+ export type { AlgorithmConfig, RateLimitResult, RateLimitStore, TokenBucketConfig, SlidingWindowConfig, FixedWindowConfig, } from "elysia-advanced-rate-limiter";
9
+ export { MemoryStore, RedisStore, ResilientStore, } from "elysia-advanced-rate-limiter";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGtD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG1D,YAAY,EACV,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAGhC,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,WAAW,EACX,UAAU,EACV,cAAc,GACf,MAAM,8BAA8B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // Decorators
2
+ export { SkipThrottle, Throttle } from "./decorators";
3
+ // Guard
4
+ export { ThrottlerGuard } from "./throttler.guard";
5
+ // Module
6
+ export { ThrottlerModule } from "./throttler.module";
7
+ // Trackers
8
+ export { userTracker } from "./trackers";
9
+ // Constants
10
+ export { THROTTLER_OPTIONS } from "./throttler.constants";
11
+ export { MemoryStore, RedisStore, ResilientStore, } from "elysia-advanced-rate-limiter";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEtD,QAAQ;AACR,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,SAAS;AACT,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,WAAW;AACX,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,YAAY;AACZ,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAmB1D,OAAO,EACL,WAAW,EACX,UAAU,EACV,cAAc,GACf,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare const THROTTLER_OPTIONS = "THROTTLER_OPTIONS";
2
+ export declare const THROTTLER_ALGORITHM = "THROTTLER:algorithm";
3
+ export declare const THROTTLER_SKIP = "THROTTLER:skip";
4
+ //# sourceMappingURL=throttler.constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttler.constants.d.ts","sourceRoot":"","sources":["../src/throttler.constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AACrD,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AACzD,eAAO,MAAM,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export const THROTTLER_OPTIONS = "THROTTLER_OPTIONS";
2
+ export const THROTTLER_ALGORITHM = "THROTTLER:algorithm";
3
+ export const THROTTLER_SKIP = "THROTTLER:skip";
4
+ //# sourceMappingURL=throttler.constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttler.constants.js","sourceRoot":"","sources":["../src/throttler.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AACrD,MAAM,CAAC,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AACzD,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { CanActivate, ExecutionContext } from "@nestjs/common";
2
+ import { Reflector } from "@nestjs/core";
3
+ import type { ThrottlerModuleOptions } from "./throttler.interfaces";
4
+ export declare class ThrottlerGuard implements CanActivate {
5
+ private readonly options;
6
+ private readonly reflector;
7
+ private readonly store;
8
+ private readonly defaultAlgorithm;
9
+ private readonly prefix;
10
+ private readonly skipIf?;
11
+ private readonly getTracker;
12
+ private readonly ignoreUserAgents;
13
+ private readonly errorMessage;
14
+ constructor(options: ThrottlerModuleOptions, reflector: Reflector);
15
+ canActivate(context: ExecutionContext): Promise<boolean>;
16
+ private defaultGetTracker;
17
+ }
18
+ //# sourceMappingURL=throttler.guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttler.guard.d.ts","sourceRoot":"","sources":["../src/throttler.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,gBAAgB,EAKjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAQzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAkBrE,qBACa,cAAe,YAAW,WAAW;IAYnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAZ5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAClD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAW;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAEsB;gBAGL,OAAO,EAAE,sBAAsB,EAC1D,SAAS,EAAE,SAAS;IAejC,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAiE9D,OAAO,CAAC,iBAAiB;CAe1B"}
@@ -0,0 +1,118 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { HttpException, HttpStatus, Inject, Injectable, } from "@nestjs/common";
14
+ import { Reflector } from "@nestjs/core";
15
+ import { MemoryStore } from "elysia-advanced-rate-limiter";
16
+ import { THROTTLER_ALGORITHM, THROTTLER_OPTIONS, THROTTLER_SKIP } from "./throttler.constants";
17
+ const MAX_RETRY_MS = 2_147_483_647;
18
+ function sanitize(val) {
19
+ return Number.isFinite(val) ? val : MAX_RETRY_MS;
20
+ }
21
+ function computeTtlMs(config) {
22
+ switch (config.algorithm) {
23
+ case "token-bucket":
24
+ return Math.ceil((config.capacity / config.refillRate) * 1000) + 60_000;
25
+ case "sliding-window":
26
+ case "fixed-window":
27
+ return config.windowMs + 60_000;
28
+ }
29
+ }
30
+ let ThrottlerGuard = class ThrottlerGuard {
31
+ options;
32
+ reflector;
33
+ store;
34
+ defaultAlgorithm;
35
+ prefix;
36
+ skipIf;
37
+ getTracker;
38
+ ignoreUserAgents;
39
+ errorMessage;
40
+ constructor(options, reflector) {
41
+ this.options = options;
42
+ this.reflector = reflector;
43
+ this.defaultAlgorithm = options.algorithm ?? {
44
+ algorithm: "token-bucket",
45
+ capacity: 100,
46
+ refillRate: 10,
47
+ };
48
+ this.store = options.store ?? new MemoryStore({ maxKeys: 100_000 });
49
+ this.prefix = options.prefix ?? "rl:";
50
+ this.skipIf = options.skipIf;
51
+ this.getTracker = options.getTracker ?? this.defaultGetTracker;
52
+ this.ignoreUserAgents = options.ignoreUserAgents ?? [];
53
+ this.errorMessage = options.errorMessage ?? "Too Many Requests";
54
+ }
55
+ async canActivate(context) {
56
+ const skipClass = this.reflector.get(THROTTLER_SKIP, context.getClass());
57
+ if (skipClass)
58
+ return true;
59
+ const skipHandler = this.reflector.get(THROTTLER_SKIP, context.getHandler());
60
+ if (skipHandler)
61
+ return true;
62
+ const req = context.switchToHttp().getRequest();
63
+ const res = context.switchToHttp().getResponse();
64
+ if (this.skipIf?.(req))
65
+ return true;
66
+ const userAgent = req.headers?.["user-agent"] ?? "";
67
+ if (this.ignoreUserAgents.some((regex) => regex.test(userAgent))) {
68
+ return true;
69
+ }
70
+ const algorithmConfig = this.reflector.getAllAndOverride(THROTTLER_ALGORITHM, [
71
+ context.getHandler(),
72
+ context.getClass(),
73
+ ]) ?? this.defaultAlgorithm;
74
+ const tracker = this.getTracker(req);
75
+ const storeKey = `${this.prefix}${tracker}`;
76
+ const ttlMs = computeTtlMs(algorithmConfig);
77
+ const nowMs = Date.now();
78
+ const result = await this.store.check(storeKey, algorithmConfig, nowMs, ttlMs);
79
+ const remaining = sanitize(result.remaining);
80
+ const resetMs = sanitize(result.resetMs);
81
+ const resetTimestamp = String(Math.ceil((nowMs + resetMs) / 1000));
82
+ res.header("X-RateLimit-Limit", String(result.limit));
83
+ res.header("X-RateLimit-Remaining", String(remaining));
84
+ res.header("X-RateLimit-Reset", resetTimestamp);
85
+ if (!result.allowed) {
86
+ const retryAfterMs = sanitize(result.retryAfterMs);
87
+ const retryAfter = String(Math.ceil(retryAfterMs / 1000));
88
+ res.header("Retry-After", retryAfter);
89
+ const body = typeof this.errorMessage === "function"
90
+ ? this.errorMessage(result)
91
+ : { statusCode: 429, message: this.errorMessage };
92
+ throw new HttpException(body, HttpStatus.TOO_MANY_REQUESTS);
93
+ }
94
+ return true;
95
+ }
96
+ defaultGetTracker(req) {
97
+ const cfIp = req.headers?.["cf-connecting-ip"];
98
+ if (cfIp)
99
+ return cfIp;
100
+ const realIp = req.headers?.["x-real-ip"];
101
+ if (realIp)
102
+ return realIp;
103
+ const xff = req.headers?.["x-forwarded-for"];
104
+ if (xff) {
105
+ const first = typeof xff === "string" ? xff.split(",")[0]?.trim() : xff[0];
106
+ if (first)
107
+ return first;
108
+ }
109
+ return req.ip ?? req.socket?.remoteAddress ?? "anonymous";
110
+ }
111
+ };
112
+ ThrottlerGuard = __decorate([
113
+ Injectable(),
114
+ __param(0, Inject(THROTTLER_OPTIONS)),
115
+ __metadata("design:paramtypes", [Object, Reflector])
116
+ ], ThrottlerGuard);
117
+ export { ThrottlerGuard };
118
+ //# sourceMappingURL=throttler.guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttler.guard.js","sourceRoot":"","sources":["../src/throttler.guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { ModuleMetadata, Type } from "@nestjs/common";
2
+ import type { AlgorithmConfig, RateLimitResult, RateLimitStore } from "elysia-advanced-rate-limiter";
3
+ export interface ThrottlerModuleOptions {
4
+ algorithm?: AlgorithmConfig;
5
+ store?: RateLimitStore;
6
+ prefix?: string;
7
+ errorMessage?: string | ((result: RateLimitResult) => string | object);
8
+ skipIf?: (request: any) => boolean;
9
+ getTracker?: (request: any) => string;
10
+ ignoreUserAgents?: RegExp[];
11
+ }
12
+ export interface ThrottlerOptionsFactory {
13
+ createThrottlerOptions(): ThrottlerModuleOptions | Promise<ThrottlerModuleOptions>;
14
+ }
15
+ export interface ThrottlerAsyncOptions extends Pick<ModuleMetadata, "imports"> {
16
+ useExisting?: Type<ThrottlerOptionsFactory>;
17
+ useClass?: Type<ThrottlerOptionsFactory>;
18
+ useFactory?: (...args: any[]) => ThrottlerModuleOptions | Promise<ThrottlerModuleOptions>;
19
+ inject?: any[];
20
+ }
21
+ //# sourceMappingURL=throttler.interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttler.interfaces.d.ts","sourceRoot":"","sources":["../src/throttler.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACf,MAAM,8BAA8B,CAAC;AAEtC,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,eAAe,KAAK,MAAM,GAAG,MAAM,CAAC,CAAC;IACvE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IACnC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,MAAM,CAAC;IACtC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,sBAAsB,IAClB,sBAAsB,GACtB,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC;IAC5E,WAAW,CAAC,EAAE,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACzC,UAAU,CAAC,EAAE,CACX,GAAG,IAAI,EAAE,GAAG,EAAE,KACX,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC9D,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;CAChB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=throttler.interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttler.interfaces.js","sourceRoot":"","sources":["../src/throttler.interfaces.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import { DynamicModule } from "@nestjs/common";
2
+ import type { ThrottlerAsyncOptions, ThrottlerModuleOptions } from "./throttler.interfaces";
3
+ export declare class ThrottlerModule {
4
+ static forRoot(options?: ThrottlerModuleOptions): DynamicModule;
5
+ static forRootAsync(options: ThrottlerAsyncOptions): DynamicModule;
6
+ private static createAsyncProviders;
7
+ private static createAsyncOptionsProvider;
8
+ }
9
+ //# sourceMappingURL=throttler.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttler.module.d.ts","sourceRoot":"","sources":["../src/throttler.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAoB,MAAM,gBAAgB,CAAC;AAGjE,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EAEvB,MAAM,wBAAwB,CAAC;AAEhC,qBACa,eAAe;IAC1B,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,sBAA2B,GAAG,aAAa;IAYnE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,qBAAqB,GAAG,aAAa;IAWlE,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAiBnC,OAAO,CAAC,MAAM,CAAC,0BAA0B;CAmB1C"}
@@ -0,0 +1,65 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var ThrottlerModule_1;
8
+ import { Module } from "@nestjs/common";
9
+ import { THROTTLER_OPTIONS } from "./throttler.constants";
10
+ import { ThrottlerGuard } from "./throttler.guard";
11
+ let ThrottlerModule = ThrottlerModule_1 = class ThrottlerModule {
12
+ static forRoot(options = {}) {
13
+ return {
14
+ module: ThrottlerModule_1,
15
+ global: true,
16
+ providers: [
17
+ { provide: THROTTLER_OPTIONS, useValue: options },
18
+ ThrottlerGuard,
19
+ ],
20
+ exports: [THROTTLER_OPTIONS, ThrottlerGuard],
21
+ };
22
+ }
23
+ static forRootAsync(options) {
24
+ const providers = this.createAsyncProviders(options);
25
+ return {
26
+ module: ThrottlerModule_1,
27
+ global: true,
28
+ imports: options.imports ?? [],
29
+ providers: [...providers, ThrottlerGuard],
30
+ exports: [THROTTLER_OPTIONS, ThrottlerGuard],
31
+ };
32
+ }
33
+ static createAsyncProviders(options) {
34
+ if (options.useExisting || options.useFactory) {
35
+ return [this.createAsyncOptionsProvider(options)];
36
+ }
37
+ if (options.useClass) {
38
+ return [
39
+ this.createAsyncOptionsProvider(options),
40
+ { provide: options.useClass, useClass: options.useClass },
41
+ ];
42
+ }
43
+ return [{ provide: THROTTLER_OPTIONS, useValue: {} }];
44
+ }
45
+ static createAsyncOptionsProvider(options) {
46
+ if (options.useFactory) {
47
+ return {
48
+ provide: THROTTLER_OPTIONS,
49
+ useFactory: options.useFactory,
50
+ inject: options.inject ?? [],
51
+ };
52
+ }
53
+ const inject = options.useExisting ?? options.useClass;
54
+ return {
55
+ provide: THROTTLER_OPTIONS,
56
+ useFactory: (factory) => factory.createThrottlerOptions(),
57
+ inject: inject ? [inject] : [],
58
+ };
59
+ }
60
+ };
61
+ ThrottlerModule = ThrottlerModule_1 = __decorate([
62
+ Module({})
63
+ ], ThrottlerModule);
64
+ export { ThrottlerModule };
65
+ //# sourceMappingURL=throttler.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttler.module.js","sourceRoot":"","sources":["../src/throttler.module.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAiB,MAAM,EAAY,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAQ5C,IAAM,eAAe,uBAArB,MAAM,eAAe;IAC1B,MAAM,CAAC,OAAO,CAAC,UAAkC,EAAE;QACjD,OAAO;YACL,MAAM,EAAE,iBAAe;YACvB,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE;gBACT,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE;gBACjD,cAAc;aACf;YACD,OAAO,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SAC7C,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,OAA8B;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACrD,OAAO;YACL,MAAM,EAAE,iBAAe;YACvB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;YAC9B,SAAS,EAAE,CAAC,GAAG,SAAS,EAAE,cAAc,CAAC;YACzC,OAAO,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SAC7C,CAAC;IACJ,CAAC;IAEO,MAAM,CAAC,oBAAoB,CACjC,OAA8B;QAE9B,IAAI,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,OAAO;gBACL,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC;gBACxC,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE;aAC1D,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IAEO,MAAM,CAAC,0BAA0B,CACvC,OAA8B;QAE9B,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACvB,OAAO;gBACL,OAAO,EAAE,iBAAiB;gBAC1B,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;aAC7B,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC;QACvD,OAAO;YACL,OAAO,EAAE,iBAAiB;YAC1B,UAAU,EAAE,CAAC,OAAgC,EAAE,EAAE,CAC/C,OAAO,CAAC,sBAAsB,EAAE;YAClC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;SAC/B,CAAC;IACJ,CAAC;CACF,CAAA;AA5DY,eAAe;IAD3B,MAAM,CAAC,EAAE,CAAC;GACE,eAAe,CA4D3B"}
@@ -0,0 +1,32 @@
1
+ export interface UserTrackerOptions {
2
+ /**
3
+ * Cookie name to look for a JWT session token.
4
+ * @default "better-auth.session_token"
5
+ */
6
+ cookieName?: string;
7
+ /**
8
+ * Custom JWT parser. Receives the raw token, returns a user ID or null.
9
+ * Default: decodes JWT payload and extracts sub/userId/id.
10
+ */
11
+ parseJwt?: (token: string) => string | null;
12
+ /**
13
+ * Fall back to IP-based tracking when no user identity is found.
14
+ * @default true
15
+ */
16
+ fallbackToIp?: boolean;
17
+ }
18
+ /**
19
+ * NestJS-compatible tracker that identifies users from JWT/session,
20
+ * with optional IP fallback.
21
+ *
22
+ * Works with Express and Fastify request objects.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * ThrottlerModule.forRoot({
27
+ * getTracker: userTracker({ cookieName: "session", fallbackToIp: true }),
28
+ * })
29
+ * ```
30
+ */
31
+ export declare function userTracker(options?: UserTrackerOptions): (req: any) => string;
32
+ //# sourceMappingURL=trackers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trackers.d.ts","sourceRoot":"","sources":["../src/trackers.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAE5C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CA6ClF"}
@@ -0,0 +1,84 @@
1
+ function defaultParseJwt(token) {
2
+ try {
3
+ const parts = token.split(".");
4
+ if (parts.length !== 3)
5
+ return null;
6
+ const payload = JSON.parse(atob(parts[1]));
7
+ return (payload.sub ?? payload.userId ?? payload.id);
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ function extractIp(req) {
14
+ const cfIp = req.headers?.["cf-connecting-ip"];
15
+ if (cfIp)
16
+ return cfIp;
17
+ const realIp = req.headers?.["x-real-ip"];
18
+ if (realIp)
19
+ return realIp;
20
+ const xff = req.headers?.["x-forwarded-for"];
21
+ if (xff) {
22
+ const first = typeof xff === "string" ? xff.split(",")[0]?.trim() : xff[0];
23
+ if (first)
24
+ return first;
25
+ }
26
+ return req.ip ?? req.socket?.remoteAddress ?? "anonymous";
27
+ }
28
+ /**
29
+ * NestJS-compatible tracker that identifies users from JWT/session,
30
+ * with optional IP fallback.
31
+ *
32
+ * Works with Express and Fastify request objects.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * ThrottlerModule.forRoot({
37
+ * getTracker: userTracker({ cookieName: "session", fallbackToIp: true }),
38
+ * })
39
+ * ```
40
+ */
41
+ export function userTracker(options = {}) {
42
+ const cookieName = options.cookieName ?? "better-auth.session_token";
43
+ const parseJwt = options.parseJwt ?? defaultParseJwt;
44
+ const fallbackToIp = options.fallbackToIp ?? true;
45
+ return (req) => {
46
+ // Try Authorization header
47
+ const auth = req.headers?.["authorization"];
48
+ if (auth?.startsWith("Bearer ")) {
49
+ const userId = parseJwt(auth.slice(7));
50
+ if (userId)
51
+ return `user:${userId}`;
52
+ }
53
+ // Try cookie
54
+ const cookieHeader = typeof req.cookies === "object"
55
+ ? req.cookies?.[cookieName]
56
+ : req.headers?.["cookie"];
57
+ if (cookieHeader) {
58
+ let token;
59
+ if (typeof req.cookies === "object") {
60
+ // Express with cookie-parser / Fastify cookie
61
+ token = req.cookies[cookieName];
62
+ }
63
+ else {
64
+ // Raw cookie header
65
+ const match = cookieHeader
66
+ .split(";")
67
+ .map((c) => c.trim())
68
+ .find((c) => c.startsWith(`${cookieName}=`));
69
+ if (match) {
70
+ token = match.split("=").slice(1).join("=");
71
+ }
72
+ }
73
+ if (token) {
74
+ const userId = parseJwt(token);
75
+ if (userId)
76
+ return `user:${userId}`;
77
+ }
78
+ }
79
+ if (fallbackToIp)
80
+ return extractIp(req);
81
+ return "anonymous";
82
+ };
83
+ }
84
+ //# sourceMappingURL=trackers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trackers.js","sourceRoot":"","sources":["../src/trackers.ts"],"names":[],"mappings":"AAoBA,SAAS,eAAe,CAAC,KAAa;IACpC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;QAC5C,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,EAAE,CAAkB,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,GAAQ;IACzB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,kBAAkB,CAAC,CAAC;IAC/C,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,iBAAiB,CAAC,CAAC;IAC7C,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3E,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IAED,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,WAAW,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,UAA8B,EAAE;IAC1D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,2BAA2B,CAAC;IACrE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC;IACrD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC;IAElD,OAAO,CAAC,GAAQ,EAAU,EAAE;QAC1B,2BAA2B;QAC3B,MAAM,IAAI,GAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,eAAe,CAAC,CAAC;QAChE,IAAI,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACvC,IAAI,MAAM;gBAAE,OAAO,QAAQ,MAAM,EAAE,CAAC;QACtC,CAAC;QAED,aAAa;QACb,MAAM,YAAY,GAChB,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;YAC7B,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC;YAC3B,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC;QAE9B,IAAI,YAAY,EAAE,CAAC;YACjB,IAAI,KAAyB,CAAC;YAE9B,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACpC,8CAA8C;gBAC9C,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAClC,CAAC;iBAAM,CAAC;gBACN,oBAAoB;gBACpB,MAAM,KAAK,GAAG,YAAY;qBACvB,KAAK,CAAC,GAAG,CAAC;qBACV,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;qBAC5B,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC;gBACvD,IAAI,KAAK,EAAE,CAAC;oBACV,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YAED,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAC/B,IAAI,MAAM;oBAAE,OAAO,QAAQ,MAAM,EAAE,CAAC;YACtC,CAAC;QACH,CAAC;QAED,IAAI,YAAY;YAAE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "nestjs-advanced-rate-limiter",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Advanced rate limiter for NestJS. Token bucket (GCRA), sliding window, fixed window. In-memory or Redis. Drop-in replacement for @nestjs/throttler.",
6
+ "author": "mrtcmn",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/mrtcmn/nestjs-advanced-rate-limitter.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/mrtcmn/nestjs-advanced-rate-limitter/issues"
14
+ },
15
+ "homepage": "https://github.com/mrtcmn/nestjs-advanced-rate-limitter#readme",
16
+ "keywords": [
17
+ "nestjs",
18
+ "rate-limiter",
19
+ "rate-limiting",
20
+ "throttler",
21
+ "token-bucket",
22
+ "gcra",
23
+ "sliding-window",
24
+ "fixed-window",
25
+ "redis",
26
+ "guard",
27
+ "decorator",
28
+ "middleware"
29
+ ],
30
+ "main": "dist/index.js",
31
+ "types": "dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "scripts": {
44
+ "build": "rm -rf dist && tsc --project tsconfig.build.json",
45
+ "typecheck": "tsc --noEmit",
46
+ "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
47
+ "test:watch": "jest --watch",
48
+ "prepublishOnly": "npm run build"
49
+ },
50
+ "dependencies": {
51
+ "elysia-advanced-rate-limiter": "^0.4.0"
52
+ },
53
+ "peerDependencies": {
54
+ "@nestjs/common": ">=10.0.0",
55
+ "@nestjs/core": ">=10.0.0",
56
+ "reflect-metadata": ">=0.1.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "reflect-metadata": {
60
+ "optional": false
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "@nestjs/common": "^10.0.0",
65
+ "@nestjs/core": "^10.0.0",
66
+ "@nestjs/platform-express": "^11.1.18",
67
+ "@nestjs/testing": "^11.1.18",
68
+ "@types/express": "^5.0.6",
69
+ "@types/jest": "^30.0.0",
70
+ "@types/supertest": "^7.2.0",
71
+ "jest": "^30.3.0",
72
+ "reflect-metadata": "^0.2.2",
73
+ "rxjs": "^7.8.1",
74
+ "supertest": "^7.2.2",
75
+ "ts-jest": "^29.4.9",
76
+ "typescript": "^5.8.3"
77
+ }
78
+ }