nestjs-ip-filter 0.2.0 → 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 (41) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +106 -132
  3. package/dist/index.js +0 -1
  4. package/dist/ipfilter-deny.exception.js +0 -1
  5. package/dist/ipfilter.constants.js +0 -1
  6. package/dist/ipfilter.guard.d.ts +2 -3
  7. package/dist/ipfilter.guard.js +11 -25
  8. package/dist/ipfilter.interface.d.ts +1 -4
  9. package/dist/ipfilter.interface.js +0 -1
  10. package/dist/ipfilter.module-definition.d.ts +7 -1
  11. package/dist/ipfilter.module-definition.js +15 -3
  12. package/dist/ipfilter.module.js +3 -10
  13. package/dist/ipfilter.service.d.ts +7 -4
  14. package/dist/ipfilter.service.js +59 -11
  15. package/package.json +36 -60
  16. package/.eslintrc.js +0 -29
  17. package/.prettierrc +0 -4
  18. package/.vscode/settings.json +0 -3
  19. package/dist/index.js.map +0 -1
  20. package/dist/ipfilter-deny.exception.js.map +0 -1
  21. package/dist/ipfilter.constants.js.map +0 -1
  22. package/dist/ipfilter.guard.js.map +0 -1
  23. package/dist/ipfilter.interface.js.map +0 -1
  24. package/dist/ipfilter.module-definition.js.map +0 -1
  25. package/dist/ipfilter.module.js.map +0 -1
  26. package/dist/ipfilter.service.js.map +0 -1
  27. package/dist/tsconfig.tsbuildinfo +0 -1
  28. package/index.js +0 -6
  29. package/index.ts +0 -1
  30. package/lib/index.ts +0 -7
  31. package/lib/ipfilter-deny.exception.ts +0 -7
  32. package/lib/ipfilter.constants.ts +0 -1
  33. package/lib/ipfilter.guard.ts +0 -56
  34. package/lib/ipfilter.interface.ts +0 -10
  35. package/lib/ipfilter.module-definition.ts +0 -5
  36. package/lib/ipfilter.module.ts +0 -22
  37. package/lib/ipfilter.service.ts +0 -37
  38. package/nest-cli.json +0 -5
  39. package/renovate.json +0 -10
  40. package/tsconfig.build.json +0 -4
  41. package/tsconfig.json +0 -24
package/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ ### Breaking changes
6
+
7
+ - Rules are IPv4/IPv6 addresses and CIDR ranges instead of regular expressions; an invalid entry throws at startup or from a setter. An IPv4-mapped rule with an IPv6 prefix under 96 is rejected.
8
+ - The client IP is `req.ip`, which follows the app's `trust proxy` setting, instead of forwarded headers read from anyone.
9
+ - The filter mode is explicit: a configured `whitelist` (even `[]`) allows only its matches; without one, everyone not blacklisted is allowed. Configuring neither list (`undefined` or `null`) throws at startup.
10
+ - `IpFilterDenyException` carries only `{ clientIp }`.
11
+ - `IpFilterService.whitelist` / `blacklist` return `string[] | undefined` (a copy, or `undefined` when not configured).
12
+ - Requires NestJS 11 or 12 (now peer dependencies) and Node.js `>=22.12.0`.
13
+ - Removed the unused `IpFilterDenyHandler` interface.
14
+
15
+ ### Fixed
16
+
17
+ - The guard crashed on GraphQL requests and denied every WebSocket/RPC message; non-HTTP contexts now pass through unfiltered.
18
+ - A rule like `10.0.0.1` also matched `110.0.0.15` (unanchored regex).
19
+ - A forged `X-Forwarded-For` header bypassed the whitelist.
20
+ - With both lists set, the whitelist was ignored.
21
+ - Denied clients received the full whitelist and blacklist in the exception response.
22
+
23
+ ### Added
24
+
25
+ - `global: false` to apply the guard only where `@UseGuards(IpFilterGuard)` is used (#519).
26
+ - `IpFilterGuard` and `IPFILTER_TOKEN` resolve in every module; register `IpFilter` once in the root module.
27
+ - IPv6 rules.
28
+ - Tests and CI for Node.js 22/24 and NestJS 11/12 (#68).
29
+
30
+ ### Removed
31
+
32
+ - The `@supercharge/request-ip` dependency; the package has no runtime dependencies.
33
+ - The `example/` project; the README covers its usage.
package/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # NestJS IP Filter
2
2
 
3
- Now we can filter the incoming requests by setting the whitelist and the blacklist IP addresses.
3
+ A NestJS guard that allows or denies requests by client IP address, using whitelists and blacklists of IPv4/IPv6 addresses and CIDR ranges.
4
4
 
5
- It will work using the built-in feature in NestJS, **Guard**, so it works like a charm as it should be and as much as it should do.
5
+ It filters HTTP requests. GraphQL, WebSocket and microservice (RPC) contexts are not filtered and pass through.
6
6
 
7
- The Guard recognizes the requester's IP address using [@supercharge/request-ip](https://www.npmjs.com/package/request-ip) package.
7
+ ## Requirements
8
8
 
9
- ## How to install
9
+ - NestJS 11 or 12
10
+ - Node.js `>=22.12.0`
11
+ - The Express platform (`@nestjs/platform-express`)
10
12
 
11
- Simply, install using NPM by the following command.
13
+ ## Install
12
14
 
13
15
  ```bash
14
16
  npm install nestjs-ip-filter
@@ -16,180 +18,152 @@ npm install nestjs-ip-filter
16
18
 
17
19
  ## Usage
18
20
 
19
- ### Use Regex for the IP list
20
-
21
- > In Express-based framework, the incoming IP address will have prefix `::ffff:` characters. So if you have put `(^192.168.)` into the whitelist, it won't work because that regex describes it must have number `1` as a first character.
22
- > So for now, you should put that prefix for your regex string like `(^::ffff:192.168.)`. Will patch the source codes in the near future.
23
-
24
- You can use the Regex format string for the IP ranges.
25
-
26
- For a simple example, if you want to allow the private network hosts in `192.168.0.1/16`, you can write the Regex string like the below.
27
-
28
- ```bash
29
- # 192.168.0.0 ~ 192.168.255.255
30
- (^192.168.)
31
- ```
32
-
33
- Check the other use cases.
34
-
35
- ```bash
36
- # 192.168.0.100 ~ 192.168.0.110
37
- (^192.168.0.1[0-1]0)
38
-
39
- # 172.16.16.10, 172.16.17.10, 172.16.18.10
40
- (^172.16.1[6-8].10)
41
-
42
- # 110~119.*
43
- (^11[0-9].*)
44
- ```
45
-
46
- And, writing in this way also works on this package.
47
-
48
- But be aware of that you should write the Regex string very carefully to avoid allowing unwanted incoming IP addresses.
49
-
50
- ### Basic usage for Whitelist
51
-
52
- You can initialize the dedicated Guard using non-async or async mode, as the other NestJS packages support.
53
-
54
- Use `register` for the static IP address list.
21
+ Import `IpFilter` once, in your root module. The guard then applies to every route, and `IpFilterGuard` and `IPFILTER_TOKEN` are available in every module.
55
22
 
56
23
  ```typescript
57
- IpFilter.register({
58
- whitelist: [
59
- '(^::1)',
60
- '(^192.168.)',
61
- '127.0.0.1',
24
+ import { Module } from '@nestjs/common';
25
+ import { IpFilter } from 'nestjs-ip-filter';
26
+
27
+ @Module({
28
+ imports: [
29
+ IpFilter.register({
30
+ whitelist: ['127.0.0.1', '::1', '192.168.0.0/16'],
31
+ }),
62
32
  ],
63
- }),
33
+ })
34
+ export class AppModule {}
64
35
  ```
65
36
 
66
- Or, use `registerAsync` for the provided IP addresses from the other Provider, such as ConfigModule or RepositoryModule for your database.
37
+ Use `registerAsync` to load the lists from another provider, such as a config service or a database.
67
38
 
68
39
  ```typescript
69
- // If it would like to get the whitelist IPs from a config file,
70
- // and if MyConfigModule provides that
71
-
72
40
  IpFilter.registerAsync({
73
- imports: [ MyConfigModule ],
74
- inject: [ MyConfigService ],
75
- useFactory: async (myConfigService: MyConfigService) => ({
76
- whitelist: myConfigService.getWhitelist(),
41
+ imports: [ConfigModule],
42
+ inject: [ConfigService],
43
+ useFactory: (config: ConfigService) => ({
44
+ whitelist: config.get<string[]>('ipFilter.whitelist'),
45
+ blacklist: config.get<string[]>('ipFilter.blacklist'),
77
46
  }),
78
- }),
47
+ });
79
48
  ```
80
49
 
81
- Yeah, you can use your method to provide some async data.
50
+ ### Rules
82
51
 
83
- After setting up the `IpFilter` module with the `register*` method, this will accept only the incoming requests for the given whitelist IP addresses.
52
+ Each entry is an IPv4 or IPv6 address, or a CIDR range:
84
53
 
85
- ### Blacklist
86
-
87
- You can deny the requests that are from the blacklist IPs.
54
+ ```typescript
55
+ '10.0.0.1'; // one address
56
+ '10.0.0.0/8'; // 10.0.0.0 – 10.255.255.255
57
+ '2001:db8::/32'; // an IPv6 range
58
+ ```
88
59
 
89
- Use `blacklist` option to let the Guard know the list.
60
+ IPv4-mapped IPv6 client addresses (`::ffff:10.1.2.3`) match IPv4 rules, so write IPv4 ranges as IPv4 CIDR.
61
+ A mapped rule with an IPv6 prefix under 96 (`::ffff:192.168.0.0/16`) is rejected: as an IPv6 range it would span all of IPv4.
62
+ An invalid entry throws when the module starts or when a list is set, so a typo never silently matches nothing.
90
63
 
91
- ```typescript
92
- // If it would like to get the whitelist IPs from a config file,
93
- // and if MyConfigModule provides that
64
+ ### How a request is evaluated
94
65
 
95
- IpFilter.registerAsync({
96
- imports: [ MyConfigModule ],
97
- inject: [ MyConfigService ],
98
- useFactory: async (myConfigService: MyConfigService) => ({
99
- whitelist: myConfigService.getWhitelist(),
100
- blacklist: myConfigService.getBlacklist(),
101
- }),
102
- }),
103
- ```
66
+ 1. A client IP in the `blacklist` is denied.
67
+ 2. If a `whitelist` is configured, only client IPs in it are allowed. An empty whitelist (`[]`) allows nobody.
68
+ 3. If no `whitelist` is configured, every client IP not denied by step 1 is allowed.
104
69
 
105
- Then its NestJS Guard will deny all the IPs which are from the blacklist.
70
+ At least one of `whitelist` and `blacklist` must be configured; otherwise the module throws at startup.
71
+ This way a missing configuration value fails loudly instead of opening or closing everything.
106
72
 
107
- ### Use IpFilterDenyException when denying a request
73
+ | `whitelist` | `blacklist` | Allowed |
74
+ | --- | --- | --- |
75
+ | `['10.0.0.0/8']` | not set | `10.0.0.0/8` only |
76
+ | not set | `['10.0.0.5']` | everyone except `10.0.0.5` |
77
+ | `['10.0.0.0/8']` | `['10.0.0.5']` | `10.0.0.0/8` except `10.0.0.5` |
78
+ | `[]` | not set | nobody |
79
+ | not set | `[]` | everyone |
80
+ | not set | not set | throws at startup |
108
81
 
109
- You can handle the denial condition by catching the HTTP exception.
82
+ ### Behind a proxy or load balancer
110
83
 
111
- If you want to throw that `IpFilterDenyException` when denial, set the option `useDenyException` to `true`.
84
+ The guard uses `req.ip`, so the client IP follows Express's [`trust proxy`](https://expressjs.com/en/guide/behind-proxies.html) setting.
85
+ Forwarded headers such as `X-Forwarded-For` are used only from proxies you trust; anyone else can forge them.
112
86
 
113
87
  ```typescript
114
- // If it would like to get the whitelist IPs from a config file,
115
- // and if MyConfigModule provides that
88
+ import { NestFactory } from '@nestjs/core';
89
+ import { NestExpressApplication } from '@nestjs/platform-express';
116
90
 
117
- IpFilter.registerAsync({
118
- imports: [ MyConfigModule ],
119
- inject: [ MyConfigService ],
120
- useFactory: async (myConfigService: MyConfigService) => ({
121
- whitelist: myConfigService.getWhitelist(),
122
- blacklist: myConfigService.getBlacklist(),
123
- useDenyException: true,
124
- }),
125
- }),
91
+ const app = await NestFactory.create<NestExpressApplication>(AppModule);
92
+ app.set('trust proxy', 'loopback'); // or the proxy's address/CIDR, or the number of hops
126
93
  ```
127
94
 
128
- Then it will throw `IpFilterDenyException` when the request is denied for the given whitelist or the blacklist.
95
+ Without it, every request behind a proxy appears to come from the proxy's address.
129
96
 
130
- So that, you can handle the denied request and can send a custom response on that handler, using NestJS **Exception Filter**.
97
+ ### Apply to some routes only
131
98
 
132
- When the Guard throws an exception, it will also carry some informative data like the incoming IP address, current whitelist and blacklist.
99
+ Set `global: false` in the root module and add `IpFilterGuard` where you need it, in any module.
133
100
 
134
- ### Inject IpFilterService
135
-
136
- If you want, you can inject `IpFilterService` into your code and can see current options you put when you initialize it. And, you can set the whitelist and the blacklist too.
101
+ ```typescript
102
+ @Module({
103
+ imports: [IpFilter.register({ whitelist: ['10.0.0.0/8'], global: false }), AdminModule],
104
+ })
105
+ export class AppModule {}
137
106
 
138
- Inject the service using the `IPFILTER_TOKEN` module.
107
+ @Controller('admin')
108
+ @UseGuards(IpFilterGuard)
109
+ export class AdminController {}
139
110
 
140
- ```typescript
141
- @Injectable()
142
- export class SomeService {
143
- constructor(
144
- @inject(IPFILTER_TOKEN)
145
- private readonly ipFilterService: IpFilterService
146
- ) {}
147
- // ...
148
- }
111
+ @Module({ controllers: [AdminController] })
112
+ export class AdminModule {}
149
113
  ```
150
114
 
151
- Then you can edit the whitelist, blacklist IP addresses dynamically.
115
+ ### Handle denied requests
116
+
117
+ By default a denied request gets NestJS's standard `403 Forbidden`.
118
+ With `useDenyException: true` the guard throws `IpFilterDenyException` instead, whose response is `{ clientIp }`, so you can catch it in an exception filter.
152
119
 
153
120
  ```typescript
154
- this.ipFilterService.whitelist = [ {NEW_WHITELIST} ];
155
- this.ipFilterService.blacklist = [ {NEW_BLACKLIST} ];
121
+ @Catch(IpFilterDenyException)
122
+ export class IpFilterDenyExceptionFilter implements ExceptionFilter {
123
+ catch(exception: IpFilterDenyException, host: ArgumentsHost) {
124
+ const { clientIp } = exception.getResponse() as { clientIp?: string };
125
+ host.switchToHttp().getResponse<Response>().status(403).json({ message: 'Forbidden', clientIp });
126
+ }
127
+ }
156
128
  ```
157
129
 
158
- ## Example project
130
+ ### Change the lists at runtime
159
131
 
160
- This repository contains an example project of this `nestjs-ip-filter` package.
132
+ Inject `IpFilterService` with `IPFILTER_TOKEN`. Setting a list replaces it and applies to the next request; an invalid entry throws and keeps the previous list.
161
133
 
162
- Move to `example` directory and run the following commands.
134
+ ```typescript
135
+ @Injectable()
136
+ export class IpRulesService {
137
+ constructor(@Inject(IPFILTER_TOKEN) private readonly ipFilter: IpFilterService) {}
163
138
 
164
- ```bash
165
- npm ci
166
- npm start:debug
139
+ block(address: string) {
140
+ this.ipFilter.blacklist = [...(this.ipFilter.blacklist ?? []), address];
141
+ }
142
+ }
167
143
  ```
168
144
 
169
- Then, the example project will run in **3001** port.
170
-
171
- With the `curl` command, you can do a simple test for the project.
172
-
173
- ```bash
174
- curl -X GET -H "Content-Type: application/json" http://localhost:3001/ipfilter
175
- ```
145
+ The getters return a copy of the list, or `undefined` when that list is not configured. Emptying a list keeps the mode: an empty whitelist still allows nobody.
146
+ Setting a whitelist on an app that had none switches it to whitelist mode, so only the whitelisted addresses pass from then on.
176
147
 
177
- It will result like the below, if your localhost IP is accepted for the whitelist.
148
+ ## Migrating from 0.x
178
149
 
179
- ```bash
180
- {"whitelist":["(^::1)","(^192.168.)","127.0.0.1"],"blacklist":[]}
181
- ```
150
+ - **Rules are addresses and CIDR ranges, not regular expressions.** Rewrite `'(^192.168.)'` as `'192.168.0.0/16'` and drop the `::ffff:` prefix workaround (write `'192.168.0.0/16'`, not `'::ffff:192.168.0.0/16'`). A regex entry now throws at startup.
151
+ - **The client IP is `req.ip`.** 0.x read `X-Forwarded-For` and similar headers from anyone, which allowed spoofing. Behind a proxy, set [`trust proxy`](#behind-a-proxy-or-load-balancer).
152
+ - **The blacklist no longer overrides the whitelist.** With both lists set, a client must be in the whitelist and not in the blacklist; 0.x let anyone outside the blacklist through.
153
+ - **Configure at least one list** (`null` counts as not configured). 0.x denied everything when both lists were empty. Set `whitelist: []` to keep that behavior, for example when the lists are filled at runtime.
154
+ - **`IpFilterDenyException` carries only `{ clientIp }`.** 0.x also sent both lists to the denied client.
155
+ - **The getters return `string[] | undefined`.**
156
+ - NestJS 11 or 12 and Node.js `>=22.12.0` are required.
182
157
 
183
- If it denies, it will result like the below. Check the `ipFilterData` field.
158
+ ## Development
184
159
 
185
160
  ```bash
186
- {"statusCode":403,"timestamp":"2022-06-13T09:29:40.452Z","ipFilterData":{"clientIp":"::ffff:127.0.0.1","whitelist":["(^::1)","(^192.168.)"],"blacklist":[]},"path":"/ipfilter"}
161
+ npm ci
162
+ npm run lint
163
+ npm test
164
+ npm run build
187
165
  ```
188
166
 
189
- [Our exception filter](example/src/exception/ipfilter-exception-filter.exception.ts) put that additional field on the typical HttpException results.
190
-
191
- You can test the projects with your local host IP address, [by editing the whitelist and the blacklist](example/src/ip-repository/ip-repository.service.ts).
192
-
193
167
  ## License
194
168
 
195
- It follows the [MIT license](LICENSE).
169
+ [MIT](LICENSE)
package/dist/index.js CHANGED
@@ -21,4 +21,3 @@ __exportStar(require("./ipfilter.interface"), exports);
21
21
  __exportStar(require("./ipfilter.module"), exports);
22
22
  __exportStar(require("./ipfilter.module-definition"), exports);
23
23
  __exportStar(require("./ipfilter.service"), exports);
24
- //# sourceMappingURL=index.js.map
@@ -8,4 +8,3 @@ class IpFilterDenyException extends common_1.HttpException {
8
8
  }
9
9
  }
10
10
  exports.IpFilterDenyException = IpFilterDenyException;
11
- //# sourceMappingURL=ipfilter-deny.exception.js.map
@@ -2,4 +2,3 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.IPFILTER_TOKEN = void 0;
4
4
  exports.IPFILTER_TOKEN = Symbol('IpFilterToken');
5
- //# sourceMappingURL=ipfilter.constants.js.map
@@ -1,8 +1,7 @@
1
1
  import { CanActivate, ExecutionContext } from '@nestjs/common';
2
- import { Observable } from 'rxjs';
3
- import { IpFilterService } from './ipfilter.service';
2
+ import type { IpFilterService } from './ipfilter.service';
4
3
  export declare class IpFilterGuard implements CanActivate {
5
4
  private readonly ipFilterService;
6
5
  constructor(ipFilterService: IpFilterService);
7
- canActivate(context: ExecutionContext): Promise<boolean> | Observable<boolean> | boolean;
6
+ canActivate(context: ExecutionContext): boolean;
8
7
  }
@@ -14,44 +14,30 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.IpFilterGuard = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
- const requestIp = require("@supercharge/request-ip");
18
- const ipfilter_service_1 = require("./ipfilter.service");
19
17
  const ipfilter_constants_1 = require("./ipfilter.constants");
20
18
  const ipfilter_deny_exception_1 = require("./ipfilter-deny.exception");
21
19
  let IpFilterGuard = class IpFilterGuard {
20
+ ipFilterService;
22
21
  constructor(ipFilterService) {
23
22
  this.ipFilterService = ipFilterService;
24
23
  }
25
24
  canActivate(context) {
26
- const request = context.switchToHttp().getRequest();
27
- const ipAddress = requestIp.getClientIp(request);
28
- const whitelist = this.ipFilterService.whitelist;
29
- const blacklist = this.ipFilterService.blacklist;
30
- let approved = false;
31
- if (whitelist.length > 0) {
32
- approved = whitelist.some((item) => {
33
- return new RegExp(item).test(ipAddress);
34
- });
35
- }
36
- if (blacklist.length > 0) {
37
- approved = !blacklist.some((item) => {
38
- return new RegExp(item).test(ipAddress);
39
- });
25
+ if (context.getType() !== 'http') {
26
+ return true;
40
27
  }
28
+ const ipAddress = context
29
+ .switchToHttp()
30
+ .getRequest().ip;
31
+ const approved = this.ipFilterService.isAllowed(ipAddress);
41
32
  if (!approved && this.ipFilterService.useDenyException) {
42
- throw new ipfilter_deny_exception_1.IpFilterDenyException({
43
- clientIp: ipAddress,
44
- whitelist: whitelist,
45
- blacklist: blacklist,
46
- }, 403);
33
+ throw new ipfilter_deny_exception_1.IpFilterDenyException({ clientIp: ipAddress }, 403);
47
34
  }
48
35
  return approved;
49
36
  }
50
37
  };
51
- IpFilterGuard = __decorate([
38
+ exports.IpFilterGuard = IpFilterGuard;
39
+ exports.IpFilterGuard = IpFilterGuard = __decorate([
52
40
  (0, common_1.Injectable)(),
53
41
  __param(0, (0, common_1.Inject)(ipfilter_constants_1.IPFILTER_TOKEN)),
54
- __metadata("design:paramtypes", [ipfilter_service_1.IpFilterService])
42
+ __metadata("design:paramtypes", [Function])
55
43
  ], IpFilterGuard);
56
- exports.IpFilterGuard = IpFilterGuard;
57
- //# sourceMappingURL=ipfilter.guard.js.map
@@ -1,8 +1,5 @@
1
- export declare type IpFilterModuleOptions = {
1
+ export type IpFilterModuleOptions = {
2
2
  whitelist?: string[];
3
3
  blacklist?: string[];
4
4
  useDenyException?: boolean;
5
5
  };
6
- export interface IpFilterDenyHandler {
7
- handle(): boolean;
8
- }
@@ -1,3 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=ipfilter.interface.js.map
@@ -1,2 +1,8 @@
1
1
  import { IpFilterModuleOptions } from './ipfilter.interface';
2
- export declare const ConfigurableModuleClass: import("@nestjs/common").ConfigurableModuleCls<IpFilterModuleOptions, "register", "create", {}>, MODULE_OPTIONS_TOKEN: string | symbol;
2
+ export declare const ConfigurableModuleClass: import("@nestjs/common").ConfigurableModuleCls<IpFilterModuleOptions, "register", "create", {
3
+ global: boolean;
4
+ }>, MODULE_OPTIONS_TOKEN: string | symbol, OPTIONS_TYPE: IpFilterModuleOptions & Partial<{
5
+ global: boolean;
6
+ }>, ASYNC_OPTIONS_TYPE: import("@nestjs/common").ConfigurableModuleAsyncOptions<IpFilterModuleOptions, "create"> & Partial<{
7
+ global: boolean;
8
+ }>;
@@ -1,7 +1,19 @@
1
1
  "use strict";
2
2
  var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.MODULE_OPTIONS_TOKEN = exports.ConfigurableModuleClass = void 0;
4
+ exports.ASYNC_OPTIONS_TYPE = exports.OPTIONS_TYPE = exports.MODULE_OPTIONS_TOKEN = exports.ConfigurableModuleClass = void 0;
5
5
  const common_1 = require("@nestjs/common");
6
- _a = new common_1.ConfigurableModuleBuilder().build(), exports.ConfigurableModuleClass = _a.ConfigurableModuleClass, exports.MODULE_OPTIONS_TOKEN = _a.MODULE_OPTIONS_TOKEN;
7
- //# sourceMappingURL=ipfilter.module-definition.js.map
6
+ const core_1 = require("@nestjs/core");
7
+ const ipfilter_guard_1 = require("./ipfilter.guard");
8
+ _a = new common_1.ConfigurableModuleBuilder()
9
+ .setExtras({ global: true }, (definition, extras) => ({
10
+ ...definition,
11
+ global: true,
12
+ providers: extras.global !== false
13
+ ? [
14
+ ...(definition.providers ?? []),
15
+ { provide: core_1.APP_GUARD, useClass: ipfilter_guard_1.IpFilterGuard },
16
+ ]
17
+ : definition.providers,
18
+ }))
19
+ .build(), exports.ConfigurableModuleClass = _a.ConfigurableModuleClass, exports.MODULE_OPTIONS_TOKEN = _a.MODULE_OPTIONS_TOKEN, exports.OPTIONS_TYPE = _a.OPTIONS_TYPE, exports.ASYNC_OPTIONS_TYPE = _a.ASYNC_OPTIONS_TYPE;
@@ -8,26 +8,19 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.IpFilter = void 0;
10
10
  const common_1 = require("@nestjs/common");
11
- const core_1 = require("@nestjs/core");
12
11
  const ipfilter_constants_1 = require("./ipfilter.constants");
13
- const ipfilter_guard_1 = require("./ipfilter.guard");
14
12
  const ipfilter_module_definition_1 = require("./ipfilter.module-definition");
15
13
  const ipfilter_service_1 = require("./ipfilter.service");
16
14
  const ipFilterServiceProvider = {
17
15
  provide: ipfilter_constants_1.IPFILTER_TOKEN,
18
16
  useClass: ipfilter_service_1.IpFilterService,
19
17
  };
20
- const ipFilterGuardProvider = {
21
- provide: core_1.APP_GUARD,
22
- useClass: ipfilter_guard_1.IpFilterGuard,
23
- };
24
18
  let IpFilter = class IpFilter extends ipfilter_module_definition_1.ConfigurableModuleClass {
25
19
  };
26
- IpFilter = __decorate([
20
+ exports.IpFilter = IpFilter;
21
+ exports.IpFilter = IpFilter = __decorate([
27
22
  (0, common_1.Module)({
28
- providers: [ipFilterServiceProvider, ipFilterGuardProvider],
23
+ providers: [ipFilterServiceProvider],
29
24
  exports: [ipFilterServiceProvider],
30
25
  })
31
26
  ], IpFilter);
32
- exports.IpFilter = IpFilter;
33
- //# sourceMappingURL=ipfilter.module.js.map
@@ -2,11 +2,14 @@ import { IpFilterModuleOptions } from './ipfilter.interface';
2
2
  export declare class IpFilterService {
3
3
  readonly options: IpFilterModuleOptions;
4
4
  useDenyException: boolean;
5
- private _whitelist;
6
- private _blacklist;
5
+ private _whitelist?;
6
+ private _blacklist?;
7
+ private whitelistRules?;
8
+ private blacklistRules?;
7
9
  constructor(options: IpFilterModuleOptions);
8
- get whitelist(): string[];
10
+ get whitelist(): string[] | undefined;
9
11
  set whitelist(whitelist: string[]);
10
- get blacklist(): string[];
12
+ get blacklist(): string[] | undefined;
11
13
  set blacklist(blacklist: string[]);
14
+ isAllowed(ip: string | undefined): boolean;
12
15
  }
@@ -14,32 +14,80 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.IpFilterService = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
+ const node_net_1 = require("node:net");
17
18
  const ipfilter_module_definition_1 = require("./ipfilter.module-definition");
19
+ const familyOf = (address) => ((0, node_net_1.isIP)(address) === 6 ? 'ipv6' : 'ipv4');
20
+ function toBlockList(rules) {
21
+ if (!Array.isArray(rules)) {
22
+ throw new Error(`IP filter rules must be an array, got ${typeof rules}`);
23
+ }
24
+ const list = new node_net_1.BlockList();
25
+ for (const raw of rules) {
26
+ const [address, prefix, ...rest] = String(raw).trim().split('/');
27
+ const family = (0, node_net_1.isIP)(address);
28
+ const maxPrefix = family === 4 ? 32 : 128;
29
+ if (family === 0 ||
30
+ rest.length > 0 ||
31
+ (prefix !== undefined &&
32
+ (!/^\d+$/.test(prefix) || Number(prefix) > maxPrefix)) ||
33
+ (/^::ffff:/i.test(address) && prefix !== undefined && Number(prefix) < 96)) {
34
+ throw new Error(`Invalid IP filter rule: "${raw}"`);
35
+ }
36
+ if (prefix === undefined) {
37
+ list.addAddress(address, familyOf(address));
38
+ }
39
+ else {
40
+ list.addSubnet(address, Number(prefix), familyOf(address));
41
+ }
42
+ }
43
+ return list;
44
+ }
18
45
  let IpFilterService = class IpFilterService {
46
+ options;
47
+ useDenyException;
48
+ _whitelist;
49
+ _blacklist;
50
+ whitelistRules;
51
+ blacklistRules;
19
52
  constructor(options) {
20
- var _a, _b, _c;
21
53
  this.options = options;
22
- this.useDenyException = (_a = options.useDenyException) !== null && _a !== void 0 ? _a : false;
23
- this._whitelist = Object.assign([], (_b = options.whitelist) !== null && _b !== void 0 ? _b : []);
24
- this._blacklist = Object.assign([], (_c = options.blacklist) !== null && _c !== void 0 ? _c : []);
54
+ if (options.whitelist == null && options.blacklist == null) {
55
+ throw new Error('IpFilter: configure "whitelist", "blacklist" or both');
56
+ }
57
+ this.useDenyException = options.useDenyException ?? false;
58
+ if (options.whitelist != null)
59
+ this.whitelist = options.whitelist;
60
+ if (options.blacklist != null)
61
+ this.blacklist = options.blacklist;
25
62
  }
26
63
  get whitelist() {
27
- return this._whitelist;
64
+ return this._whitelist && [...this._whitelist];
28
65
  }
29
66
  set whitelist(whitelist) {
30
- this._whitelist = whitelist;
67
+ this.whitelistRules = toBlockList(whitelist);
68
+ this._whitelist = [...whitelist];
31
69
  }
32
70
  get blacklist() {
33
- return this._blacklist;
71
+ return this._blacklist && [...this._blacklist];
34
72
  }
35
73
  set blacklist(blacklist) {
36
- this._blacklist = blacklist;
74
+ this.blacklistRules = toBlockList(blacklist);
75
+ this._blacklist = [...blacklist];
76
+ }
77
+ isAllowed(ip) {
78
+ if (!ip || (0, node_net_1.isIP)(ip) === 0) {
79
+ return false;
80
+ }
81
+ const family = familyOf(ip);
82
+ if (this.blacklistRules?.check(ip, family)) {
83
+ return false;
84
+ }
85
+ return this.whitelistRules?.check(ip, family) ?? true;
37
86
  }
38
87
  };
39
- IpFilterService = __decorate([
88
+ exports.IpFilterService = IpFilterService;
89
+ exports.IpFilterService = IpFilterService = __decorate([
40
90
  (0, common_1.Injectable)(),
41
91
  __param(0, (0, common_1.Inject)(ipfilter_module_definition_1.MODULE_OPTIONS_TOKEN)),
42
92
  __metadata("design:paramtypes", [Object])
43
93
  ], IpFilterService);
44
- exports.IpFilterService = IpFilterService;
45
- //# sourceMappingURL=ipfilter.service.js.map