@flowerforce/flowerbase 1.8.3 → 1.8.4-beta.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.
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs'
2
2
  import path from 'node:path'
3
3
  import cron from 'node-cron'
4
+ import { EJSON } from 'bson'
4
5
  import { AUTH_CONFIG, AUTH_DB_NAME, DB_NAME, CHANGESTREAM } from '../../constants'
5
6
  import { createEventId, sanitize } from '../../monitoring/utils'
6
7
  import { StateManager } from '../../state'
@@ -8,6 +9,9 @@ import { readJsonContent } from '../../utils'
8
9
  import { GenerateContext } from '../../utils/context'
9
10
  import { HandlerParams, Trigger, Triggers } from './interface'
10
11
 
12
+ const normalizeTriggerPayload = <T>(value: T): T =>
13
+ EJSON.deserialize(EJSON.serialize(value, { relaxed: false })) as T
14
+
11
15
  const registerOnClose = (
12
16
  app: HandlerParams['app'],
13
17
  handler: () => Promise<void> | void,
@@ -150,7 +154,8 @@ const handleCronTrigger = async ({
150
154
  currentFunction: triggerHandler,
151
155
  functionName,
152
156
  functionsList,
153
- services
157
+ services,
158
+ deserializeArgs: false
154
159
  })
155
160
  } catch (error) {
156
161
  emitTriggerEvent({
@@ -376,7 +381,7 @@ const handleAuthenticationTrigger = async ({
376
381
  meta: { ...baseMeta, event: 'LOGOUT' }
377
382
  })
378
383
  await GenerateContext({
379
- args: [{ user: userData, ...op }],
384
+ args: [normalizeTriggerPayload({ user: userData, ...op })],
380
385
  app,
381
386
  rules: StateManager.select("rules"),
382
387
  user: {}, // TODO from currentUser ??
@@ -384,7 +389,8 @@ const handleAuthenticationTrigger = async ({
384
389
  functionName,
385
390
  functionsList,
386
391
  services,
387
- runAsSystem: true
392
+ runAsSystem: true,
393
+ deserializeArgs: false
388
394
  })
389
395
  } catch (error) {
390
396
  emitTriggerEvent({
@@ -395,7 +401,6 @@ const handleAuthenticationTrigger = async ({
395
401
  meta: { ...baseMeta, event: 'LOGOUT' },
396
402
  error
397
403
  })
398
- console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error)
399
404
  }
400
405
  return
401
406
  }
@@ -433,7 +438,7 @@ const handleAuthenticationTrigger = async ({
433
438
  meta: { ...baseMeta, event: 'LOGIN' }
434
439
  })
435
440
  await GenerateContext({
436
- args: [{ user: userData, ...op }],
441
+ args: [normalizeTriggerPayload({ user: userData, ...op })],
437
442
  app,
438
443
  rules: StateManager.select("rules"),
439
444
  user: {}, // TODO from currentUser ??
@@ -441,7 +446,8 @@ const handleAuthenticationTrigger = async ({
441
446
  functionName,
442
447
  functionsList,
443
448
  services,
444
- runAsSystem: true
449
+ runAsSystem: true,
450
+ deserializeArgs: false
445
451
  })
446
452
  } catch (error) {
447
453
  emitTriggerEvent({
@@ -452,7 +458,6 @@ const handleAuthenticationTrigger = async ({
452
458
  meta: { ...baseMeta, event: 'LOGIN' },
453
459
  error
454
460
  })
455
- console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error)
456
461
  }
457
462
  return
458
463
  }
@@ -485,7 +490,9 @@ const handleAuthenticationTrigger = async ({
485
490
  meta: { ...baseMeta, event: 'DELETE' }
486
491
  })
487
492
  await GenerateContext({
488
- args: isAutoTrigger ? [userData] : [{ user: userData, ...op }],
493
+ args: isAutoTrigger
494
+ ? [normalizeTriggerPayload(userData)]
495
+ : [normalizeTriggerPayload({ user: userData, ...op })],
489
496
  app,
490
497
  rules: StateManager.select("rules"),
491
498
  user: {}, // TODO from currentUser ??
@@ -493,7 +500,8 @@ const handleAuthenticationTrigger = async ({
493
500
  functionName,
494
501
  functionsList,
495
502
  services,
496
- runAsSystem: true
503
+ runAsSystem: true,
504
+ deserializeArgs: false
497
505
  })
498
506
  } catch (error) {
499
507
  emitTriggerEvent({
@@ -504,7 +512,6 @@ const handleAuthenticationTrigger = async ({
504
512
  meta: { ...baseMeta, event: 'DELETE' },
505
513
  error
506
514
  })
507
- console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error)
508
515
  }
509
516
  return
510
517
  }
@@ -539,7 +546,9 @@ const handleAuthenticationTrigger = async ({
539
546
  meta: { ...baseMeta, event: 'UPDATE' }
540
547
  })
541
548
  await GenerateContext({
542
- args: isAutoTrigger ? [userData] : [{ user: userData, ...op }],
549
+ args: isAutoTrigger
550
+ ? [normalizeTriggerPayload(userData)]
551
+ : [normalizeTriggerPayload({ user: userData, ...op })],
543
552
  app,
544
553
  rules: StateManager.select("rules"),
545
554
  user: {}, // TODO from currentUser ??
@@ -547,7 +556,8 @@ const handleAuthenticationTrigger = async ({
547
556
  functionName,
548
557
  functionsList,
549
558
  services,
550
- runAsSystem: true
559
+ runAsSystem: true,
560
+ deserializeArgs: false
551
561
  })
552
562
  } catch (error) {
553
563
  emitTriggerEvent({
@@ -558,7 +568,6 @@ const handleAuthenticationTrigger = async ({
558
568
  meta: { ...baseMeta, event: 'UPDATE' },
559
569
  error
560
570
  })
561
- console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error)
562
571
  }
563
572
  return
564
573
  }
@@ -643,7 +652,9 @@ const handleAuthenticationTrigger = async ({
643
652
  meta: { ...baseMeta, event: 'CREATE' }
644
653
  })
645
654
  await GenerateContext({
646
- args: isAutoTrigger ? [userData] : [{ user: userData, ...op }],
655
+ args: isAutoTrigger
656
+ ? [normalizeTriggerPayload(userData)]
657
+ : [normalizeTriggerPayload({ user: userData, ...op })],
647
658
  app,
648
659
  rules: StateManager.select("rules"),
649
660
  user: {}, // TODO from currentUser ??
@@ -651,7 +662,8 @@ const handleAuthenticationTrigger = async ({
651
662
  functionName,
652
663
  functionsList,
653
664
  services,
654
- runAsSystem: true
665
+ runAsSystem: true,
666
+ deserializeArgs: false
655
667
  })
656
668
  } catch (error) {
657
669
  emitTriggerEvent({
@@ -662,7 +674,6 @@ const handleAuthenticationTrigger = async ({
662
674
  meta: { ...baseMeta, event: 'CREATE' },
663
675
  error
664
676
  })
665
- console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error)
666
677
  }
667
678
  })
668
679
  registerOnClose(
@@ -755,14 +766,15 @@ const handleDataBaseTrigger = async ({
755
766
  })
756
767
  try {
757
768
  await GenerateContext({
758
- args: [change],
769
+ args: [normalizeTriggerPayload(change)],
759
770
  app,
760
771
  rules: StateManager.select("rules"),
761
772
  user: {}, // TODO add from?
762
773
  currentFunction: triggerHandler,
763
774
  functionName,
764
775
  functionsList,
765
- services
776
+ services,
777
+ deserializeArgs: false
766
778
  })
767
779
  } catch (error) {
768
780
  emitTriggerEvent({
@@ -19,7 +19,7 @@ import {
19
19
  import { Rules } from '../../features/rules/interface'
20
20
  import { buildRulesMeta } from '../../monitoring/utils'
21
21
  import { checkValidation } from '../../utils/roles/machines'
22
- import { getWinningRole } from '../../utils/roles/machines/utils'
22
+ import { getWinningRoleAsync } from '../../utils/roles/machines/utils'
23
23
  import { emitServiceEvent } from '../monitoring'
24
24
  import { CHANGESTREAM } from '../../constants'
25
25
  import {
@@ -561,9 +561,9 @@ const getOperators: GetOperatorsFunction = (
561
561
  const resolvedOptions =
562
562
  projection || normalizedOptions
563
563
  ? {
564
- ...(normalizedOptions ?? {}),
565
- ...(projection ? { projection } : {})
566
- }
564
+ ...(normalizedOptions ?? {}),
565
+ ...(projection ? { projection } : {})
566
+ }
567
567
  : undefined
568
568
  const resolvedQuery = query ?? {}
569
569
  if (!run_as_system) {
@@ -604,7 +604,7 @@ const getOperators: GetOperatorsFunction = (
604
604
  return null
605
605
  }
606
606
 
607
- const winningRole = getWinningRole(result, user, roles)
607
+ const winningRole = await getWinningRoleAsync(result, user, roles)
608
608
 
609
609
  logDebug('findOne winningRole', {
610
610
  collection: collName,
@@ -613,15 +613,15 @@ const getOperators: GetOperatorsFunction = (
613
613
  })
614
614
  const { status, document } = winningRole
615
615
  ? await checkValidation(
616
- winningRole,
617
- {
618
- type: 'read',
619
- roles,
620
- cursor: result,
621
- expansions: getValidationExpansions(result)
622
- },
623
- user
624
- )
616
+ winningRole,
617
+ {
618
+ type: 'read',
619
+ roles,
620
+ cursor: result,
621
+ expansions: getValidationExpansions(result)
622
+ },
623
+ user
624
+ )
625
625
  : fallbackAccess(result)
626
626
 
627
627
  // Return validated document or empty object if not permitted
@@ -669,7 +669,7 @@ const getOperators: GetOperatorsFunction = (
669
669
 
670
670
  // Retrieve the document to check permissions before deleting
671
671
  const result = await collection.findOne(buildAndQuery(formattedQuery))
672
- const winningRole = getWinningRole(result, user, roles)
672
+ const winningRole = await getWinningRoleAsync(result, user, roles)
673
673
 
674
674
  logDebug('delete winningRole', {
675
675
  collection: collName,
@@ -678,15 +678,15 @@ const getOperators: GetOperatorsFunction = (
678
678
  })
679
679
  const { status } = winningRole
680
680
  ? await checkValidation(
681
- winningRole,
682
- {
683
- type: 'delete',
684
- roles,
685
- cursor: result,
686
- expansions: getValidationExpansions(result)
687
- },
688
- user
689
- )
681
+ winningRole,
682
+ {
683
+ type: 'delete',
684
+ roles,
685
+ cursor: result,
686
+ expansions: getValidationExpansions(result)
687
+ },
688
+ user
689
+ )
690
690
  : fallbackAccess(result)
691
691
 
692
692
  if (!status) {
@@ -733,19 +733,19 @@ const getOperators: GetOperatorsFunction = (
733
733
  collection.collectionName,
734
734
  CRUD_OPERATIONS.CREATE
735
735
  )
736
- const winningRole = getWinningRole(data, user, roles)
736
+ const winningRole = await getWinningRoleAsync(data, user, roles)
737
737
 
738
738
  const { status, document } = winningRole
739
739
  ? await checkValidation(
740
- winningRole,
741
- {
742
- type: 'insert',
743
- roles,
744
- cursor: data,
745
- expansions: getValidationExpansions()
746
- },
747
- user
748
- )
740
+ winningRole,
741
+ {
742
+ type: 'insert',
743
+ roles,
744
+ cursor: data,
745
+ expansions: getValidationExpansions()
746
+ },
747
+ user
748
+ )
749
749
  : fallbackAccess(data)
750
750
 
751
751
  if (!status || !isEqual(data, document)) {
@@ -823,7 +823,7 @@ const getOperators: GetOperatorsFunction = (
823
823
  throw new Error('Update not permitted')
824
824
  }
825
825
 
826
- const winningRole = getWinningRole(result, user, roles)
826
+ const winningRole = await getWinningRoleAsync(result, user, roles)
827
827
 
828
828
  // Check if the update data contains MongoDB update operators (e.g., $set, $inc)
829
829
  const updatedPaths = getUpdatedPaths(normalizedData)
@@ -831,15 +831,15 @@ const getOperators: GetOperatorsFunction = (
831
831
  // Validate update permissions
832
832
  const { status, document } = winningRole
833
833
  ? await checkValidation(
834
- winningRole,
835
- {
836
- type: 'write',
837
- roles,
838
- cursor: docToCheck,
839
- expansions: getValidationExpansions(result)
840
- },
841
- user
842
- )
834
+ winningRole,
835
+ {
836
+ type: 'write',
837
+ roles,
838
+ cursor: docToCheck,
839
+ expansions: getValidationExpansions(result)
840
+ },
841
+ user
842
+ )
843
843
  : fallbackAccess(docToCheck)
844
844
  // Ensure no unauthorized changes are made
845
845
  const areDocumentsEqual = areUpdatedFieldsAllowed(
@@ -918,31 +918,31 @@ const getOperators: GetOperatorsFunction = (
918
918
  } else {
919
919
  const [computedDoc] = Array.isArray(normalizedData)
920
920
  ? await collection
921
- .aggregate([
922
- { $match: buildAndQuery(safeQuery) },
923
- { $limit: 1 },
924
- ...normalizedData
925
- ])
926
- .toArray()
921
+ .aggregate([
922
+ { $match: buildAndQuery(safeQuery) },
923
+ { $limit: 1 },
924
+ ...normalizedData
925
+ ])
926
+ .toArray()
927
927
  : [applyDocumentUpdateOperators(currentDoc, normalizedData as Document)]
928
928
  docToCheck = computedDoc
929
929
  }
930
930
 
931
- const winningRole = getWinningRole(docToCheck, user, roles)
931
+ const winningRole = await getWinningRoleAsync(docToCheck, user, roles)
932
932
 
933
933
  const { status, document } = winningRole
934
934
  ? await checkValidation(
935
- winningRole,
936
- {
937
- type: validationType,
938
- roles,
939
- cursor: docToCheck,
940
- expansions: getValidationExpansions(
941
- validationType === 'insert' ? undefined : currentDoc
942
- )
943
- },
944
- user
945
- )
935
+ winningRole,
936
+ {
937
+ type: validationType,
938
+ roles,
939
+ cursor: docToCheck,
940
+ expansions: getValidationExpansions(
941
+ validationType === 'insert' ? undefined : currentDoc
942
+ )
943
+ },
944
+ user
945
+ )
946
946
  : fallbackAccess(docToCheck)
947
947
 
948
948
  const areDocumentsEqual = areUpdatedFieldsAllowed(
@@ -956,28 +956,28 @@ const getOperators: GetOperatorsFunction = (
956
956
 
957
957
  const updateResult = normalizedOptions
958
958
  ? await collection.findOneAndUpdate(
959
- buildAndQuery(safeQuery),
960
- normalizedData,
961
- normalizedOptions
962
- )
959
+ buildAndQuery(safeQuery),
960
+ normalizedData,
961
+ normalizedOptions
962
+ )
963
963
  : await collection.findOneAndUpdate(buildAndQuery(safeQuery), normalizedData)
964
964
  if (!updateResult) {
965
965
  emitMongoEvent('findOneAndUpdate')
966
966
  return updateResult
967
967
  }
968
968
 
969
- const readRole = getWinningRole(updateResult, user, roles)
969
+ const readRole = await getWinningRoleAsync(updateResult, user, roles)
970
970
  const readResult = readRole
971
971
  ? await checkValidation(
972
- readRole,
973
- {
974
- type: 'read',
975
- roles,
976
- cursor: updateResult,
977
- expansions: getValidationExpansions(updateResult)
978
- },
979
- user
980
- )
972
+ readRole,
973
+ {
974
+ type: 'read',
975
+ roles,
976
+ cursor: updateResult,
977
+ expansions: getValidationExpansions(updateResult)
978
+ },
979
+ user
980
+ )
981
981
  : fallbackAccess(updateResult)
982
982
 
983
983
  const sanitizedDoc = readResult.status
@@ -1026,9 +1026,9 @@ const getOperators: GetOperatorsFunction = (
1026
1026
  const resolvedOptions =
1027
1027
  projection || normalizedOptions
1028
1028
  ? {
1029
- ...(normalizedOptions ?? {}),
1030
- ...(projection ? { projection } : {})
1031
- }
1029
+ ...(normalizedOptions ?? {}),
1030
+ ...(projection ? { projection } : {})
1031
+ }
1032
1032
  : undefined
1033
1033
  if (!run_as_system) {
1034
1034
  checkDenyOperation(
@@ -1053,7 +1053,7 @@ const getOperators: GetOperatorsFunction = (
1053
1053
 
1054
1054
  const filteredResponse = await Promise.all(
1055
1055
  response.map(async (currentDoc) => {
1056
- const winningRole = getWinningRole(currentDoc, user, roles)
1056
+ const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1057
1057
 
1058
1058
  logDebug('find winningRole', {
1059
1059
  collection: collName,
@@ -1063,15 +1063,15 @@ const getOperators: GetOperatorsFunction = (
1063
1063
  })
1064
1064
  const { status, document } = winningRole
1065
1065
  ? await checkValidation(
1066
- winningRole,
1067
- {
1068
- type: 'read',
1069
- roles,
1070
- cursor: currentDoc,
1071
- expansions: getValidationExpansions(currentDoc)
1072
- },
1073
- user
1074
- )
1066
+ winningRole,
1067
+ {
1068
+ type: 'read',
1069
+ roles,
1070
+ cursor: currentDoc,
1071
+ expansions: getValidationExpansions(currentDoc)
1072
+ },
1073
+ user
1074
+ )
1075
1075
  : fallbackAccess(currentDoc)
1076
1076
 
1077
1077
  return status ? document : undefined
@@ -1190,19 +1190,19 @@ const getOperators: GetOperatorsFunction = (
1190
1190
  const allowDeleteBypass = watchPipelineRequestsDelete(requestedPipeline)
1191
1191
  const firstStep = watchFormattedQuery.length
1192
1192
  ? {
1193
- $match: allowDeleteBypass
1194
- ? {
1195
- $or: [
1196
- {
1197
- $and: watchFormattedQuery
1198
- },
1199
- { operationType: 'delete' }
1200
- ]
1201
- }
1202
- : {
1193
+ $match: allowDeleteBypass
1194
+ ? {
1195
+ $or: [
1196
+ {
1203
1197
  $and: watchFormattedQuery
1204
- }
1205
- }
1198
+ },
1199
+ { operationType: 'delete' }
1200
+ ]
1201
+ }
1202
+ : {
1203
+ $and: watchFormattedQuery
1204
+ }
1205
+ }
1206
1206
  : undefined
1207
1207
 
1208
1208
  const formattedPipeline = [firstStep, ...requestedPipeline].filter(
@@ -1221,33 +1221,33 @@ const getOperators: GetOperatorsFunction = (
1221
1221
  const isValidChange = async (change: Document) => {
1222
1222
  const { fullDocument, updateDescription } = change
1223
1223
  const hasFullDocument = !!fullDocument
1224
- const winningRole = getWinningRole(fullDocument, user, roles)
1224
+ const winningRole = await getWinningRoleAsync(fullDocument, user, roles)
1225
1225
 
1226
1226
  const fullDocumentValidation = winningRole
1227
1227
  ? await checkValidation(
1228
- winningRole,
1229
- {
1230
- type: 'read',
1231
- roles,
1232
- cursor: fullDocument,
1233
- expansions: getValidationExpansions(fullDocument)
1234
- },
1235
- user
1236
- )
1228
+ winningRole,
1229
+ {
1230
+ type: 'read',
1231
+ roles,
1232
+ cursor: fullDocument,
1233
+ expansions: getValidationExpansions(fullDocument)
1234
+ },
1235
+ user
1236
+ )
1237
1237
  : fallbackAccess(fullDocument)
1238
1238
  const { status, document } = fullDocumentValidation
1239
1239
 
1240
1240
  const { status: updatedFieldsStatus, document: updatedFields } = winningRole
1241
1241
  ? await checkValidation(
1242
- winningRole,
1243
- {
1244
- type: 'read',
1245
- roles,
1246
- cursor: updateDescription?.updatedFields,
1247
- expansions: getValidationExpansions(fullDocument)
1248
- },
1249
- user
1250
- )
1242
+ winningRole,
1243
+ {
1244
+ type: 'read',
1245
+ roles,
1246
+ cursor: updateDescription?.updatedFields,
1247
+ expansions: getValidationExpansions(fullDocument)
1248
+ },
1249
+ user
1250
+ )
1251
1251
  : fallbackAccess(updateDescription?.updatedFields)
1252
1252
 
1253
1253
  return {
@@ -1389,19 +1389,19 @@ const getOperators: GetOperatorsFunction = (
1389
1389
  // Validate each document against user's roles
1390
1390
  const filteredItems = await Promise.all(
1391
1391
  documents.map(async (currentDoc) => {
1392
- const winningRole = getWinningRole(currentDoc, user, roles)
1392
+ const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1393
1393
 
1394
1394
  const { status, document } = winningRole
1395
1395
  ? await checkValidation(
1396
- winningRole,
1397
- {
1398
- type: 'insert',
1399
- roles,
1400
- cursor: currentDoc,
1401
- expansions: getValidationExpansions()
1402
- },
1403
- user
1404
- )
1396
+ winningRole,
1397
+ {
1398
+ type: 'insert',
1399
+ roles,
1400
+ cursor: currentDoc,
1401
+ expansions: getValidationExpansions()
1402
+ },
1403
+ user
1404
+ )
1405
1405
  : fallbackAccess(currentDoc)
1406
1406
 
1407
1407
  return status ? document : undefined
@@ -1453,19 +1453,19 @@ const getOperators: GetOperatorsFunction = (
1453
1453
 
1454
1454
  const filteredItems = await Promise.all(
1455
1455
  docsToCheck.map(async (currentDoc, index) => {
1456
- const winningRole = getWinningRole(currentDoc, user, roles)
1456
+ const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1457
1457
 
1458
1458
  const { status, document } = winningRole
1459
1459
  ? await checkValidation(
1460
- winningRole,
1461
- {
1462
- type: 'write',
1463
- roles,
1464
- cursor: currentDoc,
1465
- expansions: getValidationExpansions(result[index])
1466
- },
1467
- user
1468
- )
1460
+ winningRole,
1461
+ {
1462
+ type: 'write',
1463
+ roles,
1464
+ cursor: currentDoc,
1465
+ expansions: getValidationExpansions(result[index])
1466
+ },
1467
+ user
1468
+ )
1469
1469
  : fallbackAccess(currentDoc)
1470
1470
 
1471
1471
  return status ? document : undefined
@@ -1529,19 +1529,19 @@ const getOperators: GetOperatorsFunction = (
1529
1529
  // Filter and validate each document based on user's roles
1530
1530
  const filteredItems = await Promise.all(
1531
1531
  data.map(async (currentDoc) => {
1532
- const winningRole = getWinningRole(currentDoc, user, roles)
1532
+ const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
1533
1533
 
1534
1534
  const { status, document } = winningRole
1535
1535
  ? await checkValidation(
1536
- winningRole,
1537
- {
1538
- type: 'delete',
1539
- roles,
1540
- cursor: currentDoc,
1541
- expansions: getValidationExpansions(currentDoc)
1542
- },
1543
- user
1544
- )
1536
+ winningRole,
1537
+ {
1538
+ type: 'delete',
1539
+ roles,
1540
+ cursor: currentDoc,
1541
+ expansions: getValidationExpansions(currentDoc)
1542
+ },
1543
+ user
1544
+ )
1545
1545
  : fallbackAccess(currentDoc)
1546
1546
 
1547
1547
  return status ? document : undefined