mvc-common-toolkit 1.43.9 → 1.43.11
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/dist/src/constants.d.ts +42 -0
- package/dist/src/constants.js +50 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/gateways/alibaba-cloud-gateway.d.ts +30 -0
- package/dist/src/gateways/alibaba-cloud-gateway.js +120 -0
- package/dist/src/gateways/alibaba-cloud-gateway.js.map +1 -0
- package/dist/src/gateways/http-audit-gateway.d.ts +20 -0
- package/dist/src/gateways/http-audit-gateway.js +76 -0
- package/dist/src/gateways/http-audit-gateway.js.map +1 -0
- package/dist/src/gateways/index.d.ts +5 -0
- package/dist/src/gateways/index.js +22 -0
- package/dist/src/gateways/index.js.map +1 -0
- package/dist/src/gateways/internal-auth-gateway.d.ts +8 -0
- package/dist/src/gateways/internal-auth-gateway.js +40 -0
- package/dist/src/gateways/internal-auth-gateway.js.map +1 -0
- package/dist/src/gateways/stdout-audit-gateway.d.ts +7 -0
- package/dist/src/gateways/stdout-audit-gateway.js +25 -0
- package/dist/src/gateways/stdout-audit-gateway.js.map +1 -0
- package/dist/src/gateways/webhook-audit-gateway.d.ts +14 -0
- package/dist/src/gateways/webhook-audit-gateway.js +27 -0
- package/dist/src/gateways/webhook-audit-gateway.js.map +1 -0
- package/dist/src/interfaces.d.ts +218 -0
- package/dist/src/interfaces.js +3 -0
- package/dist/src/interfaces.js.map +1 -0
- package/dist/src/models/audit-log.d.ts +77 -0
- package/dist/src/models/audit-log.js +92 -0
- package/dist/src/models/audit-log.js.map +1 -0
- package/dist/src/models/index.d.ts +1 -0
- package/dist/src/models/index.js +18 -0
- package/dist/src/models/index.js.map +1 -0
- package/dist/src/pkg/array-helper.d.ts +1 -0
- package/dist/src/pkg/array-helper.js +12 -0
- package/dist/src/pkg/array-helper.js.map +1 -0
- package/dist/src/pkg/bcrypt-helper.d.ts +2 -0
- package/dist/src/pkg/bcrypt-helper.js +36 -0
- package/dist/src/pkg/bcrypt-helper.js.map +1 -0
- package/dist/src/pkg/crypto-helper.d.ts +2 -0
- package/dist/src/pkg/crypto-helper.js +16 -0
- package/dist/src/pkg/crypto-helper.js.map +1 -0
- package/dist/src/pkg/encryption-helper.d.ts +18 -0
- package/dist/src/pkg/encryption-helper.js +89 -0
- package/dist/src/pkg/encryption-helper.js.map +1 -0
- package/dist/src/pkg/encryption-helper.spec.d.ts +1 -0
- package/dist/src/pkg/encryption-helper.spec.js +238 -0
- package/dist/src/pkg/encryption-helper.spec.js.map +1 -0
- package/dist/src/pkg/filter-helper.d.ts +2 -0
- package/dist/src/pkg/filter-helper.js +102 -0
- package/dist/src/pkg/filter-helper.js.map +1 -0
- package/dist/src/pkg/filter-helper.spec.d.ts +1 -0
- package/dist/src/pkg/filter-helper.spec.js +94 -0
- package/dist/src/pkg/filter-helper.spec.js.map +1 -0
- package/dist/src/pkg/geoip-helper.d.ts +2 -0
- package/dist/src/pkg/geoip-helper.js +32 -0
- package/dist/src/pkg/geoip-helper.js.map +1 -0
- package/dist/src/pkg/hash-helper.d.ts +1 -0
- package/dist/src/pkg/hash-helper.js +37 -0
- package/dist/src/pkg/hash-helper.js.map +1 -0
- package/dist/src/pkg/http-request-utils.d.ts +4 -0
- package/dist/src/pkg/http-request-utils.js +55 -0
- package/dist/src/pkg/http-request-utils.js.map +1 -0
- package/dist/src/pkg/index.d.ts +18 -0
- package/dist/src/pkg/index.js +45 -0
- package/dist/src/pkg/index.js.map +1 -0
- package/dist/src/pkg/key-helper.d.ts +2 -0
- package/dist/src/pkg/key-helper.js +20 -0
- package/dist/src/pkg/key-helper.js.map +1 -0
- package/dist/src/pkg/logger.d.ts +9 -0
- package/dist/src/pkg/logger.js +23 -0
- package/dist/src/pkg/logger.js.map +1 -0
- package/dist/src/pkg/object-helper.d.ts +2 -0
- package/dist/src/pkg/object-helper.js +37 -0
- package/dist/src/pkg/object-helper.js.map +1 -0
- package/dist/src/pkg/paginated-cache-registry.d.ts +8 -0
- package/dist/src/pkg/paginated-cache-registry.js +23 -0
- package/dist/src/pkg/paginated-cache-registry.js.map +1 -0
- package/dist/src/pkg/query-helper.d.ts +3 -0
- package/dist/src/pkg/query-helper.js +60 -0
- package/dist/src/pkg/query-helper.js.map +1 -0
- package/dist/src/pkg/referral-tree-utils.d.ts +33 -0
- package/dist/src/pkg/referral-tree-utils.js +71 -0
- package/dist/src/pkg/referral-tree-utils.js.map +1 -0
- package/dist/src/pkg/scripts/index.d.ts +1 -0
- package/dist/src/pkg/scripts/index.js +28 -0
- package/dist/src/pkg/scripts/index.js.map +1 -0
- package/dist/src/pkg/scripts/lua.d.ts +10 -0
- package/dist/src/pkg/scripts/lua.js +109 -0
- package/dist/src/pkg/scripts/lua.js.map +1 -0
- package/dist/src/pkg/sort-helper.d.ts +3 -0
- package/dist/src/pkg/sort-helper.js +18 -0
- package/dist/src/pkg/sort-helper.js.map +1 -0
- package/dist/src/pkg/string-utils.d.ts +10 -0
- package/dist/src/pkg/string-utils.js +79 -0
- package/dist/src/pkg/string-utils.js.map +1 -0
- package/dist/src/pkg/task-helper.d.ts +2 -0
- package/dist/src/pkg/task-helper.js +30 -0
- package/dist/src/pkg/task-helper.js.map +1 -0
- package/dist/src/pkg/workflow/delayed-task-registry.d.ts +10 -0
- package/dist/src/pkg/workflow/delayed-task-registry.js +67 -0
- package/dist/src/pkg/workflow/delayed-task-registry.js.map +1 -0
- package/dist/src/pkg/workflow/delayed-task.d.ts +18 -0
- package/dist/src/pkg/workflow/delayed-task.js +95 -0
- package/dist/src/pkg/workflow/delayed-task.js.map +1 -0
- package/dist/src/pkg/workflow/index.d.ts +5 -0
- package/dist/src/pkg/workflow/index.js +22 -0
- package/dist/src/pkg/workflow/index.js.map +1 -0
- package/dist/src/pkg/workflow/processing-milestone.d.ts +18 -0
- package/dist/src/pkg/workflow/processing-milestone.js +39 -0
- package/dist/src/pkg/workflow/processing-milestone.js.map +1 -0
- package/dist/src/pkg/workflow/retry-task.d.ts +24 -0
- package/dist/src/pkg/workflow/retry-task.js +89 -0
- package/dist/src/pkg/workflow/retry-task.js.map +1 -0
- package/dist/src/pkg/workflow/retry-task.spec.d.ts +1 -0
- package/dist/src/pkg/workflow/retry-task.spec.js +145 -0
- package/dist/src/pkg/workflow/retry-task.spec.js.map +1 -0
- package/dist/src/pkg/workflow/sync-taskqueue.d.ts +32 -0
- package/dist/src/pkg/workflow/sync-taskqueue.js +108 -0
- package/dist/src/pkg/workflow/sync-taskqueue.js.map +1 -0
- package/dist/src/pkg/worksheet.utils.d.ts +27 -0
- package/dist/src/pkg/worksheet.utils.js +116 -0
- package/dist/src/pkg/worksheet.utils.js.map +1 -0
- package/dist/src/services/audit-service.d.ts +7 -0
- package/dist/src/services/audit-service.js +32 -0
- package/dist/src/services/audit-service.js.map +1 -0
- package/dist/src/services/excel.service.d.ts +25 -0
- package/dist/src/services/excel.service.js +95 -0
- package/dist/src/services/excel.service.js.map +1 -0
- package/dist/src/services/http-service.d.ts +7 -0
- package/dist/src/services/http-service.js +67 -0
- package/dist/src/services/http-service.js.map +1 -0
- package/dist/src/services/index.d.ts +8 -0
- package/dist/src/services/index.js +25 -0
- package/dist/src/services/index.js.map +1 -0
- package/dist/src/services/kafka-service.d.ts +15 -0
- package/dist/src/services/kafka-service.js +68 -0
- package/dist/src/services/kafka-service.js.map +1 -0
- package/dist/src/services/mailer-service.d.ts +15 -0
- package/dist/src/services/mailer-service.js +44 -0
- package/dist/src/services/mailer-service.js.map +1 -0
- package/dist/src/services/paginated-cache.d.ts +16 -0
- package/dist/src/services/paginated-cache.js +115 -0
- package/dist/src/services/paginated-cache.js.map +1 -0
- package/dist/src/services/paginated-cache.spec.d.ts +1 -0
- package/dist/src/services/paginated-cache.spec.js +284 -0
- package/dist/src/services/paginated-cache.spec.js.map +1 -0
- package/dist/src/services/redis-service.d.ts +33 -0
- package/dist/src/services/redis-service.js +230 -0
- package/dist/src/services/redis-service.js.map +1 -0
- package/dist/src/services/security-service.d.ts +11 -0
- package/dist/src/services/security-service.js +68 -0
- package/dist/src/services/security-service.js.map +1 -0
- package/package.json +1 -1
- package/src/constants.ts +47 -0
- package/src/gateways/alibaba-cloud-gateway.ts +127 -0
- package/src/gateways/http-audit-gateway.ts +104 -0
- package/src/gateways/index.ts +5 -0
- package/src/gateways/internal-auth-gateway.ts +42 -0
- package/src/gateways/stdout-audit-gateway.ts +23 -0
- package/src/gateways/webhook-audit-gateway.ts +33 -0
- package/src/interfaces.ts +304 -0
- package/src/models/audit-log.ts +126 -0
- package/src/models/index.ts +1 -0
- package/src/pkg/array-helper.ts +7 -0
- package/src/pkg/bcrypt-helper.ts +9 -0
- package/src/pkg/crypto-helper.ts +18 -0
- package/src/pkg/encryption-helper.spec.ts +423 -0
- package/src/pkg/encryption-helper.ts +155 -0
- package/src/pkg/filter-helper.spec.ts +105 -0
- package/src/pkg/filter-helper.ts +139 -0
- package/src/pkg/geoip-helper.ts +5 -0
- package/src/pkg/hash-helper.ts +12 -0
- package/src/pkg/http-request-utils.ts +75 -0
- package/src/pkg/index.ts +18 -0
- package/src/pkg/key-helper.ts +20 -0
- package/src/pkg/logger.ts +23 -0
- package/src/pkg/object-helper.ts +42 -0
- package/src/pkg/paginated-cache-registry.ts +25 -0
- package/src/pkg/query-helper.ts +79 -0
- package/src/pkg/referral-tree-utils.ts +165 -0
- package/src/pkg/scripts/index.ts +1 -0
- package/src/pkg/scripts/lua.ts +112 -0
- package/src/pkg/sort-helper.ts +19 -0
- package/src/pkg/string-utils.ts +104 -0
- package/src/pkg/task-helper.ts +25 -0
- package/src/pkg/workflow/delayed-task-registry.ts +54 -0
- package/src/pkg/workflow/delayed-task.ts +106 -0
- package/src/pkg/workflow/index.ts +5 -0
- package/src/pkg/workflow/processing-milestone.ts +54 -0
- package/src/pkg/workflow/retry-task.spec.ts +194 -0
- package/src/pkg/workflow/retry-task.ts +119 -0
- package/src/pkg/workflow/sync-taskqueue.ts +118 -0
- package/src/pkg/worksheet.utils.ts +178 -0
- package/src/services/audit-service.ts +22 -0
- package/src/services/excel.service.ts +103 -0
- package/src/services/http-service.ts +71 -0
- package/src/services/index.ts +8 -0
- package/src/services/kafka-service.ts +81 -0
- package/src/services/mailer-service.ts +43 -0
- package/src/services/paginated-cache.spec.ts +519 -0
- package/src/services/paginated-cache.ts +122 -0
- package/src/services/redis-service.ts +238 -0
- package/src/services/security-service.ts +80 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import {
|
|
3
|
+
encrypt,
|
|
4
|
+
decrypt,
|
|
5
|
+
encryptWithTwoFactors,
|
|
6
|
+
decryptWithTwoFactors,
|
|
7
|
+
} from "./encryption-helper";
|
|
8
|
+
|
|
9
|
+
describe("encryption helper", () => {
|
|
10
|
+
describe("single factor encryption", () => {
|
|
11
|
+
it("should encrypt and decrypt text correctly", () => {
|
|
12
|
+
const plaintext = "Hello, World!";
|
|
13
|
+
const password = "my-secret-password";
|
|
14
|
+
|
|
15
|
+
const encrypted = encrypt(plaintext, password);
|
|
16
|
+
const decrypted = decrypt(encrypted, password);
|
|
17
|
+
|
|
18
|
+
expect(decrypted).to.equal(plaintext);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should handle empty string", () => {
|
|
22
|
+
const plaintext = "";
|
|
23
|
+
const password = "my-secret-password";
|
|
24
|
+
|
|
25
|
+
const encrypted = encrypt(plaintext, password);
|
|
26
|
+
const decrypted = decrypt(encrypted, password);
|
|
27
|
+
|
|
28
|
+
expect(decrypted).to.equal(plaintext);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should handle special characters and unicode", () => {
|
|
32
|
+
const plaintext = "Hello 世界! 🚀 测试";
|
|
33
|
+
const password = "my-secret-password";
|
|
34
|
+
|
|
35
|
+
const encrypted = encrypt(plaintext, password);
|
|
36
|
+
const decrypted = decrypt(encrypted, password);
|
|
37
|
+
|
|
38
|
+
expect(decrypted).to.equal(plaintext);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should handle long text", () => {
|
|
42
|
+
const plaintext = "A".repeat(1000);
|
|
43
|
+
const password = "my-secret-password";
|
|
44
|
+
|
|
45
|
+
const encrypted = encrypt(plaintext, password);
|
|
46
|
+
const decrypted = decrypt(encrypted, password);
|
|
47
|
+
|
|
48
|
+
expect(decrypted).to.equal(plaintext);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should produce different ciphertext for same plaintext with different passwords", () => {
|
|
52
|
+
const plaintext = "Hello, World!";
|
|
53
|
+
const password1 = "password1";
|
|
54
|
+
const password2 = "password2";
|
|
55
|
+
|
|
56
|
+
const encrypted1 = encrypt(plaintext, password1);
|
|
57
|
+
const encrypted2 = encrypt(plaintext, password2);
|
|
58
|
+
|
|
59
|
+
expect(encrypted1.ciphertext).to.not.equal(encrypted2.ciphertext);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should produce different ciphertext for same plaintext and password (due to random salt/iv)", () => {
|
|
63
|
+
const plaintext = "Hello, World!";
|
|
64
|
+
const password = "my-secret-password";
|
|
65
|
+
|
|
66
|
+
const encrypted1 = encrypt(plaintext, password);
|
|
67
|
+
const encrypted2 = encrypt(plaintext, password);
|
|
68
|
+
|
|
69
|
+
expect(encrypted1.ciphertext).to.not.equal(encrypted2.ciphertext);
|
|
70
|
+
expect(encrypted1.iv).to.not.equal(encrypted2.iv);
|
|
71
|
+
expect(encrypted1.salt).to.not.equal(encrypted2.salt);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should fail decryption with wrong password", () => {
|
|
75
|
+
const plaintext = "Hello, World!";
|
|
76
|
+
const correctPassword = "correct-password";
|
|
77
|
+
const wrongPassword = "wrong-password";
|
|
78
|
+
|
|
79
|
+
const encrypted = encrypt(plaintext, correctPassword);
|
|
80
|
+
|
|
81
|
+
expect(() => decrypt(encrypted, wrongPassword)).to.throw();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should fail decryption with corrupted payload", () => {
|
|
85
|
+
const plaintext = "Hello, World!";
|
|
86
|
+
const password = "my-secret-password";
|
|
87
|
+
|
|
88
|
+
const encrypted = encrypt(plaintext, password);
|
|
89
|
+
const corruptedPayload = {
|
|
90
|
+
...encrypted,
|
|
91
|
+
ciphertext: "corrupted-ciphertext",
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
expect(() => decrypt(corruptedPayload, password)).to.throw();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should fail decryption with corrupted auth tag", () => {
|
|
98
|
+
const plaintext = "Hello, World!";
|
|
99
|
+
const password = "my-secret-password";
|
|
100
|
+
|
|
101
|
+
const encrypted = encrypt(plaintext, password);
|
|
102
|
+
const corruptedPayload = {
|
|
103
|
+
...encrypted,
|
|
104
|
+
tag: "corrupted-tag",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
expect(() => decrypt(corruptedPayload, password)).to.throw();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should have correct payload structure", () => {
|
|
111
|
+
const plaintext = "Hello, World!";
|
|
112
|
+
const password = "my-secret-password";
|
|
113
|
+
|
|
114
|
+
const encrypted = encrypt(plaintext, password);
|
|
115
|
+
|
|
116
|
+
expect(encrypted).to.have.property("iv");
|
|
117
|
+
expect(encrypted).to.have.property("salt");
|
|
118
|
+
expect(encrypted).to.have.property("ciphertext");
|
|
119
|
+
expect(encrypted).to.have.property("tag");
|
|
120
|
+
|
|
121
|
+
expect(encrypted.iv).to.be.a("string");
|
|
122
|
+
expect(encrypted.salt).to.be.a("string");
|
|
123
|
+
expect(encrypted.ciphertext).to.be.a("string");
|
|
124
|
+
expect(encrypted.tag).to.be.a("string");
|
|
125
|
+
|
|
126
|
+
// Check that hex strings are valid
|
|
127
|
+
expect(encrypted.iv).to.match(/^[0-9a-f]+$/);
|
|
128
|
+
expect(encrypted.salt).to.match(/^[0-9a-f]+$/);
|
|
129
|
+
expect(encrypted.ciphertext).to.match(/^[0-9a-f]+$/);
|
|
130
|
+
expect(encrypted.tag).to.match(/^[0-9a-f]+$/);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("two factor encryption", () => {
|
|
135
|
+
it("should encrypt and decrypt text correctly with two factors", () => {
|
|
136
|
+
const plaintext = "Hello, World!";
|
|
137
|
+
const userPassword = "user-password";
|
|
138
|
+
const serverSecret = "server-secret";
|
|
139
|
+
|
|
140
|
+
const encrypted = encryptWithTwoFactors(
|
|
141
|
+
plaintext,
|
|
142
|
+
userPassword,
|
|
143
|
+
serverSecret
|
|
144
|
+
);
|
|
145
|
+
const decrypted = decryptWithTwoFactors(
|
|
146
|
+
encrypted,
|
|
147
|
+
userPassword,
|
|
148
|
+
serverSecret
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(decrypted).to.equal(plaintext);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should handle empty string with two factors", () => {
|
|
155
|
+
const plaintext = "";
|
|
156
|
+
const userPassword = "user-password";
|
|
157
|
+
const serverSecret = "server-secret";
|
|
158
|
+
|
|
159
|
+
const encrypted = encryptWithTwoFactors(
|
|
160
|
+
plaintext,
|
|
161
|
+
userPassword,
|
|
162
|
+
serverSecret
|
|
163
|
+
);
|
|
164
|
+
const decrypted = decryptWithTwoFactors(
|
|
165
|
+
encrypted,
|
|
166
|
+
userPassword,
|
|
167
|
+
serverSecret
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
expect(decrypted).to.equal(plaintext);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should handle special characters and unicode with two factors", () => {
|
|
174
|
+
const plaintext = "Hello 世界! 🚀 测试";
|
|
175
|
+
const userPassword = "user-password";
|
|
176
|
+
const serverSecret = "server-secret";
|
|
177
|
+
|
|
178
|
+
const encrypted = encryptWithTwoFactors(
|
|
179
|
+
plaintext,
|
|
180
|
+
userPassword,
|
|
181
|
+
serverSecret
|
|
182
|
+
);
|
|
183
|
+
const decrypted = decryptWithTwoFactors(
|
|
184
|
+
encrypted,
|
|
185
|
+
userPassword,
|
|
186
|
+
serverSecret
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(decrypted).to.equal(plaintext);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should handle long text with two factors", () => {
|
|
193
|
+
const plaintext = "A".repeat(1000);
|
|
194
|
+
const userPassword = "user-password";
|
|
195
|
+
const serverSecret = "server-secret";
|
|
196
|
+
|
|
197
|
+
const encrypted = encryptWithTwoFactors(
|
|
198
|
+
plaintext,
|
|
199
|
+
userPassword,
|
|
200
|
+
serverSecret
|
|
201
|
+
);
|
|
202
|
+
const decrypted = decryptWithTwoFactors(
|
|
203
|
+
encrypted,
|
|
204
|
+
userPassword,
|
|
205
|
+
serverSecret
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(decrypted).to.equal(plaintext);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should produce different ciphertext for same plaintext with different factors", () => {
|
|
212
|
+
const plaintext = "Hello, World!";
|
|
213
|
+
const userPassword1 = "user1";
|
|
214
|
+
const userPassword2 = "user2";
|
|
215
|
+
const serverSecret = "server-secret";
|
|
216
|
+
|
|
217
|
+
const encrypted1 = encryptWithTwoFactors(
|
|
218
|
+
plaintext,
|
|
219
|
+
userPassword1,
|
|
220
|
+
serverSecret
|
|
221
|
+
);
|
|
222
|
+
const encrypted2 = encryptWithTwoFactors(
|
|
223
|
+
plaintext,
|
|
224
|
+
userPassword2,
|
|
225
|
+
serverSecret
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
expect(encrypted1.ciphertext).to.not.equal(encrypted2.ciphertext);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should produce different ciphertext for same plaintext and factors (due to random salt/iv)", () => {
|
|
232
|
+
const plaintext = "Hello, World!";
|
|
233
|
+
const userPassword = "user-password";
|
|
234
|
+
const serverSecret = "server-secret";
|
|
235
|
+
|
|
236
|
+
const encrypted1 = encryptWithTwoFactors(
|
|
237
|
+
plaintext,
|
|
238
|
+
userPassword,
|
|
239
|
+
serverSecret
|
|
240
|
+
);
|
|
241
|
+
const encrypted2 = encryptWithTwoFactors(
|
|
242
|
+
plaintext,
|
|
243
|
+
userPassword,
|
|
244
|
+
serverSecret
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(encrypted1.ciphertext).to.not.equal(encrypted2.ciphertext);
|
|
248
|
+
expect(encrypted1.iv).to.not.equal(encrypted2.iv);
|
|
249
|
+
expect(encrypted1.saltUser).to.not.equal(encrypted2.saltUser);
|
|
250
|
+
expect(encrypted1.saltServer).to.not.equal(encrypted2.saltServer);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should fail decryption with wrong user password", () => {
|
|
254
|
+
const plaintext = "Hello, World!";
|
|
255
|
+
const correctUserPassword = "correct-user-password";
|
|
256
|
+
const wrongUserPassword = "wrong-user-password";
|
|
257
|
+
const serverSecret = "server-secret";
|
|
258
|
+
|
|
259
|
+
const encrypted = encryptWithTwoFactors(
|
|
260
|
+
plaintext,
|
|
261
|
+
correctUserPassword,
|
|
262
|
+
serverSecret
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expect(() =>
|
|
266
|
+
decryptWithTwoFactors(encrypted, wrongUserPassword, serverSecret)
|
|
267
|
+
).to.throw();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("should fail decryption with wrong server secret", () => {
|
|
271
|
+
const plaintext = "Hello, World!";
|
|
272
|
+
const userPassword = "user-password";
|
|
273
|
+
const correctServerSecret = "correct-server-secret";
|
|
274
|
+
const wrongServerSecret = "wrong-server-secret";
|
|
275
|
+
|
|
276
|
+
const encrypted = encryptWithTwoFactors(
|
|
277
|
+
plaintext,
|
|
278
|
+
userPassword,
|
|
279
|
+
correctServerSecret
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(() =>
|
|
283
|
+
decryptWithTwoFactors(encrypted, userPassword, wrongServerSecret)
|
|
284
|
+
).to.throw();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should fail decryption with corrupted payload", () => {
|
|
288
|
+
const plaintext = "Hello, World!";
|
|
289
|
+
const userPassword = "user-password";
|
|
290
|
+
const serverSecret = "server-secret";
|
|
291
|
+
|
|
292
|
+
const encrypted = encryptWithTwoFactors(
|
|
293
|
+
plaintext,
|
|
294
|
+
userPassword,
|
|
295
|
+
serverSecret
|
|
296
|
+
);
|
|
297
|
+
const corruptedPayload = {
|
|
298
|
+
...encrypted,
|
|
299
|
+
ciphertext: "corrupted-ciphertext",
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
expect(() =>
|
|
303
|
+
decryptWithTwoFactors(corruptedPayload, userPassword, serverSecret)
|
|
304
|
+
).to.throw();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should fail decryption with corrupted auth tag", () => {
|
|
308
|
+
const plaintext = "Hello, World!";
|
|
309
|
+
const userPassword = "user-password";
|
|
310
|
+
const serverSecret = "server-secret";
|
|
311
|
+
|
|
312
|
+
const encrypted = encryptWithTwoFactors(
|
|
313
|
+
plaintext,
|
|
314
|
+
userPassword,
|
|
315
|
+
serverSecret
|
|
316
|
+
);
|
|
317
|
+
const corruptedPayload = {
|
|
318
|
+
...encrypted,
|
|
319
|
+
tag: "corrupted-tag",
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
expect(() =>
|
|
323
|
+
decryptWithTwoFactors(corruptedPayload, userPassword, serverSecret)
|
|
324
|
+
).to.throw();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("should have correct payload structure for two factors", () => {
|
|
328
|
+
const plaintext = "Hello, World!";
|
|
329
|
+
const userPassword = "user-password";
|
|
330
|
+
const serverSecret = "server-secret";
|
|
331
|
+
|
|
332
|
+
const encrypted = encryptWithTwoFactors(
|
|
333
|
+
plaintext,
|
|
334
|
+
userPassword,
|
|
335
|
+
serverSecret
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
expect(encrypted).to.have.property("iv");
|
|
339
|
+
expect(encrypted).to.have.property("saltUser");
|
|
340
|
+
expect(encrypted).to.have.property("saltServer");
|
|
341
|
+
expect(encrypted).to.have.property("ciphertext");
|
|
342
|
+
expect(encrypted).to.have.property("tag");
|
|
343
|
+
|
|
344
|
+
expect(encrypted.iv).to.be.a("string");
|
|
345
|
+
expect(encrypted.saltUser).to.be.a("string");
|
|
346
|
+
expect(encrypted.saltServer).to.be.a("string");
|
|
347
|
+
expect(encrypted.ciphertext).to.be.a("string");
|
|
348
|
+
expect(encrypted.tag).to.be.a("string");
|
|
349
|
+
|
|
350
|
+
// Check that hex strings are valid
|
|
351
|
+
expect(encrypted.iv).to.match(/^[0-9a-f]+$/);
|
|
352
|
+
expect(encrypted.saltUser).to.match(/^[0-9a-f]+$/);
|
|
353
|
+
expect(encrypted.saltServer).to.match(/^[0-9a-f]+$/);
|
|
354
|
+
expect(encrypted.ciphertext).to.match(/^[0-9a-f]+$/);
|
|
355
|
+
expect(encrypted.tag).to.match(/^[0-9a-f]+$/);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("should be secure against known plaintext attacks", () => {
|
|
359
|
+
const plaintext = "Hello, World!";
|
|
360
|
+
const userPassword = "user-password";
|
|
361
|
+
const serverSecret = "server-secret";
|
|
362
|
+
|
|
363
|
+
const encrypted1 = encryptWithTwoFactors(
|
|
364
|
+
plaintext,
|
|
365
|
+
userPassword,
|
|
366
|
+
serverSecret
|
|
367
|
+
);
|
|
368
|
+
const encrypted2 = encryptWithTwoFactors(
|
|
369
|
+
plaintext,
|
|
370
|
+
userPassword,
|
|
371
|
+
serverSecret
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
// Even with same plaintext and factors, ciphertext should be different
|
|
375
|
+
expect(encrypted1.ciphertext).to.not.equal(encrypted2.ciphertext);
|
|
376
|
+
expect(encrypted1.iv).to.not.equal(encrypted2.iv);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe("edge cases and error handling", () => {
|
|
381
|
+
it("should handle very long passwords", () => {
|
|
382
|
+
const plaintext = "Hello, World!";
|
|
383
|
+
const longPassword = "A".repeat(1000);
|
|
384
|
+
|
|
385
|
+
const encrypted = encrypt(plaintext, longPassword);
|
|
386
|
+
const decrypted = decrypt(encrypted, longPassword);
|
|
387
|
+
|
|
388
|
+
expect(decrypted).to.equal(plaintext);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("should handle very short passwords", () => {
|
|
392
|
+
const plaintext = "Hello, World!";
|
|
393
|
+
const shortPassword = "a";
|
|
394
|
+
|
|
395
|
+
const encrypted = encrypt(plaintext, shortPassword);
|
|
396
|
+
const decrypted = decrypt(encrypted, shortPassword);
|
|
397
|
+
|
|
398
|
+
expect(decrypted).to.equal(plaintext);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("should handle passwords with special characters", () => {
|
|
402
|
+
const plaintext = "Hello, World!";
|
|
403
|
+
const specialPassword = "!@#$%^&*()_+-=[]{}|;':\",./<>?";
|
|
404
|
+
|
|
405
|
+
const encrypted = encrypt(plaintext, specialPassword);
|
|
406
|
+
const decrypted = decrypt(encrypted, specialPassword);
|
|
407
|
+
|
|
408
|
+
expect(decrypted).to.equal(plaintext);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("should handle binary data as string", () => {
|
|
412
|
+
const plaintext = Buffer.from([0x00, 0x01, 0x02, 0x03, 0x04]).toString(
|
|
413
|
+
"utf8"
|
|
414
|
+
);
|
|
415
|
+
const password = "my-secret-password";
|
|
416
|
+
|
|
417
|
+
const encrypted = encrypt(plaintext, password);
|
|
418
|
+
const decrypted = decrypt(encrypted, password);
|
|
419
|
+
|
|
420
|
+
expect(decrypted).to.equal(plaintext);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
randomBytes,
|
|
3
|
+
pbkdf2Sync,
|
|
4
|
+
createCipheriv,
|
|
5
|
+
createDecipheriv,
|
|
6
|
+
} from "crypto";
|
|
7
|
+
|
|
8
|
+
const PBKDF2_ITERATIONS = 310_000; // adjust upward over time
|
|
9
|
+
const KEY_LEN = 32; // 256-bit AES key
|
|
10
|
+
const IV_LEN = 12; // standard for GCM
|
|
11
|
+
const SALT_LEN = 16; // at least 16 bytes
|
|
12
|
+
|
|
13
|
+
interface EncryptedPayload {
|
|
14
|
+
iv: string; // hex
|
|
15
|
+
salt: string; // hex
|
|
16
|
+
ciphertext: string;
|
|
17
|
+
tag: string; // auth tag (hex)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface EncryptedPayload2FA {
|
|
21
|
+
iv: string;
|
|
22
|
+
saltUser: string;
|
|
23
|
+
saltServer: string;
|
|
24
|
+
ciphertext: string;
|
|
25
|
+
tag: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Encrypt a UTF-8 string using password-based AES-256-GCM
|
|
30
|
+
*/
|
|
31
|
+
export function encrypt(text: string, password: string): EncryptedPayload {
|
|
32
|
+
const salt = randomBytes(SALT_LEN);
|
|
33
|
+
const key = pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LEN, "sha256");
|
|
34
|
+
|
|
35
|
+
const iv = randomBytes(IV_LEN);
|
|
36
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
37
|
+
|
|
38
|
+
const ciphertext = Buffer.concat([
|
|
39
|
+
cipher.update(text, "utf8"),
|
|
40
|
+
cipher.final(),
|
|
41
|
+
]);
|
|
42
|
+
const tag = cipher.getAuthTag();
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
iv: iv.toString("hex"),
|
|
46
|
+
salt: salt.toString("hex"),
|
|
47
|
+
ciphertext: ciphertext.toString("hex"),
|
|
48
|
+
tag: tag.toString("hex"),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Decrypt payload produced by `encrypt`
|
|
54
|
+
*/
|
|
55
|
+
export function decrypt(payload: EncryptedPayload, password: string): string {
|
|
56
|
+
const { iv, salt, ciphertext, tag } = payload;
|
|
57
|
+
|
|
58
|
+
const key = pbkdf2Sync(
|
|
59
|
+
Buffer.from(password),
|
|
60
|
+
Buffer.from(salt, "hex"),
|
|
61
|
+
PBKDF2_ITERATIONS,
|
|
62
|
+
KEY_LEN,
|
|
63
|
+
"sha256"
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "hex"));
|
|
67
|
+
decipher.setAuthTag(Buffer.from(tag, "hex"));
|
|
68
|
+
|
|
69
|
+
const plaintext = Buffer.concat([
|
|
70
|
+
decipher.update(Buffer.from(ciphertext, "hex")),
|
|
71
|
+
decipher.final(),
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
return plaintext.toString("utf8");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function deriveKey(password: string, salt: Buffer): Buffer {
|
|
78
|
+
return pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LEN, "sha256");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function encryptWithTwoFactors(
|
|
82
|
+
plaintext: string,
|
|
83
|
+
userPassword: string,
|
|
84
|
+
serverSecret: string
|
|
85
|
+
): EncryptedPayload2FA {
|
|
86
|
+
const saltUser = randomBytes(SALT_LEN);
|
|
87
|
+
const saltServer = randomBytes(SALT_LEN);
|
|
88
|
+
|
|
89
|
+
const userKey = deriveKey(userPassword, saltUser);
|
|
90
|
+
const serverKey = deriveKey(serverSecret, saltServer);
|
|
91
|
+
|
|
92
|
+
// Final AES key: XOR of both keys
|
|
93
|
+
const finalKey = Buffer.alloc(KEY_LEN);
|
|
94
|
+
for (let i = 0; i < KEY_LEN; i++) {
|
|
95
|
+
finalKey[i] = userKey[i] ^ serverKey[i];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const iv = randomBytes(IV_LEN);
|
|
99
|
+
const cipher = createCipheriv("aes-256-gcm", finalKey, iv);
|
|
100
|
+
|
|
101
|
+
const ciphertext = Buffer.concat([
|
|
102
|
+
cipher.update(plaintext, "utf8"),
|
|
103
|
+
cipher.final(),
|
|
104
|
+
]);
|
|
105
|
+
const tag = cipher.getAuthTag();
|
|
106
|
+
|
|
107
|
+
// Cleanup sensitive buffers
|
|
108
|
+
userKey.fill(0);
|
|
109
|
+
serverKey.fill(0);
|
|
110
|
+
finalKey.fill(0);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
iv: iv.toString("hex"),
|
|
114
|
+
saltUser: saltUser.toString("hex"),
|
|
115
|
+
saltServer: saltServer.toString("hex"),
|
|
116
|
+
ciphertext: ciphertext.toString("hex"),
|
|
117
|
+
tag: tag.toString("hex"),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function decryptWithTwoFactors(
|
|
122
|
+
payload: EncryptedPayload2FA,
|
|
123
|
+
userPassword: string,
|
|
124
|
+
serverSecret: string
|
|
125
|
+
): string {
|
|
126
|
+
const userKey = deriveKey(userPassword, Buffer.from(payload.saltUser, "hex"));
|
|
127
|
+
const serverKey = deriveKey(
|
|
128
|
+
serverSecret,
|
|
129
|
+
Buffer.from(payload.saltServer, "hex")
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const finalKey = Buffer.alloc(KEY_LEN);
|
|
133
|
+
for (let i = 0; i < KEY_LEN; i++) {
|
|
134
|
+
finalKey[i] = userKey[i] ^ serverKey[i];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const decipher = createDecipheriv(
|
|
138
|
+
"aes-256-gcm",
|
|
139
|
+
finalKey,
|
|
140
|
+
Buffer.from(payload.iv, "hex")
|
|
141
|
+
);
|
|
142
|
+
decipher.setAuthTag(Buffer.from(payload.tag, "hex"));
|
|
143
|
+
|
|
144
|
+
const plaintext = Buffer.concat([
|
|
145
|
+
decipher.update(Buffer.from(payload.ciphertext, "hex")),
|
|
146
|
+
decipher.final(),
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
// Cleanup sensitive buffers
|
|
150
|
+
userKey.fill(0);
|
|
151
|
+
serverKey.fill(0);
|
|
152
|
+
finalKey.fill(0);
|
|
153
|
+
|
|
154
|
+
return plaintext.toString("utf8");
|
|
155
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
|
|
3
|
+
import { FILTER_OPERATOR } from "../constants";
|
|
4
|
+
import { IFilter } from "../interfaces";
|
|
5
|
+
import { toSimpleMongooseFilter } from "./filter-helper";
|
|
6
|
+
|
|
7
|
+
describe("filter helper", () => {
|
|
8
|
+
describe("simple mongoose filter", () => {
|
|
9
|
+
it("should parse simple equal filters correctly", () => {
|
|
10
|
+
const filters: IFilter[] = [
|
|
11
|
+
{
|
|
12
|
+
field: "name",
|
|
13
|
+
operator: FILTER_OPERATOR.EQUAL,
|
|
14
|
+
value: "andy",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
field: "email",
|
|
18
|
+
operator: FILTER_OPERATOR.EQUAL,
|
|
19
|
+
value: "andy@gmail.com",
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const parsedFilter = toSimpleMongooseFilter(filters);
|
|
24
|
+
|
|
25
|
+
expect(parsedFilter.name).to.be.equal("andy");
|
|
26
|
+
expect(parsedFilter.email).to.be.equal("andy@gmail.com");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should parse more complex filters correctly", () => {
|
|
30
|
+
const filters: IFilter[] = [
|
|
31
|
+
{
|
|
32
|
+
field: "name",
|
|
33
|
+
operator: FILTER_OPERATOR.INS_STARTS_WITH,
|
|
34
|
+
value: "andy",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
field: "email",
|
|
38
|
+
operator: FILTER_OPERATOR.INS_ENDS_WITH,
|
|
39
|
+
value: "andy@gmail.com",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
field: "age",
|
|
43
|
+
operator: FILTER_OPERATOR.IN,
|
|
44
|
+
value: [20, 30, 40],
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const parsedFilter = toSimpleMongooseFilter(filters);
|
|
49
|
+
|
|
50
|
+
expect(parsedFilter.name).to.be.deep.equal({
|
|
51
|
+
$regex: "^andy.*$",
|
|
52
|
+
$options: "i",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(parsedFilter.email).to.be.deep.equal({
|
|
56
|
+
$regex: ".*andy@gmail.com$",
|
|
57
|
+
$options: "i",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(parsedFilter.age).to.be.deep.equal([20, 30, 40]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should parse OR operator correctly", () => {
|
|
64
|
+
const filters: IFilter[] = [
|
|
65
|
+
{
|
|
66
|
+
field: null,
|
|
67
|
+
operator: FILTER_OPERATOR.OR,
|
|
68
|
+
value: [
|
|
69
|
+
{
|
|
70
|
+
field: "name",
|
|
71
|
+
operator: FILTER_OPERATOR.EQUAL,
|
|
72
|
+
value: "andy",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
field: "name",
|
|
76
|
+
operator: FILTER_OPERATOR.EQUAL,
|
|
77
|
+
value: "devstic",
|
|
78
|
+
},
|
|
79
|
+
] as IFilter[],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
field: "email",
|
|
83
|
+
operator: FILTER_OPERATOR.INS_LIKE,
|
|
84
|
+
value: "andy@gmail.com",
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const parsedFilter = toSimpleMongooseFilter(filters);
|
|
89
|
+
|
|
90
|
+
expect(parsedFilter.$or).to.be.deep.equal([
|
|
91
|
+
{
|
|
92
|
+
name: "andy",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "devstic",
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
expect(parsedFilter.email).to.be.deep.equal({
|
|
100
|
+
$regex: ".*andy@gmail.com.*",
|
|
101
|
+
$options: "i",
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|