@dismissible/nestjs-rate-limiter-hook 2.0.2-alpha.f50def9.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 +187 -0
- package/package.json +68 -0
- package/src/index.d.ts +4 -0
- package/src/index.js +8 -0
- package/src/index.js.map +1 -0
- package/src/rate-limiter-hook.config.d.ts +79 -0
- package/src/rate-limiter-hook.config.js +90 -0
- package/src/rate-limiter-hook.config.js.map +1 -0
- package/src/rate-limiter-hook.module.d.ts +42 -0
- package/src/rate-limiter-hook.module.js +76 -0
- package/src/rate-limiter-hook.module.js.map +1 -0
- package/src/rate-limiter.hook.d.ts +29 -0
- package/src/rate-limiter.hook.js +90 -0
- package/src/rate-limiter.hook.js.map +1 -0
- package/src/rate-limiter.service.d.ts +89 -0
- package/src/rate-limiter.service.js +280 -0
- package/src/rate-limiter.service.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://dismissible.io" target="_blank"><img src="https://raw.githubusercontent.com/DismissibleIo/dismissible-api/main/docs/images/dismissible_logo.png" width="120" alt="Dismissible" /></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">Never Show The Same Thing Twice!</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/@dismissible/nestjs-rate-limiter-hook" target="_blank"><img src="https://img.shields.io/npm/v/@dismissible/nestjs-rate-limiter-hook.svg" alt="NPM Version" /></a>
|
|
8
|
+
<a href="https://github.com/dismissibleio/dismissible-api/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/npm/l/@dismissible/nestjs-rate-limiter-hook.svg" alt="Package License" /></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@dismissible/nestjs-rate-limiter-hook" target="_blank"><img src="https://img.shields.io/npm/dm/@dismissible/nestjs-rate-limiter-hook.svg" alt="NPM Downloads" /></a>
|
|
10
|
+
<a href="https://github.com/dismissibleio/dismissible-api" target="_blank"><img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/dismissibleio/dismissible-api/release.yml"></a>
|
|
11
|
+
<a href="https://paypal.me/joshstuartx" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
Dismissible manages the state of your UI elements across sessions, so your users see what matters, once! No more onboarding messages reappearing on every tab, no more notifications haunting users across devices. Dismissible syncs dismissal state everywhere, so every message is intentional, never repetitive.
|
|
15
|
+
|
|
16
|
+
# @dismissible/nestjs-rate-limiter-hook
|
|
17
|
+
|
|
18
|
+
Rate limiting hook for Dismissible applications using in-memory rate limiting.
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
This library provides a lifecycle hook that integrates with the `@dismissible/nestjs-core` module to rate limit requests. It uses the `rate-limiter-flexible` library for efficient in-memory rate limiting.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @dismissible/nestjs-rate-limiter-hook
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Basic Setup
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { Module } from '@nestjs/common';
|
|
36
|
+
import { DismissibleModule } from '@dismissible/nestjs-core';
|
|
37
|
+
import { RateLimiterHookModule, RateLimiterHook } from '@dismissible/nestjs-rate-limiter-hook';
|
|
38
|
+
|
|
39
|
+
@Module({
|
|
40
|
+
imports: [
|
|
41
|
+
// Configure the rate limiter hook module
|
|
42
|
+
RateLimiterHookModule.forRoot({
|
|
43
|
+
enabled: true,
|
|
44
|
+
points: 10, // 10 requests
|
|
45
|
+
duration: 1, // per 1 second
|
|
46
|
+
keyType: ['ip'], // rate limit by IP address
|
|
47
|
+
}),
|
|
48
|
+
|
|
49
|
+
// Pass the hook to the DismissibleModule
|
|
50
|
+
DismissibleModule.forRoot({
|
|
51
|
+
hooks: [RateLimiterHook],
|
|
52
|
+
// ... other options
|
|
53
|
+
}),
|
|
54
|
+
],
|
|
55
|
+
})
|
|
56
|
+
export class AppModule {}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Async Configuration
|
|
60
|
+
|
|
61
|
+
When configuration values come from environment variables or other async sources:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { Module } from '@nestjs/common';
|
|
65
|
+
import { ConfigService } from '@nestjs/config';
|
|
66
|
+
import { DismissibleModule } from '@dismissible/nestjs-core';
|
|
67
|
+
import { RateLimiterHookModule, RateLimiterHook } from '@dismissible/nestjs-rate-limiter-hook';
|
|
68
|
+
|
|
69
|
+
@Module({
|
|
70
|
+
imports: [
|
|
71
|
+
RateLimiterHookModule.forRootAsync({
|
|
72
|
+
useFactory: (configService: ConfigService) => ({
|
|
73
|
+
enabled: configService.get('RATE_LIMITER_ENABLED', true),
|
|
74
|
+
points: configService.get('RATE_LIMITER_POINTS', 10),
|
|
75
|
+
duration: configService.get('RATE_LIMITER_DURATION', 1),
|
|
76
|
+
keyType: configService.get('RATE_LIMITER_KEY_TYPE', 'ip').split(','),
|
|
77
|
+
}),
|
|
78
|
+
inject: [ConfigService],
|
|
79
|
+
}),
|
|
80
|
+
|
|
81
|
+
DismissibleModule.forRoot({
|
|
82
|
+
hooks: [RateLimiterHook],
|
|
83
|
+
}),
|
|
84
|
+
],
|
|
85
|
+
})
|
|
86
|
+
export class AppModule {}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Configuration Options
|
|
90
|
+
|
|
91
|
+
| Option | Type | Required | Default | Description |
|
|
92
|
+
| --------------- | -------------------- | -------- | ------- | ---------------------------------------------------------------------- |
|
|
93
|
+
| `enabled` | `boolean` | Yes | - | Whether rate limiting is enabled |
|
|
94
|
+
| `points` | `number` | Yes\* | - | Number of requests allowed per duration |
|
|
95
|
+
| `duration` | `number` | Yes\* | - | Time window in seconds |
|
|
96
|
+
| `blockDuration` | `number` | No | - | Duration in seconds to block requests after limit is exceeded |
|
|
97
|
+
| `keyType` | `RateLimitKeyType[]` | Yes\* | - | Key source(s) for rate limiting: `ip`, `origin`, `referrer` |
|
|
98
|
+
| `keyMode` | `RateLimitKeyMode` | No | `and` | Mode for combining key types: `and`, `or`, `any` |
|
|
99
|
+
| `priority` | `number` | No | `-50` | Hook priority (lower numbers run first, runs after auth hooks at -100) |
|
|
100
|
+
|
|
101
|
+
\* Required when `enabled` is `true`.
|
|
102
|
+
|
|
103
|
+
### Key Types
|
|
104
|
+
|
|
105
|
+
- **`ip`**: Rate limit by IP address (extracted from `x-forwarded-for` or `x-real-ip` headers)
|
|
106
|
+
- **`origin`**: Rate limit by Origin header (useful for CORS scenarios)
|
|
107
|
+
- **`referrer`**: Rate limit by Referer header
|
|
108
|
+
|
|
109
|
+
### Key Modes
|
|
110
|
+
|
|
111
|
+
When multiple key types are specified, the `keyMode` determines how they are combined:
|
|
112
|
+
|
|
113
|
+
- **`and`** (default): Combine all key types into a single key. For example, `keyType: ['ip', 'origin']` creates keys like `192.168.1.1:example.com`. All requests from the same IP+Origin combination share the same rate limit bucket.
|
|
114
|
+
|
|
115
|
+
- **`or`**: Use the first available key type as a fallback chain. For example, with `keyType: ['ip', 'origin', 'referrer']`:
|
|
116
|
+
- If IP is available, use IP
|
|
117
|
+
- If IP is not available but Origin is, use Origin
|
|
118
|
+
- If neither IP nor Origin is available, use Referrer
|
|
119
|
+
- Useful when you want to rate limit by the most reliable identifier available
|
|
120
|
+
|
|
121
|
+
- **`any`**: Check all key types independently - the request is blocked if ANY key type exceeds the limit. Each key type gets its own rate limit bucket. For example, with `keyType: ['ip', 'origin']`:
|
|
122
|
+
- Keys are prefixed: `ip:192.168.1.1` and `origin:example.com`
|
|
123
|
+
- Both buckets are checked
|
|
124
|
+
- Request is blocked if either bucket is exhausted
|
|
125
|
+
- This is the strictest mode
|
|
126
|
+
|
|
127
|
+
## Environment Variables
|
|
128
|
+
|
|
129
|
+
When using the Dismissible API Docker image or the standalone API, these environment variables configure rate limiting:
|
|
130
|
+
|
|
131
|
+
| Variable | Description | Default |
|
|
132
|
+
| ----------------------------------------- | --------------------------------------------- | ------- |
|
|
133
|
+
| `DISMISSIBLE_RATE_LIMITER_ENABLED` | Enable rate limiting | `false` |
|
|
134
|
+
| `DISMISSIBLE_RATE_LIMITER_POINTS` | Number of requests allowed per duration | `10` |
|
|
135
|
+
| `DISMISSIBLE_RATE_LIMITER_DURATION` | Time window in seconds | `1` |
|
|
136
|
+
| `DISMISSIBLE_RATE_LIMITER_BLOCK_DURATION` | Block duration after limit exceeded (seconds) | - |
|
|
137
|
+
| `DISMISSIBLE_RATE_LIMITER_KEY_TYPE` | Key type(s) (comma-separated) | `ip` |
|
|
138
|
+
| `DISMISSIBLE_RATE_LIMITER_KEY_MODE` | Key combination mode: `and`, `or`, `any` | `and` |
|
|
139
|
+
| `DISMISSIBLE_RATE_LIMITER_PRIORITY` | Hook priority (lower runs first) | `-50` |
|
|
140
|
+
|
|
141
|
+
### Example: Enabling Rate Limiting
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
docker run -p 3001:3001 \
|
|
145
|
+
-e DISMISSIBLE_RATE_LIMITER_ENABLED=true \
|
|
146
|
+
-e DISMISSIBLE_RATE_LIMITER_POINTS=100 \
|
|
147
|
+
-e DISMISSIBLE_RATE_LIMITER_DURATION=60 \
|
|
148
|
+
-e DISMISSIBLE_RATE_LIMITER_KEY_TYPE="ip,origin" \
|
|
149
|
+
-e DISMISSIBLE_RATE_LIMITER_KEY_MODE="or" \
|
|
150
|
+
-e DISMISSIBLE_STORAGE_POSTGRES_CONNECTION_STRING="postgresql://..." \
|
|
151
|
+
dismissibleio/dismissible-api:latest
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
This configuration allows 100 requests per minute, rate limiting by IP if available, otherwise by Origin (using OR mode).
|
|
155
|
+
|
|
156
|
+
## How It Works
|
|
157
|
+
|
|
158
|
+
1. **Key Generation**: For each request, the hook generates rate limit key(s) based on the configured key types and mode:
|
|
159
|
+
- **AND mode**: Creates a single combined key (e.g., `192.168.1.1:example.com`)
|
|
160
|
+
- **OR mode**: Uses the first available key type from the list
|
|
161
|
+
- **ANY mode**: Creates separate keys for each key type (e.g., `ip:192.168.1.1`, `origin:example.com`)
|
|
162
|
+
|
|
163
|
+
2. **Rate Limit Check**: The key(s) are checked against the rate limiter. Each request consumes one "point" from the available pool(s).
|
|
164
|
+
|
|
165
|
+
3. **Request Handling**:
|
|
166
|
+
- If points remain (for all keys in ANY mode): The request proceeds
|
|
167
|
+
- If limit exceeded: The request is blocked with a `429 Too Many Requests` response
|
|
168
|
+
|
|
169
|
+
4. **Window Reset**: After the `duration` period, the points reset. If `blockDuration` is configured, blocked clients must wait for that duration before being allowed again.
|
|
170
|
+
|
|
171
|
+
## Error Responses
|
|
172
|
+
|
|
173
|
+
When rate limit is exceeded, the hook throws a `TooManyRequestsException`:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"statusCode": 429,
|
|
178
|
+
"message": "Rate limit exceeded. Please try again later.",
|
|
179
|
+
"error": "Too Many Requests"
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The exception includes a `retryAfter` property indicating how many seconds until the rate limit resets.
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dismissible/nestjs-rate-limiter-hook",
|
|
3
|
+
"version": "2.0.2-alpha.f50def9.0",
|
|
4
|
+
"description": "Rate limiting hook for Dismissible applications using in-memory rate limiting",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"types": "./src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./src/index.mjs",
|
|
10
|
+
"require": "./src/index.js",
|
|
11
|
+
"types": "./src/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE.md"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@dismissible/nestjs-hooks": "2.0.2-alpha.f50def9.0",
|
|
21
|
+
"@dismissible/nestjs-request": "2.0.2-alpha.f50def9.0",
|
|
22
|
+
"@dismissible/nestjs-logger": "2.0.2-alpha.f50def9.0",
|
|
23
|
+
"@dismissible/nestjs-validation": "2.0.2-alpha.f50def9.0",
|
|
24
|
+
"rate-limiter-flexible": "5.0.4"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@nestjs/common": "10.0.0 || ^11.0.0",
|
|
28
|
+
"@nestjs/core": "10.0.0 || ^11.0.0",
|
|
29
|
+
"class-validator": "0.14.3",
|
|
30
|
+
"class-transformer": "0.5.1",
|
|
31
|
+
"rxjs": "7.8.2"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"@nestjs/common": {
|
|
35
|
+
"optional": false
|
|
36
|
+
},
|
|
37
|
+
"@nestjs/core": {
|
|
38
|
+
"optional": false
|
|
39
|
+
},
|
|
40
|
+
"class-validator": {
|
|
41
|
+
"optional": false
|
|
42
|
+
},
|
|
43
|
+
"class-transformer": {
|
|
44
|
+
"optional": false
|
|
45
|
+
},
|
|
46
|
+
"rxjs": {
|
|
47
|
+
"optional": false
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"nestjs",
|
|
52
|
+
"rate-limiting",
|
|
53
|
+
"rate-limiter",
|
|
54
|
+
"throttle",
|
|
55
|
+
"dismissible",
|
|
56
|
+
"hook"
|
|
57
|
+
],
|
|
58
|
+
"author": "",
|
|
59
|
+
"license": "MIT",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/DismissibleIo/dismissible-api"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
66
|
+
},
|
|
67
|
+
"type": "commonjs"
|
|
68
|
+
}
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./rate-limiter-hook.config"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./rate-limiter-hook.module"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./rate-limiter.hook"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./rate-limiter.service"), exports);
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/rate-limiter-hook/src/index.ts"],"names":[],"mappings":";;;AAAA,qEAA2C;AAC3C,qEAA2C;AAC3C,8DAAoC;AACpC,iEAAuC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injection token for rate limiter hook configuration.
|
|
3
|
+
*/
|
|
4
|
+
export declare const DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* Key type for rate limiting - determines how requests are grouped.
|
|
7
|
+
*/
|
|
8
|
+
export declare enum RateLimitKeyType {
|
|
9
|
+
/** Rate limit by IP address (from x-forwarded-for or connection IP) */
|
|
10
|
+
IP = "ip",
|
|
11
|
+
/** Rate limit by Origin header */
|
|
12
|
+
ORIGIN = "origin",
|
|
13
|
+
/** Rate limit by Referer header */
|
|
14
|
+
REFERRER = "referrer"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Mode for combining multiple key types.
|
|
18
|
+
*/
|
|
19
|
+
export declare enum RateLimitKeyMode {
|
|
20
|
+
/** Combine all key types into a single key (e.g., "ip:origin") */
|
|
21
|
+
AND = "and",
|
|
22
|
+
/** Use the first available key type as a fallback chain */
|
|
23
|
+
OR = "or",
|
|
24
|
+
/** Check all key types independently - blocked if ANY limit exceeded */
|
|
25
|
+
ANY = "any"
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Configuration options for rate limiter hook.
|
|
29
|
+
*/
|
|
30
|
+
export declare class RateLimiterHookConfig {
|
|
31
|
+
readonly enabled: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Number of requests allowed per duration.
|
|
34
|
+
* Defaults to 10.
|
|
35
|
+
*/
|
|
36
|
+
readonly points: number;
|
|
37
|
+
/**
|
|
38
|
+
* Time window in seconds.
|
|
39
|
+
* Defaults to 1 second.
|
|
40
|
+
*/
|
|
41
|
+
readonly duration: number;
|
|
42
|
+
/**
|
|
43
|
+
* Optional: Duration in seconds to block requests after limit is exceeded.
|
|
44
|
+
* If not set, requests are allowed again after the duration window resets.
|
|
45
|
+
*/
|
|
46
|
+
readonly blockDuration?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Key type(s) for rate limiting.
|
|
49
|
+
* Can be a comma-separated string or array of key types.
|
|
50
|
+
* How multiple types are combined depends on the keyMode setting.
|
|
51
|
+
* Defaults to ['ip'].
|
|
52
|
+
*/
|
|
53
|
+
readonly keyType: RateLimitKeyType[];
|
|
54
|
+
/**
|
|
55
|
+
* Mode for combining key types when multiple are specified.
|
|
56
|
+
* - 'and': Combine all into single key (default) - e.g., "192.168.1.1:example.com"
|
|
57
|
+
* - 'or': Use first available key type (fallback chain)
|
|
58
|
+
* - 'any': Check each independently (strictest - blocked if ANY exceeds limit)
|
|
59
|
+
* Defaults to 'and'.
|
|
60
|
+
*/
|
|
61
|
+
readonly keyMode?: RateLimitKeyMode;
|
|
62
|
+
/**
|
|
63
|
+
* Optional: Hook priority (lower numbers run first).
|
|
64
|
+
* Defaults to -50 (runs after authentication hooks).
|
|
65
|
+
*/
|
|
66
|
+
readonly priority?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Optional: Keys that should bypass rate limiting.
|
|
69
|
+
* Can be a comma-separated string or array of strings.
|
|
70
|
+
* Matching is exact (after normalization to lowercase + trim):
|
|
71
|
+
* - IPs are matched exactly (e.g., "192.168.8.1")
|
|
72
|
+
* - Origins/referrers are matched by exact hostname when the header is a valid URL
|
|
73
|
+
* (e.g., "https://google.com/search" matches ignored key "google.com")
|
|
74
|
+
* - You may also whitelist an exact raw Origin/Referer value by providing the full string
|
|
75
|
+
* (e.g., "https://example.com:8443")
|
|
76
|
+
* @example ['google.com', '192.168.8.1', 'https://example.com:8443']
|
|
77
|
+
*/
|
|
78
|
+
readonly ignoredKeys?: string[];
|
|
79
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimiterHookConfig = exports.RateLimitKeyMode = exports.RateLimitKeyType = exports.DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const class_validator_1 = require("class-validator");
|
|
6
|
+
const class_transformer_1 = require("class-transformer");
|
|
7
|
+
const nestjs_validation_1 = require("@dismissible/nestjs-validation");
|
|
8
|
+
/**
|
|
9
|
+
* Injection token for rate limiter hook configuration.
|
|
10
|
+
*/
|
|
11
|
+
exports.DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG = Symbol('DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG');
|
|
12
|
+
/**
|
|
13
|
+
* Key type for rate limiting - determines how requests are grouped.
|
|
14
|
+
*/
|
|
15
|
+
var RateLimitKeyType;
|
|
16
|
+
(function (RateLimitKeyType) {
|
|
17
|
+
/** Rate limit by IP address (from x-forwarded-for or connection IP) */
|
|
18
|
+
RateLimitKeyType["IP"] = "ip";
|
|
19
|
+
/** Rate limit by Origin header */
|
|
20
|
+
RateLimitKeyType["ORIGIN"] = "origin";
|
|
21
|
+
/** Rate limit by Referer header */
|
|
22
|
+
RateLimitKeyType["REFERRER"] = "referrer";
|
|
23
|
+
})(RateLimitKeyType || (exports.RateLimitKeyType = RateLimitKeyType = {}));
|
|
24
|
+
/**
|
|
25
|
+
* Mode for combining multiple key types.
|
|
26
|
+
*/
|
|
27
|
+
var RateLimitKeyMode;
|
|
28
|
+
(function (RateLimitKeyMode) {
|
|
29
|
+
/** Combine all key types into a single key (e.g., "ip:origin") */
|
|
30
|
+
RateLimitKeyMode["AND"] = "and";
|
|
31
|
+
/** Use the first available key type as a fallback chain */
|
|
32
|
+
RateLimitKeyMode["OR"] = "or";
|
|
33
|
+
/** Check all key types independently - blocked if ANY limit exceeded */
|
|
34
|
+
RateLimitKeyMode["ANY"] = "any";
|
|
35
|
+
})(RateLimitKeyMode || (exports.RateLimitKeyMode = RateLimitKeyMode = {}));
|
|
36
|
+
/**
|
|
37
|
+
* Configuration options for rate limiter hook.
|
|
38
|
+
*/
|
|
39
|
+
class RateLimiterHookConfig {
|
|
40
|
+
}
|
|
41
|
+
exports.RateLimiterHookConfig = RateLimiterHookConfig;
|
|
42
|
+
tslib_1.__decorate([
|
|
43
|
+
(0, class_validator_1.IsBoolean)(),
|
|
44
|
+
(0, nestjs_validation_1.TransformBoolean)(),
|
|
45
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
46
|
+
], RateLimiterHookConfig.prototype, "enabled", void 0);
|
|
47
|
+
tslib_1.__decorate([
|
|
48
|
+
(0, class_validator_1.ValidateIf)((o) => o.enabled === true),
|
|
49
|
+
(0, class_validator_1.IsNumber)(),
|
|
50
|
+
(0, class_transformer_1.Type)(() => Number),
|
|
51
|
+
tslib_1.__metadata("design:type", Number)
|
|
52
|
+
], RateLimiterHookConfig.prototype, "points", void 0);
|
|
53
|
+
tslib_1.__decorate([
|
|
54
|
+
(0, class_validator_1.ValidateIf)((o) => o.enabled === true),
|
|
55
|
+
(0, class_validator_1.IsNumber)(),
|
|
56
|
+
(0, class_transformer_1.Type)(() => Number),
|
|
57
|
+
tslib_1.__metadata("design:type", Number)
|
|
58
|
+
], RateLimiterHookConfig.prototype, "duration", void 0);
|
|
59
|
+
tslib_1.__decorate([
|
|
60
|
+
(0, class_validator_1.IsOptional)(),
|
|
61
|
+
(0, class_validator_1.IsNumber)(),
|
|
62
|
+
(0, class_transformer_1.Type)(() => Number),
|
|
63
|
+
tslib_1.__metadata("design:type", Number)
|
|
64
|
+
], RateLimiterHookConfig.prototype, "blockDuration", void 0);
|
|
65
|
+
tslib_1.__decorate([
|
|
66
|
+
(0, class_validator_1.ValidateIf)((o) => o.enabled === true),
|
|
67
|
+
(0, class_validator_1.IsArray)(),
|
|
68
|
+
(0, class_validator_1.IsEnum)(RateLimitKeyType, { each: true }),
|
|
69
|
+
(0, nestjs_validation_1.TransformCommaSeparated)(),
|
|
70
|
+
tslib_1.__metadata("design:type", Array)
|
|
71
|
+
], RateLimiterHookConfig.prototype, "keyType", void 0);
|
|
72
|
+
tslib_1.__decorate([
|
|
73
|
+
(0, class_validator_1.IsOptional)(),
|
|
74
|
+
(0, class_validator_1.IsEnum)(RateLimitKeyMode),
|
|
75
|
+
tslib_1.__metadata("design:type", String)
|
|
76
|
+
], RateLimiterHookConfig.prototype, "keyMode", void 0);
|
|
77
|
+
tslib_1.__decorate([
|
|
78
|
+
(0, class_validator_1.IsOptional)(),
|
|
79
|
+
(0, class_validator_1.IsNumber)(),
|
|
80
|
+
(0, class_transformer_1.Type)(() => Number),
|
|
81
|
+
tslib_1.__metadata("design:type", Number)
|
|
82
|
+
], RateLimiterHookConfig.prototype, "priority", void 0);
|
|
83
|
+
tslib_1.__decorate([
|
|
84
|
+
(0, class_validator_1.IsOptional)(),
|
|
85
|
+
(0, class_validator_1.IsArray)(),
|
|
86
|
+
(0, class_validator_1.IsString)({ each: true }),
|
|
87
|
+
(0, nestjs_validation_1.TransformCommaSeparated)(),
|
|
88
|
+
tslib_1.__metadata("design:type", Array)
|
|
89
|
+
], RateLimiterHookConfig.prototype, "ignoredKeys", void 0);
|
|
90
|
+
//# sourceMappingURL=rate-limiter-hook.config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter-hook.config.js","sourceRoot":"","sources":["../../../../libs/rate-limiter-hook/src/rate-limiter-hook.config.ts"],"names":[],"mappings":";;;;AAAA,qDAQyB;AACzB,yDAAyC;AACzC,sEAA2F;AAE3F;;GAEG;AACU,QAAA,oCAAoC,GAAG,MAAM,CAAC,sCAAsC,CAAC,CAAC;AAEnG;;GAEG;AACH,IAAY,gBAOX;AAPD,WAAY,gBAAgB;IAC1B,uEAAuE;IACvE,6BAAS,CAAA;IACT,kCAAkC;IAClC,qCAAiB,CAAA;IACjB,mCAAmC;IACnC,yCAAqB,CAAA;AACvB,CAAC,EAPW,gBAAgB,gCAAhB,gBAAgB,QAO3B;AAED;;GAEG;AACH,IAAY,gBAOX;AAPD,WAAY,gBAAgB;IAC1B,kEAAkE;IAClE,+BAAW,CAAA;IACX,2DAA2D;IAC3D,6BAAS,CAAA;IACT,wEAAwE;IACxE,+BAAW,CAAA;AACb,CAAC,EAPW,gBAAgB,gCAAhB,gBAAgB,QAO3B;AAED;;GAEG;AACH,MAAa,qBAAqB;CAgFjC;AAhFD,sDAgFC;AA7EiB;IAFf,IAAA,2BAAS,GAAE;IACX,IAAA,oCAAgB,GAAE;;sDACe;AASlB;IAHf,IAAA,4BAAU,EAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC;IACrC,IAAA,0BAAQ,GAAE;IACV,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;;qDACa;AAShB;IAHf,IAAA,4BAAU,EAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC;IACrC,IAAA,0BAAQ,GAAE;IACV,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;;uDACe;AASlB;IAHf,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;;4DACoB;AAYvB;IAJf,IAAA,4BAAU,EAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC;IACrC,IAAA,yBAAO,GAAE;IACT,IAAA,wBAAM,EAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxC,IAAA,2CAAuB,GAAE;;sDACmB;AAW7B;IAFf,IAAA,4BAAU,GAAE;IACZ,IAAA,wBAAM,EAAC,gBAAgB,CAAC;;sDACkB;AAS3B;IAHf,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;;uDACe;AAiBlB;IAJf,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,GAAE;IACT,IAAA,0BAAQ,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxB,IAAA,2CAAuB,GAAE;;0DACa"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { DynamicModule, InjectionToken } from '@nestjs/common';
|
|
2
|
+
import { RateLimiterHookConfig } from './rate-limiter-hook.config';
|
|
3
|
+
/**
|
|
4
|
+
* Async module options for rate limiter hook.
|
|
5
|
+
*/
|
|
6
|
+
export interface IRateLimiterHookModuleAsyncOptions {
|
|
7
|
+
useFactory: (...args: unknown[]) => RateLimiterHookConfig | Promise<RateLimiterHookConfig>;
|
|
8
|
+
inject?: InjectionToken[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Module that provides rate limiting hook for Dismissible.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { DismissibleModule } from '@dismissible/nestjs-core';
|
|
16
|
+
* import { RateLimiterHookModule, RateLimiterHook } from '@dismissible/nestjs-rate-limiter-hook';
|
|
17
|
+
*
|
|
18
|
+
* @Module({
|
|
19
|
+
* imports: [
|
|
20
|
+
* RateLimiterHookModule.forRoot({
|
|
21
|
+
* enabled: true,
|
|
22
|
+
* points: 10,
|
|
23
|
+
* duration: 1,
|
|
24
|
+
* keyType: ['ip'],
|
|
25
|
+
* }),
|
|
26
|
+
* DismissibleModule.forRoot({
|
|
27
|
+
* hooks: [RateLimiterHook],
|
|
28
|
+
* // ... other options
|
|
29
|
+
* }),
|
|
30
|
+
* ],
|
|
31
|
+
* })
|
|
32
|
+
* export class AppModule {}
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare class RateLimiterHookModule {
|
|
36
|
+
static forRoot(config: RateLimiterHookConfig): DynamicModule;
|
|
37
|
+
/**
|
|
38
|
+
* Create module with async configuration.
|
|
39
|
+
* Useful when config values come from environment or other async sources.
|
|
40
|
+
*/
|
|
41
|
+
static forRootAsync(options: IRateLimiterHookModuleAsyncOptions): DynamicModule;
|
|
42
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var RateLimiterHookModule_1;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.RateLimiterHookModule = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const rate_limiter_hook_1 = require("./rate-limiter.hook");
|
|
8
|
+
const rate_limiter_service_1 = require("./rate-limiter.service");
|
|
9
|
+
const rate_limiter_hook_config_1 = require("./rate-limiter-hook.config");
|
|
10
|
+
/**
|
|
11
|
+
* Module that provides rate limiting hook for Dismissible.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { DismissibleModule } from '@dismissible/nestjs-core';
|
|
16
|
+
* import { RateLimiterHookModule, RateLimiterHook } from '@dismissible/nestjs-rate-limiter-hook';
|
|
17
|
+
*
|
|
18
|
+
* @Module({
|
|
19
|
+
* imports: [
|
|
20
|
+
* RateLimiterHookModule.forRoot({
|
|
21
|
+
* enabled: true,
|
|
22
|
+
* points: 10,
|
|
23
|
+
* duration: 1,
|
|
24
|
+
* keyType: ['ip'],
|
|
25
|
+
* }),
|
|
26
|
+
* DismissibleModule.forRoot({
|
|
27
|
+
* hooks: [RateLimiterHook],
|
|
28
|
+
* // ... other options
|
|
29
|
+
* }),
|
|
30
|
+
* ],
|
|
31
|
+
* })
|
|
32
|
+
* export class AppModule {}
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
let RateLimiterHookModule = RateLimiterHookModule_1 = class RateLimiterHookModule {
|
|
36
|
+
static forRoot(config) {
|
|
37
|
+
return {
|
|
38
|
+
module: RateLimiterHookModule_1,
|
|
39
|
+
providers: [
|
|
40
|
+
{
|
|
41
|
+
provide: rate_limiter_hook_config_1.DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG,
|
|
42
|
+
useValue: config,
|
|
43
|
+
},
|
|
44
|
+
rate_limiter_service_1.RateLimiterService,
|
|
45
|
+
rate_limiter_hook_1.RateLimiterHook,
|
|
46
|
+
],
|
|
47
|
+
exports: [rate_limiter_hook_1.RateLimiterHook, rate_limiter_service_1.RateLimiterService, rate_limiter_hook_config_1.DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG],
|
|
48
|
+
global: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create module with async configuration.
|
|
53
|
+
* Useful when config values come from environment or other async sources.
|
|
54
|
+
*/
|
|
55
|
+
static forRootAsync(options) {
|
|
56
|
+
return {
|
|
57
|
+
module: RateLimiterHookModule_1,
|
|
58
|
+
providers: [
|
|
59
|
+
{
|
|
60
|
+
provide: rate_limiter_hook_config_1.DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG,
|
|
61
|
+
useFactory: options.useFactory,
|
|
62
|
+
inject: options.inject ?? [],
|
|
63
|
+
},
|
|
64
|
+
rate_limiter_service_1.RateLimiterService,
|
|
65
|
+
rate_limiter_hook_1.RateLimiterHook,
|
|
66
|
+
],
|
|
67
|
+
exports: [rate_limiter_hook_1.RateLimiterHook, rate_limiter_service_1.RateLimiterService, rate_limiter_hook_config_1.DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG],
|
|
68
|
+
global: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
exports.RateLimiterHookModule = RateLimiterHookModule;
|
|
73
|
+
exports.RateLimiterHookModule = RateLimiterHookModule = RateLimiterHookModule_1 = tslib_1.__decorate([
|
|
74
|
+
(0, common_1.Module)({})
|
|
75
|
+
], RateLimiterHookModule);
|
|
76
|
+
//# sourceMappingURL=rate-limiter-hook.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter-hook.module.js","sourceRoot":"","sources":["../../../../libs/rate-limiter-hook/src/rate-limiter-hook.module.ts"],"names":[],"mappings":";;;;;AAAA,2CAAuE;AACvE,2DAAsD;AACtD,iEAA4D;AAC5D,yEAGoC;AAUpC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEI,IAAM,qBAAqB,6BAA3B,MAAM,qBAAqB;IAChC,MAAM,CAAC,OAAO,CAAC,MAA6B;QAC1C,OAAO;YACL,MAAM,EAAE,uBAAqB;YAC7B,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,+DAAoC;oBAC7C,QAAQ,EAAE,MAAM;iBACjB;gBACD,yCAAkB;gBAClB,mCAAe;aAChB;YACD,OAAO,EAAE,CAAC,mCAAe,EAAE,yCAAkB,EAAE,+DAAoC,CAAC;YACpF,MAAM,EAAE,IAAI;SACb,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,OAA2C;QAC7D,OAAO;YACL,MAAM,EAAE,uBAAqB;YAC7B,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,+DAAoC;oBAC7C,UAAU,EAAE,OAAO,CAAC,UAAU;oBAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;iBAC7B;gBACD,yCAAkB;gBAClB,mCAAe;aAChB;YACD,OAAO,EAAE,CAAC,mCAAe,EAAE,yCAAkB,EAAE,+DAAoC,CAAC;YACpF,MAAM,EAAE,IAAI;SACb,CAAC;IACJ,CAAC;CACF,CAAA;AArCY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,qBAAqB,CAqCjC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { IDismissibleLifecycleHook, IHookResult } from '@dismissible/nestjs-hooks';
|
|
2
|
+
import { IRequestContext } from '@dismissible/nestjs-request';
|
|
3
|
+
import { IDismissibleLogger } from '@dismissible/nestjs-logger';
|
|
4
|
+
import { RateLimiterService } from './rate-limiter.service';
|
|
5
|
+
import { RateLimiterHookConfig } from './rate-limiter-hook.config';
|
|
6
|
+
/**
|
|
7
|
+
* Custom error for rate limiting that includes retry information.
|
|
8
|
+
*/
|
|
9
|
+
export declare class TooManyRequestsException extends Error {
|
|
10
|
+
readonly statusCode = 429;
|
|
11
|
+
readonly retryAfter?: number;
|
|
12
|
+
constructor(message: string, retryAfterMs?: number);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Rate limiter hook that limits requests based on configured key types.
|
|
16
|
+
* This hook runs during the pre-request phase and rejects requests that exceed the rate limit.
|
|
17
|
+
*/
|
|
18
|
+
export declare class RateLimiterHook implements IDismissibleLifecycleHook {
|
|
19
|
+
private readonly rateLimiterService;
|
|
20
|
+
private readonly config;
|
|
21
|
+
private readonly logger;
|
|
22
|
+
readonly priority: number;
|
|
23
|
+
constructor(rateLimiterService: RateLimiterService, config: RateLimiterHookConfig, logger: IDismissibleLogger);
|
|
24
|
+
/**
|
|
25
|
+
* Check rate limit before processing the request.
|
|
26
|
+
* Runs before any dismissible operation.
|
|
27
|
+
*/
|
|
28
|
+
onBeforeRequest(itemId: string, userId: string, context?: IRequestContext): Promise<IHookResult>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimiterHook = exports.TooManyRequestsException = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const nestjs_logger_1 = require("@dismissible/nestjs-logger");
|
|
7
|
+
const rate_limiter_service_1 = require("./rate-limiter.service");
|
|
8
|
+
const rate_limiter_hook_config_1 = require("./rate-limiter-hook.config");
|
|
9
|
+
/**
|
|
10
|
+
* Custom error for rate limiting that includes retry information.
|
|
11
|
+
*/
|
|
12
|
+
class TooManyRequestsException extends Error {
|
|
13
|
+
constructor(message, retryAfterMs) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.statusCode = 429;
|
|
16
|
+
this.name = 'TooManyRequestsException';
|
|
17
|
+
if (retryAfterMs) {
|
|
18
|
+
// Convert milliseconds to seconds for Retry-After header
|
|
19
|
+
this.retryAfter = Math.ceil(retryAfterMs / 1000);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.TooManyRequestsException = TooManyRequestsException;
|
|
24
|
+
/**
|
|
25
|
+
* Rate limiter hook that limits requests based on configured key types.
|
|
26
|
+
* This hook runs during the pre-request phase and rejects requests that exceed the rate limit.
|
|
27
|
+
*/
|
|
28
|
+
let RateLimiterHook = class RateLimiterHook {
|
|
29
|
+
constructor(rateLimiterService, config, logger) {
|
|
30
|
+
this.rateLimiterService = rateLimiterService;
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.logger = logger;
|
|
33
|
+
this.priority = config.priority ?? -101;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check rate limit before processing the request.
|
|
37
|
+
* Runs before any dismissible operation.
|
|
38
|
+
*/
|
|
39
|
+
async onBeforeRequest(itemId, userId, context) {
|
|
40
|
+
if (!this.config.enabled) {
|
|
41
|
+
return { proceed: true };
|
|
42
|
+
}
|
|
43
|
+
// Check if request should be ignored (whitelisted)
|
|
44
|
+
if (this.rateLimiterService.isIgnored(context)) {
|
|
45
|
+
this.logger.debug('Rate limit bypassed (ignored key)', {
|
|
46
|
+
itemId,
|
|
47
|
+
userId,
|
|
48
|
+
requestId: context?.requestId,
|
|
49
|
+
});
|
|
50
|
+
return { proceed: true };
|
|
51
|
+
}
|
|
52
|
+
const keys = this.rateLimiterService.generateKeys(context);
|
|
53
|
+
this.logger.debug('Checking rate limit', {
|
|
54
|
+
itemId,
|
|
55
|
+
userId,
|
|
56
|
+
keys,
|
|
57
|
+
requestId: context?.requestId,
|
|
58
|
+
});
|
|
59
|
+
const result = await this.rateLimiterService.consumeAll(keys);
|
|
60
|
+
if (!result.allowed) {
|
|
61
|
+
this.logger.debug('Rate limit exceeded', {
|
|
62
|
+
itemId,
|
|
63
|
+
userId,
|
|
64
|
+
keys,
|
|
65
|
+
requestId: context?.requestId,
|
|
66
|
+
msBeforeNext: result.msBeforeNext,
|
|
67
|
+
});
|
|
68
|
+
throw new TooManyRequestsException('Rate limit exceeded. Please try again later.', result.msBeforeNext);
|
|
69
|
+
}
|
|
70
|
+
this.logger.debug('Request allowed', {
|
|
71
|
+
itemId,
|
|
72
|
+
userId,
|
|
73
|
+
keys,
|
|
74
|
+
requestId: context?.requestId,
|
|
75
|
+
remainingPoints: result.remainingPoints,
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
proceed: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
exports.RateLimiterHook = RateLimiterHook;
|
|
83
|
+
exports.RateLimiterHook = RateLimiterHook = tslib_1.__decorate([
|
|
84
|
+
(0, common_1.Injectable)(),
|
|
85
|
+
tslib_1.__param(1, (0, common_1.Inject)(rate_limiter_hook_config_1.DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG)),
|
|
86
|
+
tslib_1.__param(2, (0, common_1.Inject)(nestjs_logger_1.DISMISSIBLE_LOGGER)),
|
|
87
|
+
tslib_1.__metadata("design:paramtypes", [rate_limiter_service_1.RateLimiterService,
|
|
88
|
+
rate_limiter_hook_config_1.RateLimiterHookConfig, Object])
|
|
89
|
+
], RateLimiterHook);
|
|
90
|
+
//# sourceMappingURL=rate-limiter.hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.hook.js","sourceRoot":"","sources":["../../../../libs/rate-limiter-hook/src/rate-limiter.hook.ts"],"names":[],"mappings":";;;;AAAA,2CAAoD;AAGpD,8DAAoF;AACpF,iEAA4D;AAC5D,yEAGoC;AAEpC;;GAEG;AACH,MAAa,wBAAyB,SAAQ,KAAK;IAIjD,YAAY,OAAe,EAAE,YAAqB;QAChD,KAAK,CAAC,OAAO,CAAC,CAAC;QAJR,eAAU,GAAG,GAAG,CAAC;QAKxB,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,YAAY,EAAE,CAAC;YACjB,yDAAyD;YACzD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;CACF;AAZD,4DAYC;AAED;;;GAGG;AAEI,IAAM,eAAe,GAArB,MAAM,eAAe;IAG1B,YACmB,kBAAsC,EAEtC,MAA6B,EAE7B,MAA0B;QAJ1B,uBAAkB,GAAlB,kBAAkB,CAAoB;QAEtC,WAAM,GAAN,MAAM,CAAuB;QAE7B,WAAM,GAAN,MAAM,CAAoB;QAE3C,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CACnB,MAAc,EACd,MAAc,EACd,OAAyB;QAEzB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACzB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;QAED,mDAAmD;QACnD,IAAI,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,EAAE;gBACrD,MAAM;gBACN,MAAM;gBACN,SAAS,EAAE,OAAO,EAAE,SAAS;aAC9B,CAAC,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAE3D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE;YACvC,MAAM;YACN,MAAM;YACN,IAAI;YACJ,SAAS,EAAE,OAAO,EAAE,SAAS;SAC9B,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAE9D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE;gBACvC,MAAM;gBACN,MAAM;gBACN,IAAI;gBACJ,SAAS,EAAE,OAAO,EAAE,SAAS;gBAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;aAClC,CAAC,CAAC;YAEH,MAAM,IAAI,wBAAwB,CAChC,8CAA8C,EAC9C,MAAM,CAAC,YAAY,CACpB,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE;YACnC,MAAM;YACN,MAAM;YACN,IAAI;YACJ,SAAS,EAAE,OAAO,EAAE,SAAS;YAC7B,eAAe,EAAE,MAAM,CAAC,eAAe;SACxC,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;CACF,CAAA;AA1EY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,mBAAU,GAAE;IAMR,mBAAA,IAAA,eAAM,EAAC,+DAAoC,CAAC,CAAA;IAE5C,mBAAA,IAAA,eAAM,EAAC,kCAAkB,CAAC,CAAA;6CAHU,yCAAkB;QAE9B,gDAAqB;GANrC,eAAe,CA0E3B"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { IRequestContext } from '@dismissible/nestjs-request';
|
|
2
|
+
import { IDismissibleLogger } from '@dismissible/nestjs-logger';
|
|
3
|
+
import { RateLimiterHookConfig } from './rate-limiter-hook.config';
|
|
4
|
+
/**
|
|
5
|
+
* Result of a rate limit check.
|
|
6
|
+
*/
|
|
7
|
+
export interface IRateLimitResult {
|
|
8
|
+
/** Whether the request is allowed */
|
|
9
|
+
allowed: boolean;
|
|
10
|
+
/** Remaining points in the current window */
|
|
11
|
+
remainingPoints?: number;
|
|
12
|
+
/** Milliseconds until the rate limit resets */
|
|
13
|
+
msBeforeNext?: number;
|
|
14
|
+
/** Error message if rate limited */
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Service that handles rate limiting logic using rate-limiter-flexible.
|
|
19
|
+
*/
|
|
20
|
+
export declare class RateLimiterService {
|
|
21
|
+
private readonly config;
|
|
22
|
+
private readonly logger;
|
|
23
|
+
private readonly rateLimiter;
|
|
24
|
+
private readonly ignoredKeysSet;
|
|
25
|
+
constructor(config: RateLimiterHookConfig, logger: IDismissibleLogger);
|
|
26
|
+
private normalizeIgnoredKey;
|
|
27
|
+
private tryGetHostname;
|
|
28
|
+
/**
|
|
29
|
+
* Generate rate limit key(s) based on the configured mode.
|
|
30
|
+
* Returns an array of keys to check.
|
|
31
|
+
*/
|
|
32
|
+
generateKeys(context?: IRequestContext): string[];
|
|
33
|
+
/**
|
|
34
|
+
* AND mode: Combine all key types into a single key.
|
|
35
|
+
* @deprecated Use generateKeys() instead
|
|
36
|
+
*/
|
|
37
|
+
generateKey(context?: IRequestContext): string;
|
|
38
|
+
/**
|
|
39
|
+
* AND mode: Combine all key types into a single key.
|
|
40
|
+
*/
|
|
41
|
+
private generateAndKey;
|
|
42
|
+
/**
|
|
43
|
+
* OR mode: Use the first available key type (fallback chain).
|
|
44
|
+
*/
|
|
45
|
+
private generateOrKey;
|
|
46
|
+
/**
|
|
47
|
+
* ANY mode: Return all available keys separately.
|
|
48
|
+
* Each key type gets its own rate limit bucket.
|
|
49
|
+
*/
|
|
50
|
+
private generateAnyKeys;
|
|
51
|
+
/**
|
|
52
|
+
* Extract a key value based on the key type.
|
|
53
|
+
*/
|
|
54
|
+
private extractKeyValue;
|
|
55
|
+
/**
|
|
56
|
+
* Extract IP address from headers or connection.
|
|
57
|
+
*/
|
|
58
|
+
private extractIp;
|
|
59
|
+
/**
|
|
60
|
+
* Extract origin from headers.
|
|
61
|
+
*/
|
|
62
|
+
private extractOrigin;
|
|
63
|
+
/**
|
|
64
|
+
* Extract referrer from headers.
|
|
65
|
+
*/
|
|
66
|
+
private extractReferrer;
|
|
67
|
+
/**
|
|
68
|
+
* Extract all raw key values from the request context.
|
|
69
|
+
* Used for checking against ignored keys list.
|
|
70
|
+
*/
|
|
71
|
+
extractRawKeyValues(context?: IRequestContext): string[];
|
|
72
|
+
/**
|
|
73
|
+
* Check if any of the raw key values should be ignored.
|
|
74
|
+
* Uses exact matching:
|
|
75
|
+
* - IPs are matched exactly
|
|
76
|
+
* - Origins/referrers are matched by exact hostname (when parsable as a URL),
|
|
77
|
+
* and also by exact raw value (to allow whitelisting full origins/URLs).
|
|
78
|
+
*/
|
|
79
|
+
isIgnored(context?: IRequestContext): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Check if a request should be rate limited.
|
|
82
|
+
*/
|
|
83
|
+
consume(key: string): Promise<IRateLimitResult>;
|
|
84
|
+
/**
|
|
85
|
+
* Check rate limit for all provided keys.
|
|
86
|
+
* In ANY mode, blocks if ANY key exceeds the limit.
|
|
87
|
+
*/
|
|
88
|
+
consumeAll(keys: string[]): Promise<IRateLimitResult>;
|
|
89
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimiterService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const rate_limiter_flexible_1 = require("rate-limiter-flexible");
|
|
7
|
+
const nestjs_logger_1 = require("@dismissible/nestjs-logger");
|
|
8
|
+
const rate_limiter_hook_config_1 = require("./rate-limiter-hook.config");
|
|
9
|
+
/**
|
|
10
|
+
* Service that handles rate limiting logic using rate-limiter-flexible.
|
|
11
|
+
*/
|
|
12
|
+
let RateLimiterService = class RateLimiterService {
|
|
13
|
+
constructor(config, logger) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.rateLimiter = new rate_limiter_flexible_1.RateLimiterMemory({
|
|
17
|
+
points: config.points,
|
|
18
|
+
duration: config.duration,
|
|
19
|
+
blockDuration: config.blockDuration,
|
|
20
|
+
});
|
|
21
|
+
this.logger.debug('Rate limiter: Initialized', {
|
|
22
|
+
points: config.points,
|
|
23
|
+
duration: config.duration,
|
|
24
|
+
blockDuration: config.blockDuration,
|
|
25
|
+
});
|
|
26
|
+
this.ignoredKeysSet = new Set((config.ignoredKeys ?? [])
|
|
27
|
+
.map((k) => this.normalizeIgnoredKey(k))
|
|
28
|
+
.filter((k) => Boolean(k)));
|
|
29
|
+
}
|
|
30
|
+
normalizeIgnoredKey(key) {
|
|
31
|
+
const normalized = key.trim().toLowerCase();
|
|
32
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
33
|
+
}
|
|
34
|
+
tryGetHostname(value) {
|
|
35
|
+
try {
|
|
36
|
+
return new URL(value).hostname.toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate rate limit key(s) based on the configured mode.
|
|
44
|
+
* Returns an array of keys to check.
|
|
45
|
+
*/
|
|
46
|
+
generateKeys(context) {
|
|
47
|
+
const mode = this.config.keyMode ?? rate_limiter_hook_config_1.RateLimitKeyMode.AND;
|
|
48
|
+
switch (mode) {
|
|
49
|
+
case rate_limiter_hook_config_1.RateLimitKeyMode.OR:
|
|
50
|
+
return this.generateOrKey(context);
|
|
51
|
+
case rate_limiter_hook_config_1.RateLimitKeyMode.ANY:
|
|
52
|
+
return this.generateAnyKeys(context);
|
|
53
|
+
case rate_limiter_hook_config_1.RateLimitKeyMode.AND:
|
|
54
|
+
default:
|
|
55
|
+
return [this.generateAndKey(context)];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* AND mode: Combine all key types into a single key.
|
|
60
|
+
* @deprecated Use generateKeys() instead
|
|
61
|
+
*/
|
|
62
|
+
generateKey(context) {
|
|
63
|
+
return this.generateAndKey(context);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* AND mode: Combine all key types into a single key.
|
|
67
|
+
*/
|
|
68
|
+
generateAndKey(context) {
|
|
69
|
+
const keyParts = [];
|
|
70
|
+
for (const keyType of this.config.keyType) {
|
|
71
|
+
const value = this.extractKeyValue(keyType, context);
|
|
72
|
+
if (value) {
|
|
73
|
+
keyParts.push(value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// If no key parts could be extracted, use a fallback
|
|
77
|
+
if (keyParts.length === 0) {
|
|
78
|
+
return 'unknown';
|
|
79
|
+
}
|
|
80
|
+
return keyParts.join(':');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* OR mode: Use the first available key type (fallback chain).
|
|
84
|
+
*/
|
|
85
|
+
generateOrKey(context) {
|
|
86
|
+
for (const keyType of this.config.keyType) {
|
|
87
|
+
const value = this.extractKeyValue(keyType, context);
|
|
88
|
+
if (value) {
|
|
89
|
+
return [value];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return ['unknown'];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* ANY mode: Return all available keys separately.
|
|
96
|
+
* Each key type gets its own rate limit bucket.
|
|
97
|
+
*/
|
|
98
|
+
generateAnyKeys(context) {
|
|
99
|
+
const keys = [];
|
|
100
|
+
for (const keyType of this.config.keyType) {
|
|
101
|
+
const value = this.extractKeyValue(keyType, context);
|
|
102
|
+
if (value) {
|
|
103
|
+
// Prefix with key type to avoid collisions between different key types
|
|
104
|
+
keys.push(`${keyType}:${value}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (keys.length === 0) {
|
|
108
|
+
return ['unknown'];
|
|
109
|
+
}
|
|
110
|
+
return keys;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Extract a key value based on the key type.
|
|
114
|
+
*/
|
|
115
|
+
extractKeyValue(keyType, context) {
|
|
116
|
+
if (!context?.headers) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
switch (keyType) {
|
|
120
|
+
case rate_limiter_hook_config_1.RateLimitKeyType.IP:
|
|
121
|
+
return this.extractIp(context);
|
|
122
|
+
case rate_limiter_hook_config_1.RateLimitKeyType.ORIGIN:
|
|
123
|
+
return this.extractOrigin(context);
|
|
124
|
+
case rate_limiter_hook_config_1.RateLimitKeyType.REFERRER:
|
|
125
|
+
return this.extractReferrer(context);
|
|
126
|
+
default:
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Extract IP address from headers or connection.
|
|
132
|
+
*/
|
|
133
|
+
extractIp(context) {
|
|
134
|
+
// Check x-forwarded-for header first (for proxied requests)
|
|
135
|
+
const forwardedFor = context.headers['x-forwarded-for'];
|
|
136
|
+
if (forwardedFor) {
|
|
137
|
+
// x-forwarded-for can be a comma-separated list; take the first IP
|
|
138
|
+
const firstIp = forwardedFor.split(',')[0]?.trim();
|
|
139
|
+
if (firstIp) {
|
|
140
|
+
return firstIp;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Fall back to x-real-ip
|
|
144
|
+
const realIp = context.headers['x-real-ip'];
|
|
145
|
+
if (realIp) {
|
|
146
|
+
return realIp;
|
|
147
|
+
}
|
|
148
|
+
// Could also check context for direct IP if available
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Extract origin from headers.
|
|
153
|
+
*/
|
|
154
|
+
extractOrigin(context) {
|
|
155
|
+
return context.headers['origin'];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Extract referrer from headers.
|
|
159
|
+
*/
|
|
160
|
+
extractReferrer(context) {
|
|
161
|
+
// Note: The header is spelled "referer" (historical misspelling)
|
|
162
|
+
return context.headers['referer'];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Extract all raw key values from the request context.
|
|
166
|
+
* Used for checking against ignored keys list.
|
|
167
|
+
*/
|
|
168
|
+
extractRawKeyValues(context) {
|
|
169
|
+
const values = [];
|
|
170
|
+
for (const keyType of this.config.keyType) {
|
|
171
|
+
const value = this.extractKeyValue(keyType, context);
|
|
172
|
+
if (value) {
|
|
173
|
+
values.push(value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return values;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if any of the raw key values should be ignored.
|
|
180
|
+
* Uses exact matching:
|
|
181
|
+
* - IPs are matched exactly
|
|
182
|
+
* - Origins/referrers are matched by exact hostname (when parsable as a URL),
|
|
183
|
+
* and also by exact raw value (to allow whitelisting full origins/URLs).
|
|
184
|
+
*/
|
|
185
|
+
isIgnored(context) {
|
|
186
|
+
if (this.ignoredKeysSet.size === 0) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const rawValues = this.extractRawKeyValues(context);
|
|
190
|
+
for (const rawValue of rawValues) {
|
|
191
|
+
const normalizedRaw = rawValue.trim().toLowerCase();
|
|
192
|
+
if (this.ignoredKeysSet.has(normalizedRaw)) {
|
|
193
|
+
this.logger.debug('Rate limiter: Key ignored (exact)', {
|
|
194
|
+
rawValue,
|
|
195
|
+
matchedValue: normalizedRaw,
|
|
196
|
+
});
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
const hostname = this.tryGetHostname(rawValue);
|
|
200
|
+
if (hostname && this.ignoredKeysSet.has(hostname)) {
|
|
201
|
+
this.logger.debug('Rate limiter: Key ignored (hostname)', {
|
|
202
|
+
rawValue,
|
|
203
|
+
matchedValue: hostname,
|
|
204
|
+
});
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check if a request should be rate limited.
|
|
212
|
+
*/
|
|
213
|
+
async consume(key) {
|
|
214
|
+
try {
|
|
215
|
+
const result = await this.rateLimiter.consume(key);
|
|
216
|
+
this.logger.debug('Rate limiter: Request allowed', {
|
|
217
|
+
key,
|
|
218
|
+
remainingPoints: result.remainingPoints,
|
|
219
|
+
msBeforeNext: result.msBeforeNext,
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
allowed: true,
|
|
223
|
+
remainingPoints: result.remainingPoints,
|
|
224
|
+
msBeforeNext: result.msBeforeNext,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (error instanceof rate_limiter_flexible_1.RateLimiterRes) {
|
|
229
|
+
this.logger.debug('Rate limiter: Request blocked', {
|
|
230
|
+
key,
|
|
231
|
+
remainingPoints: error.remainingPoints,
|
|
232
|
+
msBeforeNext: error.msBeforeNext,
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
allowed: false,
|
|
236
|
+
remainingPoints: error.remainingPoints,
|
|
237
|
+
msBeforeNext: error.msBeforeNext,
|
|
238
|
+
error: 'Too many requests',
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Unexpected error - log and allow the request to proceed
|
|
242
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
243
|
+
this.logger.error('Rate limiter: Unexpected error', error instanceof Error ? error : new Error(errorMessage), { key });
|
|
244
|
+
return {
|
|
245
|
+
allowed: true,
|
|
246
|
+
error: 'Rate limiter error',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Check rate limit for all provided keys.
|
|
252
|
+
* In ANY mode, blocks if ANY key exceeds the limit.
|
|
253
|
+
*/
|
|
254
|
+
async consumeAll(keys) {
|
|
255
|
+
const results = [];
|
|
256
|
+
for (const key of keys) {
|
|
257
|
+
const result = await this.consume(key);
|
|
258
|
+
results.push(result);
|
|
259
|
+
// If any key is blocked, return immediately
|
|
260
|
+
if (!result.allowed) {
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// All keys allowed - return the result with the lowest remaining points
|
|
265
|
+
const minRemaining = results.reduce((min, r) => Math.min(min, r.remainingPoints ?? Infinity), Infinity);
|
|
266
|
+
return {
|
|
267
|
+
allowed: true,
|
|
268
|
+
remainingPoints: minRemaining === Infinity ? undefined : minRemaining,
|
|
269
|
+
msBeforeNext: results[0]?.msBeforeNext,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
exports.RateLimiterService = RateLimiterService;
|
|
274
|
+
exports.RateLimiterService = RateLimiterService = tslib_1.__decorate([
|
|
275
|
+
(0, common_1.Injectable)(),
|
|
276
|
+
tslib_1.__param(0, (0, common_1.Inject)(rate_limiter_hook_config_1.DISMISSIBLE_RATE_LIMITER_HOOK_CONFIG)),
|
|
277
|
+
tslib_1.__param(1, (0, common_1.Inject)(nestjs_logger_1.DISMISSIBLE_LOGGER)),
|
|
278
|
+
tslib_1.__metadata("design:paramtypes", [rate_limiter_hook_config_1.RateLimiterHookConfig, Object])
|
|
279
|
+
], RateLimiterService);
|
|
280
|
+
//# sourceMappingURL=rate-limiter.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.service.js","sourceRoot":"","sources":["../../../../libs/rate-limiter-hook/src/rate-limiter.service.ts"],"names":[],"mappings":"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"}
|