@aj-archipelago/cortex 1.4.1 β 1.4.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/lib/crypto.js +1 -65
- package/lib/keyValueStorageClient.js +53 -1
- package/package.json +1 -1
- package/pathways/system/entity/memory/sys_read_memory.js +1 -1
- package/pathways/system/entity/memory/sys_save_memory.js +1 -1
- package/pathways/system/entity/memory/sys_search_memory.js +1 -1
- package/server/pathwayResolver.js +1 -1
- package/testrun.log +35371 -0
- package/tests/integration/graphql/async/stream/vendors/openai_streaming.test.js +1 -3
- package/tests/unit/core/crypto.test.js +4 -102
- package/tests/unit/core/doubleEncryptionStorageClient.test.js +262 -0
- package/lib/doubleEncryptionStorageClient.js +0 -97
|
@@ -52,7 +52,6 @@ test('OpenAI vendor streaming over subscriptions emits OAI-style deltas', async
|
|
|
52
52
|
|
|
53
53
|
t.true(events.length > 0);
|
|
54
54
|
|
|
55
|
-
// Ensure streamed chunks (when they include model) use gpt-4.1 (not mini)
|
|
56
55
|
const models = events
|
|
57
56
|
.map(e => {
|
|
58
57
|
try {
|
|
@@ -64,8 +63,7 @@ test('OpenAI vendor streaming over subscriptions emits OAI-style deltas', async
|
|
|
64
63
|
.filter(Boolean);
|
|
65
64
|
|
|
66
65
|
if (models.length > 0) {
|
|
67
|
-
t.truthy(models.find(m => /gpt-
|
|
68
|
-
t.falsy(models.find(m => /gpt-4\.1-mini/.test(m)));
|
|
66
|
+
t.truthy(models.find(m => /gpt-5-chat/.test(m)));
|
|
69
67
|
}
|
|
70
68
|
});
|
|
71
69
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Tests for encryption and decryption functions in cortex/lib/crypto.js
|
|
3
3
|
|
|
4
4
|
import test from 'ava';
|
|
5
|
-
import { encrypt, decrypt
|
|
5
|
+
import { encrypt, decrypt } from '../../../lib/crypto.js';
|
|
6
6
|
|
|
7
7
|
// Test data
|
|
8
8
|
const testData = 'Hello, this is test data!';
|
|
@@ -35,85 +35,6 @@ test('decrypt should return original data when no key provided', t => {
|
|
|
35
35
|
t.is(result, testData);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
// Double encryption tests
|
|
39
|
-
test('doubleEncrypt should encrypt with system key only when no user key', t => {
|
|
40
|
-
const encrypted = doubleEncrypt(testData, null, systemKey);
|
|
41
|
-
t.truthy(encrypted);
|
|
42
|
-
t.not(encrypted, testData);
|
|
43
|
-
|
|
44
|
-
// Should be decryptable with system key only
|
|
45
|
-
const decrypted = decrypt(encrypted, systemKey);
|
|
46
|
-
t.is(decrypted, testData);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('doubleEncrypt should encrypt with both keys when user key provided', t => {
|
|
50
|
-
const encrypted = doubleEncrypt(testData, userKey, systemKey);
|
|
51
|
-
t.truthy(encrypted);
|
|
52
|
-
t.not(encrypted, testData);
|
|
53
|
-
|
|
54
|
-
// Should NOT be decryptable with system key only (it will be user-encrypted data)
|
|
55
|
-
const systemOnlyDecrypted = decrypt(encrypted, systemKey);
|
|
56
|
-
t.not(systemOnlyDecrypted, testData);
|
|
57
|
-
t.truthy(systemOnlyDecrypted); // Should return user-encrypted data, not null
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('doubleEncrypt should fail when no system key provided', t => {
|
|
61
|
-
const encrypted = doubleEncrypt(testData, userKey, null);
|
|
62
|
-
t.is(encrypted, null);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('doubleEncrypt should fallback to system key when user encryption fails', t => {
|
|
66
|
-
// Use invalid user key to force fallback
|
|
67
|
-
const invalidUserKey = 'invalid';
|
|
68
|
-
const encrypted = doubleEncrypt(testData, invalidUserKey, systemKey);
|
|
69
|
-
t.truthy(encrypted);
|
|
70
|
-
|
|
71
|
-
// Should be decryptable with system key only (fallback behavior)
|
|
72
|
-
const decrypted = decrypt(encrypted, systemKey);
|
|
73
|
-
t.is(decrypted, testData);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Double decryption tests
|
|
77
|
-
test('doubleDecrypt should decrypt with system key only when no user key', t => {
|
|
78
|
-
const encrypted = encrypt(testData, systemKey);
|
|
79
|
-
const decrypted = doubleDecrypt(encrypted, null, systemKey);
|
|
80
|
-
t.is(decrypted, testData);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('doubleDecrypt should decrypt double-encrypted data with both keys', t => {
|
|
84
|
-
const encrypted = doubleEncrypt(testData, userKey, systemKey);
|
|
85
|
-
const decrypted = doubleDecrypt(encrypted, userKey, systemKey);
|
|
86
|
-
t.is(decrypted, testData);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('doubleDecrypt should handle single-encrypted data when user key provided', t => {
|
|
90
|
-
// This is the key scenario we fixed!
|
|
91
|
-
const singleEncrypted = encrypt(testData, systemKey);
|
|
92
|
-
const decrypted = doubleDecrypt(singleEncrypted, userKey, systemKey);
|
|
93
|
-
t.is(decrypted, testData);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('doubleDecrypt should fail when no system key provided', t => {
|
|
97
|
-
const encrypted = encrypt(testData, systemKey);
|
|
98
|
-
const decrypted = doubleDecrypt(encrypted, userKey, null);
|
|
99
|
-
t.is(decrypted, null);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('doubleDecrypt should fail when system decryption fails', t => {
|
|
103
|
-
const encrypted = encrypt(testData, systemKey);
|
|
104
|
-
const wrongSystemKey = 'wrongkey123456789012345678901234567890123456789012345678901234567890';
|
|
105
|
-
const decrypted = doubleDecrypt(encrypted, userKey, wrongSystemKey);
|
|
106
|
-
t.is(decrypted, null);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('doubleDecrypt should return system-decrypted data when user decryption fails for double-encrypted data', t => {
|
|
110
|
-
const encrypted = doubleEncrypt(testData, userKey, systemKey);
|
|
111
|
-
const decrypted = doubleDecrypt(encrypted, wrongUserKey, systemKey);
|
|
112
|
-
// Should return the user-encrypted data (system decryption succeeded, user decryption failed)
|
|
113
|
-
t.not(decrypted, testData); // Should not be the original data
|
|
114
|
-
t.truthy(decrypted); // Should return some data (user-encrypted)
|
|
115
|
-
});
|
|
116
|
-
|
|
117
38
|
// Edge cases and error handling
|
|
118
39
|
test('encrypt should handle empty string', t => {
|
|
119
40
|
const encrypted = encrypt('', systemKey);
|
|
@@ -136,28 +57,9 @@ test('encrypt should handle unicode characters', t => {
|
|
|
136
57
|
t.is(decrypted, unicodeData);
|
|
137
58
|
});
|
|
138
59
|
|
|
139
|
-
test('
|
|
60
|
+
test('encrypt should handle JSON data', t => {
|
|
140
61
|
const jsonData = JSON.stringify({ message: 'test', number: 42, array: [1, 2, 3] });
|
|
141
|
-
const encrypted =
|
|
142
|
-
const decrypted =
|
|
62
|
+
const encrypted = encrypt(jsonData, systemKey);
|
|
63
|
+
const decrypted = decrypt(encrypted, systemKey);
|
|
143
64
|
t.is(decrypted, jsonData);
|
|
144
65
|
});
|
|
145
|
-
|
|
146
|
-
// Integration test for the specific scenario we fixed
|
|
147
|
-
test('CRITICAL: doubleDecrypt should handle mixed encryption states', t => {
|
|
148
|
-
// Simulate a migration scenario where some data is single-encrypted
|
|
149
|
-
// and some is double-encrypted, but we always pass both keys
|
|
150
|
-
|
|
151
|
-
// Single-encrypted data (old format)
|
|
152
|
-
const singleEncrypted = encrypt(testData, systemKey);
|
|
153
|
-
|
|
154
|
-
// Double-encrypted data (new format)
|
|
155
|
-
const doubleEncrypted = doubleEncrypt(testData, userKey, systemKey);
|
|
156
|
-
|
|
157
|
-
// Both should be readable with both keys provided
|
|
158
|
-
const singleDecrypted = doubleDecrypt(singleEncrypted, userKey, systemKey);
|
|
159
|
-
const doubleDecrypted = doubleDecrypt(doubleEncrypted, userKey, systemKey);
|
|
160
|
-
|
|
161
|
-
t.is(singleDecrypted, testData, 'Single-encrypted data should be readable with both keys');
|
|
162
|
-
t.is(doubleDecrypted, testData, 'Double-encrypted data should be readable with both keys');
|
|
163
|
-
});
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// doubleEncryptionStorageClient.test.js
|
|
2
|
+
// Tests for context key encryption integration in keyValueStorageClient
|
|
3
|
+
|
|
4
|
+
import test from 'ava';
|
|
5
|
+
import { setvWithDoubleEncryption, getvWithDoubleDecryption, setv, getv } from '../../../lib/keyValueStorageClient.js';
|
|
6
|
+
import { encrypt, decrypt } from '../../../lib/crypto.js';
|
|
7
|
+
|
|
8
|
+
// Test data
|
|
9
|
+
const testData = { message: 'Hello, this is test data!', number: 42, array: [1, 2, 3] };
|
|
10
|
+
const systemKey = '1234567890123456789012345678901234567890123456789012345678901234'; // 64 hex chars
|
|
11
|
+
const userKey = 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'; // 64 hex chars
|
|
12
|
+
|
|
13
|
+
// Mock the config to provide test keys
|
|
14
|
+
import { config } from '../../../config.js';
|
|
15
|
+
const originalGet = config.get;
|
|
16
|
+
const mockGet = (key) => {
|
|
17
|
+
switch (key) {
|
|
18
|
+
case 'storageConnectionString':
|
|
19
|
+
return 'redis://localhost:6379'; // Use in-memory Redis for tests
|
|
20
|
+
case 'cortexId':
|
|
21
|
+
return 'test-cortex';
|
|
22
|
+
case 'redisEncryptionKey':
|
|
23
|
+
return systemKey;
|
|
24
|
+
default:
|
|
25
|
+
return originalGet(key);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
config.get = mockGet;
|
|
29
|
+
|
|
30
|
+
// Helper function to clear storage between tests
|
|
31
|
+
async function clearStorage() {
|
|
32
|
+
// Clear any existing test data
|
|
33
|
+
try {
|
|
34
|
+
await setvWithDoubleEncryption('test-key', null, userKey);
|
|
35
|
+
await setv('test-key', null);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Ignore errors when clearing
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test.beforeEach(async t => {
|
|
42
|
+
await clearStorage();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Test 1: Double encryption/decryption with both contextKey and redisEncryptionKey
|
|
46
|
+
test('should store and retrieve data with double encryption when both keys provided', async t => {
|
|
47
|
+
const key = 'test-double-encryption';
|
|
48
|
+
|
|
49
|
+
// Store with double encryption
|
|
50
|
+
await setvWithDoubleEncryption(key, testData, userKey);
|
|
51
|
+
|
|
52
|
+
// Retrieve with double decryption
|
|
53
|
+
const retrieved = await getvWithDoubleDecryption(key, userKey);
|
|
54
|
+
|
|
55
|
+
t.deepEqual(retrieved, testData);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Test 2: Double encryption/decryption with only redisEncryptionKey (no contextKey)
|
|
59
|
+
test('should store and retrieve data with single encryption when no contextKey provided', async t => {
|
|
60
|
+
const key = 'test-single-encryption';
|
|
61
|
+
|
|
62
|
+
// Store with single encryption (no contextKey)
|
|
63
|
+
await setvWithDoubleEncryption(key, testData, null);
|
|
64
|
+
|
|
65
|
+
// Retrieve with single decryption
|
|
66
|
+
const retrieved = await getvWithDoubleDecryption(key, null);
|
|
67
|
+
|
|
68
|
+
t.deepEqual(retrieved, testData);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Test 3: Reading single-encrypted data (from keyValueStorageClient) with doubleDecryption
|
|
72
|
+
test('should read single-encrypted data from keyValueStorageClient with doubleDecryption', async t => {
|
|
73
|
+
const key = 'test-single-to-double';
|
|
74
|
+
|
|
75
|
+
// Store using keyValueStorageClient (single encryption)
|
|
76
|
+
await setv(key, testData);
|
|
77
|
+
|
|
78
|
+
// Read using doubleEncryptionStorageClient (should handle single-encrypted data)
|
|
79
|
+
const retrieved = await getvWithDoubleDecryption(key, userKey);
|
|
80
|
+
|
|
81
|
+
t.deepEqual(retrieved, testData);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Test 4: Reading single-encrypted data without contextKey
|
|
85
|
+
test('should read single-encrypted data from keyValueStorageClient without contextKey', async t => {
|
|
86
|
+
const key = 'test-single-to-double-no-context';
|
|
87
|
+
|
|
88
|
+
// Store using keyValueStorageClient (single encryption)
|
|
89
|
+
await setv(key, testData);
|
|
90
|
+
|
|
91
|
+
// Read using doubleEncryptionStorageClient without contextKey
|
|
92
|
+
const retrieved = await getvWithDoubleDecryption(key, null);
|
|
93
|
+
|
|
94
|
+
t.deepEqual(retrieved, testData);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Test 5: Reading unencrypted data with doubleDecryption
|
|
98
|
+
test('should read unencrypted data with doubleDecryption', async t => {
|
|
99
|
+
const key = 'test-unencrypted';
|
|
100
|
+
|
|
101
|
+
// Store unencrypted data using keyValueStorageClient with no encryption key
|
|
102
|
+
const originalRedisKey = config.get('redisEncryptionKey');
|
|
103
|
+
config.get = (key) => {
|
|
104
|
+
switch (key) {
|
|
105
|
+
case 'storageConnectionString':
|
|
106
|
+
return 'redis://localhost:6379';
|
|
107
|
+
case 'cortexId':
|
|
108
|
+
return 'test-cortex';
|
|
109
|
+
case 'redisEncryptionKey':
|
|
110
|
+
return null; // No encryption
|
|
111
|
+
default:
|
|
112
|
+
return originalGet(key);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Store unencrypted data
|
|
117
|
+
await setv(key, testData);
|
|
118
|
+
|
|
119
|
+
// Restore mock config
|
|
120
|
+
config.get = mockGet;
|
|
121
|
+
|
|
122
|
+
// Read using doubleEncryptionStorageClient (should handle unencrypted data)
|
|
123
|
+
const retrieved = await getvWithDoubleDecryption(key, userKey);
|
|
124
|
+
|
|
125
|
+
t.deepEqual(retrieved, testData);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Test 6: Reading unencrypted data without contextKey
|
|
129
|
+
test('should read unencrypted data without contextKey', async t => {
|
|
130
|
+
const key = 'test-unencrypted-no-context';
|
|
131
|
+
|
|
132
|
+
// Store unencrypted data using keyValueStorageClient with no encryption key
|
|
133
|
+
const originalRedisKey = config.get('redisEncryptionKey');
|
|
134
|
+
config.get = (key) => {
|
|
135
|
+
switch (key) {
|
|
136
|
+
case 'storageConnectionString':
|
|
137
|
+
return 'redis://localhost:6379';
|
|
138
|
+
case 'cortexId':
|
|
139
|
+
return 'test-cortex';
|
|
140
|
+
case 'redisEncryptionKey':
|
|
141
|
+
return null; // No encryption
|
|
142
|
+
default:
|
|
143
|
+
return originalGet(key);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Store unencrypted data
|
|
148
|
+
await setv(key, testData);
|
|
149
|
+
|
|
150
|
+
// Restore mock config
|
|
151
|
+
config.get = mockGet;
|
|
152
|
+
|
|
153
|
+
// Read using doubleEncryptionStorageClient without contextKey
|
|
154
|
+
const retrieved = await getvWithDoubleDecryption(key, null);
|
|
155
|
+
|
|
156
|
+
t.deepEqual(retrieved, testData);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Test 7: Mixed data types - some double-encrypted, some single-encrypted
|
|
160
|
+
test('should handle mixed encryption states in storage', async t => {
|
|
161
|
+
const doubleKey = 'test-mixed-double';
|
|
162
|
+
const singleKey = 'test-mixed-single';
|
|
163
|
+
|
|
164
|
+
// Store with different encryption methods
|
|
165
|
+
await setvWithDoubleEncryption(doubleKey, testData, userKey); // Double encrypted
|
|
166
|
+
await setv(singleKey, testData); // Single encrypted
|
|
167
|
+
|
|
168
|
+
// Both should be readable with doubleDecryption
|
|
169
|
+
const doubleRetrieved = await getvWithDoubleDecryption(doubleKey, userKey);
|
|
170
|
+
const singleRetrieved = await getvWithDoubleDecryption(singleKey, userKey);
|
|
171
|
+
|
|
172
|
+
t.deepEqual(doubleRetrieved, testData, 'Double-encrypted data should be readable');
|
|
173
|
+
t.deepEqual(singleRetrieved, testData, 'Single-encrypted data should be readable');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Test 8: Edge case - null/undefined data handling
|
|
177
|
+
test('should handle null and undefined data gracefully', async t => {
|
|
178
|
+
const key1 = 'test-null-data';
|
|
179
|
+
const key2 = 'test-undefined-data';
|
|
180
|
+
|
|
181
|
+
// Store null data
|
|
182
|
+
await setvWithDoubleEncryption(key1, null, userKey);
|
|
183
|
+
const retrieved1 = await getvWithDoubleDecryption(key1, userKey);
|
|
184
|
+
t.is(retrieved1, null);
|
|
185
|
+
|
|
186
|
+
// Store undefined data
|
|
187
|
+
await setvWithDoubleEncryption(key2, undefined, userKey);
|
|
188
|
+
const retrieved2 = await getvWithDoubleDecryption(key2, userKey);
|
|
189
|
+
t.is(retrieved2, undefined);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Test 9: Edge case - empty object handling
|
|
193
|
+
test('should handle empty objects', async t => {
|
|
194
|
+
const key = 'test-empty-object';
|
|
195
|
+
const emptyData = {};
|
|
196
|
+
|
|
197
|
+
await setvWithDoubleEncryption(key, emptyData, userKey);
|
|
198
|
+
const retrieved = await getvWithDoubleDecryption(key, userKey);
|
|
199
|
+
|
|
200
|
+
t.deepEqual(retrieved, emptyData);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Test 10: Context key changes between operations
|
|
204
|
+
test('should handle context key changes between operations', async t => {
|
|
205
|
+
const key1 = 'test-context-change-1';
|
|
206
|
+
const key2 = 'test-context-change-2';
|
|
207
|
+
const newUserKey = '1111111111111111111111111111111111111111111111111111111111111111';
|
|
208
|
+
|
|
209
|
+
// Store with first context key
|
|
210
|
+
await setvWithDoubleEncryption(key1, testData, userKey);
|
|
211
|
+
|
|
212
|
+
// Retrieve with same context key
|
|
213
|
+
const retrieved1 = await getvWithDoubleDecryption(key1, userKey);
|
|
214
|
+
t.deepEqual(retrieved1, testData);
|
|
215
|
+
|
|
216
|
+
// Store with different context key (different key to avoid conflicts)
|
|
217
|
+
await setvWithDoubleEncryption(key2, testData, newUserKey);
|
|
218
|
+
|
|
219
|
+
// Retrieve with new context key
|
|
220
|
+
const retrieved2 = await getvWithDoubleDecryption(key2, newUserKey);
|
|
221
|
+
t.deepEqual(retrieved2, testData);
|
|
222
|
+
|
|
223
|
+
// Verify that data encrypted with one key cannot be decrypted with another
|
|
224
|
+
const wrongKeyRetrieved = await getvWithDoubleDecryption(key1, newUserKey);
|
|
225
|
+
t.notDeepEqual(wrongKeyRetrieved, testData, 'Data encrypted with one key should not be readable with different key');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Test 11: Large data handling
|
|
229
|
+
test('should handle large data objects', async t => {
|
|
230
|
+
const key = 'test-large-data';
|
|
231
|
+
const largeData = {
|
|
232
|
+
message: 'Large data test',
|
|
233
|
+
array: Array(1000).fill(0).map((_, i) => i),
|
|
234
|
+
nested: {
|
|
235
|
+
level1: {
|
|
236
|
+
level2: {
|
|
237
|
+
level3: Array(100).fill('test')
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
await setvWithDoubleEncryption(key, largeData, userKey);
|
|
244
|
+
const retrieved = await getvWithDoubleDecryption(key, userKey);
|
|
245
|
+
|
|
246
|
+
t.deepEqual(retrieved, largeData);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Test 12: Special characters and unicode
|
|
250
|
+
test('should handle special characters and unicode', async t => {
|
|
251
|
+
const key = 'test-special-chars';
|
|
252
|
+
const specialData = {
|
|
253
|
+
message: 'Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?',
|
|
254
|
+
unicode: 'Unicode: π π ñÑéΓΓ³ΓΊ δΈζ Ψ§ΩΨΉΨ±Ψ¨ΩΨ©',
|
|
255
|
+
emoji: 'ππππ€£πππ
πππ'
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
await setvWithDoubleEncryption(key, specialData, userKey);
|
|
259
|
+
const retrieved = await getvWithDoubleDecryption(key, userKey);
|
|
260
|
+
|
|
261
|
+
t.deepEqual(retrieved, specialData);
|
|
262
|
+
});
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import Keyv from 'keyv';
|
|
2
|
-
import { config } from '../config.js';
|
|
3
|
-
import { encrypt, decrypt, doubleEncrypt, doubleDecrypt } from './crypto.js';
|
|
4
|
-
import logger from './logger.js';
|
|
5
|
-
|
|
6
|
-
const storageConnectionString = config.get('storageConnectionString');
|
|
7
|
-
const cortexId = config.get('cortexId');
|
|
8
|
-
const redisEncryptionKey = config.get('redisEncryptionKey');
|
|
9
|
-
|
|
10
|
-
// Create a keyv client to store data with double encryption support
|
|
11
|
-
const doubleEncryptionStorageClient = new Keyv(storageConnectionString, {
|
|
12
|
-
ssl: true,
|
|
13
|
-
abortConnect: false,
|
|
14
|
-
serialize: (data) => JSON.stringify(data),
|
|
15
|
-
deserialize: (data) => {
|
|
16
|
-
try {
|
|
17
|
-
return JSON.parse(data);
|
|
18
|
-
} catch (error) {
|
|
19
|
-
logger.error(`Failed to parse stored data: ${error}`);
|
|
20
|
-
return {};
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
namespace: `${cortexId}-cortex-context`
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// Handle Redis connection errors to prevent crashes
|
|
27
|
-
doubleEncryptionStorageClient.on('error', (error) => {
|
|
28
|
-
logger.error(`Keyv Redis connection error: ${error}`);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Set values with double encryption support
|
|
32
|
-
async function setvWithDoubleEncryption(key, value, userContextKey) {
|
|
33
|
-
if (!doubleEncryptionStorageClient) return false;
|
|
34
|
-
|
|
35
|
-
// Validate that system key is present
|
|
36
|
-
if (!redisEncryptionKey) {
|
|
37
|
-
logger.error('System encryption key is required but not configured');
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
// Always use doubleEncrypt which handles both scenarios:
|
|
43
|
-
// 1. systemKey only (when userContextKey is null/undefined)
|
|
44
|
-
// 2. userContextKey + systemKey (when userContextKey is provided)
|
|
45
|
-
const encryptedValue = doubleEncrypt(JSON.stringify(value), userContextKey, redisEncryptionKey);
|
|
46
|
-
|
|
47
|
-
if (!encryptedValue) {
|
|
48
|
-
logger.error('Encryption failed, cannot store data');
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return await doubleEncryptionStorageClient.set(key, encryptedValue);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
logger.error(`Failed to store data with double encryption: ${error}`);
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Get values with double decryption support
|
|
60
|
-
async function getvWithDoubleDecryption(key, userContextKey) {
|
|
61
|
-
if (!doubleEncryptionStorageClient) return null;
|
|
62
|
-
|
|
63
|
-
// Validate that system key is present
|
|
64
|
-
if (!redisEncryptionKey) {
|
|
65
|
-
logger.error('System encryption key is required but not configured');
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const encryptedData = await doubleEncryptionStorageClient.get(key);
|
|
71
|
-
if (!encryptedData) return null;
|
|
72
|
-
|
|
73
|
-
// Always use doubleDecrypt which handles both scenarios:
|
|
74
|
-
// 1. systemKey only (when userContextKey is null/undefined)
|
|
75
|
-
// 2. userContextKey + systemKey (when userContextKey is provided)
|
|
76
|
-
const decryptedData = doubleDecrypt(encryptedData, userContextKey, redisEncryptionKey);
|
|
77
|
-
if (decryptedData) {
|
|
78
|
-
return JSON.parse(decryptedData);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// If decryption fails, try to parse as plain JSON (for backward compatibility with unencrypted data)
|
|
82
|
-
try {
|
|
83
|
-
return JSON.parse(encryptedData);
|
|
84
|
-
} catch {
|
|
85
|
-
return encryptedData;
|
|
86
|
-
}
|
|
87
|
-
} catch (error) {
|
|
88
|
-
logger.error(`Failed to retrieve and decrypt data: ${error}`);
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export {
|
|
94
|
-
doubleEncryptionStorageClient,
|
|
95
|
-
setvWithDoubleEncryption,
|
|
96
|
-
getvWithDoubleDecryption
|
|
97
|
-
};
|