nest-encrypt-cycle 2.0.0 → 2.0.2
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/.claude/settings.local.json +3 -1
- package/CHANGELOG.md +48 -0
- package/README.md +41 -6
- package/dist/src/encrypt.interceptor.js +3 -2
- package/dist/src/encrypt.interceptor.js.map +1 -1
- package/dist/src/encrypt.service.d.ts +2 -0
- package/dist/src/encrypt.service.js +29 -3
- package/dist/src/encrypt.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/test-query-params.js +99 -0
- package/test-wildcard.js +154 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.2] - Wildcard Pattern Support
|
|
4
|
+
|
|
5
|
+
### ✨ New Features
|
|
6
|
+
|
|
7
|
+
**Wildcard Pattern Matching in Whitelist**
|
|
8
|
+
- Added support for wildcard (`*`) patterns in whitelist paths
|
|
9
|
+
- Wildcard matches any single path segment (not including `/`)
|
|
10
|
+
- Can use multiple wildcards in a single pattern
|
|
11
|
+
- Works alongside exact path matching
|
|
12
|
+
|
|
13
|
+
**Examples:**
|
|
14
|
+
```typescript
|
|
15
|
+
whiteList: [
|
|
16
|
+
{ method: 'GET', pathname: '/api/users/*' }, // matches /api/users/123, /api/users/abc
|
|
17
|
+
{ method: 'GET', pathname: '/api/*/profile' }, // matches /api/users/profile, /api/admin/profile
|
|
18
|
+
{ method: 'POST', pathname: '/api/*/posts/*' }, // matches /api/users/posts/1, /api/admin/posts/2
|
|
19
|
+
{ method: 'GET', pathname: '/api/health' }, // exact match (can mix with wildcards)
|
|
20
|
+
]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Performance:**
|
|
24
|
+
- Exact matches: O(1) via Set lookup
|
|
25
|
+
- Wildcard matches: O(n) where n = number of wildcard patterns
|
|
26
|
+
- Exact matches checked first for optimal performance
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## [2.0.1] - Query Parameter Fix
|
|
31
|
+
|
|
32
|
+
### 🐛 Bug Fixes
|
|
33
|
+
|
|
34
|
+
**Whitelist Query Parameter Handling**
|
|
35
|
+
- Fixed whitelist matching to ignore query parameters
|
|
36
|
+
- Example: If `/api/users` is whitelisted, now `/api/users?id=1` also matches
|
|
37
|
+
- Query strings are stripped before whitelist comparison
|
|
38
|
+
|
|
39
|
+
**Before:**
|
|
40
|
+
- Whitelist: `GET /api/users`
|
|
41
|
+
- `/api/users` → matched ✅
|
|
42
|
+
- `/api/users?id=1` → NOT matched ❌
|
|
43
|
+
|
|
44
|
+
**After:**
|
|
45
|
+
- Whitelist: `GET /api/users`
|
|
46
|
+
- `/api/users` → matched ✅
|
|
47
|
+
- `/api/users?id=1` → matched ✅
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
3
51
|
## [2.0.0] - Performance Optimization Release
|
|
4
52
|
|
|
5
53
|
### 🚀 Major Performance Improvements
|
package/README.md
CHANGED
|
@@ -9,9 +9,10 @@ NPM : https://www.npmjs.com/package/nest-encrypt-cycle
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- **High Performance**: Native Node.js crypto module with ~9,600 ops/sec throughput (89-98.5% faster than crypto-js)
|
|
12
|
+
- **Wildcard Pattern Support**: Use `*` in whitelist paths to match dynamic segments (e.g., `/api/users/*`)
|
|
12
13
|
- Transparent AES-256-CBC encryption/decryption of HTTP request and response payloads
|
|
13
14
|
- Easy integration as a global or route-scoped interceptor
|
|
14
|
-
- Optimized whitelist lookup with O(1) complexity for
|
|
15
|
+
- Optimized whitelist lookup with O(1) complexity for exact matches
|
|
15
16
|
- Secure data handling for sensitive information in NestJS APIs
|
|
16
17
|
- Zero external dependencies (uses native crypto)
|
|
17
18
|
|
|
@@ -40,17 +41,51 @@ export class AppModule {}
|
|
|
40
41
|
|
|
41
42
|
```typescript
|
|
42
43
|
export interface EncryptOptions {
|
|
43
|
-
// Hash Key
|
|
44
|
+
// Hash Key (32 bytes for AES-256)
|
|
44
45
|
key: string;
|
|
45
46
|
|
|
46
|
-
// API White List
|
|
47
|
+
// API White List - Routes that should NOT be encrypted
|
|
47
48
|
whiteList: {
|
|
48
|
-
method: string;
|
|
49
|
-
pathname: string;
|
|
49
|
+
method: string; // HTTP method: 'GET', 'POST', etc.
|
|
50
|
+
pathname: string; // Path pattern: exact or with wildcards
|
|
50
51
|
}[];
|
|
51
52
|
}
|
|
52
53
|
```
|
|
53
54
|
|
|
55
|
+
## Whitelist Configuration
|
|
56
|
+
|
|
57
|
+
The whitelist supports both **exact matching** and **wildcard patterns**:
|
|
58
|
+
|
|
59
|
+
### Exact Matching
|
|
60
|
+
```typescript
|
|
61
|
+
whiteList: [
|
|
62
|
+
{ method: 'GET', pathname: '/api/health' },
|
|
63
|
+
{ method: 'POST', pathname: '/api/webhook' },
|
|
64
|
+
]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Wildcard Patterns
|
|
68
|
+
Use `*` to match any single path segment:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
whiteList: [
|
|
72
|
+
// Match any user ID: /api/users/123, /api/users/abc, etc.
|
|
73
|
+
{ method: 'GET', pathname: '/api/users/*' },
|
|
74
|
+
|
|
75
|
+
// Match any resource's profile: /api/users/profile, /api/admin/profile
|
|
76
|
+
{ method: 'GET', pathname: '/api/*/profile' },
|
|
77
|
+
|
|
78
|
+
// Multiple wildcards: /api/users/posts/1, /api/admin/posts/2
|
|
79
|
+
{ method: 'POST', pathname: '/api/*/posts/*' },
|
|
80
|
+
]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Notes:**
|
|
84
|
+
- `*` matches exactly **one** path segment (not including `/`)
|
|
85
|
+
- `/api/users/*` matches `/api/users/123` ✅ but NOT `/api/users/123/profile` ❌
|
|
86
|
+
- Query parameters are automatically ignored: `/api/users?id=1` matches `/api/users`
|
|
87
|
+
- Exact matches are checked first (O(1)), then wildcard patterns (O(n))
|
|
88
|
+
|
|
54
89
|
## Configuration
|
|
55
90
|
|
|
56
|
-
|
|
91
|
+
**key** (string): Your AES encryption key. Must be 16, 24, or 32 bytes long for AES-128/192/256.
|
|
@@ -21,13 +21,14 @@ let EncryptInterceptor = class EncryptInterceptor {
|
|
|
21
21
|
const req = context.switchToHttp().getRequest();
|
|
22
22
|
const url = req.url || req.raw?.url;
|
|
23
23
|
const method = req.method;
|
|
24
|
+
const pathname = url.split('?')[0];
|
|
24
25
|
const isEncrypted = (req.headers['is-encrypted'] || req.headers['is-encrypted']) === 'Y';
|
|
25
|
-
this.processRequest(req,
|
|
26
|
+
this.processRequest(req, pathname, method, isEncrypted);
|
|
26
27
|
return next.handle().pipe((0, rxjs_1.map)((data) => {
|
|
27
28
|
if (data && data.encrypted === true && data.data) {
|
|
28
29
|
return data;
|
|
29
30
|
}
|
|
30
|
-
return this.processResponse(data,
|
|
31
|
+
return this.processResponse(data, pathname, method, isEncrypted);
|
|
31
32
|
}));
|
|
32
33
|
}
|
|
33
34
|
processRequest(req, url, method, isEncrypted) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypt.interceptor.js","sourceRoot":"","sources":["../../src/encrypt.interceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AACxB,+BAAuC;AACvC,uDAAmD;AAG5C,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAC7B,YAA6B,cAA8B;QAA9B,mBAAc,GAAd,cAAc,CAAgB;IAAG,CAAC;IAE/D,SAAS,CAAC,OAAyB,EAAE,IAAiB;QACpD,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAEhD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC;QACpC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QAE1B,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,KAAK,GAAG,CAAC;QAGzF,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"encrypt.interceptor.js","sourceRoot":"","sources":["../../src/encrypt.interceptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,9 +2,11 @@ import { EncryptOptions } from './types';
|
|
|
2
2
|
export declare class EncryptService {
|
|
3
3
|
private readonly options;
|
|
4
4
|
private readonly whitelistSet;
|
|
5
|
+
private readonly whitelistPatterns;
|
|
5
6
|
private readonly keyBuffer;
|
|
6
7
|
private readonly ivBuffer;
|
|
7
8
|
constructor(options: EncryptOptions);
|
|
9
|
+
private isWhitelisted;
|
|
8
10
|
encrypt(data: string, pathname: string, method: string): string;
|
|
9
11
|
decrypt(data: string, pathname: string, method: string): string;
|
|
10
12
|
}
|
|
@@ -15,12 +15,38 @@ const crypto_1 = require("crypto");
|
|
|
15
15
|
let EncryptService = class EncryptService {
|
|
16
16
|
constructor(options) {
|
|
17
17
|
this.options = options;
|
|
18
|
-
this.whitelistSet = new Set(
|
|
18
|
+
this.whitelistSet = new Set();
|
|
19
|
+
this.whitelistPatterns = [];
|
|
20
|
+
for (const item of this.options.whiteList) {
|
|
21
|
+
if (item.pathname.includes('*')) {
|
|
22
|
+
const regexPattern = item.pathname
|
|
23
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
24
|
+
.replace(/\*/g, '[^/]+');
|
|
25
|
+
this.whitelistPatterns.push({
|
|
26
|
+
method: item.method,
|
|
27
|
+
pattern: new RegExp(`^${regexPattern}$`),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
this.whitelistSet.add(`${item.method}:${item.pathname}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
19
34
|
this.keyBuffer = Buffer.from(this.options.key, 'utf8');
|
|
20
35
|
this.ivBuffer = Buffer.from(this.options.key.slice(0, 16), 'utf8');
|
|
21
36
|
}
|
|
22
|
-
|
|
37
|
+
isWhitelisted(pathname, method) {
|
|
23
38
|
if (this.whitelistSet.has(`${method}:${pathname}`)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
for (const item of this.whitelistPatterns) {
|
|
42
|
+
if (item.method === method && item.pattern.test(pathname)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
encrypt(data, pathname, method) {
|
|
49
|
+
if (this.isWhitelisted(pathname, method)) {
|
|
24
50
|
return data;
|
|
25
51
|
}
|
|
26
52
|
if (!data || typeof data !== 'string')
|
|
@@ -41,7 +67,7 @@ let EncryptService = class EncryptService {
|
|
|
41
67
|
}
|
|
42
68
|
}
|
|
43
69
|
decrypt(data, pathname, method) {
|
|
44
|
-
if (this.
|
|
70
|
+
if (this.isWhitelisted(pathname, method)) {
|
|
45
71
|
return data;
|
|
46
72
|
}
|
|
47
73
|
if (!data || typeof data !== 'string')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypt.service.js","sourceRoot":"","sources":["../../src/encrypt.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,mCAA0D;AAInD,IAAM,cAAc,GAApB,MAAM,cAAc;
|
|
1
|
+
{"version":3,"file":"encrypt.service.js","sourceRoot":"","sources":["../../src/encrypt.service.ts"],"names":[],"mappings":"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"}
|