@firecms/user_management 3.0.0-3.0.0-beta.4.pre.1.0

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/dist/UserManagementProvider.d.ts +7 -0
  4. package/dist/admin_views.d.ts +2 -0
  5. package/dist/components/index.d.ts +2 -0
  6. package/dist/components/roles/RoleChip.d.ts +5 -0
  7. package/dist/components/roles/RolesDetailsForm.d.ts +20 -0
  8. package/dist/components/roles/RolesTable.d.ts +5 -0
  9. package/dist/components/roles/RolesView.d.ts +4 -0
  10. package/dist/components/roles/default_roles.d.ts +2 -0
  11. package/dist/components/roles/index.d.ts +4 -0
  12. package/dist/components/users/UserDetailsForm.d.ts +20 -0
  13. package/dist/components/users/UsersTable.d.ts +4 -0
  14. package/dist/components/users/UsersView.d.ts +4 -0
  15. package/dist/components/users/index.d.ts +3 -0
  16. package/dist/hooks/index.d.ts +2 -0
  17. package/dist/hooks/useBuildFirestoreUserManagement.d.ts +44 -0
  18. package/dist/hooks/useUserManagement.d.ts +2 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.es.js +1263 -0
  21. package/dist/index.es.js.map +1 -0
  22. package/dist/index.umd.js +2 -0
  23. package/dist/index.umd.js.map +1 -0
  24. package/dist/types/firecms_user.d.ts +7 -0
  25. package/dist/types/index.d.ts +3 -0
  26. package/dist/types/roles.d.ts +31 -0
  27. package/dist/types/user_management.d.ts +39 -0
  28. package/dist/useUserManagementPlugin.d.ts +5 -0
  29. package/dist/utils/colors.d.ts +2 -0
  30. package/dist/utils/index.d.ts +3 -0
  31. package/dist/utils/local_storage.d.ts +3 -0
  32. package/dist/utils/permissions.d.ts +9 -0
  33. package/dist/utils/useTraceUpdate.d.ts +1 -0
  34. package/package.json +76 -0
  35. package/src/UserManagementProvider.tsx +19 -0
  36. package/src/admin_views.tsx +19 -0
  37. package/src/components/index.ts +2 -0
  38. package/src/components/roles/RoleChip.tsx +28 -0
  39. package/src/components/roles/RolesDetailsForm.tsx +402 -0
  40. package/src/components/roles/RolesTable.tsx +139 -0
  41. package/src/components/roles/RolesView.tsx +63 -0
  42. package/src/components/roles/default_roles.tsx +36 -0
  43. package/src/components/roles/index.ts +4 -0
  44. package/src/components/users/UserDetailsForm.tsx +230 -0
  45. package/src/components/users/UsersTable.tsx +178 -0
  46. package/src/components/users/UsersView.tsx +59 -0
  47. package/src/components/users/index.ts +3 -0
  48. package/src/hooks/index.ts +2 -0
  49. package/src/hooks/useBuildFirestoreUserManagement.tsx +241 -0
  50. package/src/hooks/useUserManagement.tsx +5 -0
  51. package/src/index.ts +7 -0
  52. package/src/types/firecms_user.ts +8 -0
  53. package/src/types/index.ts +3 -0
  54. package/src/types/roles.ts +41 -0
  55. package/src/types/user_management.tsx +50 -0
  56. package/src/useUserManagementPlugin.tsx +18 -0
  57. package/src/utils/colors.ts +52 -0
  58. package/src/utils/index.ts +3 -0
  59. package/src/utils/local_storage.ts +53 -0
  60. package/src/utils/permissions.ts +83 -0
  61. package/src/utils/useTraceUpdate.tsx +23 -0
  62. package/tailwind.config.js +68 -0
@@ -0,0 +1,59 @@
1
+ import { AddIcon, Button, Container, Typography } from "@firecms/ui";
2
+
3
+ import { UsersTable } from "./UsersTable";
4
+ import { UserDetailsForm } from "./UserDetailsForm";
5
+ import React, { useCallback, useState } from "react";
6
+ import { UserWithRoles } from "../../types";
7
+ import { useUserManagement } from "../../hooks/useUserManagement";
8
+
9
+ export const UsersView = function UsersView({ children }: { children?: React.ReactNode }) {
10
+
11
+ const [dialogOpen, setDialogOpen] = useState<boolean>();
12
+ const [selectedUser, setSelectedUser] = useState<UserWithRoles | undefined>();
13
+
14
+ const { users, usersLimit } = useUserManagement();
15
+
16
+ const reachedUsersLimit = usersLimit !== undefined && (users && users.length >= usersLimit);
17
+
18
+ const onUserClicked = useCallback((user: UserWithRoles) => {
19
+ setSelectedUser(user);
20
+ setDialogOpen(true);
21
+ }, []);
22
+
23
+ const handleClose = useCallback(() => {
24
+ setDialogOpen(false);
25
+ setSelectedUser(undefined);
26
+ }, []);
27
+
28
+ return (
29
+ <Container className="w-full flex flex-col py-4 gap-4" maxWidth={"6xl"}>
30
+
31
+ {children}
32
+
33
+ <div
34
+ className="flex items-center mt-12">
35
+ <Typography gutterBottom variant="h4"
36
+ className="flex-grow"
37
+ component="h4">
38
+ Users
39
+ </Typography>
40
+ <Button
41
+ size={"large"}
42
+ disabled={reachedUsersLimit}
43
+ startIcon={<AddIcon/>}
44
+ onClick={() => setDialogOpen(true)}>
45
+ Add user
46
+ </Button>
47
+ </div>
48
+
49
+ <UsersTable onUserClicked={onUserClicked}/>
50
+
51
+ <UserDetailsForm
52
+ key={selectedUser?.uid ?? "new"}
53
+ open={dialogOpen ?? false}
54
+ user={selectedUser}
55
+ handleClose={handleClose}/>
56
+
57
+ </Container>
58
+ )
59
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./UserDetailsForm";
2
+ export * from "./UsersTable";
3
+ export * from "./UsersView";
@@ -0,0 +1,2 @@
1
+ export * from "./useBuildFirestoreUserManagement";
2
+ export * from "./useUserManagement";
@@ -0,0 +1,241 @@
1
+ import React, { useCallback, useEffect, useRef } from "react";
2
+ import {
3
+ collection,
4
+ deleteDoc,
5
+ doc,
6
+ DocumentSnapshot,
7
+ Firestore,
8
+ getFirestore,
9
+ onSnapshot,
10
+ setDoc
11
+ } from "firebase/firestore";
12
+ import { FirebaseApp } from "firebase/app";
13
+ import { Role, UserManagement, UserWithRoles } from "../types";
14
+ import { AuthController, PermissionsBuilder, User } from "@firecms/core";
15
+ import { resolveUserRolePermissions } from "../utils";
16
+
17
+ type UserWithRoleIds = User & { roles: string[] };
18
+
19
+ export interface UserManagementParams {
20
+ /**
21
+ * The Firebase app to use for the user management. The config will be saved in the Firestore
22
+ * collection indicated by `configPath`.
23
+ */
24
+ firebaseApp?: FirebaseApp;
25
+ /**
26
+ * Path where the plugin users configuration is stored.
27
+ * Default: __FIRECMS/config/users
28
+ * You can specify a different path if you want to store the user management configuration in a different place.
29
+ * Please keep in mind that the FireCMS users are not necessarily the same as the Firebase users (but they can be).
30
+ * The path should be relative to the root of the Firestore database, and should always have an odd number of segments.
31
+ */
32
+ usersPath?: string;
33
+
34
+ /**
35
+ * Path where the plugin roles configuration is stored.
36
+ * Default: __FIRECMS/config/roles
37
+ */
38
+ rolesPath?: string;
39
+
40
+ usersLimit?: number;
41
+
42
+ canEditRoles?: boolean;
43
+
44
+ authController: AuthController;
45
+
46
+ /**
47
+ * If there are no roles in the database, provide a button to create the default roles.
48
+ */
49
+ allowDefaultRolesCreation?: boolean;
50
+
51
+ /**
52
+ * Include the collection config permissions in the user management system.
53
+ */
54
+ includeCollectionConfigPermissions?: boolean;
55
+
56
+ }
57
+
58
+ /**
59
+ * This hook is used to build a user management object that can be used to
60
+ * manage users and roles in a Firestore backend.
61
+ * @param backendFirebaseApp
62
+ * @param usersPath
63
+ * @param rolesPath
64
+ * @param usersLimit
65
+ * @param canEditRoles
66
+ */
67
+ export function useBuildFirestoreUserManagement({
68
+ firebaseApp,
69
+ usersPath = "__FIRECMS/config/users",
70
+ rolesPath = "__FIRECMS/config/roles",
71
+ usersLimit,
72
+ canEditRoles = true,
73
+ authController,
74
+ allowDefaultRolesCreation,
75
+ includeCollectionConfigPermissions
76
+ }: UserManagementParams): UserManagement {
77
+
78
+ const firestoreRef = useRef<Firestore>();
79
+
80
+ const [rolesLoading, setRolesLoading] = React.useState<boolean>(true);
81
+ const [usersLoading, setUsersLoading] = React.useState<boolean>(true);
82
+ const [roles, setRoles] = React.useState<Role[]>([]);
83
+ const [usersWithRoleIds, setUsersWithRoleIds] = React.useState<UserWithRoleIds[]>([]);
84
+
85
+ const users = usersWithRoleIds.map(u => ({
86
+ ...u,
87
+ roles: roles.filter(r => u.roles?.includes(r.id))
88
+ }) as UserWithRoles);
89
+
90
+ const [rolesError, setRolesError] = React.useState<Error | undefined>();
91
+ const [usersError, setUsersError] = React.useState<Error | undefined>();
92
+
93
+ const loading = rolesLoading || usersLoading;
94
+
95
+ const loggedInUser: UserWithRoles | undefined = users.find(u => u.email?.toLowerCase() === authController.user?.email?.toLowerCase());
96
+ // console.log("authController", authController);
97
+ // if (!loading && !authController.authLoading) {
98
+ // const user = authController.user;
99
+ // if (user) {
100
+ // loggedInUser = users.find(u => u.email?.toLowerCase() === user.email?.toLowerCase());
101
+ // }
102
+ // }
103
+
104
+ useEffect(() => {
105
+ if (!firebaseApp) return;
106
+ firestoreRef.current = getFirestore(firebaseApp);
107
+ }, [firebaseApp]);
108
+
109
+ useEffect(() => {
110
+ if (!firebaseApp || !rolesPath) return;
111
+ const firestore = getFirestore(firebaseApp);
112
+
113
+ return onSnapshot(collection(firestore, rolesPath),
114
+ {
115
+ next: (snapshot) => {
116
+ setRolesError(undefined);
117
+ try {
118
+ const newRoles = docsToRoles(snapshot.docs);
119
+ setRoles(newRoles);
120
+ } catch (e) {
121
+ // console.error(e);
122
+ setRolesError(e as Error);
123
+ }
124
+ setRolesLoading(false);
125
+ },
126
+ error: (e) => {
127
+ setRolesError(e);
128
+ setRolesLoading(false);
129
+ }
130
+ }
131
+ );
132
+ }, [firebaseApp, rolesPath]);
133
+
134
+ useEffect(() => {
135
+ if (!firebaseApp || !usersPath) return;
136
+ const firestore = getFirestore(firebaseApp);
137
+
138
+ return onSnapshot(collection(firestore, usersPath),
139
+ {
140
+ next: (snapshot) => {
141
+ setUsersError(undefined);
142
+ try {
143
+ const newUsers = docsToUsers(snapshot.docs);
144
+ setUsersWithRoleIds(newUsers);
145
+ } catch (e) {
146
+ setUsersError(e as Error);
147
+ }
148
+ setUsersLoading(false);
149
+ },
150
+ error: (e) => {
151
+ setUsersError(e);
152
+ setUsersLoading(false);
153
+ }
154
+ }
155
+ );
156
+ }, [firebaseApp, usersPath]);
157
+
158
+ const saveUser = useCallback(async (user: UserWithRoles): Promise<UserWithRoles> => {
159
+ const firestore = firestoreRef.current;
160
+ if (!firestore || !usersPath) throw Error("useFirestoreConfigurationPersistence Firestore not initialised");
161
+ console.debug("Persisting user", user);
162
+ const roleIds = user.roles.map(r => r.id);
163
+ const {
164
+ uid,
165
+ ...userData
166
+ } = user;
167
+ return setDoc(doc(firestore, usersPath, uid), { ...userData, roles: roleIds }, { merge: true }).then(() => user);
168
+ }, [usersPath]);
169
+
170
+ const saveRole = useCallback((role: Role): Promise<void> => {
171
+ const firestore = firestoreRef.current;
172
+ if (!firestore || !rolesPath) throw Error("useFirestoreConfigurationPersistence Firestore not initialised");
173
+ console.debug("Persisting role", role);
174
+ const {
175
+ id,
176
+ ...roleData
177
+ } = role;
178
+ const ref = doc(firestore, rolesPath, id);
179
+ return setDoc(ref, roleData, { merge: true });
180
+ }, [rolesPath]);
181
+
182
+ const deleteUser = useCallback(async (user: UserWithRoles): Promise<void> => {
183
+ const firestore = firestoreRef.current;
184
+ if (!firestore || !usersPath) throw Error("useFirestoreConfigurationPersistence Firestore not initialised");
185
+ console.debug("Deleting", user);
186
+ const { uid } = user;
187
+ return deleteDoc(doc(firestore, usersPath, uid));
188
+ }, [usersPath]);
189
+
190
+ const deleteRole = useCallback((role: Role): Promise<void> => {
191
+ const firestore = firestoreRef.current;
192
+ if (!firestore || !rolesPath) throw Error("useFirestoreConfigurationPersistence Firestore not initialised");
193
+ console.debug("Deleting", role);
194
+ const { id } = role;
195
+ const ref = doc(firestore, rolesPath, id);
196
+ return deleteDoc(ref);
197
+ }, [rolesPath]);
198
+
199
+ const collectionPermissions: PermissionsBuilder = useCallback(({
200
+ collection,
201
+ }) => resolveUserRolePermissions({
202
+ collection,
203
+ user: loggedInUser ?? null
204
+ }), [loggedInUser?.uid]);
205
+
206
+ return {
207
+ loading,
208
+ loggedInUser,
209
+ roles,
210
+ users,
211
+ saveUser,
212
+ saveRole,
213
+ deleteUser,
214
+ deleteRole,
215
+ usersLimit,
216
+ canEditRoles: canEditRoles === undefined ? true : canEditRoles,
217
+ allowDefaultRolesCreation: allowDefaultRolesCreation === undefined ? true : allowDefaultRolesCreation,
218
+ includeCollectionConfigPermissions: Boolean(includeCollectionConfigPermissions),
219
+ collectionPermissions
220
+ }
221
+ }
222
+
223
+ const docsToUsers = (docs: DocumentSnapshot[]): (UserWithRoleIds)[] => {
224
+ return docs.map((doc) => {
225
+ const data = doc.data() as any;
226
+ const newVar = {
227
+ uid: doc.id,
228
+ ...data,
229
+ created_on: data?.created_on?.toDate(),
230
+ updated_on: data?.updated_on?.toDate()
231
+ };
232
+ return newVar as (UserWithRoleIds);
233
+ });
234
+ }
235
+
236
+ const docsToRoles = (docs: DocumentSnapshot[]): Role[] => {
237
+ return docs.map((doc) => ({
238
+ id: doc.id,
239
+ ...doc.data()
240
+ } as Role));
241
+ }
@@ -0,0 +1,5 @@
1
+ import { useContext } from "react";
2
+ import { UserManagement } from "../types/user_management";
3
+ import { UserManagementContext } from "../UserManagementProvider";
4
+
5
+ export const useUserManagement = () => useContext<UserManagement>(UserManagementContext);
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from "./hooks";
2
+ export * from "./components";
3
+ export * from "./types";
4
+ export * from "./utils";
5
+ export * from "./useUserManagementPlugin";
6
+ export * from "./UserManagementProvider";
7
+ export * from "./admin_views";
@@ -0,0 +1,8 @@
1
+ import { User } from "@firecms/core";
2
+ import { Role } from "./roles";
3
+
4
+ export type UserWithRoles = User & {
5
+ updated_on?: Date;
6
+ created_on?: Date;
7
+ roles: Role[];
8
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./firecms_user";
2
+ export * from "./roles";
3
+ export * from "./user_management";
@@ -0,0 +1,41 @@
1
+ import { Permissions } from "@firecms/core";
2
+
3
+ export type Role = {
4
+
5
+ /**
6
+ * ID of the role
7
+ */
8
+ id: string;
9
+
10
+ /**
11
+ * Name of the role
12
+ */
13
+ name: string;
14
+
15
+ /**
16
+ * If this flag is true, the user can perform any action
17
+ */
18
+ isAdmin?: boolean;
19
+
20
+ /**
21
+ * Default permissions for all collections for this role.
22
+ * You can override this values at the collection level using
23
+ * {@link collectionPermissions}
24
+ */
25
+ defaultPermissions?: Permissions;
26
+
27
+ /**
28
+ * Record of stripped collection ids to their permissions.
29
+ * @see stripCollectionPath
30
+ */
31
+ collectionPermissions?: Record<string, Permissions>;
32
+
33
+ config?: {
34
+
35
+ createCollections?: boolean;
36
+
37
+ editCollections?: boolean | "own";
38
+
39
+ deleteCollections?: boolean | "own";
40
+ }
41
+ }
@@ -0,0 +1,50 @@
1
+ import { UserWithRoles } from "./firecms_user";
2
+ import { Role } from "./roles";
3
+ import { PermissionsBuilder } from "@firecms/core";
4
+
5
+ export type UserManagement<USER extends UserWithRoles = UserWithRoles> = {
6
+
7
+ loading: boolean;
8
+
9
+ /**
10
+ * The user currently logged in, in the user management system.
11
+ * This is the same user that is logged in the Authenticator, but with the roles
12
+ * and permissions loaded.
13
+ */
14
+ loggedInUser: USER | undefined;
15
+
16
+ users: USER[];
17
+ saveUser: (user: USER) => Promise<USER>;
18
+ deleteUser: (user: USER) => Promise<void>;
19
+
20
+ roles: Role[];
21
+ saveRole: (role: Role) => Promise<void>;
22
+ deleteRole: (role: Role) => Promise<void>;
23
+
24
+ /**
25
+ * Maximum number of users that can be created.
26
+ */
27
+ usersLimit?: number;
28
+
29
+ /**
30
+ * Can the logged user edit roles?
31
+ */
32
+ canEditRoles?: boolean;
33
+
34
+ /**
35
+ * Include a button to create default roles, in case there are no roles in the system.
36
+ */
37
+ allowDefaultRolesCreation?: boolean;
38
+
39
+ /**
40
+ * Include the collection config permissions in the user management system.
41
+ */
42
+ includeCollectionConfigPermissions?: boolean;
43
+
44
+ /**
45
+ * Get a permissions builder that defines which operations can be performed by a user in a collection.
46
+ * The permission builder generated should be based in the user roles and the collection config.
47
+ */
48
+ collectionPermissions: PermissionsBuilder;
49
+
50
+ };
@@ -0,0 +1,18 @@
1
+ import { AuthController, FireCMSPlugin } from "@firecms/core";
2
+ import { UserManagementProvider } from "./UserManagementProvider";
3
+ import { UserManagement } from "./types";
4
+
5
+ export function useUserManagementPlugin({ userManagement }: {
6
+ userManagement: UserManagement,
7
+ }): FireCMSPlugin {
8
+ return {
9
+ name: "User management plugin",
10
+ loading: userManagement.loading,
11
+ provider: {
12
+ Component: UserManagementProvider,
13
+ props: {
14
+ userManagement
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,52 @@
1
+ export function darkenColor(hexColor: string, darkenBy = 10): string {
2
+ // Check input validity
3
+ if (!/^#([0-9A-Fa-f]{3}){1,2}$/.test(hexColor)) {
4
+ throw new Error("Invalid color format");
5
+ }
6
+
7
+ // If shorthand form, convert to full form
8
+ let color = hexColor.substring(1).split("");
9
+ if (color.length === 3) {
10
+ color = [color[0], color[0], color[1], color[1], color[2], color[2]];
11
+ }
12
+
13
+ // Convert to RGB values
14
+ let r = parseInt(color[0] + color[1], 16);
15
+ let g = parseInt(color[2] + color[3], 16);
16
+ let b = parseInt(color[4] + color[5], 16);
17
+
18
+ // Reduce each color component by the specified percentage (darkenBy)
19
+ r = Math.floor(r * (1 - darkenBy / 100));
20
+ g = Math.floor(g * (1 - darkenBy / 100));
21
+ b = Math.floor(b * (1 - darkenBy / 100));
22
+
23
+ // Recombine into hex and return
24
+ return "#" +
25
+ (r < 16 ? "0" : "") + r.toString(16) +
26
+ (g < 16 ? "0" : "") + g.toString(16) +
27
+ (b < 16 ? "0" : "") + b.toString(16);
28
+ }
29
+
30
+ export function hexToRgbaWithOpacity(hexColor: string, opacity = 10): string {
31
+ // Check input validity
32
+ if (!/^#([0-9A-Fa-f]{3}){1,2}$/.test(hexColor)) {
33
+ throw new Error("Invalid color format");
34
+ }
35
+
36
+ // If shorthand form, convert to full form
37
+ let color = hexColor.substring(1).split("");
38
+ if (color.length === 3) {
39
+ color = [color[0], color[0], color[1], color[1], color[2], color[2]];
40
+ }
41
+
42
+ // Convert to RGB values
43
+ const r = parseInt(color[0] + color[1], 16);
44
+ const g = parseInt(color[2] + color[3], 16);
45
+ const b = parseInt(color[4] + color[5], 16);
46
+
47
+ // Convert opacity to a decimal for CSS
48
+ const alpha = opacity / 100;
49
+
50
+ // Construct and return the RGBA color
51
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
52
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./permissions";
2
+ export * from "./local_storage";
3
+ export * from "./colors";
@@ -0,0 +1,53 @@
1
+ // const tokens = new Map<string, {
2
+ // token: string,
3
+ // expiry: Date
4
+ // }>();
5
+
6
+ export function cacheDelegatedLoginToken(projectId: string, delegatedToken?: string) {
7
+ if (!delegatedToken) {
8
+ return;
9
+ }
10
+
11
+ const data = parseJwt(delegatedToken);
12
+ // @ts-ignore
13
+ const expiry = new Date(data.exp * 1000);
14
+ localStorage.setItem(`auth_token::${projectId}`, JSON.stringify({
15
+ token: delegatedToken,
16
+ expiry
17
+ }));
18
+
19
+ }
20
+
21
+ export function getDelegatedLoginTokenFromCache(projectId: string) {
22
+ const entry = localStorage.getItem(`auth_token::${projectId}`);
23
+ if (entry) {
24
+ const data = JSON.parse(entry);
25
+ data.expiry = new Date(data.expiry);
26
+ if (data.expiry > new Date()) {
27
+ return data.token;
28
+ }
29
+ }
30
+ return undefined;
31
+ }
32
+
33
+ export function clearDelegatedLoginTokensCache() {
34
+ for (let i = 0; i < localStorage.length; i++) {
35
+ const key = localStorage.key(i);
36
+ if (key?.startsWith("auth_token::")) {
37
+ localStorage.removeItem(key);
38
+ }
39
+ }
40
+ }
41
+
42
+ function parseJwt(token?: string): object {
43
+ if (!token) {
44
+ throw new Error("No JWT token");
45
+ }
46
+ const base64Url = token.split(".")[1];
47
+ const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
48
+ const jsonPayload = decodeURIComponent(window.atob(base64).split("").map(function (c) {
49
+ return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
50
+ }).join(""));
51
+
52
+ return JSON.parse(jsonPayload);
53
+ }
@@ -0,0 +1,83 @@
1
+ import { CMSType, EntityCollection, Permissions } from "@firecms/core";
2
+ import { Role, UserWithRoles } from "../types";
3
+
4
+ export const RESERVED_GROUPS = ["Admin"];
5
+
6
+ const DEFAULT_PERMISSIONS = {
7
+ read: false,
8
+ edit: false,
9
+ create: false,
10
+ delete: false
11
+ };
12
+
13
+ export function resolveUserRolePermissions<UserType extends UserWithRoles>
14
+ ({ collection, user }: {
15
+ collection: EntityCollection<any>,
16
+ user: UserType | null
17
+ }): Permissions {
18
+
19
+ const roles = user?.roles;
20
+ if (!roles) {
21
+ return DEFAULT_PERMISSIONS;
22
+ } else if (collection.ownerId === user?.uid) {
23
+ return {
24
+ read: true,
25
+ create: true,
26
+ edit: true,
27
+ delete: true
28
+ };
29
+ } else {
30
+ const basePermissions = {
31
+ read: false,
32
+ create: false,
33
+ edit: false,
34
+ delete: false
35
+ };
36
+
37
+ return roles
38
+ .map(role => resolveCollectionRole(role, collection.id))
39
+ .reduce(mergePermissions, basePermissions);
40
+ }
41
+ }
42
+
43
+ function resolveCollectionRole(role: Role, id: string): Permissions {
44
+
45
+ const basePermissions = {
46
+ read: role.isAdmin || role.defaultPermissions?.read,
47
+ create: role.isAdmin || role.defaultPermissions?.create,
48
+ edit: role.isAdmin || role.defaultPermissions?.edit,
49
+ delete: role.isAdmin || role.defaultPermissions?.delete
50
+ };
51
+ if (role.collectionPermissions && role.collectionPermissions[id]) {
52
+ return mergePermissions(role.collectionPermissions[id], basePermissions);
53
+ } else if (role.defaultPermissions) {
54
+ return mergePermissions(role.defaultPermissions, basePermissions);
55
+ } else {
56
+ return basePermissions;
57
+ }
58
+ }
59
+
60
+ const mergePermissions = (permA: Permissions, permB: Permissions) => {
61
+ return {
62
+ read: permA.read || permB.read,
63
+ create: permA.create || permB.create,
64
+ edit: permA.edit || permB.edit,
65
+ delete: permA.delete || permB.delete
66
+ };
67
+ }
68
+
69
+ export function getUserRoles(roles: Role[], fireCMSUser: UserWithRoles): Role[] | undefined {
70
+ return !roles
71
+ ? undefined
72
+ : (fireCMSUser.roles
73
+ ? fireCMSUser.roles
74
+ .map(role => roles.find((r) => r.id === role.id))
75
+ .filter(Boolean) as Role[]
76
+ : []);
77
+ }
78
+
79
+ export const areRolesEqual = (rolesA: Role[], rolesB: Role[]) => {
80
+ const rolesAIds = rolesA.map(r => r.id);
81
+ const rolesBIds = rolesB.map(r => r.id);
82
+ return rolesAIds.length === rolesB.length && rolesAIds.every((role) => rolesBIds.includes(role));
83
+ }
@@ -0,0 +1,23 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ function printChanged(props: any, prev: any, path = "", depth = 0) {
4
+ if (depth > 10) {
5
+ return;
6
+ }
7
+ if (props && prev && typeof props === "object" && typeof prev === "object") {
8
+ Object.keys(props).forEach((key) => {
9
+ printChanged(props[key], prev[key], path + "." + key, depth + 1);
10
+ });
11
+ } else if (props !== prev) {
12
+ console.log("Changed props:", path);
13
+ }
14
+
15
+ }
16
+
17
+ export function useTraceUpdate(props: any) {
18
+ const prev = useRef(props);
19
+ useEffect(() => {
20
+ printChanged(props, prev.current, "");
21
+ prev.current = props;
22
+ });
23
+ }