@flowerforce/flowerbase 1.7.6-beta.0 → 1.7.6-beta.1
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/README.md +125 -1
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +3 -8
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +11 -1
- package/dist/features/encryption/interface.d.ts +36 -0
- package/dist/features/encryption/interface.d.ts.map +1 -0
- package/dist/features/encryption/interface.js +2 -0
- package/dist/features/encryption/utils.d.ts +9 -0
- package/dist/features/encryption/utils.d.ts.map +1 -0
- package/dist/features/encryption/utils.js +34 -0
- package/dist/features/rules/utils.d.ts.map +1 -1
- package/dist/features/rules/utils.js +1 -11
- package/dist/features/triggers/index.d.ts.map +1 -1
- package/dist/features/triggers/index.js +4 -0
- package/dist/features/triggers/utils.d.ts.map +1 -1
- package/dist/features/triggers/utils.js +30 -38
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/monitoring/plugin.d.ts.map +1 -1
- package/dist/monitoring/plugin.js +31 -0
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +9 -7
- package/dist/services/mongodb-atlas/model.d.ts +2 -1
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +14 -3
- package/dist/utils/initializer/mongodbCSFLE.d.ts +69 -0
- package/dist/utils/initializer/mongodbCSFLE.d.ts.map +1 -0
- package/dist/utils/initializer/mongodbCSFLE.js +131 -0
- package/dist/utils/initializer/registerPlugins.d.ts +5 -1
- package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
- package/dist/utils/initializer/registerPlugins.js +27 -5
- package/package.json +4 -2
- package/src/auth/providers/custom-function/controller.ts +4 -10
- package/src/constants.ts +11 -2
- package/src/features/encryption/interface.ts +46 -0
- package/src/features/encryption/utils.ts +22 -0
- package/src/features/rules/utils.ts +1 -11
- package/src/features/triggers/index.ts +5 -1
- package/src/features/triggers/utils.ts +31 -42
- package/src/index.ts +10 -2
- package/src/monitoring/plugin.ts +33 -0
- package/src/monitoring/ui.collections.js +7 -10
- package/src/monitoring/ui.css +378 -0
- package/src/monitoring/ui.endpoints.js +5 -10
- package/src/monitoring/ui.events.js +2 -4
- package/src/monitoring/ui.functions.js +64 -71
- package/src/monitoring/ui.html +8 -0
- package/src/monitoring/ui.js +189 -0
- package/src/monitoring/ui.shared.js +237 -2
- package/src/monitoring/ui.triggers.js +2 -3
- package/src/monitoring/ui.users.js +5 -9
- package/src/services/mongodb-atlas/index.ts +10 -13
- package/src/services/mongodb-atlas/model.ts +3 -1
- package/src/types/fastify-raw-body.d.ts +0 -9
- package/src/utils/__tests__/mongodbCSFLE.test.ts +105 -0
- package/src/utils/index.ts +12 -1
- package/src/utils/initializer/mongodbCSFLE.ts +224 -0
- package/src/utils/initializer/registerPlugins.ts +45 -10
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
return output || ' ';
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
-
const
|
|
128
|
+
const highlightJsonText = (text) => {
|
|
129
129
|
if (!text) return ' ';
|
|
130
130
|
const regex = /"(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|(-?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?)|\btrue\b|\bfalse\b|\bnull\b/g;
|
|
131
131
|
let output = '';
|
|
@@ -150,6 +150,239 @@
|
|
|
150
150
|
return output || ' ';
|
|
151
151
|
};
|
|
152
152
|
|
|
153
|
+
const renderJsonPrimitive = (value) => {
|
|
154
|
+
if (typeof value === 'string') {
|
|
155
|
+
return '<span class="token string">' + escapeHtml(JSON.stringify(value)) + '</span>';
|
|
156
|
+
}
|
|
157
|
+
if (typeof value === 'number') {
|
|
158
|
+
return '<span class="token number">' + escapeHtml(String(value)) + '</span>';
|
|
159
|
+
}
|
|
160
|
+
if (typeof value === 'boolean' || value === null) {
|
|
161
|
+
return '<span class="token literal">' + escapeHtml(String(value)) + '</span>';
|
|
162
|
+
}
|
|
163
|
+
return '<span class="token literal">' + escapeHtml(safeStringify(value)) + '</span>';
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const renderJsonKey = (key) => {
|
|
167
|
+
return '<span class="token key">' + escapeHtml(JSON.stringify(key)) + '</span><span class="json-punct">: </span>';
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const renderJsonLine = (depth, content, className) => {
|
|
171
|
+
return '<span class="json-line ' + (className || '') + '" style="--json-depth:' + depth + ';">' + content + '</span>';
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const getJsonSummaryLabel = (value) => {
|
|
175
|
+
if (Array.isArray(value)) {
|
|
176
|
+
const size = value.length;
|
|
177
|
+
return size + ' item' + (size === 1 ? '' : 's');
|
|
178
|
+
}
|
|
179
|
+
const size = Object.keys(value || {}).length;
|
|
180
|
+
return size + ' key' + (size === 1 ? '' : 's');
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const renderJsonNode = (value, depth, keyHtml, withComma) => {
|
|
184
|
+
if (Array.isArray(value) || (value && typeof value === 'object')) {
|
|
185
|
+
const isArray = Array.isArray(value);
|
|
186
|
+
const items = isArray
|
|
187
|
+
? value.map((item, index) => ({ key: String(index), value: item }))
|
|
188
|
+
: Object.keys(value).map((key) => ({ key, value: value[key] }));
|
|
189
|
+
const openChar = isArray ? '[' : '{';
|
|
190
|
+
const closeChar = isArray ? ']' : '}';
|
|
191
|
+
if (!items.length) {
|
|
192
|
+
return renderJsonLine(
|
|
193
|
+
depth,
|
|
194
|
+
'<span class="json-toggle-spacer"></span>' +
|
|
195
|
+
(keyHtml || '') +
|
|
196
|
+
'<span class="json-brace">' + openChar + closeChar + '</span>' +
|
|
197
|
+
(withComma ? '<span class="json-punct">,</span>' : ''),
|
|
198
|
+
'json-single'
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
const children = items
|
|
202
|
+
.map((entry, index) =>
|
|
203
|
+
renderJsonNode(
|
|
204
|
+
entry.value,
|
|
205
|
+
depth + 1,
|
|
206
|
+
isArray ? '' : renderJsonKey(entry.key),
|
|
207
|
+
index < items.length - 1
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
.join('');
|
|
211
|
+
const summary = escapeHtml(getJsonSummaryLabel(value));
|
|
212
|
+
return (
|
|
213
|
+
'<span class="json-node">' +
|
|
214
|
+
renderJsonLine(
|
|
215
|
+
depth,
|
|
216
|
+
'<button type="button" class="json-toggle" data-json-toggle aria-expanded="true" title="Collapse">▾</button>' +
|
|
217
|
+
(keyHtml || '') +
|
|
218
|
+
'<span class="json-brace">' + openChar + '</span>' +
|
|
219
|
+
'<span class="json-summary">' +
|
|
220
|
+
'<span class="json-ellipsis"> … </span>' +
|
|
221
|
+
'<span class="token literal">' + summary + '</span> ' +
|
|
222
|
+
'<span class="json-brace">' + closeChar + '</span>' +
|
|
223
|
+
(withComma ? '<span class="json-punct">,</span>' : '') +
|
|
224
|
+
'</span>',
|
|
225
|
+
'json-open'
|
|
226
|
+
) +
|
|
227
|
+
'<span class="json-children">' + children + '</span>' +
|
|
228
|
+
renderJsonLine(
|
|
229
|
+
depth,
|
|
230
|
+
'<span class="json-toggle-spacer"></span><span class="json-brace">' + closeChar + '</span>' +
|
|
231
|
+
(withComma ? '<span class="json-punct">,</span>' : ''),
|
|
232
|
+
'json-close'
|
|
233
|
+
) +
|
|
234
|
+
'</span>'
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return renderJsonLine(
|
|
238
|
+
depth,
|
|
239
|
+
'<span class="json-toggle-spacer"></span>' +
|
|
240
|
+
(keyHtml || '') +
|
|
241
|
+
renderJsonPrimitive(value) +
|
|
242
|
+
(withComma ? '<span class="json-punct">,</span>' : ''),
|
|
243
|
+
'json-single'
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const renderCollapsibleJson = (text) => {
|
|
248
|
+
if (!text) return ' ';
|
|
249
|
+
const parsed = JSON.parse(text);
|
|
250
|
+
return '<span class="json-tree">' + renderJsonNode(parsed, 0, '', false) + '</span>';
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const highlightJson = (text, options) => {
|
|
254
|
+
if (!text) return ' ';
|
|
255
|
+
const collapsible = !!(options && options.collapsible);
|
|
256
|
+
if (!collapsible) return highlightJsonText(text);
|
|
257
|
+
try {
|
|
258
|
+
return renderCollapsibleJson(text);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
return highlightJsonText(text);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const bindJsonToggleHandlers = () => {
|
|
265
|
+
if (state.__jsonToggleBound) return;
|
|
266
|
+
state.__jsonToggleBound = true;
|
|
267
|
+
document.addEventListener('click', (event) => {
|
|
268
|
+
const toggle = event.target && event.target.closest
|
|
269
|
+
? event.target.closest('[data-json-toggle]')
|
|
270
|
+
: null;
|
|
271
|
+
if (!toggle) return;
|
|
272
|
+
const node = toggle.closest('.json-node');
|
|
273
|
+
if (!node) return;
|
|
274
|
+
event.preventDefault();
|
|
275
|
+
const collapsed = node.classList.toggle('is-collapsed');
|
|
276
|
+
toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
|
277
|
+
toggle.setAttribute('title', collapsed ? 'Expand' : 'Collapse');
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
bindJsonToggleHandlers();
|
|
282
|
+
|
|
283
|
+
const getJsonViewerStore = () => {
|
|
284
|
+
if (!state.__jsonViewerStore || typeof state.__jsonViewerStore.get !== 'function') {
|
|
285
|
+
state.__jsonViewerStore = new WeakMap();
|
|
286
|
+
}
|
|
287
|
+
return state.__jsonViewerStore;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const getCodeMirrorLib = () => {
|
|
291
|
+
if (typeof window === 'undefined') return null;
|
|
292
|
+
const codeMirror = window.CodeMirror;
|
|
293
|
+
if (!codeMirror || typeof codeMirror !== 'function') return null;
|
|
294
|
+
return codeMirror;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const clearJsonViewer = (element, placeholder) => {
|
|
298
|
+
if (!element) return;
|
|
299
|
+
const store = getJsonViewerStore();
|
|
300
|
+
const editor = store.get(element);
|
|
301
|
+
if (editor && typeof editor.getWrapperElement === 'function') {
|
|
302
|
+
const wrapper = editor.getWrapperElement();
|
|
303
|
+
if (wrapper && wrapper.parentNode === element) {
|
|
304
|
+
wrapper.parentNode.removeChild(wrapper);
|
|
305
|
+
}
|
|
306
|
+
store.delete(element);
|
|
307
|
+
}
|
|
308
|
+
element.classList.remove('cm-json-host');
|
|
309
|
+
element.classList.remove('json-highlight');
|
|
310
|
+
element.textContent = placeholder || '';
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const renderJsonViewer = (element, value, options) => {
|
|
314
|
+
if (!element) return;
|
|
315
|
+
const opts = options || {};
|
|
316
|
+
let text = '';
|
|
317
|
+
let mode = { name: 'javascript', json: true };
|
|
318
|
+
|
|
319
|
+
if (typeof value === 'string') {
|
|
320
|
+
text = value;
|
|
321
|
+
const trimmed = text.trim();
|
|
322
|
+
if (trimmed) {
|
|
323
|
+
try {
|
|
324
|
+
const parsed = JSON.parse(trimmed);
|
|
325
|
+
if (opts.pretty !== false) {
|
|
326
|
+
text = JSON.stringify(parsed, null, 2);
|
|
327
|
+
}
|
|
328
|
+
} catch (err) {
|
|
329
|
+
mode = 'text/plain';
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
mode = 'text/plain';
|
|
333
|
+
}
|
|
334
|
+
} else if (value === undefined || value === null) {
|
|
335
|
+
text = '';
|
|
336
|
+
mode = 'text/plain';
|
|
337
|
+
} else {
|
|
338
|
+
try {
|
|
339
|
+
text = JSON.stringify(value, null, 2);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
text = safeStringify(value);
|
|
342
|
+
mode = 'text/plain';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const CodeMirrorLib = getCodeMirrorLib();
|
|
347
|
+
if (!CodeMirrorLib) {
|
|
348
|
+
element.classList.add('json-highlight');
|
|
349
|
+
element.innerHTML = highlightJson(text || '', { collapsible: opts.collapsible !== false });
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const store = getJsonViewerStore();
|
|
354
|
+
let editor = store.get(element);
|
|
355
|
+
if (!editor) {
|
|
356
|
+
editor = CodeMirrorLib((node) => {
|
|
357
|
+
element.innerHTML = '';
|
|
358
|
+
element.appendChild(node);
|
|
359
|
+
}, {
|
|
360
|
+
lineNumbers: true,
|
|
361
|
+
lineWrapping: false,
|
|
362
|
+
readOnly: 'nocursor',
|
|
363
|
+
foldGutter: true,
|
|
364
|
+
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
|
365
|
+
mode
|
|
366
|
+
});
|
|
367
|
+
store.set(element, editor);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const collapsible = opts.collapsible !== false;
|
|
371
|
+
editor.setOption('lineNumbers', opts.lineNumbers !== false);
|
|
372
|
+
editor.setOption('readOnly', opts.readOnly === false ? false : 'nocursor');
|
|
373
|
+
editor.setOption('foldGutter', collapsible);
|
|
374
|
+
editor.setOption('gutters', collapsible
|
|
375
|
+
? ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
|
|
376
|
+
: ['CodeMirror-linenumbers']);
|
|
377
|
+
editor.setOption('mode', mode);
|
|
378
|
+
editor.setValue(text || '');
|
|
379
|
+
if (typeof editor.clearHistory === 'function') editor.clearHistory();
|
|
380
|
+
if (typeof editor.refresh === 'function') editor.refresh();
|
|
381
|
+
|
|
382
|
+
element.classList.remove('json-highlight');
|
|
383
|
+
element.classList.add('cm-json-host');
|
|
384
|
+
};
|
|
385
|
+
|
|
153
386
|
const setActiveTab = (tab) => {
|
|
154
387
|
if (!dom.tabButtons || !dom.tabPanels) return;
|
|
155
388
|
dom.tabButtons.forEach((item) => {
|
|
@@ -173,7 +406,9 @@
|
|
|
173
406
|
escapeHtml,
|
|
174
407
|
safeStringify,
|
|
175
408
|
highlightCode,
|
|
176
|
-
highlightJson
|
|
409
|
+
highlightJson,
|
|
410
|
+
renderJsonViewer,
|
|
411
|
+
clearJsonViewer
|
|
177
412
|
},
|
|
178
413
|
helpers: {
|
|
179
414
|
setActiveTab
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
dom.refreshTriggers = document.getElementById('refreshTriggers');
|
|
16
16
|
|
|
17
17
|
const { triggerList, triggerDetail, triggerFunctionButton, refreshTriggers } = dom;
|
|
18
|
-
const { api,
|
|
18
|
+
const { api, renderJsonViewer } = utils;
|
|
19
19
|
const { setActiveTab } = helpers;
|
|
20
20
|
|
|
21
21
|
const buildTriggerFunctionMap = (items) => {
|
|
@@ -71,8 +71,7 @@
|
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
if (triggerDetail) {
|
|
74
|
-
triggerDetail
|
|
75
|
-
triggerDetail.innerHTML = highlightJson(JSON.stringify(entry, null, 2) || '');
|
|
74
|
+
renderJsonViewer(triggerDetail, entry, { collapsible: true });
|
|
76
75
|
}
|
|
77
76
|
if (triggerFunctionButton) {
|
|
78
77
|
if (fnName) {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
openCreateUser,
|
|
67
67
|
closeCreateUser
|
|
68
68
|
} = dom;
|
|
69
|
-
const { api, formatDateTime,
|
|
69
|
+
const { api, formatDateTime, renderJsonViewer, clearJsonViewer } = utils;
|
|
70
70
|
const { setActiveTab } = helpers;
|
|
71
71
|
|
|
72
72
|
const USER_DETAIL_PLACEHOLDER = 'select a user to inspect';
|
|
@@ -92,23 +92,19 @@
|
|
|
92
92
|
const setUserDetailContent = (entry) => {
|
|
93
93
|
if (!userDetail) return;
|
|
94
94
|
if (!entry) {
|
|
95
|
-
userDetail
|
|
96
|
-
userDetail.textContent = USER_DETAIL_PLACEHOLDER;
|
|
95
|
+
clearJsonViewer(userDetail, USER_DETAIL_PLACEHOLDER);
|
|
97
96
|
return;
|
|
98
97
|
}
|
|
99
|
-
userDetail
|
|
100
|
-
userDetail.innerHTML = highlightJson(JSON.stringify(entry, null, 2) || '');
|
|
98
|
+
renderJsonViewer(userDetail, entry, { collapsible: true });
|
|
101
99
|
};
|
|
102
100
|
|
|
103
101
|
const setUserConfigContent = (element, value, placeholder) => {
|
|
104
102
|
if (!element) return;
|
|
105
103
|
if (!value) {
|
|
106
|
-
element
|
|
107
|
-
element.textContent = placeholder;
|
|
104
|
+
clearJsonViewer(element, placeholder);
|
|
108
105
|
return;
|
|
109
106
|
}
|
|
110
|
-
element
|
|
111
|
-
element.innerHTML = highlightJson(JSON.stringify(value, null, 2) || '');
|
|
107
|
+
renderJsonViewer(element, value, { collapsible: true });
|
|
112
108
|
};
|
|
113
109
|
|
|
114
110
|
const renderUserConfig = () => {
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
ChangeStreamOptions,
|
|
8
8
|
ClientSession,
|
|
9
9
|
ClientSessionOptions,
|
|
10
|
-
Collection,
|
|
11
10
|
Document,
|
|
12
11
|
EventsDescription,
|
|
13
12
|
FindOneAndUpdateOptions,
|
|
@@ -22,6 +21,7 @@ import { buildRulesMeta } from '../../monitoring/utils'
|
|
|
22
21
|
import { checkValidation } from '../../utils/roles/machines'
|
|
23
22
|
import { getWinningRole } from '../../utils/roles/machines/utils'
|
|
24
23
|
import { emitServiceEvent } from '../monitoring'
|
|
24
|
+
import { CHANGESTREAM } from '../../constants'
|
|
25
25
|
import {
|
|
26
26
|
CRUD_OPERATIONS,
|
|
27
27
|
GetOperatorsFunction,
|
|
@@ -388,9 +388,10 @@ const areUpdatedFieldsAllowed = (
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
const getOperators: GetOperatorsFunction = (
|
|
391
|
-
|
|
392
|
-
{ rules, collName, user, run_as_system, monitoringOrigin }
|
|
391
|
+
mongo,
|
|
392
|
+
{ rules, dbName, collName, user, run_as_system, monitoringOrigin }
|
|
393
393
|
) => {
|
|
394
|
+
const collection = mongo.client.db(dbName).collection(collName)
|
|
394
395
|
const normalizedRules: Rules = rules ?? ({} as Rules)
|
|
395
396
|
const collectionRules = normalizedRules[collName]
|
|
396
397
|
const filters = collectionRules?.filters ?? []
|
|
@@ -996,6 +997,7 @@ const getOperators: GetOperatorsFunction = (
|
|
|
996
997
|
* This allows fine-grained control over what change events a user can observe, based on roles and filters.
|
|
997
998
|
*/
|
|
998
999
|
watch: (pipelineOrOptions = [], options) => {
|
|
1000
|
+
const changestreamCollection = mongo[CHANGESTREAM].client.db(dbName).collection(collName)
|
|
999
1001
|
try {
|
|
1000
1002
|
const {
|
|
1001
1003
|
pipeline,
|
|
@@ -1025,7 +1027,7 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1025
1027
|
|
|
1026
1028
|
const formattedPipeline = [firstStep, ...extraMatches, ...pipeline].filter(Boolean) as Document[]
|
|
1027
1029
|
|
|
1028
|
-
const result =
|
|
1030
|
+
const result = changestreamCollection.watch(formattedPipeline, watchOptions)
|
|
1029
1031
|
const originalOn = result.on.bind(result)
|
|
1030
1032
|
|
|
1031
1033
|
/**
|
|
@@ -1107,7 +1109,7 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1107
1109
|
}
|
|
1108
1110
|
|
|
1109
1111
|
// System mode: no filtering applied
|
|
1110
|
-
const result =
|
|
1112
|
+
const result = changestreamCollection.watch([...extraMatches, ...pipeline], watchOptions)
|
|
1111
1113
|
emitMongoEvent('watch')
|
|
1112
1114
|
return result
|
|
1113
1115
|
} catch (error) {
|
|
@@ -1387,15 +1389,10 @@ const MongodbAtlas: MongodbAtlasFunction = (
|
|
|
1387
1389
|
db: (dbName: string) => {
|
|
1388
1390
|
return {
|
|
1389
1391
|
collection: (collName: string) => {
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
collection: (name: string) => Collection<Document>
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
const collection: Collection<Document> = mongoClient.db(dbName).collection(collName)
|
|
1396
|
-
return getOperators(collection, {
|
|
1397
|
-
rules,
|
|
1392
|
+
return getOperators(app.mongo, {
|
|
1393
|
+
dbName,
|
|
1398
1394
|
collName,
|
|
1395
|
+
rules,
|
|
1399
1396
|
user,
|
|
1400
1397
|
run_as_system,
|
|
1401
1398
|
monitoringOrigin: monitoring?.invokedFrom
|
|
@@ -44,9 +44,10 @@ export type GetValidRuleParams<T extends Role | Filter> = {
|
|
|
44
44
|
type Method<T extends keyof Collection<Document>> = Collection<Document>[T]
|
|
45
45
|
|
|
46
46
|
export type GetOperatorsFunction = (
|
|
47
|
-
|
|
47
|
+
mongoInstance: FastifyInstance["mongo"],
|
|
48
48
|
{
|
|
49
49
|
rules,
|
|
50
|
+
dbName,
|
|
50
51
|
collName,
|
|
51
52
|
user,
|
|
52
53
|
run_as_system,
|
|
@@ -55,6 +56,7 @@ export type GetOperatorsFunction = (
|
|
|
55
56
|
user?: User
|
|
56
57
|
rules?: Rules
|
|
57
58
|
run_as_system?: boolean
|
|
59
|
+
dbName: string
|
|
58
60
|
collName: string
|
|
59
61
|
monitoringOrigin?: string
|
|
60
62
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import 'fastify'
|
|
2
2
|
import type { FastifyJWT } from '@fastify/jwt'
|
|
3
|
-
import { Db, MongoClient } from 'mongodb'
|
|
4
3
|
|
|
5
4
|
declare module 'fastify' {
|
|
6
5
|
interface FastifyRequest {
|
|
@@ -11,12 +10,4 @@ declare module 'fastify' {
|
|
|
11
10
|
interface FastifyContextConfig {
|
|
12
11
|
rawBody?: boolean
|
|
13
12
|
}
|
|
14
|
-
|
|
15
|
-
interface FastifyInstance {
|
|
16
|
-
mongo?: {
|
|
17
|
-
client: MongoClient
|
|
18
|
-
db?: Db
|
|
19
|
-
ObjectId: typeof import('mongodb').ObjectId
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
13
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { UUID } from 'mongodb'
|
|
2
|
+
import type { EncryptionSchemas } from '../../features/encryption/interface'
|
|
3
|
+
import { buildSchemaMap } from '../initializer/mongodbCSFLE'
|
|
4
|
+
|
|
5
|
+
describe('buildSchemaMap', () => {
|
|
6
|
+
const genericSchemas: EncryptionSchemas = {
|
|
7
|
+
'appDb.records': {
|
|
8
|
+
bsonType: 'object',
|
|
9
|
+
encryptMetadata: {
|
|
10
|
+
keyAlias: 'root-key'
|
|
11
|
+
},
|
|
12
|
+
properties: {
|
|
13
|
+
publicText: {
|
|
14
|
+
encrypt: {
|
|
15
|
+
bsonType: 'string',
|
|
16
|
+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
protectedText: {
|
|
20
|
+
encrypt: {
|
|
21
|
+
bsonType: 'string',
|
|
22
|
+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic',
|
|
23
|
+
keyAlias: 'root-key'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
nestedObject: {
|
|
27
|
+
bsonType: 'object',
|
|
28
|
+
encryptMetadata: { keyAlias: 'nested-key' },
|
|
29
|
+
properties: {
|
|
30
|
+
deepObject: {
|
|
31
|
+
bsonType: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
deepSecret: {
|
|
34
|
+
encrypt: {
|
|
35
|
+
bsonType: 'string',
|
|
36
|
+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random',
|
|
37
|
+
keyAlias: 'deep-key'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
it('resolves keyAlias to keyId for root and nested schemas', () => {
|
|
49
|
+
const rootKeyId = new UUID()
|
|
50
|
+
const nestedKeyId = new UUID()
|
|
51
|
+
const deepKeyId = new UUID()
|
|
52
|
+
|
|
53
|
+
const schemaMap = buildSchemaMap(genericSchemas, [
|
|
54
|
+
{ dataKeyAlias: 'root-key', dataKeyId: rootKeyId },
|
|
55
|
+
{ dataKeyAlias: 'nested-key', dataKeyId: nestedKeyId },
|
|
56
|
+
{ dataKeyAlias: 'deep-key', dataKeyId: deepKeyId }
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
expect(schemaMap['appDb.records']).toEqual({
|
|
60
|
+
bsonType: 'object',
|
|
61
|
+
encryptMetadata: { keyId: [rootKeyId] },
|
|
62
|
+
properties: {
|
|
63
|
+
publicText: {
|
|
64
|
+
encrypt: {
|
|
65
|
+
bsonType: 'string',
|
|
66
|
+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
protectedText: {
|
|
70
|
+
encrypt: {
|
|
71
|
+
bsonType: 'string',
|
|
72
|
+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic',
|
|
73
|
+
keyId: [rootKeyId]
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
nestedObject: {
|
|
77
|
+
bsonType: 'object',
|
|
78
|
+
encryptMetadata: { keyId: [nestedKeyId] },
|
|
79
|
+
properties: {
|
|
80
|
+
deepObject: {
|
|
81
|
+
bsonType: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
deepSecret: {
|
|
84
|
+
encrypt: {
|
|
85
|
+
bsonType: 'string',
|
|
86
|
+
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random',
|
|
87
|
+
keyId: [deepKeyId]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('throws when nested keyAlias cannot be resolved', () => {
|
|
99
|
+
const rootKeyId = new UUID()
|
|
100
|
+
|
|
101
|
+
expect(() =>
|
|
102
|
+
buildSchemaMap(genericSchemas, [{ dataKeyAlias: 'root-key', dataKeyId: rootKeyId }])
|
|
103
|
+
).toThrow('Key with alias deep-key could not be found in the Key Vault.')
|
|
104
|
+
})
|
|
105
|
+
})
|
package/src/utils/index.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
2
3
|
|
|
3
4
|
export const readFileContent = (filePath: string) => fs.readFileSync(filePath, 'utf-8')
|
|
4
5
|
export const readJsonContent = (filePath: string) =>
|
|
5
6
|
JSON.parse(readFileContent(filePath)) as unknown
|
|
7
|
+
|
|
8
|
+
export const recursivelyCollectFiles = (dir: string): string[] => {
|
|
9
|
+
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
10
|
+
const fullPath = path.join(dir, entry.name)
|
|
11
|
+
if (entry.isDirectory()) {
|
|
12
|
+
return recursivelyCollectFiles(fullPath)
|
|
13
|
+
}
|
|
14
|
+
return entry.isFile() ? [fullPath] : []
|
|
15
|
+
})
|
|
16
|
+
}
|