@flowerforce/flowerbase 1.3.1-beta.3 → 1.3.1-beta.5

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.
@@ -1,3 +1,4 @@
1
+ import { Document } from 'mongodb';
1
2
  import { ExecuteQueryParams, Functions } from './interface';
2
3
  /**
3
4
  * > Loads the functions config json file
@@ -15,12 +16,12 @@ export declare const executeQuery: ({ currentMethod, query, update, filter, opti
15
16
  findOne: () => Promise<unknown>;
16
17
  count: () => Promise<number>;
17
18
  deleteOne: () => Promise<unknown>;
18
- insertOne: () => Promise<import("mongodb/mongodb").InsertOneResult<import("bson").Document>>;
19
- updateOne: () => Promise<unknown> | import("mongodb/mongodb").FindCursor<any> | import("mongodb/mongodb").ChangeStream<import("bson").Document, import("bson").Document> | import("mongodb/mongodb").AggregationCursor<import("bson").Document>;
20
- findOneAndUpdate: () => Promise<import("bson").Document | null>;
21
- aggregate: () => Promise<import("bson").Document[]>;
22
- insertMany: () => Promise<import("mongodb/mongodb").InsertManyResult<import("bson").Document>>;
23
- updateMany: () => Promise<import("mongodb/mongodb").UpdateResult<import("bson").Document>>;
24
- deleteMany: () => Promise<import("mongodb/mongodb").DeleteResult>;
19
+ insertOne: () => Promise<import("mongodb").InsertOneResult<Document>>;
20
+ updateOne: () => Promise<unknown> | import("mongodb").FindCursor<any> | import("mongodb").ChangeStream<Document, Document> | import("mongodb").AggregationCursor<Document>;
21
+ findOneAndUpdate: () => Promise<Document | null>;
22
+ aggregate: () => Promise<Document[]>;
23
+ insertMany: () => Promise<import("mongodb").InsertManyResult<Document>>;
24
+ updateMany: () => Promise<import("mongodb").UpdateResult<Document>>;
25
+ deleteMany: () => Promise<import("mongodb").DeleteResult>;
25
26
  }>;
26
27
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/functions/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE3D;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAU,gBAAuB,KAAG,OAAO,CAAC,SAAS,CAwB9E,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAU,+GAWhC,kBAAkB;;;;;;;;;;;;EA+DpB,CAAA"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/functions/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAGlC,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE3D;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAU,gBAAuB,KAAG,OAAO,CAAC,SAAS,CAwB9E,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAU,+GAWhC,kBAAkB;;;;;;;;;;;;EA4EpB,CAAA"}
@@ -70,16 +70,29 @@ const executeQuery = (_a) => __awaiter(void 0, [_a], void 0, function* ({ curren
70
70
  : typeof returnNewDocument === 'boolean'
71
71
  ? { returnDocument: returnNewDocument ? 'after' : 'before' }
72
72
  : undefined;
73
+ const parsedOptions = resolvedOptions ? bson_1.EJSON.deserialize(resolvedOptions) : undefined;
73
74
  return {
74
75
  find: () => __awaiter(void 0, void 0, void 0, function* () {
75
- return yield currentMethod(bson_1.EJSON.deserialize(resolvedQuery)).toArray();
76
+ return yield (() => {
77
+ const cursor = currentMethod(bson_1.EJSON.deserialize(resolvedQuery));
78
+ if (parsedOptions === null || parsedOptions === void 0 ? void 0 : parsedOptions.sort) {
79
+ cursor.sort(parsedOptions.sort);
80
+ }
81
+ if (typeof (parsedOptions === null || parsedOptions === void 0 ? void 0 : parsedOptions.skip) === 'number') {
82
+ cursor.skip(parsedOptions.skip);
83
+ }
84
+ if (typeof (parsedOptions === null || parsedOptions === void 0 ? void 0 : parsedOptions.limit) === 'number') {
85
+ cursor.limit(parsedOptions.limit);
86
+ }
87
+ return cursor.toArray();
88
+ })();
76
89
  }),
77
90
  findOne: () => currentMethod(bson_1.EJSON.deserialize(resolvedQuery)),
78
- count: () => currentMethod(bson_1.EJSON.deserialize(resolvedQuery), resolvedOptions ? bson_1.EJSON.deserialize(resolvedOptions) : undefined),
91
+ count: () => currentMethod(bson_1.EJSON.deserialize(resolvedQuery), parsedOptions),
79
92
  deleteOne: () => currentMethod(bson_1.EJSON.deserialize(resolvedQuery)),
80
93
  insertOne: () => currentMethod(bson_1.EJSON.deserialize(document)),
81
94
  updateOne: () => currentMethod(bson_1.EJSON.deserialize(resolvedQuery), bson_1.EJSON.deserialize(resolvedUpdate)),
82
- findOneAndUpdate: () => currentMethod(bson_1.EJSON.deserialize(resolvedQuery), bson_1.EJSON.deserialize(resolvedUpdate), resolvedOptions ? bson_1.EJSON.deserialize(resolvedOptions) : undefined),
95
+ findOneAndUpdate: () => currentMethod(bson_1.EJSON.deserialize(resolvedQuery), bson_1.EJSON.deserialize(resolvedUpdate), parsedOptions),
83
96
  aggregate: () => __awaiter(void 0, void 0, void 0, function* () {
84
97
  return (yield currentMethod(bson_1.EJSON.deserialize(pipeline), {}, // TODO -> ADD OPTIONS
85
98
  isClient)).toArray();
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAyC,oBAAoB,EAAE,MAAM,SAAS,CAAA;AA4yBrF,QAAA,MAAM,YAAY,EAAE,oBAsBlB,CAAA;AAEF,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/mongodb-atlas/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAyC,oBAAoB,EAAE,MAAM,SAAS,CAAA;AA60BrF,QAAA,MAAM,YAAY,EAAE,oBAsBlB,CAAA;AAEF,eAAe,YAAY,CAAA"}
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ const get_1 = __importDefault(require("lodash/get"));
15
16
  const isEqual_1 = __importDefault(require("lodash/isEqual"));
16
17
  const machines_1 = require("../../utils/roles/machines");
17
18
  const utils_1 = require("../../utils/roles/machines/utils");
@@ -36,6 +37,29 @@ const logService = (message, payload) => {
36
37
  return;
37
38
  console.log('[service-debug]', message, payload !== null && payload !== void 0 ? payload : '');
38
39
  };
40
+ const getUpdatedPaths = (update) => {
41
+ const entries = Object.entries(update !== null && update !== void 0 ? update : {});
42
+ const hasOperators = entries.some(([key]) => key.startsWith('$'));
43
+ if (!hasOperators) {
44
+ return Object.keys(update !== null && update !== void 0 ? update : {});
45
+ }
46
+ const paths = new Set();
47
+ for (const [key, value] of entries) {
48
+ if (!key.startsWith('$'))
49
+ continue;
50
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
51
+ Object.keys(value).forEach((path) => paths.add(path));
52
+ }
53
+ }
54
+ return [...paths];
55
+ };
56
+ const areUpdatedFieldsAllowed = (filtered, updated, updatedPaths) => {
57
+ if (!filtered)
58
+ return false;
59
+ if (!updatedPaths.length)
60
+ return (0, isEqual_1.default)(filtered, updated);
61
+ return updatedPaths.every((path) => (0, isEqual_1.default)((0, get_1.default)(filtered, path), (0, get_1.default)(updated, path)));
62
+ };
39
63
  const getOperators = (collection, { rules, collName, user, run_as_system }) => {
40
64
  var _a, _b;
41
65
  const normalizedRules = rules !== null && rules !== void 0 ? rules : {};
@@ -238,6 +262,7 @@ const getOperators = (collection, { rules, collName, user, run_as_system }) => {
238
262
  const winningRole = (0, utils_1.getWinningRole)(result, user, roles);
239
263
  // Check if the update data contains MongoDB update operators (e.g., $set, $inc)
240
264
  const hasOperators = Object.keys(data).some((key) => key.startsWith('$'));
265
+ const updatedPaths = getUpdatedPaths(data);
241
266
  // Flatten the update object to extract the actual fields being modified
242
267
  // const docToCheck = hasOperators
243
268
  // ? Object.values(data).reduce((acc, operation) => ({ ...acc, ...operation }), {})
@@ -264,7 +289,7 @@ const getOperators = (collection, { rules, collName, user, run_as_system }) => {
264
289
  }, user)
265
290
  : fallbackAccess(docToCheck);
266
291
  // Ensure no unauthorized changes are made
267
- const areDocumentsEqual = (0, isEqual_1.default)(document, docToCheck);
292
+ const areDocumentsEqual = areUpdatedFieldsAllowed(document, docToCheck, updatedPaths);
268
293
  if (!status || !areDocumentsEqual) {
269
294
  throw new Error('Update not permitted');
270
295
  }
@@ -296,6 +321,7 @@ const getOperators = (collection, { rules, collName, user, run_as_system }) => {
296
321
  }
297
322
  const winningRole = (0, utils_1.getWinningRole)(result, user, roles);
298
323
  const hasOperators = Object.keys(data).some((key) => key.startsWith('$'));
324
+ const updatedPaths = Array.isArray(data) ? [] : getUpdatedPaths(data);
299
325
  const pipeline = [
300
326
  {
301
327
  $match: { $and: safeQuery }
@@ -316,7 +342,7 @@ const getOperators = (collection, { rules, collName, user, run_as_system }) => {
316
342
  expansions: {}
317
343
  }, user)
318
344
  : fallbackAccess(docToCheck);
319
- const areDocumentsEqual = (0, isEqual_1.default)(document, docToCheck);
345
+ const areDocumentsEqual = areUpdatedFieldsAllowed(document, docToCheck, updatedPaths);
320
346
  if (!status || !areDocumentsEqual) {
321
347
  throw new Error('Update not permitted');
322
348
  }
@@ -574,6 +600,7 @@ const getOperators = (collection, { rules, collName, user, run_as_system }) => {
574
600
  }
575
601
  // Check if the update data contains MongoDB update operators (e.g., $set, $inc)
576
602
  const hasOperators = Object.keys(data).some((key) => key.startsWith('$'));
603
+ const updatedPaths = getUpdatedPaths(data);
577
604
  // Flatten the update object to extract the actual fields being modified
578
605
  // const docToCheck = hasOperators
579
606
  // ? Object.values(data).reduce((acc, operation) => ({ ...acc, ...operation }), {})
@@ -600,7 +627,7 @@ const getOperators = (collection, { rules, collName, user, run_as_system }) => {
600
627
  return status ? document : undefined;
601
628
  })));
602
629
  // Ensure no unauthorized changes are made
603
- const areDocumentsEqual = (0, isEqual_1.default)(docsToCheck, filteredItems);
630
+ const areDocumentsEqual = docsToCheck.every((doc, index) => areUpdatedFieldsAllowed(filteredItems[index], doc, updatedPaths));
604
631
  if (!areDocumentsEqual) {
605
632
  console.log('check1 In updateMany --> (!areDocumentsEqual)');
606
633
  throw new Error('Update not permitted');
@@ -36,7 +36,7 @@ exports.hashPassword = hashPassword;
36
36
  const comparePassword = (plaintext, storedPassword) => __awaiter(void 0, void 0, void 0, function* () {
37
37
  const [storedHash, storedSalt] = storedPassword.split('.');
38
38
  if (!storedHash || !storedSalt) {
39
- throw new Error(`Invalid stored password: ${storedPassword}`);
39
+ throw new Error('Invalid credentials');
40
40
  }
41
41
  const storedBuffer = Buffer.from(storedHash, 'hex');
42
42
  const buffer = (yield scrypt(plaintext, storedSalt, 64));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase",
3
- "version": "1.3.1-beta.3",
3
+ "version": "1.3.1-beta.5",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs'
2
2
  import path from 'node:path'
3
+ import { Document } from 'mongodb'
3
4
  import { EJSON } from 'bson'
4
5
  import { GetOperatorsFunction } from '../../services/mongodb-atlas/model'
5
6
  import { ExecuteQueryParams, Functions } from './interface'
@@ -65,11 +66,24 @@ export const executeQuery = async ({
65
66
  : typeof returnNewDocument === 'boolean'
66
67
  ? { returnDocument: returnNewDocument ? 'after' : 'before' }
67
68
  : undefined
69
+ const parsedOptions = resolvedOptions ? EJSON.deserialize(resolvedOptions) : undefined
68
70
  return {
69
71
  find: async () =>
70
- await (currentMethod as ReturnType<GetOperatorsFunction>['find'])(
71
- EJSON.deserialize(resolvedQuery)
72
- ).toArray(),
72
+ await (() => {
73
+ const cursor = (currentMethod as ReturnType<GetOperatorsFunction>['find'])(
74
+ EJSON.deserialize(resolvedQuery)
75
+ )
76
+ if (parsedOptions?.sort) {
77
+ cursor.sort(parsedOptions.sort as Document)
78
+ }
79
+ if (typeof parsedOptions?.skip === 'number') {
80
+ cursor.skip(parsedOptions.skip)
81
+ }
82
+ if (typeof parsedOptions?.limit === 'number') {
83
+ cursor.limit(parsedOptions.limit)
84
+ }
85
+ return cursor.toArray()
86
+ })(),
73
87
  findOne: () =>
74
88
  (currentMethod as ReturnType<GetOperatorsFunction>['findOne'])(
75
89
  EJSON.deserialize(resolvedQuery)
@@ -77,7 +91,7 @@ export const executeQuery = async ({
77
91
  count: () =>
78
92
  (currentMethod as ReturnType<GetOperatorsFunction>['count'])(
79
93
  EJSON.deserialize(resolvedQuery),
80
- resolvedOptions ? EJSON.deserialize(resolvedOptions) : undefined
94
+ parsedOptions
81
95
  ),
82
96
  deleteOne: () =>
83
97
  (currentMethod as ReturnType<GetOperatorsFunction>['deleteOne'])(
@@ -92,7 +106,7 @@ export const executeQuery = async ({
92
106
  (currentMethod as ReturnType<GetOperatorsFunction>['findOneAndUpdate'])(
93
107
  EJSON.deserialize(resolvedQuery),
94
108
  EJSON.deserialize(resolvedUpdate),
95
- resolvedOptions ? EJSON.deserialize(resolvedOptions) : undefined
109
+ parsedOptions
96
110
  ),
97
111
  aggregate: async () =>
98
112
  (await (currentMethod as ReturnType<GetOperatorsFunction>['aggregate'])(
@@ -1,3 +1,4 @@
1
+ import get from 'lodash/get'
1
2
  import isEqual from 'lodash/isEqual'
2
3
  import {
3
4
  Collection,
@@ -42,6 +43,34 @@ const logService = (message: string, payload?: unknown) => {
42
43
  console.log('[service-debug]', message, payload ?? '')
43
44
  }
44
45
 
46
+ const getUpdatedPaths = (update: Document) => {
47
+ const entries = Object.entries(update ?? {})
48
+ const hasOperators = entries.some(([key]) => key.startsWith('$'))
49
+
50
+ if (!hasOperators) {
51
+ return Object.keys(update ?? {})
52
+ }
53
+
54
+ const paths = new Set<string>()
55
+ for (const [key, value] of entries) {
56
+ if (!key.startsWith('$')) continue
57
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
58
+ Object.keys(value as Document).forEach((path) => paths.add(path))
59
+ }
60
+ }
61
+ return [...paths]
62
+ }
63
+
64
+ const areUpdatedFieldsAllowed = (
65
+ filtered: Document | null | undefined,
66
+ updated: Document,
67
+ updatedPaths: string[]
68
+ ) => {
69
+ if (!filtered) return false
70
+ if (!updatedPaths.length) return isEqual(filtered, updated)
71
+ return updatedPaths.every((path) => isEqual(get(filtered, path), get(updated, path)))
72
+ }
73
+
45
74
  const getOperators: GetOperatorsFunction = (
46
75
  collection,
47
76
  { rules, collName, user, run_as_system }
@@ -273,6 +302,7 @@ const getOperators: GetOperatorsFunction = (
273
302
 
274
303
  // Check if the update data contains MongoDB update operators (e.g., $set, $inc)
275
304
  const hasOperators = Object.keys(data).some((key) => key.startsWith('$'))
305
+ const updatedPaths = getUpdatedPaths(data as Document)
276
306
 
277
307
  // Flatten the update object to extract the actual fields being modified
278
308
  // const docToCheck = hasOperators
@@ -304,7 +334,7 @@ const getOperators: GetOperatorsFunction = (
304
334
  )
305
335
  : fallbackAccess(docToCheck)
306
336
  // Ensure no unauthorized changes are made
307
- const areDocumentsEqual = isEqual(document, docToCheck)
337
+ const areDocumentsEqual = areUpdatedFieldsAllowed(document, docToCheck, updatedPaths)
308
338
 
309
339
  if (!status || !areDocumentsEqual) {
310
340
  throw new Error('Update not permitted')
@@ -343,6 +373,7 @@ const getOperators: GetOperatorsFunction = (
343
373
 
344
374
  const winningRole = getWinningRole(result, user, roles)
345
375
  const hasOperators = Object.keys(data).some((key) => key.startsWith('$'))
376
+ const updatedPaths = Array.isArray(data) ? [] : getUpdatedPaths(data as Document)
346
377
  const pipeline = [
347
378
  {
348
379
  $match: { $and: safeQuery }
@@ -369,7 +400,7 @@ const getOperators: GetOperatorsFunction = (
369
400
  )
370
401
  : fallbackAccess(docToCheck)
371
402
 
372
- const areDocumentsEqual = isEqual(document, docToCheck)
403
+ const areDocumentsEqual = areUpdatedFieldsAllowed(document, docToCheck, updatedPaths)
373
404
  if (!status || !areDocumentsEqual) {
374
405
  throw new Error('Update not permitted')
375
406
  }
@@ -704,6 +735,7 @@ const getOperators: GetOperatorsFunction = (
704
735
 
705
736
  // Check if the update data contains MongoDB update operators (e.g., $set, $inc)
706
737
  const hasOperators = Object.keys(data).some((key) => key.startsWith('$'))
738
+ const updatedPaths = getUpdatedPaths(data as Document)
707
739
 
708
740
  // Flatten the update object to extract the actual fields being modified
709
741
  // const docToCheck = hasOperators
@@ -743,7 +775,9 @@ const getOperators: GetOperatorsFunction = (
743
775
  )
744
776
 
745
777
  // Ensure no unauthorized changes are made
746
- const areDocumentsEqual = isEqual(docsToCheck, filteredItems)
778
+ const areDocumentsEqual = docsToCheck.every((doc, index) =>
779
+ areUpdatedFieldsAllowed(filteredItems[index], doc, updatedPaths)
780
+ )
747
781
 
748
782
  if (!areDocumentsEqual) {
749
783
  console.log('check1 In updateMany --> (!areDocumentsEqual)')
@@ -24,7 +24,7 @@ export const comparePassword = async (plaintext: string, storedPassword: string)
24
24
  const [storedHash, storedSalt] = storedPassword.split('.')
25
25
 
26
26
  if (!storedHash || !storedSalt) {
27
- throw new Error(`Invalid stored password: ${storedPassword}`);
27
+ throw new Error('Invalid credentials');
28
28
  }
29
29
 
30
30
  const storedBuffer = Buffer.from(storedHash, 'hex')