@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.
- package/lib/src/access/collection.d.ts +4 -4
- package/lib/src/access/collection.js +28 -18
- package/lib/src/access/document.d.ts +1 -1
- package/lib/src/access/document.js +2 -2
- package/lib/src/access/fieldAccess.d.ts +8 -0
- package/lib/src/access/fieldAccess.js +76 -0
- package/lib/src/access/getRelatedCollections.d.ts +1 -1
- package/lib/src/access/getRelatedCollections.js +3 -3
- package/lib/src/access/hasDependencyAccess.d.ts +1 -1
- package/lib/src/access/hasDependencyAccess.js +2 -2
- package/lib/src/access/write/addRecord.d.ts +1 -1
- package/lib/src/access/write/addRecord.js +3 -3
- package/lib/src/access/write/updateRecord.d.ts +1 -1
- package/lib/src/access/write/updateRecord.js +3 -3
- package/lib/src/main.d.ts +2 -1
- package/lib/src/main.js +2 -1
- package/lib/src/operations/addInitialValues.d.ts +2 -2
- package/lib/src/operations/addInitialValues.js +4 -3
- package/lib/src/operations/prepareDenormalized.js +78 -1
- package/lib/src/schema/getIndexFields.d.ts +1 -0
- package/lib/src/schema/getIndexFields.js +70 -13
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -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
|
-
|
|
30
|
-
if (
|
|
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
|
-
|
|
37
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
2
|
-
export declare const addInitialValues: (data: StokerRecord, collectionSchema: CollectionSchema, customization: CollectionCustomization,
|
|
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,
|
|
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
|
-
!
|
|
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 (!
|
|
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 (
|
|
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
|
-
(
|
|
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
|
|
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
|
});
|