@stoker-platform/utils 0.2.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.
Files changed (126) hide show
  1. package/lib/src/access/collection.d.ts +9 -0
  2. package/lib/src/access/collection.js +59 -0
  3. package/lib/src/access/document.d.ts +3 -0
  4. package/lib/src/access/document.js +191 -0
  5. package/lib/src/access/getCollectionRestrictions.d.ts +7 -0
  6. package/lib/src/access/getCollectionRestrictions.js +71 -0
  7. package/lib/src/access/getRecordSubcollections.d.ts +2 -0
  8. package/lib/src/access/getRecordSubcollections.js +15 -0
  9. package/lib/src/access/getRelatedCollections.d.ts +2 -0
  10. package/lib/src/access/getRelatedCollections.js +24 -0
  11. package/lib/src/access/hasDependencyAccess.d.ts +2 -0
  12. package/lib/src/access/hasDependencyAccess.js +24 -0
  13. package/lib/src/access/isPaginationEnabled.d.ts +2 -0
  14. package/lib/src/access/isPaginationEnabled.js +35 -0
  15. package/lib/src/access/permissions.d.ts +2 -0
  16. package/lib/src/access/permissions.js +543 -0
  17. package/lib/src/access/read/getOne.d.ts +2 -0
  18. package/lib/src/access/read/getOne.js +19 -0
  19. package/lib/src/access/read/getSome.d.ts +2 -0
  20. package/lib/src/access/read/getSome.js +21 -0
  21. package/lib/src/access/roleHasOperationAccess.d.ts +2 -0
  22. package/lib/src/access/roleHasOperationAccess.js +7 -0
  23. package/lib/src/access/write/addRecord.d.ts +2 -0
  24. package/lib/src/access/write/addRecord.js +40 -0
  25. package/lib/src/access/write/deleteRecord.d.ts +2 -0
  26. package/lib/src/access/write/deleteRecord.js +26 -0
  27. package/lib/src/access/write/updateRecord.d.ts +2 -0
  28. package/lib/src/access/write/updateRecord.js +61 -0
  29. package/lib/src/getConfigValue.d.ts +12 -0
  30. package/lib/src/getConfigValue.js +83 -0
  31. package/lib/src/getCustomization.d.ts +4 -0
  32. package/lib/src/getCustomization.js +29 -0
  33. package/lib/src/getFieldCustomization.d.ts +7 -0
  34. package/lib/src/getFieldCustomization.js +3 -0
  35. package/lib/src/main.d.ts +60 -0
  36. package/lib/src/main.js +60 -0
  37. package/lib/src/operations/addInitialValues.d.ts +2 -0
  38. package/lib/src/operations/addInitialValues.js +27 -0
  39. package/lib/src/operations/addLowercaseFields.d.ts +2 -0
  40. package/lib/src/operations/addLowercaseFields.js +12 -0
  41. package/lib/src/operations/addRelationArrays.d.ts +2 -0
  42. package/lib/src/operations/addRelationArrays.js +60 -0
  43. package/lib/src/operations/addSystemFields.d.ts +2 -0
  44. package/lib/src/operations/addSystemFields.js +17 -0
  45. package/lib/src/operations/getDateRange.d.ts +5 -0
  46. package/lib/src/operations/getDateRange.js +56 -0
  47. package/lib/src/operations/getExtendedSchema.d.ts +2 -0
  48. package/lib/src/operations/getExtendedSchema.js +23 -0
  49. package/lib/src/operations/getFinalRecord.d.ts +1 -0
  50. package/lib/src/operations/getFinalRecord.js +11 -0
  51. package/lib/src/operations/getInputSchema.d.ts +15 -0
  52. package/lib/src/operations/getInputSchema.js +352 -0
  53. package/lib/src/operations/getLowercaseFields.d.ts +2 -0
  54. package/lib/src/operations/getLowercaseFields.js +15 -0
  55. package/lib/src/operations/getSingleFieldRelations.d.ts +2 -0
  56. package/lib/src/operations/getSingleFieldRelations.js +27 -0
  57. package/lib/src/operations/getZodSchema.d.ts +15 -0
  58. package/lib/src/operations/getZodSchema.js +303 -0
  59. package/lib/src/operations/isDeleteSentinel.d.ts +1 -0
  60. package/lib/src/operations/isDeleteSentinel.js +4 -0
  61. package/lib/src/operations/isSortingEnabled.d.ts +2 -0
  62. package/lib/src/operations/isSortingEnabled.js +7 -0
  63. package/lib/src/operations/isValidUniqueFieldValue.d.ts +1 -0
  64. package/lib/src/operations/isValidUniqueFieldValue.js +25 -0
  65. package/lib/src/operations/parseDate.d.ts +1 -0
  66. package/lib/src/operations/parseDate.js +4 -0
  67. package/lib/src/operations/prepareDenormalized.d.ts +8 -0
  68. package/lib/src/operations/prepareDenormalized.js +312 -0
  69. package/lib/src/operations/removeDeleteSentinels.d.ts +2 -0
  70. package/lib/src/operations/removeDeleteSentinels.js +15 -0
  71. package/lib/src/operations/removeDeletedFields.d.ts +2 -0
  72. package/lib/src/operations/removeDeletedFields.js +14 -0
  73. package/lib/src/operations/removeEmptyStrings.d.ts +2 -0
  74. package/lib/src/operations/removeEmptyStrings.js +14 -0
  75. package/lib/src/operations/removePrivateFields.d.ts +2 -0
  76. package/lib/src/operations/removePrivateFields.js +14 -0
  77. package/lib/src/operations/removeUndefined.d.ts +2 -0
  78. package/lib/src/operations/removeUndefined.js +14 -0
  79. package/lib/src/operations/retryOperation.d.ts +1 -0
  80. package/lib/src/operations/retryOperation.js +21 -0
  81. package/lib/src/operations/runHooks.d.ts +17 -0
  82. package/lib/src/operations/runHooks.js +18 -0
  83. package/lib/src/operations/sanitizeDownloadFilename.d.ts +1 -0
  84. package/lib/src/operations/sanitizeDownloadFilename.js +18 -0
  85. package/lib/src/operations/sanitizeEmailInput.d.ts +5 -0
  86. package/lib/src/operations/sanitizeEmailInput.js +73 -0
  87. package/lib/src/operations/updateFieldReference.d.ts +2 -0
  88. package/lib/src/operations/updateFieldReference.js +14 -0
  89. package/lib/src/operations/validateRecord.d.ts +2 -0
  90. package/lib/src/operations/validateRecord.js +18 -0
  91. package/lib/src/operations/validateStorageName.d.ts +1 -0
  92. package/lib/src/operations/validateStorageName.js +19 -0
  93. package/lib/src/schema/getAccessFields.d.ts +2 -0
  94. package/lib/src/schema/getAccessFields.js +62 -0
  95. package/lib/src/schema/getCollection.d.ts +2 -0
  96. package/lib/src/schema/getCollection.js +3 -0
  97. package/lib/src/schema/getDependencyFields.d.ts +6 -0
  98. package/lib/src/schema/getDependencyFields.js +22 -0
  99. package/lib/src/schema/getField.d.ts +2 -0
  100. package/lib/src/schema/getField.js +3 -0
  101. package/lib/src/schema/getFieldNames.d.ts +2 -0
  102. package/lib/src/schema/getFieldNames.js +3 -0
  103. package/lib/src/schema/getIndexFields.d.ts +9 -0
  104. package/lib/src/schema/getIndexFields.js +184 -0
  105. package/lib/src/schema/getInverseRelationType.d.ts +1 -0
  106. package/lib/src/schema/getInverseRelationType.js +15 -0
  107. package/lib/src/schema/getPathCollections.d.ts +2 -0
  108. package/lib/src/schema/getPathCollections.js +12 -0
  109. package/lib/src/schema/getRecordSystemFields.d.ts +2 -0
  110. package/lib/src/schema/getRecordSystemFields.js +11 -0
  111. package/lib/src/schema/getRelationLists.d.ts +5 -0
  112. package/lib/src/schema/getRelationLists.js +13 -0
  113. package/lib/src/schema/getSubcollections.d.ts +2 -0
  114. package/lib/src/schema/getSubcollections.js +9 -0
  115. package/lib/src/schema/getSystemFieldsSchema.d.ts +2 -0
  116. package/lib/src/schema/getSystemFieldsSchema.js +52 -0
  117. package/lib/src/schema/isDependencyField.d.ts +2 -0
  118. package/lib/src/schema/isDependencyField.js +18 -0
  119. package/lib/src/schema/isIncludedField.d.ts +2 -0
  120. package/lib/src/schema/isIncludedField.js +18 -0
  121. package/lib/src/schema/isRelationField.d.ts +2 -0
  122. package/lib/src/schema/isRelationField.js +3 -0
  123. package/lib/src/schema/system-fields.d.ts +2 -0
  124. package/lib/src/schema/system-fields.js +13 -0
  125. package/lib/tsconfig.tsbuildinfo +1 -0
  126. package/package.json +35 -0
@@ -0,0 +1,543 @@
1
+ import isEqual from "lodash/isEqual.js";
2
+ import { getField } from "../schema/getField.js";
3
+ import { isRelationField } from "../schema/isRelationField.js";
4
+ import { collectionAccess, collectionAuthAccess } from "./collection.js";
5
+ /* eslint-disable security/detect-object-injection */
6
+ const logErrors = true;
7
+ const validatePermissions = (schema, permissions) => {
8
+ let granted = true;
9
+ let errorDetails = "";
10
+ Object.keys(permissions).forEach((key) => {
11
+ if (!["Doc_ID", "Role", "Collection", "Enabled", "collections"].includes(key)) {
12
+ granted = false;
13
+ errorDetails = "Permissions object must contain Doc_ID, Role, Collection, Enabled, and collections";
14
+ return;
15
+ }
16
+ });
17
+ if (!permissions.collections)
18
+ return granted;
19
+ Object.keys(permissions.collections).forEach((key) => {
20
+ if (!Object.keys(schema.collections).includes(key)) {
21
+ errorDetails = `Collection ${key} not found in schema`;
22
+ granted = false;
23
+ return;
24
+ }
25
+ const permissionCollection = schema.collections[key];
26
+ const collectionPermissions = permissions.collections?.[key];
27
+ if (!collectionPermissions)
28
+ return;
29
+ if (!collectionPermissions.operations) {
30
+ errorDetails = `Collection ${key} does not have operations`;
31
+ granted = false;
32
+ return;
33
+ }
34
+ if (!Object.keys(collectionPermissions).every((operation) => [
35
+ "operations",
36
+ "auth",
37
+ "restrictEntities",
38
+ "individualEntities",
39
+ "parentEntities",
40
+ "parentPropertyEntities",
41
+ "recordProperty",
42
+ "recordUser",
43
+ "recordOwner",
44
+ ].includes(operation))) {
45
+ errorDetails = `Collection ${key} has invalid properties`;
46
+ granted = false;
47
+ return;
48
+ }
49
+ if (!collectionPermissions.operations.every((operation) => ["Read", "Create", "Update", "Delete"].includes(operation))) {
50
+ errorDetails = `Collection ${key} has invalid operations`;
51
+ granted = false;
52
+ return;
53
+ }
54
+ if (collectionPermissions.auth &&
55
+ (typeof collectionPermissions.auth !== "boolean" || !permissionCollection.auth)) {
56
+ errorDetails = `Collection ${key} has invalid auth value`;
57
+ granted = false;
58
+ return;
59
+ }
60
+ if (collectionPermissions.restrictEntities && !permissionCollection.access.entityRestrictions?.restrictions) {
61
+ errorDetails = `Collection ${key} has invalid restrictEntities value`;
62
+ granted = false;
63
+ return;
64
+ }
65
+ Object.entries(collectionPermissions).forEach(([operation, values]) => {
66
+ if (operation !== "operations" && operation !== "auth" && operation !== "restrictEntities") {
67
+ let restrictionType;
68
+ if (operation === "recordOwner") {
69
+ restrictionType = "Record_Owner";
70
+ }
71
+ else if (operation === "recordUser") {
72
+ restrictionType = "Record_User";
73
+ }
74
+ else if (operation === "recordProperty") {
75
+ restrictionType = "Record_Property";
76
+ }
77
+ else if (operation === "individualEntities") {
78
+ restrictionType = "Individual";
79
+ }
80
+ else if (operation === "parentEntities") {
81
+ restrictionType = "Parent";
82
+ }
83
+ else if (operation === "parentPropertyEntities") {
84
+ restrictionType = "Parent_Property";
85
+ }
86
+ const attributeRestrictions = permissionCollection.access.attributeRestrictions?.filter((restriction) => restriction.type === restrictionType &&
87
+ restriction.roles.some((role) => role.role === permissions.Role));
88
+ const entityRestrictions = permissionCollection.access.entityRestrictions?.restrictions?.filter((restriction) => restriction.type === restrictionType &&
89
+ restriction.roles.some((role) => role.role === permissions.Role));
90
+ if (permissionCollection.access.entityRestrictions?.assignable &&
91
+ !(Array.isArray(permissionCollection.access.entityRestrictions.assignable) &&
92
+ permissionCollection.access.entityRestrictions.assignable.every((assignable) => schema.config.roles.includes(assignable)))) {
93
+ errorDetails = `Collection ${key} has invalid entity restrictions assignable value`;
94
+ granted = false;
95
+ return;
96
+ }
97
+ if (attributeRestrictions?.length || entityRestrictions?.length) {
98
+ if (operation === "Record_Owner" ||
99
+ operation === "Record_User" ||
100
+ operation === "Record_Property") {
101
+ const restrictionKeys = Object.keys(values);
102
+ if (restrictionKeys.length !== 1 ||
103
+ restrictionKeys[0] !== "active" ||
104
+ typeof values.active !== "boolean") {
105
+ errorDetails = `Collection ${key} has invalid attribute restriction value`;
106
+ granted = false;
107
+ return;
108
+ }
109
+ }
110
+ if (operation === "Individual") {
111
+ if (!Array.isArray(values)) {
112
+ errorDetails = `Collection ${key} has invalid individual entities value`;
113
+ granted = false;
114
+ return;
115
+ }
116
+ }
117
+ if (operation === "Parent") {
118
+ const restrictionKeys = Object.keys(values);
119
+ if (!(restrictionKeys.length === 1 &&
120
+ Object.keys(schema.collections).includes(restrictionKeys[0]) &&
121
+ Array.isArray(values[restrictionKeys[0]]))) {
122
+ errorDetails = `Collection ${key} has invalid parent entities value`;
123
+ granted = false;
124
+ return;
125
+ }
126
+ }
127
+ if (operation === "Parent_Property") {
128
+ const restrictionKeys = Object.keys(values);
129
+ if (!(restrictionKeys.length === 1 &&
130
+ Object.keys(schema.collections).includes(restrictionKeys[0]) &&
131
+ typeof values[restrictionKeys[0]] === "object" &&
132
+ values[restrictionKeys[0]] !== null)) {
133
+ errorDetails = `Collection ${key} has invalid parent property entities value`;
134
+ granted = false;
135
+ return;
136
+ }
137
+ }
138
+ }
139
+ else {
140
+ errorDetails = `Collection ${key} has invalid restriction type ${operation}`;
141
+ granted = false;
142
+ return;
143
+ }
144
+ }
145
+ });
146
+ });
147
+ if (logErrors && errorDetails) {
148
+ console.error(`PERMISSION_DENIED: ${errorDetails}`);
149
+ }
150
+ return granted;
151
+ };
152
+ const enforceRoleRestrictions = (schema, permissions, role) => {
153
+ let granted = true;
154
+ let errorDetails = "";
155
+ Object.values(schema.collections)
156
+ .filter((authCollection) => authCollection.auth)
157
+ .forEach((authCollection) => {
158
+ const collectionPermissions = permissions.collections?.[authCollection.labels.collection];
159
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
160
+ if (collectionPermissions?.auth && !authCollection.access.auth?.includes(permissions.Role)) {
161
+ errorDetails = "Record cannot have auth access to collection";
162
+ granted = false;
163
+ return;
164
+ }
165
+ });
166
+ for (const collection of Object.values(schema.collections)) {
167
+ const { labels, access, fields } = collection;
168
+ const { operations, attributeRestrictions, entityRestrictions } = access;
169
+ const assignable = operations.assignable;
170
+ const collectionPermissions = permissions.collections?.[labels.collection];
171
+ const operationTypes = ["read", "create", "update", "delete"];
172
+ if (assignable !== true && !(typeof assignable === "object" && assignable.includes(role))) {
173
+ operationTypes.forEach((operationType) => {
174
+ const operationTypeUpper = (operationType.charAt(0).toUpperCase() + operationType.slice(1));
175
+ if (operationType !== "assignable") {
176
+ const hasAccessOperation = !!access.operations[operationType]?.includes(role);
177
+ const hasCollectionPermission = !!collectionPermissions?.operations.includes(operationTypeUpper);
178
+ if (!hasAccessOperation && hasCollectionPermission) {
179
+ errorDetails = `Collection ${labels.collection} has excess ${operationType} operation for role ${role}`;
180
+ granted = false;
181
+ return;
182
+ }
183
+ if (hasAccessOperation && !hasCollectionPermission) {
184
+ errorDetails = `Collection ${labels.collection} has missing ${operationType} operation for role ${role}`;
185
+ granted = false;
186
+ return;
187
+ }
188
+ }
189
+ });
190
+ }
191
+ else {
192
+ operationTypes.forEach((operationType) => {
193
+ const operationTypeUpper = (operationType.charAt(0).toUpperCase() + operationType.slice(1));
194
+ if (operationType !== "assignable") {
195
+ const hasAccessOperation = !!access.operations[operationType]?.includes(role);
196
+ const hasCollectionPermission = !!collectionPermissions?.operations.includes(operationTypeUpper);
197
+ if (!hasAccessOperation && hasCollectionPermission) {
198
+ errorDetails = `Collection ${labels.collection} has excess ${operationType} operation for role ${role}`;
199
+ granted = false;
200
+ return;
201
+ }
202
+ }
203
+ });
204
+ }
205
+ if (!attributeRestrictions && !entityRestrictions)
206
+ continue;
207
+ attributeRestrictions?.forEach((attributeRestriction) => {
208
+ for (const restrictionRole of attributeRestriction.roles) {
209
+ if (restrictionRole.role === role && !restrictionRole.assignable) {
210
+ if (attributeRestriction.type === "Record_Owner") {
211
+ const recordOwner = permissions.collections?.[labels.collection]?.recordOwner;
212
+ if (!recordOwner?.active) {
213
+ errorDetails = `Collection ${labels.collection} is missing Record_Owner restriction for role ${role}`;
214
+ granted = false;
215
+ return;
216
+ }
217
+ }
218
+ if (attributeRestriction.type === "Record_User") {
219
+ const recordUser = permissions.collections?.[labels.collection]?.recordUser;
220
+ if (!recordUser?.active) {
221
+ errorDetails = `Collection ${labels.collection} is missing Record_User restriction for role ${role}`;
222
+ granted = false;
223
+ return;
224
+ }
225
+ }
226
+ if (attributeRestriction.type === "Record_Property") {
227
+ const recordProperty = permissions.collections?.[labels.collection]?.recordProperty;
228
+ if (!recordProperty?.active) {
229
+ errorDetails = `Collection ${labels.collection} is missing Record_Property restriction for role ${role}`;
230
+ granted = false;
231
+ return;
232
+ }
233
+ }
234
+ }
235
+ }
236
+ });
237
+ if (entityRestrictions?.assignable?.includes(role))
238
+ continue;
239
+ let hasEntityRestriction = false;
240
+ entityRestrictions?.restrictions?.forEach((entityRestriction) => {
241
+ for (const restrictionRole of entityRestriction.roles) {
242
+ if (restrictionRole.role === role) {
243
+ hasEntityRestriction = true;
244
+ if (entityRestriction.type === "Individual") {
245
+ const individualEntities = permissions.collections?.[labels.collection]?.individualEntities;
246
+ if (!individualEntities) {
247
+ errorDetails = `Collection ${labels.collection} is missing individual entities`;
248
+ granted = false;
249
+ return;
250
+ }
251
+ }
252
+ if (entityRestriction.type === "Parent") {
253
+ const field = getField(fields, entityRestriction.collectionField);
254
+ if (!isRelationField(field)) {
255
+ granted = false;
256
+ return;
257
+ }
258
+ const parentEntities = permissions.collections?.[labels.collection]?.parentEntities;
259
+ if (!parentEntities) {
260
+ errorDetails = `Collection ${labels.collection} is missing parent entities`;
261
+ granted = false;
262
+ return;
263
+ }
264
+ }
265
+ if (entityRestriction.type === "Parent_Property") {
266
+ const field = getField(fields, entityRestriction.collectionField);
267
+ if (!isRelationField(field)) {
268
+ granted = false;
269
+ return;
270
+ }
271
+ const parentPropertyEntities = permissions.collections?.[labels.collection]?.parentPropertyEntities?.[field.collection];
272
+ if (!parentPropertyEntities) {
273
+ errorDetails = `Collection ${labels.collection} is missing parent property entities`;
274
+ granted = false;
275
+ return;
276
+ }
277
+ }
278
+ }
279
+ }
280
+ });
281
+ if (hasEntityRestriction && !permissions.collections?.[labels.collection]?.restrictEntities) {
282
+ errorDetails = `Collection ${labels.collection} must have restrictEntities set to true`;
283
+ granted = false;
284
+ return;
285
+ }
286
+ }
287
+ if (logErrors && errorDetails) {
288
+ console.error(`PERMISSION_DENIED: ${errorDetails}`);
289
+ }
290
+ return granted;
291
+ };
292
+ const restrictedPermissions = (operation, collectionSchema, schema, currentUserRole, role, permissions, originalRecord, originalRole, originalPermissions) => {
293
+ const { access } = collectionSchema;
294
+ const { permissionWriteRestrictions } = access;
295
+ let granted = true;
296
+ let errorDetails = "";
297
+ const writeableRoles = permissionWriteRestrictions?.filter((restriction) => currentUserRole === restriction.userRole);
298
+ if (writeableRoles?.length &&
299
+ !writeableRoles.some((restriction) => restriction.recordRole === role)) {
300
+ errorDetails = `User ${currentUserRole} does not have write access to record with role ${role}`;
301
+ granted = false;
302
+ return granted;
303
+ }
304
+ if (operation === "update" &&
305
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
306
+ originalRecord.User_ID &&
307
+ writeableRoles?.length &&
308
+ !writeableRoles.some((restriction) => restriction.recordRole === originalRole)) {
309
+ errorDetails = `User ${currentUserRole} does not have write access to record with role ${role}`;
310
+ granted = false;
311
+ return granted;
312
+ }
313
+ permissionWriteRestrictions?.forEach((restriction) => {
314
+ if (currentUserRole === restriction.userRole && restriction.recordRole === role) {
315
+ if (operation !== "delete" && permissions) {
316
+ Object.keys(schema.collections).forEach((collectionName) => {
317
+ const collectionPermissions = permissions.collections?.[collectionName];
318
+ const originalCollectionPermissions = originalPermissions?.collections?.[collectionName];
319
+ if (operation === "update" && isEqual(collectionPermissions, originalCollectionPermissions))
320
+ return;
321
+ if (collectionPermissions) {
322
+ const collectionPermission = restriction.collections.some((collection) => collection.collection === collectionName);
323
+ if (!collectionPermission) {
324
+ errorDetails = `User ${currentUserRole} does not have write access to collection ${collectionName}`;
325
+ granted = false;
326
+ return;
327
+ }
328
+ else if (collectionPermission) {
329
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
330
+ const collectionRestrictions = restriction.collections.find((collection) => collection.collection === collectionName);
331
+ if (!(collectionPermissions.operations.every((operation) => collectionRestrictions.operations.includes(operation)) ||
332
+ (operation === "update" &&
333
+ isEqual(collectionPermissions.operations, originalCollectionPermissions?.operations)))) {
334
+ errorDetails = `User ${currentUserRole} does not have write access to all included operations for collection ${collectionName}`;
335
+ granted = false;
336
+ return;
337
+ }
338
+ if (!collectionRestrictions.auth &&
339
+ collectionPermissions.auth &&
340
+ !(operation === "update" &&
341
+ isEqual(collectionPermissions.auth, originalCollectionPermissions?.auth))) {
342
+ errorDetails = `User ${currentUserRole} does not have write access to auth for collection ${collectionName}`;
343
+ granted = false;
344
+ return;
345
+ }
346
+ const attributeRestrictionKeys = {
347
+ recordOwner: "Record_Owner",
348
+ recordUser: "Record_User",
349
+ recordProperty: "Record_Property",
350
+ };
351
+ Object.keys(attributeRestrictionKeys).forEach((attributeRestrictionKey) => {
352
+ const attributeRestrictionType = attributeRestrictionKeys[attributeRestrictionKey];
353
+ if (collectionRestrictions.attributeRestrictions?.includes(attributeRestrictionType) &&
354
+ !(collectionPermissions[attributeRestrictionKey]?.active ||
355
+ (operation === "update" &&
356
+ isEqual(collectionPermissions[attributeRestrictionKey], originalCollectionPermissions?.[attributeRestrictionKey])))) {
357
+ errorDetails = `User ${currentUserRole} does not have write access to attribute restriction ${attributeRestrictionKey} for collection ${collectionName}`;
358
+ granted = false;
359
+ return;
360
+ }
361
+ });
362
+ if (collectionRestrictions.restrictEntities &&
363
+ !collectionPermissions.restrictEntities &&
364
+ !(operation === "update" &&
365
+ isEqual(collectionPermissions.restrictEntities, originalCollectionPermissions?.restrictEntities))) {
366
+ errorDetails = `User ${currentUserRole} does not have write access to restrictEntities for collection ${collectionName}`;
367
+ granted = false;
368
+ return;
369
+ }
370
+ }
371
+ }
372
+ });
373
+ }
374
+ }
375
+ });
376
+ if (logErrors && errorDetails) {
377
+ console.error(`PERMISSION_DENIED: ${errorDetails}`);
378
+ }
379
+ return granted;
380
+ };
381
+ const restrictPrivileges = (schema, currentUserPermissions, permissions, originalPermissions) => {
382
+ let granted = true;
383
+ let errorDetails = "";
384
+ Object.values(schema.collections).forEach((collection) => {
385
+ const collectionPermissions = permissions.collections?.[collection.labels.collection];
386
+ const originalCollectionPermissions = originalPermissions?.collections?.[collection.labels.collection];
387
+ const currentUserCollectionPermissions = currentUserPermissions?.collections?.[collection.labels.collection];
388
+ if (collectionPermissions?.auth &&
389
+ !originalCollectionPermissions?.auth &&
390
+ !collectionAuthAccess(currentUserCollectionPermissions)) {
391
+ errorDetails = `User does not have auth access to collection ${collection.labels.collection}`;
392
+ granted = false;
393
+ return;
394
+ }
395
+ for (const operation of collectionPermissions?.operations || []) {
396
+ if (!originalCollectionPermissions?.operations.includes(operation)) {
397
+ if (!collectionAccess("Read", currentUserCollectionPermissions) ||
398
+ !collectionAccess(operation, currentUserCollectionPermissions)) {
399
+ errorDetails = `User does not have ${operation.toLowerCase()} access to collection ${collection.labels.collection}`;
400
+ granted = false;
401
+ return;
402
+ }
403
+ }
404
+ }
405
+ if (!collectionPermissions?.recordOwner &&
406
+ originalCollectionPermissions?.recordOwner &&
407
+ currentUserCollectionPermissions?.recordOwner) {
408
+ errorDetails = `User does not have write access to recordOwner for collection ${collection.labels.collection}`;
409
+ granted = false;
410
+ return;
411
+ }
412
+ if (!collectionPermissions?.recordUser &&
413
+ originalCollectionPermissions?.recordUser &&
414
+ currentUserCollectionPermissions?.recordUser) {
415
+ errorDetails = `User does not have write access to recordUser for collection ${collection.labels.collection}`;
416
+ granted = false;
417
+ return;
418
+ }
419
+ if (!collectionPermissions?.recordProperty &&
420
+ originalCollectionPermissions?.recordProperty &&
421
+ currentUserCollectionPermissions?.recordProperty) {
422
+ errorDetails = `User does not have write access to recordProperty for collection ${collection.labels.collection}`;
423
+ granted = false;
424
+ return;
425
+ }
426
+ if (!collectionPermissions?.restrictEntities &&
427
+ originalCollectionPermissions?.restrictEntities &&
428
+ currentUserCollectionPermissions?.restrictEntities) {
429
+ errorDetails = `User does not have write access to restrictEntities for collection ${collection.labels.collection}`;
430
+ granted = false;
431
+ return;
432
+ }
433
+ });
434
+ if (logErrors && errorDetails) {
435
+ console.error(`PERMISSION_DENIED: ${errorDetails}`);
436
+ }
437
+ return granted;
438
+ };
439
+ /* eslint-enable security/detect-object-injection */
440
+ export const permissionsWriteAccess = (operation, record, docId, collectionSchema, schema, currentUserId, currentUserPermissions, permissions, originalPermissions, originalRecord, userOperation) => {
441
+ const { labels } = collectionSchema;
442
+ let granted = true;
443
+ let errorDetails = "";
444
+ if (!record.Role) {
445
+ errorDetails = "Record does not have a role";
446
+ granted = false;
447
+ return granted;
448
+ }
449
+ // Ensure original records are provided for update operations
450
+ if (operation === "update" && !originalRecord) {
451
+ errorDetails = "Original record is required for update operations";
452
+ granted = false;
453
+ return granted;
454
+ }
455
+ if (operation === "update" && currentUserId && userOperation === "update" && permissions && !originalPermissions) {
456
+ errorDetails = "Original permissions are required for this operation";
457
+ granted = false;
458
+ return granted;
459
+ }
460
+ // Operations that assess the current user's permissions
461
+ if (currentUserId) {
462
+ if (!currentUserPermissions?.Role) {
463
+ errorDetails = "Current user does not have a role";
464
+ granted = false;
465
+ return granted;
466
+ }
467
+ if (!restrictedPermissions(operation, collectionSchema, schema, currentUserPermissions.Role, record.Role, permissions, originalRecord, originalRecord?.Role, originalPermissions)) {
468
+ granted = false;
469
+ return granted;
470
+ }
471
+ if (operation === "update" && userOperation !== "delete" && originalRecord?.User_ID) {
472
+ if (record.User_ID !== originalRecord.User_ID) {
473
+ errorDetails = "User ID does not match original user ID";
474
+ granted = false;
475
+ return granted;
476
+ }
477
+ }
478
+ if (operation === "update" && originalRecord) {
479
+ if (originalRecord.User_ID) {
480
+ if (currentUserId === originalRecord.User_ID &&
481
+ (permissions ||
482
+ record.Role !== originalRecord.Role ||
483
+ (record.Enabled !== undefined && record.Enabled !== originalRecord.Enabled))) {
484
+ errorDetails = "User cannot update their own record";
485
+ granted = false;
486
+ return granted;
487
+ }
488
+ }
489
+ }
490
+ if (operation === "delete") {
491
+ if (currentUserId === record.User_ID) {
492
+ errorDetails = "User cannot delete their own record";
493
+ granted = false;
494
+ return granted;
495
+ }
496
+ }
497
+ }
498
+ // Permissions and record validation
499
+ if (operation === "delete" || userOperation === "delete" || !permissions) {
500
+ return granted;
501
+ }
502
+ if (!permissions.Role) {
503
+ errorDetails = "Permissions do not have a role";
504
+ granted = false;
505
+ return granted;
506
+ }
507
+ if (!validatePermissions(schema, permissions))
508
+ granted = false;
509
+ if (permissions.Collection && permissions.Collection !== labels.collection) {
510
+ errorDetails = "Permissions collection does not match record collection";
511
+ granted = false;
512
+ return granted;
513
+ }
514
+ if (permissions.Role !== record.Role) {
515
+ errorDetails = "Permissions role does not match record role";
516
+ granted = false;
517
+ return granted;
518
+ }
519
+ if (permissions.Enabled !== undefined && !!permissions.Enabled !== !!record.Enabled) {
520
+ errorDetails = "Permissions enabled state does not match record enabled state";
521
+ granted = false;
522
+ return granted;
523
+ }
524
+ if (permissions.Doc_ID && permissions.Doc_ID !== docId) {
525
+ errorDetails = "Permissions doc ID does not match record doc ID";
526
+ granted = false;
527
+ return granted;
528
+ }
529
+ if (!enforceRoleRestrictions(schema, permissions, permissions.Role)) {
530
+ granted = false;
531
+ return granted;
532
+ }
533
+ if (currentUserId && currentUserPermissions && originalPermissions) {
534
+ if (!restrictPrivileges(schema, currentUserPermissions, permissions, originalPermissions)) {
535
+ granted = false;
536
+ return granted;
537
+ }
538
+ }
539
+ if (logErrors && errorDetails) {
540
+ console.error(`PERMISSION_DENIED: ${errorDetails}`);
541
+ }
542
+ return granted;
543
+ };
@@ -0,0 +1,2 @@
1
+ import { CollectionSchema, CollectionsSchema, StokerPermissions, StokerRecord } from "@stoker-platform/types";
2
+ export declare const getOneAccessControl: (record: StokerRecord, collectionSchema: CollectionSchema, schema: CollectionsSchema, userId: string, permissions: StokerPermissions) => Promise<void>;
@@ -0,0 +1,19 @@
1
+ import { collectionAccess } from "../collection.js";
2
+ import { documentAccess } from "../document.js";
3
+ export const getOneAccessControl = async (record, collectionSchema, schema, userId, permissions) => {
4
+ const { labels } = collectionSchema;
5
+ // eslint-disable-next-line security/detect-object-injection
6
+ const collectionPermissions = permissions.collections?.[labels.collection];
7
+ let granted = true;
8
+ if (!collectionPermissions) {
9
+ granted = false;
10
+ return;
11
+ }
12
+ if (!collectionAccess("Read", collectionPermissions))
13
+ granted = false;
14
+ if (!documentAccess("Read", collectionSchema, schema, userId, permissions, record))
15
+ granted = false;
16
+ if (!granted)
17
+ throw new Error("PERMISSION_DENIED");
18
+ return;
19
+ };
@@ -0,0 +1,2 @@
1
+ import { CollectionSchema, CollectionsSchema, StokerPermissions, StokerRecord } from "@stoker-platform/types";
2
+ export declare const getSomeAccessControl: (documents: StokerRecord[], collectionSchema: CollectionSchema, schema: CollectionsSchema, userId: string, permissions: StokerPermissions) => Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { collectionAccess } from "../collection.js";
2
+ import { documentAccess } from "../document.js";
3
+ export const getSomeAccessControl = async (documents, collectionSchema, schema, userId, permissions) => {
4
+ const { labels } = collectionSchema;
5
+ // eslint-disable-next-line security/detect-object-injection
6
+ const collectionPermissions = permissions.collections?.[labels.collection];
7
+ let granted = true;
8
+ if (!collectionPermissions) {
9
+ granted = false;
10
+ return;
11
+ }
12
+ if (!collectionAccess("Read", collectionPermissions))
13
+ granted = false;
14
+ documents.forEach((document) => {
15
+ if (!documentAccess("Read", collectionSchema, schema, userId, permissions, document))
16
+ granted = false;
17
+ });
18
+ if (!granted)
19
+ throw new Error("PERMISSION_DENIED");
20
+ return;
21
+ };
@@ -0,0 +1,2 @@
1
+ import { CollectionSchema, StokerRole } from "@stoker-platform/types";
2
+ export declare const roleHasOperationAccess: (collection: CollectionSchema, role: StokerRole, operation: "read" | "create" | "update" | "delete") => boolean | undefined;
@@ -0,0 +1,7 @@
1
+ export const roleHasOperationAccess = (collection, role, operation) => {
2
+ const { access } = collection;
3
+ return (access.operations.assignable === true ||
4
+ (typeof access.operations.assignable === "object" && access.operations.assignable.includes(role)) ||
5
+ // eslint-disable-next-line security/detect-object-injection
6
+ access.operations[operation]?.includes(role));
7
+ };
@@ -0,0 +1,2 @@
1
+ import { CollectionSchema, CollectionsSchema, StokerPermissions, StokerRecord } from "@stoker-platform/types";
2
+ export declare const addRecordAccessControl: (record: StokerRecord, docId: string, collectionSchema: CollectionSchema, schema: CollectionsSchema, currentUserId?: string, currentUserPermissions?: StokerPermissions, permissions?: StokerPermissions) => void;
@@ -0,0 +1,40 @@
1
+ import { collectionAccess, collectionAuthAccess, privateFieldAccess, restrictCreateAccess } from "../collection.js";
2
+ import { documentAccess } from "../document.js";
3
+ import { permissionsWriteAccess } from "../permissions.js";
4
+ export const addRecordAccessControl = (record, docId, collectionSchema, schema, currentUserId, currentUserPermissions, permissions) => {
5
+ const { labels, fields } = collectionSchema;
6
+ // eslint-disable-next-line security/detect-object-injection
7
+ const collectionPermissions = currentUserPermissions?.collections?.[labels.collection];
8
+ let granted = true;
9
+ if (currentUserId && !collectionPermissions) {
10
+ throw new Error("PERMISSION_DENIED");
11
+ }
12
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
13
+ if (currentUserId && !collectionAccess("Create", collectionPermissions))
14
+ granted = false;
15
+ if (currentUserId &&
16
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17
+ !documentAccess("Create", collectionSchema, schema, currentUserId, currentUserPermissions, record))
18
+ granted = false;
19
+ if (collectionSchema.auth && permissions) {
20
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21
+ if (currentUserId && !collectionAuthAccess(collectionPermissions))
22
+ granted = false;
23
+ if (!permissionsWriteAccess("create", record, docId, collectionSchema, schema, currentUserId, currentUserPermissions, permissions)) {
24
+ granted = false;
25
+ }
26
+ }
27
+ for (const field of fields) {
28
+ const value = record[field.name];
29
+ if (field.access) {
30
+ if (!privateFieldAccess(field, currentUserPermissions) && value !== undefined) {
31
+ granted = false;
32
+ }
33
+ }
34
+ if (value !== undefined && !restrictCreateAccess(field, currentUserPermissions))
35
+ granted = false;
36
+ }
37
+ if (!granted)
38
+ throw new Error("PERMISSION_DENIED");
39
+ return;
40
+ };
@@ -0,0 +1,2 @@
1
+ import { CollectionSchema, CollectionsSchema, StokerPermissions, StokerRecord } from "@stoker-platform/types";
2
+ export declare const deleteRecordAccessControl: (record: StokerRecord, docId: string, collectionSchema: CollectionSchema, schema: CollectionsSchema, currentUserId: string, currentUserPermissions: StokerPermissions) => void;