nestjs-shield 1.0.0 → 1.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.
- package/README.md +2 -1
- package/dist/apply-to.js +9 -0
- package/dist/checks/auto-ban.check.js +4 -2
- package/dist/shield.engine.d.ts +2 -0
- package/dist/shield.engine.js +51 -32
- package/dist/shield.module.d.ts +8 -3
- package/dist/shield.module.js +17 -2
- package/dist/shield.types.d.ts +1 -0
- package/dist/storage/redis.storage.js +40 -16
- package/dist/utils/banner.util.d.ts +10 -0
- package/dist/utils/banner.util.js +75 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Or from GitHub Packages (published as `@yassinos-coder/nestjs-shield`) — first
|
|
|
37
37
|
GITHUB_TOKEN=ghp_xxx npm install @yassinos-coder/nestjs-shield
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Node ≥ 18, NestJS 9 / 10 / 11.
|
|
40
|
+
Node ≥ 18, NestJS 9 / 10 / 11 / 12. NestJS 12 applications require Node ≥ 20.
|
|
41
41
|
|
|
42
42
|
## Quick start
|
|
43
43
|
|
|
@@ -129,6 +129,7 @@ interface ShieldConfig {
|
|
|
129
129
|
window: number; // ms — count window
|
|
130
130
|
banDuration: number; // ms — initial ban
|
|
131
131
|
escalate?: boolean; // 2× each repeat ban, default true
|
|
132
|
+
maxBanDuration?: number; // ms — cap on escalated ban duration
|
|
132
133
|
};
|
|
133
134
|
|
|
134
135
|
slowDown?: {
|
package/dist/apply-to.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Shield = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
4
5
|
const shield_engine_1 = require("./shield.engine");
|
|
5
6
|
const shield_middleware_1 = require("./shield.middleware");
|
|
6
7
|
const shield_constants_1 = require("./shield.constants");
|
|
7
8
|
const memory_storage_1 = require("./storage/memory.storage");
|
|
8
9
|
const redis_storage_1 = require("./storage/redis.storage");
|
|
10
|
+
const banner_util_1 = require("./utils/banner.util");
|
|
9
11
|
function buildStorage(option) {
|
|
10
12
|
if (!option || option === 'memory')
|
|
11
13
|
return new memory_storage_1.MemoryStorage();
|
|
@@ -24,9 +26,11 @@ function buildStorage(option) {
|
|
|
24
26
|
class Shield {
|
|
25
27
|
static applyTo(app, config = {}) {
|
|
26
28
|
let engine = null;
|
|
29
|
+
let resolvedConfig = config;
|
|
27
30
|
if (typeof app.get === 'function') {
|
|
28
31
|
try {
|
|
29
32
|
engine = app.get(shield_constants_1.SHIELD_ENGINE);
|
|
33
|
+
resolvedConfig = engine.getConfig();
|
|
30
34
|
}
|
|
31
35
|
catch {
|
|
32
36
|
engine = null;
|
|
@@ -37,6 +41,11 @@ class Shield {
|
|
|
37
41
|
engine = new shield_engine_1.ShieldEngine(config, storage);
|
|
38
42
|
}
|
|
39
43
|
app.use((0, shield_middleware_1.createShieldMiddleware)(engine));
|
|
44
|
+
if (resolvedConfig.enabled !== false && engine.markBannerShown()) {
|
|
45
|
+
const { banner, summary } = banner_util_1.BannerUtil.build(resolvedConfig);
|
|
46
|
+
process.stdout.write(banner + '\n');
|
|
47
|
+
new common_1.Logger('Shield').log(summary);
|
|
48
|
+
}
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
exports.Shield = Shield;
|
|
@@ -33,8 +33,10 @@ class AutoBanCheck {
|
|
|
33
33
|
return;
|
|
34
34
|
const banCountKey = `${shield_constants_1.KEY_BAN_COUNT}:${ip}`;
|
|
35
35
|
const banCountResult = await storage.increment(banCountKey, config.banDuration * 16);
|
|
36
|
-
const
|
|
37
|
-
const
|
|
36
|
+
const exponent = Math.min(Math.max(0, banCountResult.count - 1), 30);
|
|
37
|
+
const escalation = config.escalate === false ? 1 : Math.pow(2, exponent);
|
|
38
|
+
const computed = config.banDuration * escalation;
|
|
39
|
+
const duration = config.maxBanDuration !== undefined ? Math.min(computed, config.maxBanDuration) : computed;
|
|
38
40
|
const expiresAt = Date.now() + duration;
|
|
39
41
|
await storage.set(`${shield_constants_1.KEY_BAN}:${ip}`, String(expiresAt), duration);
|
|
40
42
|
await storage.delete(violationsKey);
|
package/dist/shield.engine.d.ts
CHANGED
|
@@ -11,9 +11,11 @@ export interface EngineDecision {
|
|
|
11
11
|
export declare class ShieldEngine {
|
|
12
12
|
private readonly config;
|
|
13
13
|
private readonly storage;
|
|
14
|
+
private bannerShown;
|
|
14
15
|
constructor(config: ShieldConfig, storage: ShieldStorage);
|
|
15
16
|
getConfig(): ShieldConfig;
|
|
16
17
|
getStorage(): ShieldStorage;
|
|
18
|
+
markBannerShown(): boolean;
|
|
17
19
|
run(req: AnyRequest, res: AnyResponse, overrides?: DecoratorOverrides): Promise<EngineDecision>;
|
|
18
20
|
private merge;
|
|
19
21
|
private mergeRateLimit;
|
package/dist/shield.engine.js
CHANGED
|
@@ -31,6 +31,7 @@ let ShieldEngine = class ShieldEngine {
|
|
|
31
31
|
constructor(config, storage) {
|
|
32
32
|
this.config = config;
|
|
33
33
|
this.storage = storage;
|
|
34
|
+
this.bannerShown = false;
|
|
34
35
|
}
|
|
35
36
|
getConfig() {
|
|
36
37
|
return this.config;
|
|
@@ -38,6 +39,12 @@ let ShieldEngine = class ShieldEngine {
|
|
|
38
39
|
getStorage() {
|
|
39
40
|
return this.storage;
|
|
40
41
|
}
|
|
42
|
+
markBannerShown() {
|
|
43
|
+
if (this.bannerShown)
|
|
44
|
+
return false;
|
|
45
|
+
this.bannerShown = true;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
41
48
|
async run(req, res, overrides = {}) {
|
|
42
49
|
if (this.config.enabled === false)
|
|
43
50
|
return { allowed: true, ip: '' };
|
|
@@ -141,42 +148,54 @@ let ShieldEngine = class ShieldEngine {
|
|
|
141
148
|
}
|
|
142
149
|
release = burstOut.release;
|
|
143
150
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (!rl.allowed) {
|
|
153
|
-
if (this.config.response?.rateLimit429?.includeRetryAfter !== false && rl.retryAfterMs) {
|
|
154
|
-
headers_util_1.HeadersUtil.writeRetryAfter(res, rl.retryAfterMs);
|
|
151
|
+
try {
|
|
152
|
+
const rateLimit = this.mergeRateLimit(overrides);
|
|
153
|
+
let rateLimitTtl = 0;
|
|
154
|
+
if (!skipSet.has('rate-limit') && rateLimit) {
|
|
155
|
+
const rl = await rate_limit_check_1.RateLimitCheck.check(this.storage, req, ip, rateLimit);
|
|
156
|
+
rateLimitTtl = rateLimit.ttl;
|
|
157
|
+
if (rateLimit.headers !== false) {
|
|
158
|
+
headers_util_1.HeadersUtil.writeRateLimit(res, rateLimit.standardHeaders ?? 'draft-7', rl.limit, rl.remaining, rl.resetMs);
|
|
155
159
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
160
|
+
if (!rl.allowed) {
|
|
161
|
+
if (this.config.response?.rateLimit429?.includeRetryAfter !== false && rl.retryAfterMs) {
|
|
162
|
+
headers_util_1.HeadersUtil.writeRetryAfter(res, rl.retryAfterMs);
|
|
163
|
+
}
|
|
164
|
+
await auto_ban_check_1.AutoBanCheck.recordViolation(this.storage, ip, this.config.autoBan);
|
|
165
|
+
if (release)
|
|
166
|
+
await release();
|
|
167
|
+
this.notifyReject(req, res, ip, 'rate-limit', rl.reason ?? 'rate limited', 429, rl.retryAfterMs);
|
|
168
|
+
return {
|
|
169
|
+
allowed: false,
|
|
170
|
+
ip,
|
|
171
|
+
exception: new shield_exceptions_1.ShieldRateLimitException({
|
|
172
|
+
message: this.config.response?.rateLimit429?.message ?? rl.reason ?? 'Too many requests',
|
|
173
|
+
code: this.config.response?.rateLimit429?.code,
|
|
174
|
+
layer: 'rate-limit',
|
|
175
|
+
retryAfter: rl.retryAfterMs ? Math.ceil(rl.retryAfterMs / 1000) : undefined,
|
|
176
|
+
}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
let delayMs;
|
|
181
|
+
const slowDown = (overrides.slowDown ?? this.config.slowDown);
|
|
182
|
+
if (!skipSet.has('slow-down') && slowDown) {
|
|
183
|
+
const sd = await slow_down_check_1.SlowDownCheck.check(this.storage, ip, rateLimitTtl || 60_000, slowDown);
|
|
184
|
+
if (sd.delayMs && sd.delayMs > 0)
|
|
185
|
+
delayMs = sd.delayMs;
|
|
170
186
|
}
|
|
187
|
+
return { allowed: true, ip, delayMs, release };
|
|
171
188
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
catch (err) {
|
|
190
|
+
if (release) {
|
|
191
|
+
try {
|
|
192
|
+
await release();
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
throw err;
|
|
178
198
|
}
|
|
179
|
-
return { allowed: true, ip, delayMs, release };
|
|
180
199
|
}
|
|
181
200
|
merge(base, override) {
|
|
182
201
|
if (!base && !override)
|
package/dist/shield.module.d.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { DynamicModule, ModuleMetadata, OnModuleDestroy, Type } from '@nestjs/common';
|
|
1
|
+
import { DynamicModule, ModuleMetadata, OnApplicationBootstrap, OnModuleDestroy, Type } from '@nestjs/common';
|
|
2
|
+
import { ShieldEngine } from './shield.engine';
|
|
2
3
|
import type { ShieldConfig } from './shield.types';
|
|
3
4
|
import type { ShieldStorage } from './storage/shield-storage.interface';
|
|
4
5
|
export interface ShieldAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
5
6
|
useFactory: (...args: unknown[]) => Promise<ShieldConfig> | ShieldConfig;
|
|
6
7
|
inject?: (string | symbol | Type<unknown>)[];
|
|
7
8
|
}
|
|
8
|
-
export declare class ShieldModule implements OnModuleDestroy {
|
|
9
|
+
export declare class ShieldModule implements OnApplicationBootstrap, OnModuleDestroy {
|
|
9
10
|
private readonly storage;
|
|
10
|
-
|
|
11
|
+
private readonly config;
|
|
12
|
+
private readonly engine;
|
|
13
|
+
private readonly logger;
|
|
14
|
+
constructor(storage: ShieldStorage, config: ShieldConfig, engine: ShieldEngine);
|
|
15
|
+
onApplicationBootstrap(): void;
|
|
11
16
|
static forRoot(config?: ShieldConfig): DynamicModule;
|
|
12
17
|
static forRootAsync(options: ShieldAsyncOptions): DynamicModule;
|
|
13
18
|
onModuleDestroy(): Promise<void>;
|
package/dist/shield.module.js
CHANGED
|
@@ -21,6 +21,7 @@ const shield_engine_1 = require("./shield.engine");
|
|
|
21
21
|
const shield_guard_1 = require("./shield.guard");
|
|
22
22
|
const memory_storage_1 = require("./storage/memory.storage");
|
|
23
23
|
const redis_storage_1 = require("./storage/redis.storage");
|
|
24
|
+
const banner_util_1 = require("./utils/banner.util");
|
|
24
25
|
function buildStorage(option) {
|
|
25
26
|
if (!option || option === 'memory')
|
|
26
27
|
return new memory_storage_1.MemoryStorage();
|
|
@@ -37,8 +38,20 @@ function buildStorage(option) {
|
|
|
37
38
|
return option;
|
|
38
39
|
}
|
|
39
40
|
let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
40
|
-
constructor(storage) {
|
|
41
|
+
constructor(storage, config, engine) {
|
|
41
42
|
this.storage = storage;
|
|
43
|
+
this.config = config;
|
|
44
|
+
this.engine = engine;
|
|
45
|
+
this.logger = new common_1.Logger('Shield');
|
|
46
|
+
}
|
|
47
|
+
onApplicationBootstrap() {
|
|
48
|
+
if (this.config.enabled === false)
|
|
49
|
+
return;
|
|
50
|
+
if (!this.engine.markBannerShown())
|
|
51
|
+
return;
|
|
52
|
+
const { banner, summary } = banner_util_1.BannerUtil.build(this.config);
|
|
53
|
+
process.stdout.write(banner + '\n');
|
|
54
|
+
this.logger.log(summary);
|
|
42
55
|
}
|
|
43
56
|
static forRoot(config = {}) {
|
|
44
57
|
const providers = [
|
|
@@ -101,5 +114,7 @@ exports.ShieldModule = ShieldModule;
|
|
|
101
114
|
exports.ShieldModule = ShieldModule = ShieldModule_1 = __decorate([
|
|
102
115
|
(0, common_1.Module)({}),
|
|
103
116
|
__param(0, (0, common_1.Inject)(shield_constants_1.SHIELD_STORAGE)),
|
|
104
|
-
|
|
117
|
+
__param(1, (0, common_1.Inject)(shield_constants_1.SHIELD_CONFIG)),
|
|
118
|
+
__param(2, (0, common_1.Inject)(shield_constants_1.SHIELD_ENGINE)),
|
|
119
|
+
__metadata("design:paramtypes", [Object, Object, shield_engine_1.ShieldEngine])
|
|
105
120
|
], ShieldModule);
|
package/dist/shield.types.d.ts
CHANGED
|
@@ -99,6 +99,31 @@ end
|
|
|
99
99
|
|
|
100
100
|
return { allowed, count, resetMs }
|
|
101
101
|
`;
|
|
102
|
+
const INCREMENT_LUA = `
|
|
103
|
+
local key = KEYS[1]
|
|
104
|
+
local by = tonumber(ARGV[1])
|
|
105
|
+
local ttlMs = tonumber(ARGV[2])
|
|
106
|
+
local count = redis.call('INCRBY', key, by)
|
|
107
|
+
if count == by then
|
|
108
|
+
redis.call('PEXPIRE', key, ttlMs)
|
|
109
|
+
end
|
|
110
|
+
local ttl = redis.call('PTTL', key)
|
|
111
|
+
if ttl < 0 then ttl = 0 end
|
|
112
|
+
return { count, ttl }
|
|
113
|
+
`;
|
|
114
|
+
const BURST_INCREMENT_LUA = `
|
|
115
|
+
local count = redis.call('INCR', KEYS[1])
|
|
116
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[1])
|
|
117
|
+
return count
|
|
118
|
+
`;
|
|
119
|
+
const BURST_DECREMENT_LUA = `
|
|
120
|
+
local count = tonumber(redis.call('GET', KEYS[1]) or '0')
|
|
121
|
+
if count <= 1 then
|
|
122
|
+
redis.call('DEL', KEYS[1])
|
|
123
|
+
return 0
|
|
124
|
+
end
|
|
125
|
+
return redis.call('DECR', KEYS[1])
|
|
126
|
+
`;
|
|
102
127
|
const SLIDING_COUNTER_LUA = `
|
|
103
128
|
local cur = KEYS[1]
|
|
104
129
|
local prev = KEYS[2]
|
|
@@ -129,10 +154,11 @@ class RedisStorage {
|
|
|
129
154
|
}
|
|
130
155
|
async increment(key, ttlMs, by = 1) {
|
|
131
156
|
const k = this.k(key);
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
157
|
+
const res = (await this.runScript('shieldIncrement', INCREMENT_LUA, [k], [by, ttlMs]));
|
|
158
|
+
return {
|
|
159
|
+
count: Number(res[0]),
|
|
160
|
+
expiresAt: Date.now() + Math.max(0, Number(res[1])),
|
|
161
|
+
};
|
|
136
162
|
}
|
|
137
163
|
async consumeToken(key, capacity, refillPerMs, cost = 1) {
|
|
138
164
|
const k = this.k(key);
|
|
@@ -153,11 +179,9 @@ class RedisStorage {
|
|
|
153
179
|
}
|
|
154
180
|
async fixedWindow(key, ttlMs, limit) {
|
|
155
181
|
const k = this.k(key);
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const ttl = await this.client.pttl(k);
|
|
160
|
-
return { count, allowed: count <= limit, resetMs: Math.max(0, ttl), limit };
|
|
182
|
+
const res = (await this.runScript('shieldIncrement', INCREMENT_LUA, [k], [1, ttlMs]));
|
|
183
|
+
const count = Number(res[0]);
|
|
184
|
+
return { count, allowed: count <= limit, resetMs: Math.max(0, Number(res[1])), limit };
|
|
161
185
|
}
|
|
162
186
|
async slidingWindowCounter(key, ttlMs, limit) {
|
|
163
187
|
const now = Date.now();
|
|
@@ -212,16 +236,13 @@ class RedisStorage {
|
|
|
212
236
|
}
|
|
213
237
|
async incrementConcurrent(key) {
|
|
214
238
|
const k = this.k(`burst:${key}`);
|
|
215
|
-
const next = await this.
|
|
216
|
-
|
|
217
|
-
return next;
|
|
239
|
+
const next = await this.runScript('shieldBurstIncrement', BURST_INCREMENT_LUA, [k], [60_000]);
|
|
240
|
+
return Number(next);
|
|
218
241
|
}
|
|
219
242
|
async decrementConcurrent(key) {
|
|
220
243
|
const k = this.k(`burst:${key}`);
|
|
221
|
-
const next = await this.
|
|
222
|
-
|
|
223
|
-
await this.client.del(k);
|
|
224
|
-
return Math.max(0, next);
|
|
244
|
+
const next = await this.runScript('shieldBurstDecrement', BURST_DECREMENT_LUA, [k], []);
|
|
245
|
+
return Math.max(0, Number(next));
|
|
225
246
|
}
|
|
226
247
|
k(key) {
|
|
227
248
|
return this.prefix ? `${this.prefix}:${key}` : key;
|
|
@@ -241,6 +262,9 @@ class RedisStorage {
|
|
|
241
262
|
this.client.defineCommand('shieldLeakyBucket', { numberOfKeys: 1, lua: LEAKY_BUCKET_LUA });
|
|
242
263
|
this.client.defineCommand('shieldSlidingLog', { numberOfKeys: 1, lua: SLIDING_LOG_LUA });
|
|
243
264
|
this.client.defineCommand('shieldSlidingCounter', { numberOfKeys: 2, lua: SLIDING_COUNTER_LUA });
|
|
265
|
+
this.client.defineCommand('shieldIncrement', { numberOfKeys: 1, lua: INCREMENT_LUA });
|
|
266
|
+
this.client.defineCommand('shieldBurstIncrement', { numberOfKeys: 1, lua: BURST_INCREMENT_LUA });
|
|
267
|
+
this.client.defineCommand('shieldBurstDecrement', { numberOfKeys: 1, lua: BURST_DECREMENT_LUA });
|
|
244
268
|
this.commandsDefined = true;
|
|
245
269
|
}
|
|
246
270
|
catch {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BannerUtil = void 0;
|
|
4
|
+
class BannerUtil {
|
|
5
|
+
static build(config) {
|
|
6
|
+
const version = BannerUtil.version();
|
|
7
|
+
const title = `nestjs-shield v${version} -- up and running`;
|
|
8
|
+
const inner = ` ${title} `;
|
|
9
|
+
const width = Math.max(inner.length, 50);
|
|
10
|
+
const padded = BannerUtil.centerPad(inner, width);
|
|
11
|
+
const top = ' +' + '-'.repeat(width) + '+';
|
|
12
|
+
const mid = ' |' + ' '.repeat(width) + '|';
|
|
13
|
+
const banner = [
|
|
14
|
+
'',
|
|
15
|
+
top,
|
|
16
|
+
mid,
|
|
17
|
+
' |' + padded + '|',
|
|
18
|
+
mid,
|
|
19
|
+
' +' + '-'.repeat(width) + '+',
|
|
20
|
+
'',
|
|
21
|
+
].join('\n');
|
|
22
|
+
const layers = [];
|
|
23
|
+
if (config.whitelist)
|
|
24
|
+
layers.push('whitelist');
|
|
25
|
+
if (config.blacklist)
|
|
26
|
+
layers.push('blacklist');
|
|
27
|
+
if (config.autoBan)
|
|
28
|
+
layers.push('auto-ban');
|
|
29
|
+
if (config.userAgent)
|
|
30
|
+
layers.push('user-agent');
|
|
31
|
+
if (config.payload)
|
|
32
|
+
layers.push('payload');
|
|
33
|
+
if (config.burst)
|
|
34
|
+
layers.push('burst');
|
|
35
|
+
if (config.rateLimit)
|
|
36
|
+
layers.push('rate-limit');
|
|
37
|
+
if (config.slowDown)
|
|
38
|
+
layers.push('slow-down');
|
|
39
|
+
const parts = [`storage=${BannerUtil.storageLabel(config)}`];
|
|
40
|
+
if (config.rateLimit) {
|
|
41
|
+
const rl = config.rateLimit;
|
|
42
|
+
const seconds = Math.max(1, Math.round(rl.ttl / 1000));
|
|
43
|
+
parts.push(`rate=${rl.limit}/${seconds}s (${rl.algorithm ?? 'token-bucket'})`);
|
|
44
|
+
}
|
|
45
|
+
if (layers.length)
|
|
46
|
+
parts.push(`layers=[${layers.join(', ')}]`);
|
|
47
|
+
return { banner, summary: parts.join(' | ') };
|
|
48
|
+
}
|
|
49
|
+
static centerPad(text, width) {
|
|
50
|
+
if (text.length >= width)
|
|
51
|
+
return text.slice(0, width);
|
|
52
|
+
const total = width - text.length;
|
|
53
|
+
const left = Math.floor(total / 2);
|
|
54
|
+
const right = total - left;
|
|
55
|
+
return ' '.repeat(left) + text + ' '.repeat(right);
|
|
56
|
+
}
|
|
57
|
+
static storageLabel(config) {
|
|
58
|
+
const s = config.storage;
|
|
59
|
+
if (!s || s === 'memory')
|
|
60
|
+
return 'memory';
|
|
61
|
+
if (typeof s === 'object' && 'type' in s)
|
|
62
|
+
return s.type;
|
|
63
|
+
return 'custom';
|
|
64
|
+
}
|
|
65
|
+
static version() {
|
|
66
|
+
try {
|
|
67
|
+
const pkg = require('../../package.json');
|
|
68
|
+
return pkg.version ?? '?';
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return '?';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.BannerUtil = BannerUtil;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestjs-shield",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Layered API protection for NestJS: rate limiting, IP allow/block lists, auto-ban, slow-down, UA filtering, burst caps, payload limits. Pluggable storage (memory/Redis).",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"node": ">=18"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0",
|
|
55
|
-
"@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0",
|
|
54
|
+
"@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0",
|
|
55
|
+
"@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0",
|
|
56
56
|
"reflect-metadata": "^0.1.13 || ^0.2.0",
|
|
57
57
|
"rxjs": "^7.0.0"
|
|
58
58
|
},
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"ipaddr.js": "^2.2.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@nestjs/common": "^
|
|
69
|
-
"@nestjs/core": "^
|
|
70
|
-
"@types/node": "^
|
|
68
|
+
"@nestjs/common": "^12.0.1",
|
|
69
|
+
"@nestjs/core": "^12.0.1",
|
|
70
|
+
"@types/node": "^22.0.0",
|
|
71
71
|
"ioredis": "^5.4.1",
|
|
72
72
|
"reflect-metadata": "^0.2.2",
|
|
73
73
|
"rimraf": "^5.0.7",
|