@flowerforce/flowerbase 1.7.5 → 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/controller.d.ts.map +1 -1
- package/dist/auth/controller.js +11 -10
- package/dist/auth/plugins/jwt.js +1 -1
- package/dist/auth/providers/anon-user/controller.js +1 -1
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +28 -7
- package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.js +15 -14
- package/dist/auth/utils.d.ts +1 -0
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +14 -3
- 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 +5 -1
- package/dist/features/triggers/utils.js +3 -3
- 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/monitoring/routes/users.d.ts.map +1 -1
- package/dist/monitoring/routes/users.js +7 -6
- package/dist/monitoring/utils.d.ts.map +1 -1
- package/dist/monitoring/utils.js +5 -4
- package/dist/services/api/index.d.ts +4 -0
- package/dist/services/api/index.d.ts.map +1 -1
- package/dist/services/api/utils.d.ts +1 -0
- package/dist/services/api/utils.d.ts.map +1 -1
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.d.ts.map +1 -1
- 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/shared/handleUserDeletion.js +1 -1
- package/dist/shared/handleUserRegistration.js +2 -2
- package/dist/utils/context/helpers.d.ts +12 -0
- package/dist/utils/context/helpers.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/exposeRoutes.js +1 -1
- 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/dist/utils/rules-matcher/interface.d.ts +5 -1
- package/dist/utils/rules-matcher/interface.d.ts.map +1 -1
- package/dist/utils/rules-matcher/interface.js +2 -0
- package/dist/utils/rules-matcher/utils.d.ts.map +1 -1
- package/dist/utils/rules-matcher/utils.js +51 -16
- package/package.json +4 -2
- package/src/auth/__tests__/controller.test.ts +1 -0
- package/src/auth/controller.ts +12 -11
- package/src/auth/plugins/jwt.ts +2 -2
- package/src/auth/providers/anon-user/__tests__/controller.test.ts +1 -0
- package/src/auth/providers/anon-user/controller.ts +2 -2
- package/src/auth/providers/custom-function/controller.ts +29 -8
- package/src/auth/providers/local-userpass/controller.ts +16 -15
- package/src/auth/utils.ts +1 -0
- package/src/constants.ts +14 -4
- 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/__tests__/index.test.ts +1 -0
- package/src/features/triggers/index.ts +6 -2
- package/src/features/triggers/utils.ts +4 -4
- package/src/index.ts +10 -2
- package/src/monitoring/plugin.ts +33 -0
- package/src/monitoring/routes/users.ts +8 -7
- package/src/monitoring/ui.collections.js +7 -10
- package/src/monitoring/ui.css +383 -1
- package/src/monitoring/ui.endpoints.js +5 -10
- package/src/monitoring/ui.events.js +4 -6
- 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 +239 -3
- package/src/monitoring/ui.triggers.js +2 -3
- package/src/monitoring/ui.users.js +5 -9
- package/src/monitoring/utils.ts +6 -5
- package/src/services/mongodb-atlas/index.ts +10 -13
- package/src/services/mongodb-atlas/model.ts +3 -1
- package/src/shared/handleUserDeletion.ts +2 -2
- package/src/shared/handleUserRegistration.ts +3 -3
- package/src/types/fastify-raw-body.d.ts +0 -9
- package/src/utils/__tests__/mongodbCSFLE.test.ts +105 -0
- package/src/utils/__tests__/operators.test.ts +24 -0
- package/src/utils/__tests__/rule.test.ts +39 -0
- package/src/utils/__tests__/rulesMatcherInterfaces.test.ts +2 -0
- package/src/utils/index.ts +12 -1
- package/src/utils/initializer/exposeRoutes.ts +2 -2
- package/src/utils/initializer/mongodbCSFLE.ts +224 -0
- package/src/utils/initializer/registerPlugins.ts +45 -10
- package/src/utils/rules-matcher/interface.ts +5 -1
- package/src/utils/rules-matcher/utils.ts +78 -32
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
month: '2-digit',
|
|
53
53
|
day: '2-digit',
|
|
54
54
|
hour: '2-digit',
|
|
55
|
-
minute: '2-digit'
|
|
55
|
+
minute: '2-digit',
|
|
56
|
+
second: '2-digit'
|
|
56
57
|
});
|
|
57
58
|
};
|
|
58
59
|
|
|
@@ -124,7 +125,7 @@
|
|
|
124
125
|
return output || ' ';
|
|
125
126
|
};
|
|
126
127
|
|
|
127
|
-
const
|
|
128
|
+
const highlightJsonText = (text) => {
|
|
128
129
|
if (!text) return ' ';
|
|
129
130
|
const regex = /"(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|(-?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?)|\btrue\b|\bfalse\b|\bnull\b/g;
|
|
130
131
|
let output = '';
|
|
@@ -149,6 +150,239 @@
|
|
|
149
150
|
return output || ' ';
|
|
150
151
|
};
|
|
151
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
|
+
|
|
152
386
|
const setActiveTab = (tab) => {
|
|
153
387
|
if (!dom.tabButtons || !dom.tabPanels) return;
|
|
154
388
|
dom.tabButtons.forEach((item) => {
|
|
@@ -172,7 +406,9 @@
|
|
|
172
406
|
escapeHtml,
|
|
173
407
|
safeStringify,
|
|
174
408
|
highlightCode,
|
|
175
|
-
highlightJson
|
|
409
|
+
highlightJson,
|
|
410
|
+
renderJsonViewer,
|
|
411
|
+
clearJsonViewer
|
|
176
412
|
},
|
|
177
413
|
helpers: {
|
|
178
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 = () => {
|
package/src/monitoring/utils.ts
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import type { FastifyInstance, FastifyRequest } from 'fastify'
|
|
4
4
|
import { ObjectId } from 'mongodb'
|
|
5
|
-
import { AUTH_CONFIG, DB_NAME, DEFAULT_CONFIG } from '../constants'
|
|
5
|
+
import { AUTH_CONFIG, AUTH_DB_NAME, DB_NAME, DEFAULT_CONFIG } from '../constants'
|
|
6
6
|
import type { Rules } from '../features/rules/interface'
|
|
7
7
|
import { getValidRule } from '../services/mongodb-atlas/utils'
|
|
8
8
|
import { checkApplyWhen } from '../utils/roles/machines/utils'
|
|
@@ -380,7 +380,8 @@ export const resolveUserContext = async (
|
|
|
380
380
|
if (!userId) return undefined
|
|
381
381
|
const normalizedUserId = userId.trim()
|
|
382
382
|
|
|
383
|
-
const
|
|
383
|
+
const authDb = app.mongo.client.db(AUTH_DB_NAME)
|
|
384
|
+
const customDb = app.mongo.client.db(DB_NAME)
|
|
384
385
|
const authCollection = AUTH_CONFIG.authCollection ?? 'auth_users'
|
|
385
386
|
const userCollection = AUTH_CONFIG.userCollection
|
|
386
387
|
const userIdField = AUTH_CONFIG.user_id_field ?? 'id'
|
|
@@ -388,14 +389,14 @@ export const resolveUserContext = async (
|
|
|
388
389
|
const authSelector = isObjectId
|
|
389
390
|
? { _id: new ObjectId(normalizedUserId) }
|
|
390
391
|
: { id: normalizedUserId }
|
|
391
|
-
const authUser = await
|
|
392
|
+
const authUser = await authDb.collection(authCollection).findOne(authSelector)
|
|
392
393
|
|
|
393
394
|
let customUser: Record<string, unknown> | null = null
|
|
394
395
|
if (userCollection) {
|
|
395
396
|
const customSelector = { [userIdField]: normalizedUserId }
|
|
396
|
-
customUser = await
|
|
397
|
+
customUser = await customDb.collection(userCollection).findOne(customSelector)
|
|
397
398
|
if (!customUser && isObjectId) {
|
|
398
|
-
customUser = await
|
|
399
|
+
customUser = await customDb.collection(userCollection).findOne({ _id: new ObjectId(normalizedUserId) })
|
|
399
400
|
}
|
|
400
401
|
}
|
|
401
402
|
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import { ObjectId } from "bson"
|
|
2
|
-
import { AUTH_CONFIG,
|
|
2
|
+
import { AUTH_CONFIG, AUTH_DB_NAME } from "../constants"
|
|
3
3
|
import { emitServiceEvent } from "../services/monitoring"
|
|
4
4
|
import { HandleUserDeletion } from "./models/handleUserDeletion.model"
|
|
5
5
|
|
|
@@ -33,7 +33,7 @@ const handleUserDeletion: HandleUserDeletion = (app, opt) => async ({ id, email
|
|
|
33
33
|
|
|
34
34
|
const { authCollection } = AUTH_CONFIG
|
|
35
35
|
const mongo = app?.mongo
|
|
36
|
-
const db = mongo.client.db(
|
|
36
|
+
const db = mongo.client.db(AUTH_DB_NAME)
|
|
37
37
|
const collection = db.collection<Record<string, unknown>>(authCollection!)
|
|
38
38
|
let query: Record<string, unknown>
|
|
39
39
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AUTH_CONFIG,
|
|
1
|
+
import { AUTH_CONFIG, AUTH_DB_NAME } from "../constants"
|
|
2
|
+
import { emitServiceEvent } from "../services/monitoring"
|
|
2
3
|
import { StateManager } from "../state"
|
|
3
4
|
import { GenerateContext } from "../utils/context"
|
|
4
5
|
import { generateToken, hashPassword } from "../utils/crypto"
|
|
5
|
-
import { emitServiceEvent } from "../services/monitoring"
|
|
6
6
|
import { HandleUserRegistration } from "./models/handleUserRegistration.model"
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -35,7 +35,7 @@ const handleUserRegistration: HandleUserRegistration = (app, opt) => async ({ em
|
|
|
35
35
|
const runConfirmationFunction = localUserpassConfig?.runConfirmationFunction === true
|
|
36
36
|
const confirmationFunctionName = localUserpassConfig?.confirmationFunctionName
|
|
37
37
|
const mongo = app?.mongo
|
|
38
|
-
const db = mongo.client.db(
|
|
38
|
+
const db = mongo.client.db(AUTH_DB_NAME)
|
|
39
39
|
const hashedPassword = await hashPassword(password)
|
|
40
40
|
|
|
41
41
|
const existingUser = await db?.collection(authCollection!).findOne({ email })
|
|
@@ -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
|
+
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ObjectId } from 'mongodb'
|
|
1
2
|
import { operators } from '../rules-matcher/utils'
|
|
2
3
|
|
|
3
4
|
describe('operators', () => {
|
|
@@ -8,10 +9,15 @@ describe('operators', () => {
|
|
|
8
9
|
it('should check equals values', () => {
|
|
9
10
|
expect(operators.$eq('a', 'a')).toBe(true)
|
|
10
11
|
expect(!operators.$eq('a', 'b')).toBe(true)
|
|
12
|
+
const id = new ObjectId()
|
|
13
|
+
expect(operators.$eq(id, id.toHexString())).toBe(true)
|
|
14
|
+
expect(operators.$eq(id, new ObjectId(id.toHexString()))).toBe(true)
|
|
11
15
|
})
|
|
12
16
|
it('should check different values', () => {
|
|
13
17
|
expect(operators.$ne('a', 'a')).toBe(false)
|
|
14
18
|
expect(operators.$ne('a', 'b')).toBe(true)
|
|
19
|
+
const id = new ObjectId()
|
|
20
|
+
expect(operators.$ne(id, id.toHexString())).toBe(false)
|
|
15
21
|
})
|
|
16
22
|
it('should check string length values', () => {
|
|
17
23
|
// test $strGt
|
|
@@ -69,6 +75,8 @@ describe('operators', () => {
|
|
|
69
75
|
expect(operators.$in([3, 4], [3, 4, 5])).toBe(true)
|
|
70
76
|
expect(operators.$in([3, 6], [3, 4, 5])).toBe(true)
|
|
71
77
|
expect(operators.$in({ name: 'ciao' }, [{ name: 'ciao' }, 4, 5])).toBe(false)
|
|
78
|
+
const id = new ObjectId()
|
|
79
|
+
expect(operators.$in(id, [id.toHexString()])).toBe(true)
|
|
72
80
|
})
|
|
73
81
|
it("should check if a value isn't in an array", () => {
|
|
74
82
|
expect(operators.$nin(2, [3])).toBe(true)
|
|
@@ -76,6 +84,8 @@ describe('operators', () => {
|
|
|
76
84
|
expect(operators.$nin([3, 4], [3, 4, 5])).toBe(false)
|
|
77
85
|
expect(operators.$nin([3, 6], [3, 4, 5])).toBe(false)
|
|
78
86
|
expect(operators.$nin({ name: 'ciao' }, [{ name: 'ciao' }, 4, 5])).toBe(true)
|
|
87
|
+
const id = new ObjectId()
|
|
88
|
+
expect(operators.$nin(id, [id.toHexString()])).toBe(false)
|
|
79
89
|
})
|
|
80
90
|
it('should find all values in an array', () => {
|
|
81
91
|
expect(operators.$all(2, [3])).toBe(false)
|
|
@@ -85,6 +95,8 @@ describe('operators', () => {
|
|
|
85
95
|
expect(operators.$all([3, 6], [3, 4, 5])).toBe(false)
|
|
86
96
|
expect(operators.$all({ name: 'ciao' }, [{ name: 'ciao' }, 4, 5])).toBe(false)
|
|
87
97
|
expect(operators.$all([{ name: 'ciao' }, 4, 5], [{ name: 'ciao' }, 4, 5])).toBe(false)
|
|
98
|
+
const id = new ObjectId()
|
|
99
|
+
expect(operators.$all([id], [id.toHexString()])).toBe(true)
|
|
88
100
|
})
|
|
89
101
|
it('should check array size', () => {
|
|
90
102
|
expect(operators.$size([1, 2, 3], 3)).toBe(true)
|
|
@@ -100,4 +112,16 @@ describe('operators', () => {
|
|
|
100
112
|
expect(operators.$regex('1234567890', numberRegex)).toBe(true)
|
|
101
113
|
expect(operators.$regex('12345r', numberRegex)).toBe(false)
|
|
102
114
|
})
|
|
115
|
+
|
|
116
|
+
it('should support %stringToOid conversion operator', () => {
|
|
117
|
+
const id = new ObjectId()
|
|
118
|
+
expect(operators['%stringToOid'](id, id.toHexString())).toBe(true)
|
|
119
|
+
expect(operators['%stringToOid'](id, 'not-an-object-id')).toBe(false)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should support %oidToString conversion operator', () => {
|
|
123
|
+
const id = new ObjectId()
|
|
124
|
+
expect(operators['%oidToString'](id.toHexString(), id)).toBe(true)
|
|
125
|
+
expect(operators['%oidToString']('not-matching', id)).toBe(false)
|
|
126
|
+
})
|
|
103
127
|
})
|