nest-ratelimit-pro 1.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/LICENSE +21 -0
- package/README.md +359 -0
- package/dist/constants.d.ts +13 -0
- package/dist/constants.js +17 -0
- package/dist/constants.js.map +1 -0
- package/dist/decorators/rate-limit.decorator.d.ts +3 -0
- package/dist/decorators/rate-limit.decorator.js +14 -0
- package/dist/decorators/rate-limit.decorator.js.map +1 -0
- package/dist/exceptions/rate-limit.exception.d.ts +5 -0
- package/dist/exceptions/rate-limit.exception.js +17 -0
- package/dist/exceptions/rate-limit.exception.js.map +1 -0
- package/dist/guards/rate-limit.guard.d.ts +12 -0
- package/dist/guards/rate-limit.guard.js +75 -0
- package/dist/guards/rate-limit.guard.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/rate-limit-options.interface.d.ts +45 -0
- package/dist/interfaces/rate-limit-options.interface.js +19 -0
- package/dist/interfaces/rate-limit-options.interface.js.map +1 -0
- package/dist/interfaces/rate-limit.interface.d.ts +20 -0
- package/dist/interfaces/rate-limit.interface.js +3 -0
- package/dist/interfaces/rate-limit.interface.js.map +1 -0
- package/dist/interfaces/storage.interface.d.ts +12 -0
- package/dist/interfaces/storage.interface.js +3 -0
- package/dist/interfaces/storage.interface.js.map +1 -0
- package/dist/rate-limit.module.d.ts +8 -0
- package/dist/rate-limit.module.js +76 -0
- package/dist/rate-limit.module.js.map +1 -0
- package/dist/services/rate-limit.service.d.ts +18 -0
- package/dist/services/rate-limit.service.js +164 -0
- package/dist/services/rate-limit.service.js.map +1 -0
- package/dist/storage/memory.storage.d.ts +19 -0
- package/dist/storage/memory.storage.js +124 -0
- package/dist/storage/memory.storage.js.map +1 -0
- package/dist/utils/key.utils.d.ts +5 -0
- package/dist/utils/key.utils.js +47 -0
- package/dist/utils/key.utils.js.map +1 -0
- package/dist/utils/time-utils.d.ts +4 -0
- package/dist/utils/time-utils.js +30 -0
- package/dist/utils/time-utils.js.map +1 -0
- package/package.json +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alireza Aminzadeh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# nest-ratelimit-pro
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/nest-ratelimit-pro)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/syeedalireza/nest-ratelimit-pro)
|
|
6
|
+
|
|
7
|
+
Advanced rate limiting and throttling middleware for NestJS with support for multiple algorithms, distributed systems, and flexible storage backends.
|
|
8
|
+
|
|
9
|
+
## 🚀 Features
|
|
10
|
+
|
|
11
|
+
- **Multiple Rate Limiting Algorithms**
|
|
12
|
+
- Fixed Window Counter
|
|
13
|
+
- Sliding Window Log
|
|
14
|
+
- Token Bucket
|
|
15
|
+
- Sliding Window Counter
|
|
16
|
+
|
|
17
|
+
- **Flexible Key Strategies**
|
|
18
|
+
- IP-based rate limiting
|
|
19
|
+
- User ID-based (requires authentication)
|
|
20
|
+
- API Key-based
|
|
21
|
+
- Custom key extraction
|
|
22
|
+
|
|
23
|
+
- **Storage Backends**
|
|
24
|
+
- In-memory storage (single instance)
|
|
25
|
+
- Redis support (distributed)
|
|
26
|
+
- Custom storage adapters
|
|
27
|
+
|
|
28
|
+
- **Advanced Features**
|
|
29
|
+
- Cost-based rate limiting
|
|
30
|
+
- Per-route configuration
|
|
31
|
+
- Skip conditions
|
|
32
|
+
- Automatic cleanup
|
|
33
|
+
- Response headers
|
|
34
|
+
- Custom error messages
|
|
35
|
+
|
|
36
|
+
- **Production-Ready**
|
|
37
|
+
- TypeScript with strict typing
|
|
38
|
+
- Comprehensive test coverage
|
|
39
|
+
- Docker support
|
|
40
|
+
- Monitoring & metrics ready
|
|
41
|
+
|
|
42
|
+
## 📦 Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install nest-ratelimit-pro
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Optional dependencies:**
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# For Redis storage
|
|
52
|
+
npm install ioredis
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 🔧 Quick Start
|
|
56
|
+
|
|
57
|
+
### 1. Basic Setup
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { Module } from '@nestjs/common';
|
|
61
|
+
import { RateLimitModule } from 'nest-ratelimit-pro';
|
|
62
|
+
|
|
63
|
+
@Module({
|
|
64
|
+
imports: [
|
|
65
|
+
RateLimitModule.forRoot({
|
|
66
|
+
limit: 100, // 100 requests
|
|
67
|
+
duration: '1m', // per 1 minute
|
|
68
|
+
keyStrategy: 'ip', // Rate limit by IP address
|
|
69
|
+
}),
|
|
70
|
+
],
|
|
71
|
+
})
|
|
72
|
+
export class AppModule {}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Apply Guard to Controllers
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { Controller, Get, UseGuards } from '@nestjs/common';
|
|
79
|
+
import { RateLimitGuard } from 'nest-ratelimit-pro';
|
|
80
|
+
|
|
81
|
+
@Controller('api')
|
|
82
|
+
@UseGuards(RateLimitGuard)
|
|
83
|
+
export class ApiController {
|
|
84
|
+
@Get('data')
|
|
85
|
+
async getData() {
|
|
86
|
+
return { message: 'Success!' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 3. Per-Route Configuration
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { RateLimit } from 'nest-ratelimit-pro';
|
|
95
|
+
|
|
96
|
+
@Controller('api')
|
|
97
|
+
export class ApiController {
|
|
98
|
+
// Override global limit for this route
|
|
99
|
+
@RateLimit({ limit: 5, duration: '1m' })
|
|
100
|
+
@Get('expensive')
|
|
101
|
+
async expensiveOperation() {
|
|
102
|
+
return { data: 'Heavy computation' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Skip rate limiting for this route
|
|
106
|
+
@SkipRateLimit()
|
|
107
|
+
@Get('health')
|
|
108
|
+
async healthCheck() {
|
|
109
|
+
return { status: 'ok' };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 📖 Configuration Options
|
|
115
|
+
|
|
116
|
+
### Module Options
|
|
117
|
+
|
|
118
|
+
| Option | Type | Default | Description |
|
|
119
|
+
|--------|------|---------|-------------|
|
|
120
|
+
| `limit` | `number` | **Required** | Maximum requests allowed |
|
|
121
|
+
| `duration` | `string` | **Required** | Time window (e.g., '1m', '1h') |
|
|
122
|
+
| `algorithm` | `RateLimitAlgorithm` | `FIXED_WINDOW` | Algorithm to use |
|
|
123
|
+
| `keyStrategy` | `RateLimitKeyStrategy` | `IP` | How to identify requesters |
|
|
124
|
+
| `keyExtractor` | `Function` | - | Custom key extraction |
|
|
125
|
+
| `storage` | `Type<RateLimitStorage>` | `MemoryStorage` | Storage backend |
|
|
126
|
+
| `keyPrefix` | `string` | `'ratelimit:'` | Prefix for storage keys |
|
|
127
|
+
| `apiKeyHeader` | `string` | `'X-API-Key'` | API key header name |
|
|
128
|
+
| `enableCostBased` | `boolean` | `false` | Enable cost-based limiting |
|
|
129
|
+
| `blockDuration` | `number` | `0` | Block duration in seconds |
|
|
130
|
+
| `skipPaths` | `string[]` | `[]` | Paths to skip |
|
|
131
|
+
| `enabled` | `boolean` | `true` | Enable/disable globally |
|
|
132
|
+
| `errorMessage` | `string` | - | Custom error message |
|
|
133
|
+
| `includeHeaders` | `boolean` | `true` | Include rate limit headers |
|
|
134
|
+
|
|
135
|
+
## 🎯 Rate Limiting Algorithms
|
|
136
|
+
|
|
137
|
+
### Fixed Window
|
|
138
|
+
|
|
139
|
+
Simple counter that resets at fixed intervals:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
RateLimitModule.forRoot({
|
|
143
|
+
algorithm: RateLimitAlgorithm.FIXED_WINDOW,
|
|
144
|
+
limit: 100,
|
|
145
|
+
duration: '1m',
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Pros:** Simple, low memory
|
|
150
|
+
**Cons:** Burst at window edges
|
|
151
|
+
|
|
152
|
+
### Sliding Window
|
|
153
|
+
|
|
154
|
+
More accurate, prevents burst:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
RateLimitModule.forRoot({
|
|
158
|
+
algorithm: RateLimitAlgorithm.SLIDING_WINDOW,
|
|
159
|
+
limit: 100,
|
|
160
|
+
duration: '1m',
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Pros:** Smooth rate limiting
|
|
165
|
+
**Cons:** Higher memory usage
|
|
166
|
+
|
|
167
|
+
### Token Bucket
|
|
168
|
+
|
|
169
|
+
Allows bursts with token refill:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
RateLimitModule.forRoot({
|
|
173
|
+
algorithm: RateLimitAlgorithm.TOKEN_BUCKET,
|
|
174
|
+
limit: 100,
|
|
175
|
+
duration: '1m',
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Pros:** Flexible, allows controlled bursts
|
|
180
|
+
**Cons:** More complex
|
|
181
|
+
|
|
182
|
+
## 🔑 Key Strategies
|
|
183
|
+
|
|
184
|
+
### IP-Based
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
RateLimitModule.forRoot({
|
|
188
|
+
keyStrategy: RateLimitKeyStrategy.IP,
|
|
189
|
+
limit: 100,
|
|
190
|
+
duration: '1m',
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### User ID-Based
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
RateLimitModule.forRoot({
|
|
198
|
+
keyStrategy: RateLimitKeyStrategy.USER_ID,
|
|
199
|
+
limit: 1000,
|
|
200
|
+
duration: '1h',
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Requires authentication middleware that sets `request.user.id`.
|
|
205
|
+
|
|
206
|
+
### API Key-Based
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
RateLimitModule.forRoot({
|
|
210
|
+
keyStrategy: RateLimitKeyStrategy.API_KEY,
|
|
211
|
+
apiKeyHeader: 'X-API-Key',
|
|
212
|
+
limit: 10000,
|
|
213
|
+
duration: '1d',
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Custom Key Extractor
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
RateLimitModule.forRoot({
|
|
221
|
+
keyStrategy: RateLimitKeyStrategy.CUSTOM,
|
|
222
|
+
keyExtractor: (request) => {
|
|
223
|
+
return `${request.user?.id}-${request.headers['x-tenant-id']}`;
|
|
224
|
+
},
|
|
225
|
+
limit: 100,
|
|
226
|
+
duration: '1m',
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## 💰 Cost-Based Rate Limiting
|
|
231
|
+
|
|
232
|
+
Assign different costs to different operations:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
@Controller('api')
|
|
236
|
+
export class ApiController {
|
|
237
|
+
@RateLimit({ cost: 1 })
|
|
238
|
+
@Get('light')
|
|
239
|
+
async lightOperation() {
|
|
240
|
+
return { data: 'Quick response' };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@RateLimit({ cost: 10 })
|
|
244
|
+
@Post('heavy')
|
|
245
|
+
async heavyOperation() {
|
|
246
|
+
return { data: 'Expensive computation' };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
With a limit of 100, you can make:
|
|
252
|
+
- 100 light operations, or
|
|
253
|
+
- 10 heavy operations, or
|
|
254
|
+
- Any combination totaling 100 cost
|
|
255
|
+
|
|
256
|
+
## 🗄️ Storage Backends
|
|
257
|
+
|
|
258
|
+
### Memory Storage (Default)
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { MemoryRateLimitStorage } from 'nest-ratelimit-pro';
|
|
262
|
+
|
|
263
|
+
RateLimitModule.forRoot({
|
|
264
|
+
storage: MemoryRateLimitStorage,
|
|
265
|
+
limit: 100,
|
|
266
|
+
duration: '1m',
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Use for:** Single-instance applications
|
|
271
|
+
|
|
272
|
+
### Redis Storage
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { RedisRateLimitStorage } from 'nest-ratelimit-pro';
|
|
276
|
+
|
|
277
|
+
RateLimitModule.forRoot({
|
|
278
|
+
storage: RedisRateLimitStorage,
|
|
279
|
+
redisUrl: 'redis://localhost:6379',
|
|
280
|
+
limit: 100,
|
|
281
|
+
duration: '1m',
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Use for:** Distributed applications, multiple instances
|
|
286
|
+
|
|
287
|
+
## 📊 Response Headers
|
|
288
|
+
|
|
289
|
+
When rate limiting is active, these headers are included:
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
X-RateLimit-Limit: 100
|
|
293
|
+
X-RateLimit-Remaining: 45
|
|
294
|
+
X-RateLimit-Reset: 1234567890
|
|
295
|
+
Retry-After: 30 (when limit exceeded)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## 🛡️ Skip Conditions
|
|
299
|
+
|
|
300
|
+
### Skip Specific Paths
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
RateLimitModule.forRoot({
|
|
304
|
+
skipPaths: ['/health', '/metrics', '/public/*'],
|
|
305
|
+
limit: 100,
|
|
306
|
+
duration: '1m',
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Skip with Custom Logic
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
RateLimitModule.forRoot({
|
|
314
|
+
skipIf: (request) => {
|
|
315
|
+
return request.headers['x-internal-request'] === 'true';
|
|
316
|
+
},
|
|
317
|
+
limit: 100,
|
|
318
|
+
duration: '1m',
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## 🧪 Testing
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
npm test # Run tests
|
|
326
|
+
npm run test:cov # With coverage
|
|
327
|
+
npm run test:watch # Watch mode
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## 📝 Example Application
|
|
331
|
+
|
|
332
|
+
See the [examples](./examples) directory for complete implementations:
|
|
333
|
+
- Basic rate limiting
|
|
334
|
+
- Cost-based limiting
|
|
335
|
+
- Redis integration
|
|
336
|
+
- Custom storage adapter
|
|
337
|
+
|
|
338
|
+
## 🤝 Contributing
|
|
339
|
+
|
|
340
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
341
|
+
|
|
342
|
+
## 📄 License
|
|
343
|
+
|
|
344
|
+
MIT License - see [LICENSE](./LICENSE) file for details.
|
|
345
|
+
|
|
346
|
+
## 👤 Author
|
|
347
|
+
|
|
348
|
+
**Alireza Aminzadeh**
|
|
349
|
+
- Email: alireza.aminzadeh@hotmail.com
|
|
350
|
+
- GitHub: [@syeedalireza](https://github.com/syeedalireza)
|
|
351
|
+
- NPM: [@syeedalireza](https://www.npmjs.com/~syeedalireza)
|
|
352
|
+
|
|
353
|
+
## ⭐ Support
|
|
354
|
+
|
|
355
|
+
If this package helped you, please give it a ⭐ on [GitHub](https://github.com/syeedalireza/nest-ratelimit-pro)!
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
**Built with ❤️ for the NestJS community**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const RATE_LIMIT_OPTIONS = "RATE_LIMIT_OPTIONS";
|
|
2
|
+
export declare const RATE_LIMIT_STORAGE = "RATE_LIMIT_STORAGE";
|
|
3
|
+
export declare const RATE_LIMIT_SERVICE = "RATE_LIMIT_SERVICE";
|
|
4
|
+
export declare const RATE_LIMIT_METADATA_KEY = "ratelimit:metadata";
|
|
5
|
+
export declare const DEFAULT_KEY_PREFIX = "ratelimit:";
|
|
6
|
+
export declare const DEFAULT_API_KEY_HEADER = "X-API-Key";
|
|
7
|
+
export declare const DEFAULT_LIMIT = 100;
|
|
8
|
+
export declare const DEFAULT_DURATION = "1m";
|
|
9
|
+
export declare const HEADER_LIMIT = "X-RateLimit-Limit";
|
|
10
|
+
export declare const HEADER_REMAINING = "X-RateLimit-Remaining";
|
|
11
|
+
export declare const HEADER_RESET = "X-RateLimit-Reset";
|
|
12
|
+
export declare const HEADER_RETRY_AFTER = "Retry-After";
|
|
13
|
+
export declare const DEFAULT_ERROR_MESSAGE = "Too many requests. Please try again later.";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_ERROR_MESSAGE = exports.HEADER_RETRY_AFTER = exports.HEADER_RESET = exports.HEADER_REMAINING = exports.HEADER_LIMIT = exports.DEFAULT_DURATION = exports.DEFAULT_LIMIT = exports.DEFAULT_API_KEY_HEADER = exports.DEFAULT_KEY_PREFIX = exports.RATE_LIMIT_METADATA_KEY = exports.RATE_LIMIT_SERVICE = exports.RATE_LIMIT_STORAGE = exports.RATE_LIMIT_OPTIONS = void 0;
|
|
4
|
+
exports.RATE_LIMIT_OPTIONS = 'RATE_LIMIT_OPTIONS';
|
|
5
|
+
exports.RATE_LIMIT_STORAGE = 'RATE_LIMIT_STORAGE';
|
|
6
|
+
exports.RATE_LIMIT_SERVICE = 'RATE_LIMIT_SERVICE';
|
|
7
|
+
exports.RATE_LIMIT_METADATA_KEY = 'ratelimit:metadata';
|
|
8
|
+
exports.DEFAULT_KEY_PREFIX = 'ratelimit:';
|
|
9
|
+
exports.DEFAULT_API_KEY_HEADER = 'X-API-Key';
|
|
10
|
+
exports.DEFAULT_LIMIT = 100;
|
|
11
|
+
exports.DEFAULT_DURATION = '1m';
|
|
12
|
+
exports.HEADER_LIMIT = 'X-RateLimit-Limit';
|
|
13
|
+
exports.HEADER_REMAINING = 'X-RateLimit-Remaining';
|
|
14
|
+
exports.HEADER_RESET = 'X-RateLimit-Reset';
|
|
15
|
+
exports.HEADER_RETRY_AFTER = 'Retry-After';
|
|
16
|
+
exports.DEFAULT_ERROR_MESSAGE = 'Too many requests. Please try again later.';
|
|
17
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAGa,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;AAC1C,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;AAC1C,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;AAK1C,QAAA,uBAAuB,GAAG,oBAAoB,CAAC;AAK/C,QAAA,kBAAkB,GAAG,YAAY,CAAC;AAClC,QAAA,sBAAsB,GAAG,WAAW,CAAC;AACrC,QAAA,aAAa,GAAG,GAAG,CAAC;AACpB,QAAA,gBAAgB,GAAG,IAAI,CAAC;AAKxB,QAAA,YAAY,GAAG,mBAAmB,CAAC;AACnC,QAAA,gBAAgB,GAAG,uBAAuB,CAAC;AAC3C,QAAA,YAAY,GAAG,mBAAmB,CAAC;AACnC,QAAA,kBAAkB,GAAG,aAAa,CAAC;AAKnC,QAAA,qBAAqB,GAAG,4CAA4C,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SkipRateLimit = exports.RateLimit = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const constants_1 = require("../constants");
|
|
6
|
+
const RateLimit = (options) => {
|
|
7
|
+
return (0, common_1.SetMetadata)(constants_1.RATE_LIMIT_METADATA_KEY, options || {});
|
|
8
|
+
};
|
|
9
|
+
exports.RateLimit = RateLimit;
|
|
10
|
+
const SkipRateLimit = () => {
|
|
11
|
+
return (0, common_1.SetMetadata)(constants_1.RATE_LIMIT_METADATA_KEY, { skip: true });
|
|
12
|
+
};
|
|
13
|
+
exports.SkipRateLimit = SkipRateLimit;
|
|
14
|
+
//# sourceMappingURL=rate-limit.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.decorator.js","sourceRoot":"","sources":["../../src/decorators/rate-limit.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA6C;AAE7C,4CAAuD;AAyBhD,MAAM,SAAS,GAAG,CAAC,OAAmC,EAAmB,EAAE;IAChF,OAAO,IAAA,oBAAW,EAAC,mCAAuB,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;AAC7D,CAAC,CAAC;AAFW,QAAA,SAAS,aAEpB;AAcK,MAAM,aAAa,GAAG,GAAoB,EAAE;IACjD,OAAO,IAAA,oBAAW,EAAC,mCAAuB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAC9D,CAAC,CAAC;AAFW,QAAA,aAAa,iBAExB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimitExceededException = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
class RateLimitExceededException extends common_1.HttpException {
|
|
6
|
+
constructor(message = 'Too many requests. Please try again later.', retryAfter) {
|
|
7
|
+
super({
|
|
8
|
+
statusCode: common_1.HttpStatus.TOO_MANY_REQUESTS,
|
|
9
|
+
message,
|
|
10
|
+
error: 'Too Many Requests',
|
|
11
|
+
retryAfter,
|
|
12
|
+
}, common_1.HttpStatus.TOO_MANY_REQUESTS);
|
|
13
|
+
this.retryAfter = retryAfter;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.RateLimitExceededException = RateLimitExceededException;
|
|
17
|
+
//# sourceMappingURL=rate-limit.exception.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.exception.js","sourceRoot":"","sources":["../../src/exceptions/rate-limit.exception.ts"],"names":[],"mappings":";;;AAAA,2CAA2D;AAK3D,MAAa,0BAA2B,SAAQ,sBAAa;IAC3D,YACE,UAAkB,4CAA4C,EAC9C,UAAmB;QAEnC,KAAK,CACH;YACE,UAAU,EAAE,mBAAU,CAAC,iBAAiB;YACxC,OAAO;YACP,KAAK,EAAE,mBAAmB;YAC1B,UAAU;SACX,EACD,mBAAU,CAAC,iBAAiB,CAC7B,CAAC;QAVc,eAAU,GAAV,UAAU,CAAS;IAWrC,CAAC;CACF;AAfD,gEAeC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
import { RateLimitService } from '../services/rate-limit.service';
|
|
4
|
+
import { RateLimitModuleOptions } from '../interfaces/rate-limit-options.interface';
|
|
5
|
+
export declare class RateLimitGuard implements CanActivate {
|
|
6
|
+
private readonly reflector;
|
|
7
|
+
private readonly rateLimitService;
|
|
8
|
+
private readonly options;
|
|
9
|
+
constructor(reflector: Reflector, rateLimitService: RateLimitService, options: RateLimitModuleOptions);
|
|
10
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
11
|
+
private shouldSkipPath;
|
|
12
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.RateLimitGuard = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const core_1 = require("@nestjs/core");
|
|
18
|
+
const rate_limit_service_1 = require("../services/rate-limit.service");
|
|
19
|
+
const rate_limit_exception_1 = require("../exceptions/rate-limit.exception");
|
|
20
|
+
const constants_1 = require("../constants");
|
|
21
|
+
const constants_2 = require("../constants");
|
|
22
|
+
let RateLimitGuard = class RateLimitGuard {
|
|
23
|
+
constructor(reflector, rateLimitService, options) {
|
|
24
|
+
this.reflector = reflector;
|
|
25
|
+
this.rateLimitService = rateLimitService;
|
|
26
|
+
this.options = options;
|
|
27
|
+
}
|
|
28
|
+
async canActivate(context) {
|
|
29
|
+
const request = context.switchToHttp().getRequest();
|
|
30
|
+
const response = context.switchToHttp().getResponse();
|
|
31
|
+
const decoratorOptions = this.reflector.get(constants_1.RATE_LIMIT_METADATA_KEY, context.getHandler());
|
|
32
|
+
if (decoratorOptions?.skip) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (this.shouldSkipPath(request.path)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const result = await this.rateLimitService.check(request, {
|
|
39
|
+
cost: decoratorOptions?.cost,
|
|
40
|
+
});
|
|
41
|
+
if (this.options.includeHeaders !== false) {
|
|
42
|
+
response.setHeader(constants_1.HEADER_LIMIT, result.limit.toString());
|
|
43
|
+
response.setHeader(constants_1.HEADER_REMAINING, result.remaining.toString());
|
|
44
|
+
response.setHeader(constants_1.HEADER_RESET, result.resetAt.toString());
|
|
45
|
+
}
|
|
46
|
+
if (!result.allowed) {
|
|
47
|
+
if (result.retryAfter) {
|
|
48
|
+
response.setHeader(constants_1.HEADER_RETRY_AFTER, result.retryAfter.toString());
|
|
49
|
+
}
|
|
50
|
+
const errorMessage = decoratorOptions?.errorMessage || this.options.errorMessage || constants_1.DEFAULT_ERROR_MESSAGE;
|
|
51
|
+
throw new rate_limit_exception_1.RateLimitExceededException(errorMessage, result.retryAfter);
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
shouldSkipPath(path) {
|
|
56
|
+
if (!this.options.skipPaths || this.options.skipPaths.length === 0) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return this.options.skipPaths.some((skipPath) => {
|
|
60
|
+
if (skipPath.endsWith('*')) {
|
|
61
|
+
const prefix = skipPath.slice(0, -1);
|
|
62
|
+
return path.startsWith(prefix);
|
|
63
|
+
}
|
|
64
|
+
return path === skipPath;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
exports.RateLimitGuard = RateLimitGuard;
|
|
69
|
+
exports.RateLimitGuard = RateLimitGuard = __decorate([
|
|
70
|
+
(0, common_1.Injectable)(),
|
|
71
|
+
__param(2, (0, common_1.Inject)(constants_2.RATE_LIMIT_OPTIONS)),
|
|
72
|
+
__metadata("design:paramtypes", [core_1.Reflector,
|
|
73
|
+
rate_limit_service_1.RateLimitService, Object])
|
|
74
|
+
], RateLimitGuard);
|
|
75
|
+
//# sourceMappingURL=rate-limit.guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.guard.js","sourceRoot":"","sources":["../../src/guards/rate-limit.guard.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './rate-limit.module';
|
|
2
|
+
export * from './services/rate-limit.service';
|
|
3
|
+
export * from './guards/rate-limit.guard';
|
|
4
|
+
export * from './decorators/rate-limit.decorator';
|
|
5
|
+
export * from './interfaces/rate-limit.interface';
|
|
6
|
+
export * from './interfaces/rate-limit-options.interface';
|
|
7
|
+
export * from './interfaces/storage.interface';
|
|
8
|
+
export * from './storage/memory.storage';
|
|
9
|
+
export * from './exceptions/rate-limit.exception';
|
|
10
|
+
export * from './constants';
|
|
11
|
+
export * from './utils/time-utils';
|
|
12
|
+
export * from './utils/key.utils';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./rate-limit.module"), exports);
|
|
18
|
+
__exportStar(require("./services/rate-limit.service"), exports);
|
|
19
|
+
__exportStar(require("./guards/rate-limit.guard"), exports);
|
|
20
|
+
__exportStar(require("./decorators/rate-limit.decorator"), exports);
|
|
21
|
+
__exportStar(require("./interfaces/rate-limit.interface"), exports);
|
|
22
|
+
__exportStar(require("./interfaces/rate-limit-options.interface"), exports);
|
|
23
|
+
__exportStar(require("./interfaces/storage.interface"), exports);
|
|
24
|
+
__exportStar(require("./storage/memory.storage"), exports);
|
|
25
|
+
__exportStar(require("./exceptions/rate-limit.exception"), exports);
|
|
26
|
+
__exportStar(require("./constants"), exports);
|
|
27
|
+
__exportStar(require("./utils/time-utils"), exports);
|
|
28
|
+
__exportStar(require("./utils/key.utils"), exports);
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AACA,sDAAoC;AAGpC,gEAA8C;AAG9C,4DAA0C;AAG1C,oEAAkD;AAGlD,oEAAkD;AAClD,4EAA0D;AAC1D,iEAA+C;AAG/C,2DAAyC;AAGzC,oEAAkD;AAGlD,8CAA4B;AAG5B,qDAAmC;AACnC,oDAAkC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ModuleMetadata, Type } from '@nestjs/common';
|
|
2
|
+
import { RateLimitStorage } from './storage.interface';
|
|
3
|
+
export declare enum RateLimitAlgorithm {
|
|
4
|
+
TOKEN_BUCKET = "token-bucket",
|
|
5
|
+
FIXED_WINDOW = "fixed-window",
|
|
6
|
+
SLIDING_WINDOW = "sliding-window",
|
|
7
|
+
SLIDING_WINDOW_COUNTER = "sliding-window-counter"
|
|
8
|
+
}
|
|
9
|
+
export declare enum RateLimitKeyStrategy {
|
|
10
|
+
IP = "ip",
|
|
11
|
+
USER_ID = "user-id",
|
|
12
|
+
API_KEY = "api-key",
|
|
13
|
+
CUSTOM = "custom",
|
|
14
|
+
COMBINED = "combined"
|
|
15
|
+
}
|
|
16
|
+
export interface RateLimitModuleOptions {
|
|
17
|
+
algorithm?: RateLimitAlgorithm;
|
|
18
|
+
limit: number;
|
|
19
|
+
duration: string;
|
|
20
|
+
keyStrategy?: RateLimitKeyStrategy;
|
|
21
|
+
keyExtractor?: (request: any) => string | Promise<string>;
|
|
22
|
+
storage?: Type<RateLimitStorage>;
|
|
23
|
+
redisUrl?: string;
|
|
24
|
+
keyPrefix?: string;
|
|
25
|
+
apiKeyHeader?: string;
|
|
26
|
+
enableCostBased?: boolean;
|
|
27
|
+
blockDuration?: number;
|
|
28
|
+
skipPaths?: string[];
|
|
29
|
+
skipIf?: (request: any) => boolean | Promise<boolean>;
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
errorMessage?: string;
|
|
32
|
+
includeHeaders?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface RateLimitModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
35
|
+
useFactory?: (...args: any[]) => Promise<RateLimitModuleOptions> | RateLimitModuleOptions;
|
|
36
|
+
inject?: any[];
|
|
37
|
+
}
|
|
38
|
+
export interface RateLimitDecoratorOptions {
|
|
39
|
+
limit?: number;
|
|
40
|
+
duration?: string;
|
|
41
|
+
keyExtractor?: (request: any) => string | Promise<string>;
|
|
42
|
+
cost?: number;
|
|
43
|
+
skip?: boolean;
|
|
44
|
+
errorMessage?: string;
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimitKeyStrategy = exports.RateLimitAlgorithm = void 0;
|
|
4
|
+
var RateLimitAlgorithm;
|
|
5
|
+
(function (RateLimitAlgorithm) {
|
|
6
|
+
RateLimitAlgorithm["TOKEN_BUCKET"] = "token-bucket";
|
|
7
|
+
RateLimitAlgorithm["FIXED_WINDOW"] = "fixed-window";
|
|
8
|
+
RateLimitAlgorithm["SLIDING_WINDOW"] = "sliding-window";
|
|
9
|
+
RateLimitAlgorithm["SLIDING_WINDOW_COUNTER"] = "sliding-window-counter";
|
|
10
|
+
})(RateLimitAlgorithm || (exports.RateLimitAlgorithm = RateLimitAlgorithm = {}));
|
|
11
|
+
var RateLimitKeyStrategy;
|
|
12
|
+
(function (RateLimitKeyStrategy) {
|
|
13
|
+
RateLimitKeyStrategy["IP"] = "ip";
|
|
14
|
+
RateLimitKeyStrategy["USER_ID"] = "user-id";
|
|
15
|
+
RateLimitKeyStrategy["API_KEY"] = "api-key";
|
|
16
|
+
RateLimitKeyStrategy["CUSTOM"] = "custom";
|
|
17
|
+
RateLimitKeyStrategy["COMBINED"] = "combined";
|
|
18
|
+
})(RateLimitKeyStrategy || (exports.RateLimitKeyStrategy = RateLimitKeyStrategy = {}));
|
|
19
|
+
//# sourceMappingURL=rate-limit-options.interface.js.map
|