@stoker-platform/utils 0.5.77 → 0.5.78

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.
@@ -3,7 +3,7 @@ export declare const collectionAuthAccess: (permissions: CollectionPermissions)
3
3
  export declare const collectionAccess: (operation: "Read" | "Create" | "Update" | "Delete", permissions: CollectionPermissions) => boolean | undefined;
4
4
  export declare const collectionSomeWriteAccess: (permissions: CollectionPermissions) => boolean | undefined;
5
5
  export declare const collectionAllWriteAccess: (permissions: CollectionPermissions) => boolean | undefined;
6
- export declare const privateFieldAccess: (field: CollectionField, permissions?: StokerPermissions) => boolean | undefined;
7
- export declare const restrictCreateAccess: (field: CollectionField, permissions?: StokerPermissions) => boolean;
8
- export declare const restrictUpdateAccess: (field: CollectionField, permissions?: StokerPermissions) => boolean;
9
- export declare const canUpdateField: (collection: CollectionSchema, field: CollectionField, permissions: StokerPermissions) => boolean | undefined;
6
+ export declare const privateFieldAccess: (field: CollectionField, permissions?: StokerPermissions, collection?: CollectionSchema, claims?: Record<string, unknown>) => boolean | undefined;
7
+ export declare const restrictCreateAccess: (field: CollectionField, permissions?: StokerPermissions, collectionName?: string, claims?: Record<string, unknown>) => boolean;
8
+ export declare const restrictUpdateAccess: (field: CollectionField, permissions?: StokerPermissions, collectionName?: string, claims?: Record<string, unknown>) => boolean;
9
+ export declare const canUpdateField: (collection: CollectionSchema, field: CollectionField, permissions: StokerPermissions, claims: Record<string, unknown>) => boolean | undefined;
@@ -1,3 +1,4 @@
1
+ import { evaluateFieldAccessCondition, fieldAccessGroupAccess, isFieldAccessGroupReference } from "./fieldAccess.js";
1
2
  export const collectionAuthAccess = (permissions) => {
2
3
  return !!permissions.auth;
3
4
  };
@@ -19,44 +20,53 @@ export const collectionAllWriteAccess = (permissions) => {
19
20
  }
20
21
  return;
21
22
  };
22
- export const privateFieldAccess = (field, permissions) => {
23
+ export const privateFieldAccess = (field, permissions, collection, claims) => {
23
24
  if (!permissions)
24
25
  return true;
25
26
  if (!permissions.Role)
26
27
  return false;
28
+ if (isFieldAccessGroupReference(field.access)) {
29
+ if (!collection || !claims)
30
+ return false;
31
+ return fieldAccessGroupAccess(field, collection, permissions, claims);
32
+ }
27
33
  return field.access?.includes(permissions.Role);
28
34
  };
29
- export const restrictCreateAccess = (field, permissions) => {
30
- if (!permissions)
31
- return true;
32
- if (!permissions.Role)
33
- return false;
34
- if (field.restrictCreate === true)
35
+ const restrictWriteAccess = (restriction, permissions, collectionName, claims) => {
36
+ if (restriction === true)
35
37
  return false;
36
- else if (typeof field.restrictCreate === "object") {
37
- return field.restrictCreate?.includes(permissions.Role);
38
+ if (Array.isArray(restriction)) {
39
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
40
+ return restriction.includes(permissions.Role);
41
+ }
42
+ if (typeof restriction === "object" && restriction !== null) {
43
+ if (!collectionName || !claims)
44
+ return false;
45
+ return evaluateFieldAccessCondition(restriction, collectionName, permissions, claims);
38
46
  }
39
47
  return true;
40
48
  };
41
- export const restrictUpdateAccess = (field, permissions) => {
49
+ export const restrictCreateAccess = (field, permissions, collectionName, claims) => {
42
50
  if (!permissions)
43
51
  return true;
44
52
  if (!permissions.Role)
45
53
  return false;
46
- if (field.restrictUpdate === true)
54
+ return restrictWriteAccess(field.restrictCreate, permissions, collectionName, claims);
55
+ };
56
+ export const restrictUpdateAccess = (field, permissions, collectionName, claims) => {
57
+ if (!permissions)
58
+ return true;
59
+ if (!permissions.Role)
47
60
  return false;
48
- else if (typeof field.restrictUpdate === "object") {
49
- return field.restrictUpdate?.includes(permissions.Role);
50
- }
51
- return true;
61
+ return restrictWriteAccess(field.restrictUpdate, permissions, collectionName, claims);
52
62
  };
53
- export const canUpdateField = (collection, field, permissions) => {
63
+ export const canUpdateField = (collection, field, permissions, claims) => {
54
64
  const { labels } = collection;
55
65
  return (permissions.collections &&
56
66
  // eslint-disable-next-line security/detect-object-injection
57
67
  collectionAccess("Update", permissions.collections[labels.collection]) &&
58
- (!field.access || privateFieldAccess(field, permissions)) &&
59
- restrictUpdateAccess(field, permissions) &&
68
+ (!field.access || privateFieldAccess(field, permissions, collection, claims)) &&
69
+ restrictUpdateAccess(field, permissions, labels.collection, claims) &&
60
70
  !(collection.auth &&
61
71
  !permissions.collections?.[labels.collection].auth &&
62
72
  ["Enabled", "Role", "Name", "Email", "Photo_URL"].includes(field.name)));
@@ -1,3 +1,3 @@
1
1
  import { CollectionSchema, CollectionsSchema, StokerPermissions, StokerRecord } from "@stoker-platform/types";
2
2
  export declare const documentAccess: (operation: "Read" | "Create" | "Update" | "Delete", collectionSchema: CollectionSchema, schema: CollectionsSchema, userId: string, permissions: StokerPermissions, record: StokerRecord) => boolean;
3
- export declare const dependencyAccess: (collectionSchema: CollectionSchema, schema: CollectionsSchema, userId: string, permissions: StokerPermissions, record: StokerRecord) => boolean;
3
+ export declare const dependencyAccess: (collectionSchema: CollectionSchema, schema: CollectionsSchema, userId: string, permissions: StokerPermissions, record: StokerRecord, claims: Record<string, unknown>) => boolean;
@@ -177,13 +177,13 @@ export const documentAccess = (operation, collectionSchema, schema, userId, perm
177
177
  granted = false;
178
178
  return granted;
179
179
  };
180
- export const dependencyAccess = (collectionSchema, schema, userId, permissions, record) => {
180
+ export const dependencyAccess = (collectionSchema, schema, userId, permissions, record, claims) => {
181
181
  let granted = true;
182
182
  if (!permissions.Role) {
183
183
  granted = false;
184
184
  return granted;
185
185
  }
186
- if (hasDependencyAccess(collectionSchema, schema, permissions).length === 0)
186
+ if (hasDependencyAccess(collectionSchema, schema, permissions, claims).length === 0)
187
187
  granted = false;
188
188
  if (!filterAccess("Read", collectionSchema, schema, userId, permissions, record, true))
189
189
  granted = false;
@@ -0,0 +1,8 @@
1
+ import { CollectionField, CollectionSchema, FieldAccessCondition, FieldAccessGroupReference, StokerPermissions, StokerRole } from "@stoker-platform/types";
2
+ export declare const isFieldAccessGroupReference: (access?: StokerRole[] | FieldAccessGroupReference) => access is FieldAccessGroupReference;
3
+ export declare const getFieldAccessRoles: (access?: StokerRole[] | FieldAccessGroupReference) => StokerRole[] | undefined;
4
+ export declare const fieldRoleProjectionAccess: (field: CollectionField, role: StokerRole) => boolean;
5
+ export declare const getFieldAccessGroupCondition: (field: CollectionField, collection: CollectionSchema) => FieldAccessCondition | undefined;
6
+ export declare const getFieldAccessGroupFields: (collection: CollectionSchema) => Record<string, CollectionField[]>;
7
+ export declare const evaluateFieldAccessCondition: (condition: FieldAccessCondition, collectionName: string, permissions?: StokerPermissions, claims?: Record<string, unknown>) => boolean;
8
+ export declare const fieldAccessGroupAccess: (field: CollectionField, collection: CollectionSchema, permissions: StokerPermissions, claims: Record<string, unknown>) => boolean;
@@ -0,0 +1,76 @@
1
+ export const isFieldAccessGroupReference = (access) => {
2
+ return !!access && !Array.isArray(access);
3
+ };
4
+ export const getFieldAccessRoles = (access) => {
5
+ return Array.isArray(access) ? access : undefined;
6
+ };
7
+ export const fieldRoleProjectionAccess = (field, role) => {
8
+ if (!field.access)
9
+ return true;
10
+ if (isFieldAccessGroupReference(field.access))
11
+ return false;
12
+ return field.access.includes(role);
13
+ };
14
+ export const getFieldAccessGroupCondition = (field, collection) => {
15
+ if (!isFieldAccessGroupReference(field.access))
16
+ return;
17
+ return collection.fieldAccessGroups?.[field.access.group];
18
+ };
19
+ export const getFieldAccessGroupFields = (collection) => {
20
+ const groups = {};
21
+ Object.keys(collection.fieldAccessGroups || {}).forEach((key) => {
22
+ // eslint-disable-next-line security/detect-object-injection
23
+ groups[key] = [];
24
+ });
25
+ collection.fields.forEach((field) => {
26
+ if (isFieldAccessGroupReference(field.access)) {
27
+ const { group } = field.access;
28
+ // eslint-disable-next-line security/detect-object-injection
29
+ if (groups[group])
30
+ groups[group].push(field);
31
+ }
32
+ });
33
+ return groups;
34
+ };
35
+ export const evaluateFieldAccessCondition = (condition, collectionName, permissions, claims) => {
36
+ if (!permissions)
37
+ return true;
38
+ // eslint-disable-next-line security/detect-object-injection
39
+ const collectionPermissions = permissions.collections?.[collectionName];
40
+ const checks = [];
41
+ if (condition.collectionAuth !== undefined) {
42
+ checks.push(!!collectionPermissions?.auth === condition.collectionAuth);
43
+ }
44
+ if (condition.roles) {
45
+ checks.push(!!permissions.Role && condition.roles.includes(permissions.Role));
46
+ }
47
+ if (condition.claims) {
48
+ checks.push(Object.entries(condition.claims).every(([claim, expected]) => {
49
+ // eslint-disable-next-line security/detect-object-injection
50
+ let value = claims?.[claim];
51
+ if (value === undefined && claim === "role")
52
+ value = permissions.Role;
53
+ if (Array.isArray(expected))
54
+ return expected.some((item) => item === value);
55
+ return value === expected;
56
+ }));
57
+ }
58
+ if (condition.restrictions) {
59
+ const { recordOwner, recordUser, recordProperty, restrictEntities } = condition.restrictions;
60
+ checks.push((recordOwner === undefined || !!collectionPermissions?.recordOwner?.active === recordOwner) &&
61
+ (recordUser === undefined || !!collectionPermissions?.recordUser?.active === recordUser) &&
62
+ (recordProperty === undefined || !!collectionPermissions?.recordProperty?.active === recordProperty) &&
63
+ (restrictEntities === undefined || !!collectionPermissions?.restrictEntities === restrictEntities));
64
+ }
65
+ if (checks.length === 0)
66
+ return false;
67
+ if (condition.match === "all")
68
+ return checks.every(Boolean);
69
+ return checks.some(Boolean);
70
+ };
71
+ export const fieldAccessGroupAccess = (field, collection, permissions, claims) => {
72
+ const condition = getFieldAccessGroupCondition(field, collection);
73
+ if (!condition)
74
+ return false;
75
+ return evaluateFieldAccessCondition(condition, collection.labels.collection, permissions, claims);
76
+ };
@@ -1,2 +1,2 @@
1
1
  import { CollectionSchema, CollectionsSchema, StokerPermissions } from "@stoker-platform/types";
2
- export declare const getRelatedCollections: (collectionSchema: CollectionSchema, schema: CollectionsSchema, permissions?: StokerPermissions) => string[];
2
+ export declare const getRelatedCollections: (collectionSchema: CollectionSchema, schema: CollectionsSchema, permissions?: StokerPermissions, claims?: Record<string, unknown>) => string[];
@@ -1,11 +1,11 @@
1
1
  import { isRelationField } from "../schema/isRelationField.js";
2
2
  import { collectionAccess, privateFieldAccess } from "../access/collection.js";
3
3
  import { hasDependencyAccess } from "./hasDependencyAccess.js";
4
- export const getRelatedCollections = (collectionSchema, schema, permissions) => {
4
+ export const getRelatedCollections = (collectionSchema, schema, permissions, claims) => {
5
5
  const relatedCollections = [];
6
6
  for (const field of collectionSchema.fields) {
7
7
  if (isRelationField(field)) {
8
- if (field.access && (!permissions || !privateFieldAccess(field, permissions)))
8
+ if (field.access && (!permissions || !privateFieldAccess(field, permissions, collectionSchema, claims)))
9
9
  continue;
10
10
  const relatedCollection = schema.collections[field.collection];
11
11
  if (!relatedCollection)
@@ -15,7 +15,7 @@ export const getRelatedCollections = (collectionSchema, schema, permissions) =>
15
15
  continue;
16
16
  if (!permissions ||
17
17
  collectionAccess("Read", relatedCollectionPermissions) ||
18
- hasDependencyAccess(collectionSchema, schema, permissions)) {
18
+ hasDependencyAccess(collectionSchema, schema, permissions, claims ?? {})) {
19
19
  relatedCollections.push(field.collection);
20
20
  }
21
21
  }
@@ -1,2 +1,2 @@
1
1
  import { CollectionSchema, CollectionsSchema, DependencyField, StokerPermissions } from "@stoker-platform/types";
2
- export declare const hasDependencyAccess: (collection: CollectionSchema, schema: CollectionsSchema, permissions: StokerPermissions) => DependencyField[];
2
+ export declare const hasDependencyAccess: (collection: CollectionSchema, schema: CollectionsSchema, permissions: StokerPermissions, claims: Record<string, unknown>) => DependencyField[];
@@ -1,13 +1,13 @@
1
1
  import { privateFieldAccess } from "./collection.js";
2
2
  import { isRelationField } from "../schema/isRelationField.js";
3
- export const hasDependencyAccess = (collection, schema, permissions) => {
3
+ export const hasDependencyAccess = (collection, schema, permissions, claims) => {
4
4
  const collections = Object.values(schema.collections);
5
5
  const { labels } = collection;
6
6
  const hasDependencyAccess = [];
7
7
  for (const collectionSchema of collections) {
8
8
  const { fields: dependencyFields } = collectionSchema;
9
9
  for (const field of dependencyFields) {
10
- if (field.access && !privateFieldAccess(field, permissions))
10
+ if (field.access && !privateFieldAccess(field, permissions, collectionSchema, claims))
11
11
  continue;
12
12
  if (!permissions.Role)
13
13
  continue;
@@ -1,2 +1,2 @@
1
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;
2
+ export declare const addRecordAccessControl: (record: StokerRecord, docId: string, collectionSchema: CollectionSchema, schema: CollectionsSchema, currentUserId?: string, currentUserPermissions?: StokerPermissions, permissions?: StokerPermissions, claims?: Record<string, unknown>) => void;
@@ -1,7 +1,7 @@
1
1
  import { collectionAccess, collectionAuthAccess, privateFieldAccess, restrictCreateAccess } from "../collection.js";
2
2
  import { documentAccess } from "../document.js";
3
3
  import { permissionsWriteAccess } from "../permissions.js";
4
- export const addRecordAccessControl = (record, docId, collectionSchema, schema, currentUserId, currentUserPermissions, permissions) => {
4
+ export const addRecordAccessControl = (record, docId, collectionSchema, schema, currentUserId, currentUserPermissions, permissions, claims) => {
5
5
  const { labels, fields } = collectionSchema;
6
6
  // eslint-disable-next-line security/detect-object-injection
7
7
  const collectionPermissions = currentUserPermissions?.collections?.[labels.collection];
@@ -27,11 +27,11 @@ export const addRecordAccessControl = (record, docId, collectionSchema, schema,
27
27
  for (const field of fields) {
28
28
  const value = record[field.name];
29
29
  if (field.access) {
30
- if (!privateFieldAccess(field, currentUserPermissions) && value !== undefined) {
30
+ if (!privateFieldAccess(field, currentUserPermissions, collectionSchema, claims) && value !== undefined) {
31
31
  granted = false;
32
32
  }
33
33
  }
34
- if (value !== undefined && !restrictCreateAccess(field, currentUserPermissions))
34
+ if (value !== undefined && !restrictCreateAccess(field, currentUserPermissions, labels.collection, claims))
35
35
  granted = false;
36
36
  }
37
37
  if (!granted)
@@ -1,2 +1,2 @@
1
1
  import { CollectionSchema, CollectionsSchema, StokerPermissions, StokerRecord } from "@stoker-platform/types";
2
- export declare const updateRecordAccessControl: (partial: StokerRecord, originalRecord: StokerRecord, docId: string, collectionSchema: CollectionSchema, schema: CollectionsSchema, currentUserId?: string, currentUserPermissions?: StokerPermissions, userOperation?: string, permissions?: StokerPermissions, originalPermissions?: StokerPermissions | undefined) => void;
2
+ export declare const updateRecordAccessControl: (partial: StokerRecord, originalRecord: StokerRecord, docId: string, collectionSchema: CollectionSchema, schema: CollectionsSchema, currentUserId?: string, currentUserPermissions?: StokerPermissions, userOperation?: string, permissions?: StokerPermissions, originalPermissions?: StokerPermissions | undefined, claims?: Record<string, unknown>) => void;
@@ -2,7 +2,7 @@ import { collectionAccess, collectionAuthAccess, privateFieldAccess, restrictUpd
2
2
  import { documentAccess } from "../document.js";
3
3
  import { permissionsWriteAccess } from "../permissions.js";
4
4
  import { removeDeleteSentinels } from "../../operations/removeDeleteSentinels.js";
5
- export const updateRecordAccessControl = (partial, originalRecord, docId, collectionSchema, schema, currentUserId, currentUserPermissions, userOperation, permissions, originalPermissions) => {
5
+ export const updateRecordAccessControl = (partial, originalRecord, docId, collectionSchema, schema, currentUserId, currentUserPermissions, userOperation, permissions, originalPermissions, claims) => {
6
6
  const { labels, fields } = collectionSchema;
7
7
  // eslint-disable-next-line security/detect-object-injection
8
8
  const collectionPermissions = currentUserPermissions?.collections?.[labels.collection];
@@ -50,12 +50,12 @@ export const updateRecordAccessControl = (partial, originalRecord, docId, collec
50
50
  for (const field of fields) {
51
51
  const value = partial[field.name];
52
52
  if (field.access) {
53
- if (!privateFieldAccess(field, currentUserPermissions) && value !== undefined) {
53
+ if (!privateFieldAccess(field, currentUserPermissions, collectionSchema, claims) && value !== undefined) {
54
54
  errorDetails = `Authenticated user does not have access to field ${field.name}`;
55
55
  granted = false;
56
56
  }
57
57
  }
58
- if (value !== undefined && !restrictUpdateAccess(field, currentUserPermissions)) {
58
+ if (value !== undefined && !restrictUpdateAccess(field, currentUserPermissions, labels.collection, claims)) {
59
59
  errorDetails = `Authenticated user does not have Update access to field ${field.name}`;
60
60
  granted = false;
61
61
  }
package/lib/src/main.d.ts CHANGED
@@ -20,7 +20,8 @@ export { getCollection } from "./schema/getCollection.js";
20
20
  export { getDependencyFields } from "./schema/getDependencyFields.js";
21
21
  export { getField } from "./schema/getField.js";
22
22
  export { getFieldNames } from "./schema/getFieldNames.js";
23
- export { getRoleFields, getDependencyAccessFields, getDependencyIndexFields, getRoleGroups, getRoleGroup, getRoleExcludedFields, getUserRoleGroups, getAllRoleGroups, } from "./schema/getIndexFields.js";
23
+ export { getRoleFields, getDependencyAccessFields, getDependencyIndexFields, getFieldAccessGroupIndexFields, getRoleGroups, getRoleGroup, getRoleExcludedFields, getUserRoleGroups, getAllRoleGroups, } from "./schema/getIndexFields.js";
24
+ export { isFieldAccessGroupReference, getFieldAccessRoles, fieldRoleProjectionAccess, getFieldAccessGroupCondition, getFieldAccessGroupFields, evaluateFieldAccessCondition, fieldAccessGroupAccess, } from "./access/fieldAccess.js";
24
25
  export { getAccessFields } from "./schema/getAccessFields.js";
25
26
  export { getSystemFieldsSchema } from "./schema/getSystemFieldsSchema.js";
26
27
  export { isRelationField } from "./schema/isRelationField.js";
package/lib/src/main.js CHANGED
@@ -20,7 +20,8 @@ export { getCollection } from "./schema/getCollection.js";
20
20
  export { getDependencyFields } from "./schema/getDependencyFields.js";
21
21
  export { getField } from "./schema/getField.js";
22
22
  export { getFieldNames } from "./schema/getFieldNames.js";
23
- export { getRoleFields, getDependencyAccessFields, getDependencyIndexFields, getRoleGroups, getRoleGroup, getRoleExcludedFields, getUserRoleGroups, getAllRoleGroups, } from "./schema/getIndexFields.js";
23
+ export { getRoleFields, getDependencyAccessFields, getDependencyIndexFields, getFieldAccessGroupIndexFields, getRoleGroups, getRoleGroup, getRoleExcludedFields, getUserRoleGroups, getAllRoleGroups, } from "./schema/getIndexFields.js";
24
+ export { isFieldAccessGroupReference, getFieldAccessRoles, fieldRoleProjectionAccess, getFieldAccessGroupCondition, getFieldAccessGroupFields, evaluateFieldAccessCondition, fieldAccessGroupAccess, } from "./access/fieldAccess.js";
24
25
  export { getAccessFields } from "./schema/getAccessFields.js";
25
26
  export { getSystemFieldsSchema } from "./schema/getSystemFieldsSchema.js";
26
27
  export { isRelationField } from "./schema/isRelationField.js";
@@ -1,2 +1,2 @@
1
- import { CollectionSchema, CollectionCustomization, StokerRecord, StokerRole } from "@stoker-platform/types";
2
- export declare const addInitialValues: (data: StokerRecord, collectionSchema: CollectionSchema, customization: CollectionCustomization, role?: StokerRole) => Promise<void>;
1
+ import { CollectionSchema, CollectionCustomization, StokerPermissions, StokerRecord } from "@stoker-platform/types";
2
+ export declare const addInitialValues: (data: StokerRecord, collectionSchema: CollectionSchema, customization: CollectionCustomization, permissions?: StokerPermissions, claims?: Record<string, unknown>) => Promise<void>;
@@ -1,13 +1,14 @@
1
+ import { privateFieldAccess } from "../access/collection.js";
1
2
  import { tryPromise } from "../getConfigValue.js";
2
3
  import { getField } from "../schema/getField.js";
3
4
  import { isRelationField } from "../schema/isRelationField.js";
4
5
  import { getLowercaseFields } from "./getLowercaseFields.js";
5
- export const addInitialValues = async (data, collectionSchema, customization, role) => {
6
+ export const addInitialValues = async (data, collectionSchema, customization, permissions, claims) => {
6
7
  const { fields } = collectionSchema;
7
8
  for (const field of customization.fields) {
8
9
  const fieldSchema = getField(fields, field.name);
9
10
  if (field.custom?.initialValue !== undefined &&
10
- !(role && fieldSchema.access && !fieldSchema.access.includes(role))) {
11
+ (!fieldSchema.access || privateFieldAccess(fieldSchema, permissions, collectionSchema, claims || {}))) {
11
12
  data[field.name] = await tryPromise(field.custom.initialValue, [data]);
12
13
  const lowercaseFields = getLowercaseFields(collectionSchema, [fieldSchema]);
13
14
  if (lowercaseFields.size === 1) {
@@ -16,7 +17,7 @@ export const addInitialValues = async (data, collectionSchema, customization, ro
16
17
  }
17
18
  }
18
19
  for (const field of fields) {
19
- if (!(role && field.access && !field.access.includes(role))) {
20
+ if (!field.access || privateFieldAccess(field, permissions, collectionSchema, claims || {})) {
20
21
  if ("autoIncrement" in field && field.autoIncrement && !data[field.name]) {
21
22
  data[field.name] = "Pending";
22
23
  }
@@ -1,5 +1,6 @@
1
1
  import { isRelationField } from "../schema/isRelationField.js";
2
- import { getDependencyIndexFields, getRoleExcludedFields } from "../schema/getIndexFields.js";
2
+ import { getDependencyIndexFields, getFieldAccessGroupIndexFields, getRoleExcludedFields, } from "../schema/getIndexFields.js";
3
+ import { getFieldAccessGroupFields } from "../access/fieldAccess.js";
3
4
  import { isDependencyField } from "../schema/isDependencyField.js";
4
5
  import { getFieldNames } from "../schema/getFieldNames.js";
5
6
  import { getField } from "../schema/getField.js";
@@ -64,6 +65,43 @@ options, allRoleGroups, arrayUnion, arrayRemove, deleteField, dependencyRef, uni
64
65
  }
65
66
  }
66
67
  });
68
+ const fieldAccessGroups = getFieldAccessGroupFields(collectionSchema);
69
+ Object.entries(fieldAccessGroups).forEach(([groupKey, groupFields]) => {
70
+ if (groupFields.length === 0)
71
+ return;
72
+ const overlayFieldsSchema = getFieldAccessGroupIndexFields(groupKey, collectionSchema);
73
+ const overlayRecord = {};
74
+ overlayFieldsSchema.forEach((overlayField) => {
75
+ const names = [
76
+ overlayField.name,
77
+ `${overlayField.name}_Array`,
78
+ `${overlayField.name}_Single`,
79
+ `${overlayField.name}_Lowercase`,
80
+ ];
81
+ names.forEach((name) => {
82
+ // eslint-disable-next-line security/detect-object-injection
83
+ if (record[name] !== undefined)
84
+ overlayRecord[name] = record[name];
85
+ });
86
+ });
87
+ if (operation === "create") {
88
+ overlayRecord.Collection_Path ||= path;
89
+ overlayRecord.Collection_Path_String = path.join("/");
90
+ batch.set(privateRef(groupKey), overlayRecord);
91
+ if (batchSize)
92
+ batchSize.size++;
93
+ }
94
+ if (operation === "update" && Object.keys(overlayRecord).length > 0) {
95
+ batch.update(privateRef(groupKey), overlayRecord);
96
+ if (batchSize)
97
+ batchSize.size++;
98
+ }
99
+ if (operation === "delete") {
100
+ batch.delete(privateRef(groupKey));
101
+ if (batchSize)
102
+ batchSize.size++;
103
+ }
104
+ });
67
105
  const roleGroups = allRoleGroups[collectionSchema.labels.collection];
68
106
  for (const group of roleGroups) {
69
107
  const excludedFields = getRoleExcludedFields(group, collectionSchema);
@@ -110,6 +148,16 @@ options, allRoleGroups, arrayUnion, arrayRemove, deleteField, dependencyRef, uni
110
148
  batchSize++;
111
149
  }
112
150
  }
151
+ const fieldAccessGroups = getFieldAccessGroupFields(targetSchema);
152
+ for (const [groupKey, groupFields] of Object.entries(fieldAccessGroups)) {
153
+ if (groupFields.length === 0)
154
+ continue;
155
+ const overlayIndexFields = getFieldAccessGroupIndexFields(groupKey, targetSchema);
156
+ if (overlayIndexFields.some((overlayField) => overlayField.name === targetField.name) &&
157
+ isRelationField(targetField)) {
158
+ batchSize++;
159
+ }
160
+ }
113
161
  return batchSize;
114
162
  };
115
163
  const deleteFields = (field, targetSchema, targetField, id, relationPath) => {
@@ -142,6 +190,16 @@ options, allRoleGroups, arrayUnion, arrayRemove, deleteField, dependencyRef, uni
142
190
  batch.update(twoWayPrivateRef(field, group.key, id), fieldUpdate);
143
191
  }
144
192
  }
193
+ const fieldAccessGroups = getFieldAccessGroupFields(targetSchema);
194
+ for (const [groupKey, groupFields] of Object.entries(fieldAccessGroups)) {
195
+ if (groupFields.length === 0)
196
+ continue;
197
+ const overlayIndexFields = getFieldAccessGroupIndexFields(groupKey, targetSchema);
198
+ if (overlayIndexFields.some((overlayField) => overlayField.name === targetField.name) &&
199
+ isRelationField(targetField)) {
200
+ batch.update(twoWayPrivateRef(field, groupKey, id), fieldUpdate);
201
+ }
202
+ }
145
203
  };
146
204
  if (operation === "delete")
147
205
  return;
@@ -246,6 +304,25 @@ options, allRoleGroups, arrayUnion, arrayRemove, deleteField, dependencyRef, uni
246
304
  }
247
305
  }
248
306
  }
307
+ const fieldAccessGroups = getFieldAccessGroupFields(targetSchema);
308
+ for (const [groupKey, groupFields] of Object.entries(fieldAccessGroups)) {
309
+ if (groupFields.length === 0)
310
+ continue;
311
+ const overlayIndexFields = getFieldAccessGroupIndexFields(groupKey, targetSchema);
312
+ if (overlayIndexFields.some((overlayField) => overlayField.name === targetField.name)) {
313
+ const overlayFieldUpdate = { ...fieldUpdate };
314
+ Object.keys(systemFields).forEach((systemField) => {
315
+ if (overlayIndexFields.some((overlayField) => overlayField.name === systemField)) {
316
+ // eslint-disable-next-line security/detect-object-injection
317
+ overlayFieldUpdate[systemField] = systemFields[systemField];
318
+ }
319
+ });
320
+ if (Object.keys(overlayFieldUpdate).length > 0) {
321
+ batch.update(twoWayPrivateRef(field, groupKey, id), overlayFieldUpdate);
322
+ batchSize.size++;
323
+ }
324
+ }
325
+ }
249
326
  }
250
327
  }
251
328
  }
@@ -2,6 +2,7 @@ import type { CollectionField, CollectionSchema, CollectionsSchema, StokerPermis
2
2
  export declare const getRoleFields: (collection: CollectionSchema, role: StokerRole) => CollectionField[];
3
3
  export declare const getDependencyAccessFields: (field: CollectionField, collection: CollectionSchema, schema: CollectionsSchema) => CollectionField[];
4
4
  export declare const getDependencyIndexFields: (field: CollectionField, collection: CollectionSchema, schema: CollectionsSchema) => CollectionField[];
5
+ export declare const getFieldAccessGroupIndexFields: (groupKey: string, collection: CollectionSchema) => CollectionField[];
5
6
  export declare const getRoleGroups: (collection: CollectionSchema, schema: CollectionsSchema) => Set<RoleGroup>;
6
7
  export declare const getRoleGroup: (role: StokerRole, collection: CollectionSchema, schema: CollectionsSchema) => RoleGroup | undefined;
7
8
  export declare const getRoleExcludedFields: (roleGroup: RoleGroup, collection: CollectionSchema) => CollectionField[];
@@ -5,6 +5,7 @@ import { getAccessFields } from "./getAccessFields.js";
5
5
  import { getDependencyFields } from "./getDependencyFields.js";
6
6
  import { roleHasOperationAccess } from "../access/roleHasOperationAccess.js";
7
7
  import { isRelationField } from "./isRelationField.js";
8
+ import { fieldRoleProjectionAccess, getFieldAccessGroupFields } from "../access/fieldAccess.js";
8
9
  export const getRoleFields = (collection, role) => {
9
10
  const indexFields = [];
10
11
  const { fields, roleSystemFields, preloadCache, queries } = collection;
@@ -15,7 +16,7 @@ export const getRoleFields = (collection, role) => {
15
16
  required: true,
16
17
  });
17
18
  fields.forEach((field) => {
18
- if (!field.access || field.access.includes(role)) {
19
+ if (fieldRoleProjectionAccess(field, role)) {
19
20
  indexFields.push(field);
20
21
  }
21
22
  });
@@ -35,7 +36,7 @@ export const getRoleFields = (collection, role) => {
35
36
  const queryField = getField(fields.concat(systemFieldsSchema), query.field);
36
37
  if ((!query.roles || query.roles.includes(role)) &&
37
38
  queryField &&
38
- (!queryField.access || queryField.access?.includes(role)) &&
39
+ fieldRoleProjectionAccess(queryField, role) &&
39
40
  systemFields.includes(query.field)) {
40
41
  indexFields.push(getField(systemFieldsSchema, query.field));
41
42
  }
@@ -52,16 +53,6 @@ export const getDependencyAccessFields = (field, collection, schema) => {
52
53
  const indexFields = [];
53
54
  const { fields, preloadCache } = collection;
54
55
  const systemFieldsSchema = getSystemFieldsSchema();
55
- if (preloadCache?.range) {
56
- preloadCache.range.fields.forEach((field) => {
57
- if (systemFields.includes(field)) {
58
- indexFields.push(getField(systemFieldsSchema, field));
59
- }
60
- else {
61
- indexFields.push(getField(fields, field));
62
- }
63
- });
64
- }
65
56
  const dependentRoles = new Set();
66
57
  const dependentFields = getDependencyFields(collection, schema);
67
58
  Object.entries(dependentFields).map(([fieldName, collectionValues]) => {
@@ -76,6 +67,16 @@ export const getDependencyAccessFields = (field, collection, schema) => {
76
67
  dependentRoles.forEach((role) => {
77
68
  indexFields.push(...getAccessFields(collection, role));
78
69
  });
70
+ if (preloadCache?.range) {
71
+ preloadCache.range.fields.forEach((field) => {
72
+ if (systemFields.includes(field)) {
73
+ indexFields.push(getField(systemFieldsSchema, field));
74
+ }
75
+ else if (Array.from(dependentRoles).some((role) => fieldRoleProjectionAccess(getField(fields, field), role))) {
76
+ indexFields.push(getField(fields, field));
77
+ }
78
+ });
79
+ }
79
80
  return [...new Set(indexFields)];
80
81
  };
81
82
  export const getDependencyIndexFields = (field, collection, schema) => {
@@ -121,6 +122,62 @@ export const getDependencyIndexFields = (field, collection, schema) => {
121
122
  indexFields.push(...getDependencyAccessFields(field, collection, schema));
122
123
  return [...new Set(indexFields)];
123
124
  };
125
+ export const getFieldAccessGroupIndexFields = (groupKey, collection) => {
126
+ // eslint-disable-next-line security/detect-object-injection
127
+ if (!collection.fieldAccessGroups?.[groupKey])
128
+ return [];
129
+ const indexFields = [];
130
+ const { fields, softDelete, preloadCache, roleSystemFields, queries } = collection;
131
+ const systemFieldsSchema = getSystemFieldsSchema();
132
+ indexFields.push({
133
+ name: "Collection_Path",
134
+ type: "Array",
135
+ required: true,
136
+ });
137
+ indexFields.push({
138
+ name: "id",
139
+ type: "String",
140
+ required: true,
141
+ });
142
+ // eslint-disable-next-line security/detect-object-injection
143
+ const groupFields = getFieldAccessGroupFields(collection)[groupKey] || [];
144
+ indexFields.push(...groupFields);
145
+ // eslint-disable-next-line security/detect-object-injection
146
+ const applicableRoles = collection.fieldAccessGroups[groupKey].applicableRoles;
147
+ if (softDelete) {
148
+ const softDeleteField = getField(fields, softDelete.archivedField);
149
+ if (softDeleteField && !groupFields.includes(softDeleteField)) {
150
+ indexFields.push(softDeleteField);
151
+ }
152
+ }
153
+ if (preloadCache?.range) {
154
+ preloadCache.range.fields.forEach((field) => {
155
+ if (systemFields.includes(field)) {
156
+ indexFields.push(getField(systemFieldsSchema, field));
157
+ }
158
+ else if (applicableRoles.some((role) => fieldRoleProjectionAccess(getField(fields, field), role))) {
159
+ indexFields.push(getField(fields, field));
160
+ }
161
+ });
162
+ }
163
+ roleSystemFields
164
+ ?.filter((systemField) => systemField.field !== "Collection_Path" &&
165
+ applicableRoles.some((role) => !systemField.roles || systemField.roles.includes(role)))
166
+ .forEach((systemField) => {
167
+ indexFields.push(getField(systemFieldsSchema, systemField.field));
168
+ });
169
+ queries?.forEach((query) => {
170
+ const queryField = getField(fields.concat(systemFieldsSchema), query.field);
171
+ if (queryField &&
172
+ applicableRoles.some((role) => (!query.roles || query.roles.includes(role)) && fieldRoleProjectionAccess(queryField, role))) {
173
+ indexFields.push(queryField);
174
+ }
175
+ });
176
+ for (const role of applicableRoles) {
177
+ indexFields.push(...getAccessFields(collection, role));
178
+ }
179
+ return [...new Set(indexFields)];
180
+ };
124
181
  export const getRoleGroups = (collection, schema) => {
125
182
  const { preloadCache } = collection;
126
183
  const roleGroups = new Set();
@@ -194,7 +251,7 @@ export const getAllRoleGroups = (schema, permissions, collections) => {
194
251
  roleGroupsArray.map((roleGroup) => {
195
252
  roleGroup.fields.forEach((field) => {
196
253
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
197
- if (permissions && field.access && !field.access?.includes(permissions.Role)) {
254
+ if (permissions && field.access && !fieldRoleProjectionAccess(field, permissions.Role)) {
198
255
  roleGroup.fields = roleGroup.fields.filter((groupField) => groupField.name !== field.name);
199
256
  }
200
257
  });