nestjs-shield 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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +361 -0
  3. package/dist/algorithms/fixed-window.d.ts +4 -0
  4. package/dist/algorithms/fixed-window.js +9 -0
  5. package/dist/algorithms/leaky-bucket.d.ts +4 -0
  6. package/dist/algorithms/leaky-bucket.js +10 -0
  7. package/dist/algorithms/sliding-window-counter.d.ts +4 -0
  8. package/dist/algorithms/sliding-window-counter.js +9 -0
  9. package/dist/algorithms/sliding-window-log.d.ts +4 -0
  10. package/dist/algorithms/sliding-window-log.js +9 -0
  11. package/dist/algorithms/token-bucket.d.ts +4 -0
  12. package/dist/algorithms/token-bucket.js +10 -0
  13. package/dist/apply-to.d.ts +8 -0
  14. package/dist/apply-to.js +42 -0
  15. package/dist/checks/auto-ban.check.d.ts +6 -0
  16. package/dist/checks/auto-ban.check.js +43 -0
  17. package/dist/checks/blacklist.check.d.ts +4 -0
  18. package/dist/checks/blacklist.check.js +20 -0
  19. package/dist/checks/burst.check.d.ts +5 -0
  20. package/dist/checks/burst.check.js +28 -0
  21. package/dist/checks/payload.check.d.ts +5 -0
  22. package/dist/checks/payload.check.js +46 -0
  23. package/dist/checks/rate-limit.check.d.ts +9 -0
  24. package/dist/checks/rate-limit.check.js +59 -0
  25. package/dist/checks/slow-down.check.d.ts +5 -0
  26. package/dist/checks/slow-down.check.js +19 -0
  27. package/dist/checks/user-agent.check.d.ts +4 -0
  28. package/dist/checks/user-agent.check.js +31 -0
  29. package/dist/checks/whitelist.check.d.ts +4 -0
  30. package/dist/checks/whitelist.check.js +15 -0
  31. package/dist/decorators/blacklist.decorator.d.ts +2 -0
  32. package/dist/decorators/blacklist.decorator.js +7 -0
  33. package/dist/decorators/burst-limit.decorator.d.ts +2 -0
  34. package/dist/decorators/burst-limit.decorator.js +7 -0
  35. package/dist/decorators/max-payload.decorator.d.ts +2 -0
  36. package/dist/decorators/max-payload.decorator.js +7 -0
  37. package/dist/decorators/rate-limit.decorator.d.ts +2 -0
  38. package/dist/decorators/rate-limit.decorator.js +7 -0
  39. package/dist/decorators/skip-shield.decorator.d.ts +2 -0
  40. package/dist/decorators/skip-shield.decorator.js +7 -0
  41. package/dist/decorators/slow-down.decorator.d.ts +2 -0
  42. package/dist/decorators/slow-down.decorator.js +7 -0
  43. package/dist/decorators/user-agent-policy.decorator.d.ts +2 -0
  44. package/dist/decorators/user-agent-policy.decorator.js +7 -0
  45. package/dist/decorators/whitelist.decorator.d.ts +2 -0
  46. package/dist/decorators/whitelist.decorator.js +7 -0
  47. package/dist/exceptions/shield.exceptions.d.ts +23 -0
  48. package/dist/exceptions/shield.exceptions.js +40 -0
  49. package/dist/index.d.ts +32 -0
  50. package/dist/index.js +58 -0
  51. package/dist/shield.constants.d.ts +22 -0
  52. package/dist/shield.constants.js +33 -0
  53. package/dist/shield.engine.d.ts +21 -0
  54. package/dist/shield.engine.js +215 -0
  55. package/dist/shield.guard.d.ts +10 -0
  56. package/dist/shield.guard.js +89 -0
  57. package/dist/shield.middleware.d.ts +5 -0
  58. package/dist/shield.middleware.js +47 -0
  59. package/dist/shield.module.d.ts +14 -0
  60. package/dist/shield.module.js +105 -0
  61. package/dist/shield.types.d.ts +141 -0
  62. package/dist/shield.types.js +2 -0
  63. package/dist/storage/memory.storage.d.ts +30 -0
  64. package/dist/storage/memory.storage.js +201 -0
  65. package/dist/storage/redis.storage.d.ts +44 -0
  66. package/dist/storage/redis.storage.js +258 -0
  67. package/dist/storage/shield-storage.interface.d.ts +30 -0
  68. package/dist/storage/shield-storage.interface.js +2 -0
  69. package/dist/utils/headers.util.d.ts +5 -0
  70. package/dist/utils/headers.util.js +23 -0
  71. package/dist/utils/ip.util.d.ts +6 -0
  72. package/dist/utils/ip.util.js +100 -0
  73. package/dist/utils/key.util.d.ts +5 -0
  74. package/dist/utils/key.util.js +24 -0
  75. package/dist/utils/ua.util.d.ts +4 -0
  76. package/dist/utils/ua.util.js +26 -0
  77. package/package.json +77 -0
@@ -0,0 +1,215 @@
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.ShieldEngine = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const auto_ban_check_1 = require("./checks/auto-ban.check");
18
+ const blacklist_check_1 = require("./checks/blacklist.check");
19
+ const burst_check_1 = require("./checks/burst.check");
20
+ const payload_check_1 = require("./checks/payload.check");
21
+ const rate_limit_check_1 = require("./checks/rate-limit.check");
22
+ const slow_down_check_1 = require("./checks/slow-down.check");
23
+ const user_agent_check_1 = require("./checks/user-agent.check");
24
+ const whitelist_check_1 = require("./checks/whitelist.check");
25
+ const shield_exceptions_1 = require("./exceptions/shield.exceptions");
26
+ const shield_constants_1 = require("./shield.constants");
27
+ const headers_util_1 = require("./utils/headers.util");
28
+ const ip_util_1 = require("./utils/ip.util");
29
+ const ua_util_1 = require("./utils/ua.util");
30
+ let ShieldEngine = class ShieldEngine {
31
+ constructor(config, storage) {
32
+ this.config = config;
33
+ this.storage = storage;
34
+ }
35
+ getConfig() {
36
+ return this.config;
37
+ }
38
+ getStorage() {
39
+ return this.storage;
40
+ }
41
+ async run(req, res, overrides = {}) {
42
+ if (this.config.enabled === false)
43
+ return { allowed: true, ip: '' };
44
+ const ip = this.config.ipResolver
45
+ ? this.config.ipResolver(req)
46
+ : ip_util_1.IpUtil.resolve(req, this.config.trustProxy);
47
+ if (overrides.skip === true)
48
+ return { allowed: true, ip };
49
+ const skipSet = new Set(Array.isArray(overrides.skip) ? overrides.skip : []);
50
+ const whitelist = this.merge(this.config.whitelist, overrides.whitelist);
51
+ if (!skipSet.has('whitelist')) {
52
+ const wl = whitelist_check_1.WhitelistCheck.run(ip, whitelist);
53
+ if (wl.allowed && wl.layer === 'whitelist')
54
+ return { allowed: true, ip };
55
+ }
56
+ const blacklist = this.merge(this.config.blacklist, overrides.blacklist);
57
+ if (!skipSet.has('blacklist')) {
58
+ const bl = blacklist_check_1.BlacklistCheck.run(ip, blacklist);
59
+ if (!bl.allowed) {
60
+ await auto_ban_check_1.AutoBanCheck.recordViolation(this.storage, ip, this.config.autoBan);
61
+ this.notifyReject(req, res, ip, bl.layer ?? 'blacklist', bl.reason ?? 'blocked', bl.status ?? 403);
62
+ return {
63
+ allowed: false,
64
+ ip,
65
+ exception: new shield_exceptions_1.ShieldBlockedException({
66
+ message: this.config.response?.blocked403?.message ?? bl.reason ?? 'Forbidden',
67
+ code: this.config.response?.blocked403?.code,
68
+ layer: 'blacklist',
69
+ status: bl.status,
70
+ }),
71
+ };
72
+ }
73
+ }
74
+ if (!skipSet.has('auto-ban')) {
75
+ const ab = await auto_ban_check_1.AutoBanCheck.check(this.storage, ip, this.config.autoBan);
76
+ if (!ab.allowed) {
77
+ if (ab.retryAfterMs)
78
+ headers_util_1.HeadersUtil.writeRetryAfter(res, ab.retryAfterMs);
79
+ this.notifyReject(req, res, ip, 'auto-ban', ab.reason ?? 'banned', ab.status ?? 403, ab.retryAfterMs);
80
+ return {
81
+ allowed: false,
82
+ ip,
83
+ exception: new shield_exceptions_1.ShieldBlockedException({
84
+ message: this.config.response?.blocked403?.message ?? ab.reason ?? 'Forbidden',
85
+ code: this.config.response?.blocked403?.code,
86
+ layer: 'auto-ban',
87
+ retryAfter: ab.retryAfterMs ? Math.ceil(ab.retryAfterMs / 1000) : undefined,
88
+ }),
89
+ };
90
+ }
91
+ }
92
+ const ua = ua_util_1.UaUtil.extract(req.headers);
93
+ const userAgent = (overrides.userAgent ?? this.config.userAgent);
94
+ if (!skipSet.has('user-agent')) {
95
+ const uaOut = user_agent_check_1.UserAgentCheck.run(ua, userAgent);
96
+ if (!uaOut.allowed) {
97
+ await auto_ban_check_1.AutoBanCheck.recordViolation(this.storage, ip, this.config.autoBan);
98
+ this.notifyReject(req, res, ip, 'user-agent', uaOut.reason ?? 'blocked', 403);
99
+ return {
100
+ allowed: false,
101
+ ip,
102
+ exception: new shield_exceptions_1.ShieldBlockedException({
103
+ message: this.config.response?.blocked403?.message ?? uaOut.reason ?? 'Forbidden',
104
+ code: this.config.response?.blocked403?.code,
105
+ layer: 'user-agent',
106
+ }),
107
+ };
108
+ }
109
+ }
110
+ const payload = (overrides.maxPayload ?? this.config.payload);
111
+ if (!skipSet.has('payload')) {
112
+ const pl = payload_check_1.PayloadCheck.run(req, payload);
113
+ if (!pl.allowed) {
114
+ this.notifyReject(req, res, ip, 'payload', pl.reason ?? 'too large', pl.status ?? 413);
115
+ return {
116
+ allowed: false,
117
+ ip,
118
+ exception: new shield_exceptions_1.ShieldPayloadException({
119
+ message: this.config.response?.payload413?.message ?? pl.reason ?? 'Payload too large',
120
+ code: this.config.response?.payload413?.code,
121
+ layer: 'payload',
122
+ }),
123
+ };
124
+ }
125
+ }
126
+ let release;
127
+ const burst = (overrides.burst ?? this.config.burst);
128
+ if (!skipSet.has('burst') && burst) {
129
+ const burstOut = await burst_check_1.BurstCheck.check(this.storage, ip, burst);
130
+ if (!burstOut.allowed) {
131
+ this.notifyReject(req, res, ip, 'burst', burstOut.reason ?? 'too many concurrent', 429);
132
+ return {
133
+ allowed: false,
134
+ ip,
135
+ exception: new shield_exceptions_1.ShieldRateLimitException({
136
+ message: this.config.response?.rateLimit429?.message ?? burstOut.reason ?? 'Too many concurrent requests',
137
+ code: this.config.response?.rateLimit429?.code,
138
+ layer: 'burst',
139
+ }),
140
+ };
141
+ }
142
+ release = burstOut.release;
143
+ }
144
+ const rateLimit = this.mergeRateLimit(overrides);
145
+ let rateLimitTtl = 0;
146
+ if (!skipSet.has('rate-limit') && rateLimit) {
147
+ const rl = await rate_limit_check_1.RateLimitCheck.check(this.storage, req, ip, rateLimit);
148
+ rateLimitTtl = rateLimit.ttl;
149
+ if (rateLimit.headers !== false) {
150
+ headers_util_1.HeadersUtil.writeRateLimit(res, rateLimit.standardHeaders ?? 'draft-7', rl.limit, rl.remaining, rl.resetMs);
151
+ }
152
+ if (!rl.allowed) {
153
+ if (this.config.response?.rateLimit429?.includeRetryAfter !== false && rl.retryAfterMs) {
154
+ headers_util_1.HeadersUtil.writeRetryAfter(res, rl.retryAfterMs);
155
+ }
156
+ await auto_ban_check_1.AutoBanCheck.recordViolation(this.storage, ip, this.config.autoBan);
157
+ if (release)
158
+ await release();
159
+ this.notifyReject(req, res, ip, 'rate-limit', rl.reason ?? 'rate limited', 429, rl.retryAfterMs);
160
+ return {
161
+ allowed: false,
162
+ ip,
163
+ exception: new shield_exceptions_1.ShieldRateLimitException({
164
+ message: this.config.response?.rateLimit429?.message ?? rl.reason ?? 'Too many requests',
165
+ code: this.config.response?.rateLimit429?.code,
166
+ layer: 'rate-limit',
167
+ retryAfter: rl.retryAfterMs ? Math.ceil(rl.retryAfterMs / 1000) : undefined,
168
+ }),
169
+ };
170
+ }
171
+ }
172
+ let delayMs;
173
+ const slowDown = (overrides.slowDown ?? this.config.slowDown);
174
+ if (!skipSet.has('slow-down') && slowDown) {
175
+ const sd = await slow_down_check_1.SlowDownCheck.check(this.storage, ip, rateLimitTtl || 60_000, slowDown);
176
+ if (sd.delayMs && sd.delayMs > 0)
177
+ delayMs = sd.delayMs;
178
+ }
179
+ return { allowed: true, ip, delayMs, release };
180
+ }
181
+ merge(base, override) {
182
+ if (!base && !override)
183
+ return undefined;
184
+ if (!base)
185
+ return override;
186
+ if (!override)
187
+ return base;
188
+ return { ...base, ...override };
189
+ }
190
+ mergeRateLimit(overrides) {
191
+ if (!this.config.rateLimit && !overrides.rateLimit)
192
+ return undefined;
193
+ if (overrides.rateLimit) {
194
+ return { ...(this.config.rateLimit ?? {}), ...overrides.rateLimit };
195
+ }
196
+ return this.config.rateLimit;
197
+ }
198
+ notifyReject(req, res, ip, layer, reason, status, retryAfterMs) {
199
+ const hook = this.config.response?.onReject;
200
+ if (!hook)
201
+ return;
202
+ try {
203
+ hook(req, res, { ip, layer, reason, status, retryAfterMs });
204
+ }
205
+ catch {
206
+ }
207
+ }
208
+ };
209
+ exports.ShieldEngine = ShieldEngine;
210
+ exports.ShieldEngine = ShieldEngine = __decorate([
211
+ (0, common_1.Injectable)(),
212
+ __param(0, (0, common_1.Inject)(shield_constants_1.SHIELD_CONFIG)),
213
+ __param(1, (0, common_1.Inject)(shield_constants_1.SHIELD_STORAGE)),
214
+ __metadata("design:paramtypes", [Object, Object])
215
+ ], ShieldEngine);
@@ -0,0 +1,10 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ import { ShieldEngine } from './shield.engine';
4
+ export declare class ShieldGuard implements CanActivate {
5
+ private readonly engine;
6
+ private readonly reflector;
7
+ constructor(engine: ShieldEngine, reflector: Reflector);
8
+ canActivate(context: ExecutionContext): Promise<boolean>;
9
+ private collectOverrides;
10
+ }
@@ -0,0 +1,89 @@
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.ShieldGuard = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@nestjs/core");
18
+ const shield_constants_1 = require("./shield.constants");
19
+ const shield_engine_1 = require("./shield.engine");
20
+ let ShieldGuard = class ShieldGuard {
21
+ constructor(engine, reflector) {
22
+ this.engine = engine;
23
+ this.reflector = reflector;
24
+ }
25
+ async canActivate(context) {
26
+ if (context.getType() !== 'http')
27
+ return true;
28
+ const http = context.switchToHttp();
29
+ const req = http.getRequest();
30
+ const res = http.getResponse();
31
+ const overrides = this.collectOverrides(context);
32
+ const decision = await this.engine.run(req, res, overrides);
33
+ if (!decision.allowed) {
34
+ if (decision.exception)
35
+ throw decision.exception;
36
+ return false;
37
+ }
38
+ if (decision.release) {
39
+ const fired = { done: false };
40
+ const fire = () => {
41
+ if (fired.done)
42
+ return;
43
+ fired.done = true;
44
+ Promise.resolve(decision.release?.()).catch(() => undefined);
45
+ };
46
+ if (typeof res.on === 'function') {
47
+ res.on('finish', fire);
48
+ res.on('close', fire);
49
+ }
50
+ else {
51
+ setImmediate(fire);
52
+ }
53
+ }
54
+ if (decision.delayMs && decision.delayMs > 0) {
55
+ await new Promise((r) => setTimeout(r, decision.delayMs));
56
+ }
57
+ return true;
58
+ }
59
+ collectOverrides(context) {
60
+ const targets = [context.getHandler(), context.getClass()];
61
+ const skip = this.reflector.getAllAndOverride(shield_constants_1.META_SKIP, targets);
62
+ const rateLimit = this.reflector.getAllAndOverride(shield_constants_1.META_RATE_LIMIT, targets);
63
+ const blacklist = this.reflector.getAllAndOverride(shield_constants_1.META_BLACKLIST, targets);
64
+ const whitelist = this.reflector.getAllAndOverride(shield_constants_1.META_WHITELIST, targets);
65
+ const slowDown = this.reflector.getAllAndOverride(shield_constants_1.META_SLOW_DOWN, targets);
66
+ const maxPayload = this.reflector.getAllAndOverride(shield_constants_1.META_MAX_PAYLOAD, targets);
67
+ const burst = this.reflector.getAllAndOverride(shield_constants_1.META_BURST, targets);
68
+ const userAgent = this.reflector.getAllAndOverride(shield_constants_1.META_UA, targets);
69
+ return {
70
+ skip,
71
+ rateLimit: rateLimit
72
+ ? { ...rateLimit, limit: rateLimit.limit, ttl: rateLimit.ttl }
73
+ : undefined,
74
+ blacklist,
75
+ whitelist,
76
+ slowDown,
77
+ maxPayload,
78
+ burst,
79
+ userAgent,
80
+ };
81
+ }
82
+ };
83
+ exports.ShieldGuard = ShieldGuard;
84
+ exports.ShieldGuard = ShieldGuard = __decorate([
85
+ (0, common_1.Injectable)(),
86
+ __param(0, (0, common_1.Inject)(shield_constants_1.SHIELD_ENGINE)),
87
+ __metadata("design:paramtypes", [shield_engine_1.ShieldEngine,
88
+ core_1.Reflector])
89
+ ], ShieldGuard);
@@ -0,0 +1,5 @@
1
+ import type { ShieldEngine } from './shield.engine';
2
+ import type { AnyRequest, AnyResponse } from './shield.types';
3
+ type NextFn = (err?: unknown) => void;
4
+ export declare function createShieldMiddleware(engine: ShieldEngine): (req: AnyRequest, res: AnyResponse, next: NextFn) => Promise<void>;
5
+ export {};
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createShieldMiddleware = createShieldMiddleware;
4
+ function createShieldMiddleware(engine) {
5
+ return async function shieldMiddleware(req, res, next) {
6
+ try {
7
+ const decision = await engine.run(req, res);
8
+ if (!decision.allowed && decision.exception) {
9
+ const exc = decision.exception;
10
+ const response = exc.getResponse();
11
+ const status = exc.getStatus();
12
+ if (typeof res.status === 'function') {
13
+ const r = res.status(status);
14
+ if (typeof r.json === 'function') {
15
+ r.json(response);
16
+ return;
17
+ }
18
+ }
19
+ if (typeof res.setHeader === 'function') {
20
+ res.setHeader('Content-Type', 'application/json');
21
+ }
22
+ res.statusCode = status;
23
+ if (typeof res.end === 'function')
24
+ res.end(JSON.stringify(response));
25
+ return;
26
+ }
27
+ if (decision.release && typeof res.on === 'function') {
28
+ const fired = { done: false };
29
+ const fire = () => {
30
+ if (fired.done)
31
+ return;
32
+ fired.done = true;
33
+ Promise.resolve(decision.release?.()).catch(() => undefined);
34
+ };
35
+ res.on('finish', fire);
36
+ res.on('close', fire);
37
+ }
38
+ if (decision.delayMs && decision.delayMs > 0) {
39
+ await new Promise((r) => setTimeout(r, decision.delayMs));
40
+ }
41
+ next();
42
+ }
43
+ catch (err) {
44
+ next(err);
45
+ }
46
+ };
47
+ }
@@ -0,0 +1,14 @@
1
+ import { DynamicModule, ModuleMetadata, OnModuleDestroy, Type } from '@nestjs/common';
2
+ import type { ShieldConfig } from './shield.types';
3
+ import type { ShieldStorage } from './storage/shield-storage.interface';
4
+ export interface ShieldAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
5
+ useFactory: (...args: unknown[]) => Promise<ShieldConfig> | ShieldConfig;
6
+ inject?: (string | symbol | Type<unknown>)[];
7
+ }
8
+ export declare class ShieldModule implements OnModuleDestroy {
9
+ private readonly storage;
10
+ constructor(storage: ShieldStorage);
11
+ static forRoot(config?: ShieldConfig): DynamicModule;
12
+ static forRootAsync(options: ShieldAsyncOptions): DynamicModule;
13
+ onModuleDestroy(): Promise<void>;
14
+ }
@@ -0,0 +1,105 @@
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
+ var ShieldModule_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.ShieldModule = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const core_1 = require("@nestjs/core");
19
+ const shield_constants_1 = require("./shield.constants");
20
+ const shield_engine_1 = require("./shield.engine");
21
+ const shield_guard_1 = require("./shield.guard");
22
+ const memory_storage_1 = require("./storage/memory.storage");
23
+ const redis_storage_1 = require("./storage/redis.storage");
24
+ function buildStorage(option) {
25
+ if (!option || option === 'memory')
26
+ return new memory_storage_1.MemoryStorage();
27
+ if (typeof option === 'object' && 'type' in option) {
28
+ if (option.type === 'memory')
29
+ return new memory_storage_1.MemoryStorage({ maxKeys: option.maxKeys });
30
+ if (option.type === 'redis') {
31
+ return new redis_storage_1.RedisStorage({
32
+ client: option.client,
33
+ keyPrefix: option.keyPrefix,
34
+ });
35
+ }
36
+ }
37
+ return option;
38
+ }
39
+ let ShieldModule = ShieldModule_1 = class ShieldModule {
40
+ constructor(storage) {
41
+ this.storage = storage;
42
+ }
43
+ static forRoot(config = {}) {
44
+ const providers = [
45
+ { provide: shield_constants_1.SHIELD_CONFIG, useValue: config },
46
+ {
47
+ provide: shield_constants_1.SHIELD_STORAGE,
48
+ useFactory: () => buildStorage(config.storage),
49
+ },
50
+ core_1.Reflector,
51
+ {
52
+ provide: shield_constants_1.SHIELD_ENGINE,
53
+ useFactory: (cfg, storage) => new shield_engine_1.ShieldEngine(cfg, storage),
54
+ inject: [shield_constants_1.SHIELD_CONFIG, shield_constants_1.SHIELD_STORAGE],
55
+ },
56
+ shield_guard_1.ShieldGuard,
57
+ { provide: core_1.APP_GUARD, useExisting: shield_guard_1.ShieldGuard },
58
+ ];
59
+ return {
60
+ module: ShieldModule_1,
61
+ global: true,
62
+ providers,
63
+ exports: [shield_constants_1.SHIELD_CONFIG, shield_constants_1.SHIELD_STORAGE, shield_constants_1.SHIELD_ENGINE, shield_guard_1.ShieldGuard],
64
+ };
65
+ }
66
+ static forRootAsync(options) {
67
+ const providers = [
68
+ {
69
+ provide: shield_constants_1.SHIELD_CONFIG,
70
+ useFactory: options.useFactory,
71
+ inject: options.inject ?? [],
72
+ },
73
+ {
74
+ provide: shield_constants_1.SHIELD_STORAGE,
75
+ useFactory: (cfg) => buildStorage(cfg.storage),
76
+ inject: [shield_constants_1.SHIELD_CONFIG],
77
+ },
78
+ core_1.Reflector,
79
+ {
80
+ provide: shield_constants_1.SHIELD_ENGINE,
81
+ useFactory: (cfg, storage) => new shield_engine_1.ShieldEngine(cfg, storage),
82
+ inject: [shield_constants_1.SHIELD_CONFIG, shield_constants_1.SHIELD_STORAGE],
83
+ },
84
+ shield_guard_1.ShieldGuard,
85
+ { provide: core_1.APP_GUARD, useExisting: shield_guard_1.ShieldGuard },
86
+ ];
87
+ return {
88
+ module: ShieldModule_1,
89
+ global: true,
90
+ imports: options.imports ?? [],
91
+ providers,
92
+ exports: [shield_constants_1.SHIELD_CONFIG, shield_constants_1.SHIELD_STORAGE, shield_constants_1.SHIELD_ENGINE, shield_guard_1.ShieldGuard],
93
+ };
94
+ }
95
+ async onModuleDestroy() {
96
+ if (this.storage?.dispose)
97
+ await this.storage.dispose();
98
+ }
99
+ };
100
+ exports.ShieldModule = ShieldModule;
101
+ exports.ShieldModule = ShieldModule = ShieldModule_1 = __decorate([
102
+ (0, common_1.Module)({}),
103
+ __param(0, (0, common_1.Inject)(shield_constants_1.SHIELD_STORAGE)),
104
+ __metadata("design:paramtypes", [Object])
105
+ ], ShieldModule);
@@ -0,0 +1,141 @@
1
+ import type { ShieldLayer } from './shield.constants';
2
+ import type { ShieldStorage } from './storage/shield-storage.interface';
3
+ export type RateLimitAlgorithm = 'token-bucket' | 'sliding-window' | 'sliding-window-log' | 'fixed-window' | 'leaky-bucket';
4
+ export type KeyByOption = 'ip' | {
5
+ header: string;
6
+ } | ((req: AnyRequest) => string);
7
+ export interface RateLimitConfig {
8
+ algorithm?: RateLimitAlgorithm;
9
+ limit: number;
10
+ ttl: number;
11
+ keyBy?: KeyByOption;
12
+ skip?: (req: AnyRequest) => boolean;
13
+ headers?: boolean;
14
+ standardHeaders?: 'draft-6' | 'draft-7';
15
+ }
16
+ export interface IpListConfig {
17
+ ips?: string[];
18
+ cidrs?: string[];
19
+ }
20
+ export interface BlacklistConfig extends IpListConfig {
21
+ statusCode?: number;
22
+ }
23
+ export interface AutoBanConfig {
24
+ threshold: number;
25
+ window: number;
26
+ banDuration: number;
27
+ escalate?: boolean;
28
+ }
29
+ export interface SlowDownConfig {
30
+ delayAfter: number;
31
+ delayMs: number | ((hit: number) => number);
32
+ maxDelayMs?: number;
33
+ }
34
+ export interface UserAgentConfig {
35
+ block?: (string | RegExp)[];
36
+ allow?: (string | RegExp)[];
37
+ requirePresent?: boolean;
38
+ }
39
+ export interface BurstConfig {
40
+ maxConcurrent: number;
41
+ }
42
+ export interface PayloadConfig {
43
+ maxBodyBytes?: number;
44
+ maxHeaderBytes?: number;
45
+ }
46
+ export interface RejectInfo {
47
+ layer: ShieldLayer;
48
+ ip: string;
49
+ reason: string;
50
+ status: number;
51
+ retryAfterMs?: number;
52
+ }
53
+ export interface ResponseConfig {
54
+ rateLimit429?: {
55
+ message?: string;
56
+ code?: string;
57
+ includeRetryAfter?: boolean;
58
+ };
59
+ blocked403?: {
60
+ message?: string;
61
+ code?: string;
62
+ };
63
+ payload413?: {
64
+ message?: string;
65
+ code?: string;
66
+ };
67
+ onReject?: (req: AnyRequest, res: AnyResponse, info: RejectInfo) => void;
68
+ }
69
+ export type StorageOption = ShieldStorage | 'memory' | {
70
+ type: 'memory';
71
+ maxKeys?: number;
72
+ } | {
73
+ type: 'redis';
74
+ client: unknown;
75
+ keyPrefix?: string;
76
+ };
77
+ export interface ShieldConfig {
78
+ enabled?: boolean;
79
+ trustProxy?: boolean | number;
80
+ ipResolver?: (req: AnyRequest) => string;
81
+ storage?: StorageOption;
82
+ rateLimit?: RateLimitConfig;
83
+ whitelist?: IpListConfig;
84
+ blacklist?: BlacklistConfig;
85
+ autoBan?: AutoBanConfig;
86
+ slowDown?: SlowDownConfig;
87
+ userAgent?: UserAgentConfig;
88
+ burst?: BurstConfig;
89
+ payload?: PayloadConfig;
90
+ response?: ResponseConfig;
91
+ }
92
+ export type AnyRequest = {
93
+ ip?: string;
94
+ ips?: string[];
95
+ headers: Record<string, string | string[] | undefined>;
96
+ socket?: {
97
+ remoteAddress?: string;
98
+ };
99
+ connection?: {
100
+ remoteAddress?: string;
101
+ };
102
+ method?: string;
103
+ url?: string;
104
+ body?: unknown;
105
+ [key: string]: unknown;
106
+ };
107
+ export type AnyResponse = {
108
+ statusCode?: number;
109
+ setHeader: (name: string, value: string | number) => void;
110
+ status?: (code: number) => AnyResponse;
111
+ json?: (body: unknown) => AnyResponse;
112
+ end?: (body?: unknown) => AnyResponse;
113
+ on?: (event: string, cb: () => void) => void;
114
+ [key: string]: unknown;
115
+ };
116
+ export interface DecoratorOverrides {
117
+ skip?: true | ShieldLayer[];
118
+ rateLimit?: Partial<RateLimitConfig> & {
119
+ limit: number;
120
+ ttl: number;
121
+ };
122
+ blacklist?: IpListConfig;
123
+ whitelist?: IpListConfig;
124
+ slowDown?: SlowDownConfig;
125
+ maxPayload?: PayloadConfig;
126
+ burst?: BurstConfig;
127
+ userAgent?: UserAgentConfig;
128
+ }
129
+ export interface CheckOutcome {
130
+ allowed: boolean;
131
+ layer?: ShieldLayer;
132
+ status?: number;
133
+ reason?: string;
134
+ retryAfterMs?: number;
135
+ headers?: Record<string, string | number>;
136
+ delayMs?: number;
137
+ release?: () => Promise<void> | void;
138
+ }
139
+ export interface ResolvedConfig extends Omit<ShieldConfig, 'rateLimit'> {
140
+ rateLimit?: RateLimitConfig;
141
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,30 @@
1
+ import type { CounterResult, ShieldStorage, TokenBucketResult, WindowResult } from './shield-storage.interface';
2
+ export interface MemoryStorageOptions {
3
+ maxKeys?: number;
4
+ }
5
+ export declare class MemoryStorage implements ShieldStorage {
6
+ private readonly maxKeys;
7
+ private readonly map;
8
+ private readonly buckets;
9
+ private readonly logs;
10
+ private readonly counters;
11
+ private sweepTimer;
12
+ constructor(opts?: MemoryStorageOptions);
13
+ increment(key: string, ttlMs: number, by?: number): Promise<CounterResult>;
14
+ consumeToken(key: string, capacity: number, refillPerMs: number, cost?: number): Promise<TokenBucketResult>;
15
+ fixedWindow(key: string, ttlMs: number, limit: number): Promise<WindowResult>;
16
+ slidingWindowCounter(key: string, ttlMs: number, limit: number): Promise<WindowResult>;
17
+ slidingWindowLog(key: string, ttlMs: number, limit: number, now?: number): Promise<WindowResult>;
18
+ leakyBucket(key: string, capacity: number, leakPerMs: number): Promise<TokenBucketResult>;
19
+ get(key: string): Promise<string | null>;
20
+ set(key: string, value: string, ttlMs: number): Promise<void>;
21
+ delete(key: string): Promise<void>;
22
+ incrementConcurrent(key: string): Promise<number>;
23
+ decrementConcurrent(key: string): Promise<number>;
24
+ dispose(): void;
25
+ private set_;
26
+ private evictIfNeeded;
27
+ private ttlFromRefill;
28
+ private startSweeper;
29
+ private sweep;
30
+ }