@firecms/user_management 3.0.0-canary.8 → 3.0.0-canary.80

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 (43) hide show
  1. package/LICENSE +113 -21
  2. package/dist/UserManagementProvider.d.ts +4 -3
  3. package/dist/components/roles/RoleChip.d.ts +1 -1
  4. package/dist/components/roles/RolesDetailsForm.d.ts +1 -2
  5. package/dist/components/roles/RolesTable.d.ts +1 -1
  6. package/dist/components/roles/default_roles.d.ts +1 -1
  7. package/dist/components/users/UserDetailsForm.d.ts +2 -2
  8. package/dist/components/users/UsersTable.d.ts +2 -2
  9. package/dist/hooks/index.d.ts +1 -1
  10. package/dist/hooks/{useBuildFirestoreUserManagement.d.ts → useFirestoreUserManagement.d.ts} +8 -4
  11. package/dist/hooks/useUserManagement.d.ts +3 -2
  12. package/dist/index.es.js +569 -493
  13. package/dist/index.es.js.map +1 -1
  14. package/dist/index.umd.js +1 -1
  15. package/dist/index.umd.js.map +1 -1
  16. package/dist/types/index.d.ts +1 -2
  17. package/dist/types/persisted_user.d.ts +5 -0
  18. package/dist/types/user_management.d.ts +18 -10
  19. package/dist/useUserManagementPlugin.d.ts +6 -1
  20. package/dist/utils/permissions.d.ts +3 -4
  21. package/package.json +14 -31
  22. package/src/UserManagementProvider.tsx +4 -3
  23. package/src/components/roles/RoleChip.tsx +1 -1
  24. package/src/components/roles/RolesDetailsForm.tsx +1 -2
  25. package/src/components/roles/RolesTable.tsx +1 -2
  26. package/src/components/roles/RolesView.tsx +1 -2
  27. package/src/components/roles/default_roles.tsx +1 -1
  28. package/src/components/users/UserDetailsForm.tsx +14 -13
  29. package/src/components/users/UsersTable.tsx +6 -6
  30. package/src/components/users/UsersView.tsx +3 -3
  31. package/src/hooks/index.ts +1 -1
  32. package/src/hooks/{useBuildFirestoreUserManagement.tsx → useFirestoreUserManagement.tsx} +92 -56
  33. package/src/hooks/useUserManagement.tsx +3 -3
  34. package/src/types/index.ts +1 -2
  35. package/src/types/persisted_user.ts +6 -0
  36. package/src/types/user_management.tsx +22 -11
  37. package/src/useUserManagementPlugin.tsx +89 -3
  38. package/src/utils/permissions.ts +7 -5
  39. package/dist/types/firecms_user.d.ts +0 -7
  40. package/dist/types/roles.d.ts +0 -31
  41. package/src/types/firecms_user.ts +0 -8
  42. package/src/types/roles.ts +0 -41
  43. package/tailwind.config.js +0 -68
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/user_management",
3
3
  "type": "module",
4
- "version": "3.0.0-canary.8",
4
+ "version": "3.0.0-canary.80",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -22,39 +22,29 @@
22
22
  "require": "./dist/index.umd.js",
23
23
  "types": "./dist/index.d.ts"
24
24
  },
25
- "./package.json": "./package.json",
26
- "./tailwind.config.js": "./tailwind.config.js"
25
+ "./package.json": "./package.json"
27
26
  },
28
- "packageManager": "yarn@4.1.0",
29
27
  "main": "./dist/index.umd.js",
30
28
  "module": "./dist/index.es.js",
31
29
  "types": "dist/index.d.ts",
32
30
  "source": "src/index.ts",
33
31
  "dependencies": {
34
- "@firecms/core": "^3.0.0-canary.8",
35
- "@firecms/formex": "^3.0.0-canary.8",
36
- "@firecms/ui": "^3.0.0-canary.8",
32
+ "@firecms/core": "^3.0.0-canary.80",
33
+ "@firecms/formex": "^3.0.0-canary.80",
34
+ "@firecms/ui": "^3.0.0-canary.80",
37
35
  "date-fns": "^3.6.0"
38
36
  },
39
37
  "peerDependencies": {
40
- "firebase": "^10.7.1",
41
- "react": "^18.2.0",
42
- "react-dom": "^18.2.0"
38
+ "firebase": "^10.12.2",
39
+ "react": "^18.3.1",
40
+ "react-dom": "^18.3.1"
43
41
  },
44
42
  "devDependencies": {
45
- "@types/node": "^20.11.30",
46
- "@types/react": "^18.2.67",
47
- "@types/react-dom": "^18.2.22",
48
- "@typescript-eslint/parser": "^7.3.1",
49
- "eslint": "^8.57.0",
50
- "eslint-config-standard": "^17.1.0",
51
- "eslint-plugin-import": "^2.29.1",
52
- "eslint-plugin-n": "^16.6.2",
53
- "eslint-plugin-promise": "^6.1.1",
54
- "eslint-plugin-react": "^7.34.1",
55
- "eslint-plugin-react-hooks": "^4.6.0",
56
- "typescript": "^5.4.2",
57
- "vite": "^5.1.6"
43
+ "@types/node": "^20.14.9",
44
+ "@types/react": "^18.3.3",
45
+ "@types/react-dom": "^18.3.0",
46
+ "typescript": "^5.5.3",
47
+ "vite": "^5.3.2"
58
48
  },
59
49
  "scripts": {
60
50
  "dev": "vite",
@@ -64,14 +54,7 @@
64
54
  "files": [
65
55
  "dist",
66
56
  "src",
67
- "tailwind.config.js",
68
57
  "bin"
69
58
  ],
70
- "eslintConfig": {
71
- "extends": [
72
- "react-app",
73
- "react-app/jest"
74
- ]
75
- },
76
- "gitHead": "6d3cbe74b9d5ac0fbd7ee9110a92d0188a252f02"
59
+ "gitHead": "e41e7401fda1929c44300ada0b993e5b0703b7a1"
77
60
  }
@@ -1,13 +1,14 @@
1
1
  import React, { PropsWithChildren } from "react";
2
- import { UserManagement, UserWithRoles } from "./types";
2
+ import { UserManagement } from "./types";
3
+ import { User } from "@firecms/core";
3
4
 
4
5
  export const UserManagementContext = React.createContext<UserManagement<any>>({} as any);
5
6
 
6
- export interface UserManagementProviderProps<U extends UserWithRoles = UserWithRoles> {
7
+ export interface UserManagementProviderProps<U extends User = User> {
7
8
  userManagement: UserManagement<U>
8
9
  }
9
10
 
10
- export function UserManagementProvider<U extends UserWithRoles = UserWithRoles>({
11
+ export function UserManagementProvider<U extends User = User>({
11
12
  children,
12
13
  userManagement
13
14
  }: PropsWithChildren<UserManagementProviderProps<U>>) {
@@ -1,5 +1,5 @@
1
1
  import { Chip, getColorSchemeForSeed } from "@firecms/ui";
2
- import { Role } from "../../types";
2
+ import { Role } from "@firecms/core";
3
3
 
4
4
  export type RoleChipProps = {
5
5
  role: Role;
@@ -1,7 +1,7 @@
1
1
  import React, { useCallback, useState } from "react";
2
2
  import * as Yup from "yup";
3
3
 
4
- import { EntityCollection, FieldCaption, toSnakeCase, } from "@firecms/core";
4
+ import { EntityCollection, FieldCaption, Role, toSnakeCase, } from "@firecms/core";
5
5
  import {
6
6
  Button,
7
7
  Checkbox,
@@ -24,7 +24,6 @@ import {
24
24
  } from "@firecms/ui";
25
25
  import { useUserManagement } from "../../hooks";
26
26
  import { Formex, getIn, useCreateFormex } from "@firecms/formex";
27
- import { Role } from "../../types";
28
27
 
29
28
  export const RoleYupSchema = Yup.object().shape({
30
29
  id: Yup.string().required("Required"),
@@ -13,9 +13,8 @@ import {
13
13
  Tooltip,
14
14
  Typography
15
15
  } from "@firecms/ui";
16
- import { DeleteConfirmationDialog } from "@firecms/core";
16
+ import { DeleteConfirmationDialog, Role } from "@firecms/core";
17
17
  import { useUserManagement } from "../../hooks";
18
- import { Role } from "../../types";
19
18
  import { RoleChip } from "./RoleChip";
20
19
  import { DEFAULT_ROLES } from "./default_roles";
21
20
 
@@ -1,11 +1,10 @@
1
1
  import React, { useCallback, useState } from "react";
2
2
 
3
- import { useNavigationController } from "@firecms/core";
3
+ import { Role, useNavigationController } from "@firecms/core";
4
4
  import { AddIcon, Button, Container, Tooltip, Typography } from "@firecms/ui";
5
5
  import { RolesTable } from "./RolesTable";
6
6
  import { RolesDetailsForm } from "./RolesDetailsForm";
7
7
  import { useUserManagement } from "../../hooks";
8
- import { Role } from "../../types";
9
8
 
10
9
  export const RolesView = React.memo(
11
10
  function RolesView({ children }: { children?: React.ReactNode }) {
@@ -1,4 +1,4 @@
1
- import { Role } from "../../types";
1
+ import { Role } from "@firecms/core";
2
2
 
3
3
  export const DEFAULT_ROLES: Role[] = [
4
4
  {
@@ -12,10 +12,9 @@ import {
12
12
  TextField,
13
13
  Typography,
14
14
  } from "@firecms/ui";
15
- import { FieldCaption, useSnackbarController } from "@firecms/core";
15
+ import { FieldCaption, Role, useAuthController, User, useSnackbarController } from "@firecms/core";
16
16
  import { Formex, useCreateFormex } from "@firecms/formex";
17
17
 
18
- import { Role, UserWithRoles } from "../../types";
19
18
  import { areRolesEqual } from "../../utils";
20
19
  import { useUserManagement } from "../../hooks";
21
20
  import { RoleChip } from "../roles";
@@ -26,17 +25,17 @@ export const UserYupSchema = Yup.object().shape({
26
25
  roles: Yup.array().min(1)
27
26
  });
28
27
 
29
- function canUserBeEdited(loggedUser: UserWithRoles, user: UserWithRoles, users: UserWithRoles[], roles: Role[], prevUser?: UserWithRoles) {
30
- const admins = users.filter(u => u.roles.map(r => r.id).includes("admin"));
31
- const loggedUserIsAdmin = loggedUser.roles.map(r => r.id).includes("admin");
32
- const didRolesChange = !prevUser || !areRolesEqual(prevUser.roles, user.roles);
28
+ function canUserBeEdited(loggedUser: User, user: User, users: User[], roles: Role[], prevUser?: User) {
29
+ const admins = users.filter(u => u.roles?.map(r => r.id).includes("admin"));
30
+ const loggedUserIsAdmin = loggedUser.roles?.map(r => r.id).includes("admin");
31
+ const didRolesChange = !prevUser || !areRolesEqual(prevUser.roles ?? [], user.roles ?? []);
33
32
 
34
33
  if (didRolesChange && !loggedUserIsAdmin) {
35
34
  throw new Error("Only admins can change roles");
36
35
  }
37
36
 
38
37
  // was the admin role removed
39
- const adminRoleRemoved = prevUser && prevUser.roles.map(r => r.id).includes("admin") && !user.roles.map(r => r.id).includes("admin");
38
+ const adminRoleRemoved = prevUser && prevUser.roles?.map(r => r.id).includes("admin") && !user.roles?.map(r => r.id).includes("admin");
40
39
 
41
40
  // avoid removing the last admin
42
41
  if (adminRoleRemoved && admins.length === 1) {
@@ -51,20 +50,22 @@ export function UserDetailsForm({
51
50
  handleClose
52
51
  }: {
53
52
  open: boolean,
54
- user?: UserWithRoles,
53
+ user?: User,
55
54
  handleClose: () => void
56
55
  }) {
57
56
 
58
57
  const snackbarController = useSnackbarController();
59
58
  const {
60
- loggedInUser,
59
+ user: loggedInUser
60
+ } = useAuthController();
61
+ const {
61
62
  saveUser,
62
63
  users,
63
64
  roles,
64
65
  } = useUserManagement();
65
66
  const isNewUser = !userProp;
66
67
 
67
- const onUserUpdated = useCallback((savedUser: UserWithRoles): Promise<UserWithRoles> => {
68
+ const onUserUpdated = useCallback((savedUser: User): Promise<User> => {
68
69
  if (!loggedInUser) {
69
70
  throw new Error("Logged user not found");
70
71
  }
@@ -81,7 +82,7 @@ export function UserDetailsForm({
81
82
  displayName: "",
82
83
  email: "",
83
84
  roles: roles.filter(r => r.id === "editor")
84
- } as UserWithRoles,
85
+ } as User,
85
86
  validation: (values) => {
86
87
  return UserYupSchema.validate(values, { abortEarly: false })
87
88
  .then(() => {
@@ -93,7 +94,7 @@ export function UserDetailsForm({
93
94
  }, {});
94
95
  });
95
96
  },
96
- onSubmit: (user: UserWithRoles, formexController) => {
97
+ onSubmit: (user: User, formexController) => {
97
98
 
98
99
  return onUserUpdated(user)
99
100
  .then(() => {
@@ -181,7 +182,7 @@ export function UserDetailsForm({
181
182
  <div className={"col-span-12"}>
182
183
  <MultiSelect
183
184
  label="Roles"
184
- value={values.roles.map(r => r.id) ?? []}
185
+ value={values.roles?.map(r => r.id) ?? []}
185
186
  onMultiValueChange={(value: string[]) => setFieldValue("roles", value.map(id => roles.find(r => r.id === id) as Role))}
186
187
  renderValue={(value: string) => {
187
188
  const userRole = roles
@@ -5,9 +5,9 @@ import * as locales from "date-fns/locale";
5
5
 
6
6
  import {
7
7
  defaultDateFormat,
8
- DeleteConfirmationDialog,
8
+ DeleteConfirmationDialog, Role,
9
9
  useAuthController,
10
- useCustomizationController,
10
+ useCustomizationController, User,
11
11
  useSnackbarController
12
12
  } from "@firecms/core";
13
13
  import {
@@ -23,19 +23,19 @@ import {
23
23
  Tooltip,
24
24
  Typography,
25
25
  } from "@firecms/ui";
26
- import { Role, UserWithRoles } from "../../types";
27
26
  import { useUserManagement } from "../../hooks";
28
27
  import { RoleChip } from "../roles";
28
+ import { PersistedUser } from "../../types";
29
29
 
30
30
  export function UsersTable({ onUserClicked }: {
31
- onUserClicked: (user: UserWithRoles) => void;
31
+ onUserClicked: (user: User) => void;
32
32
  }) {
33
33
 
34
34
  const {
35
35
  users,
36
36
  saveUser,
37
37
  deleteUser
38
- } = useUserManagement();
38
+ } = useUserManagement<PersistedUser>();
39
39
 
40
40
  const authController = useAuthController();
41
41
  const snackbarController = useSnackbarController();
@@ -44,7 +44,7 @@ export function UsersTable({ onUserClicked }: {
44
44
  const dateUtilsLocale = customizationController?.locale ? locales[customizationController?.locale as keyof typeof locales] : undefined;
45
45
  const dateFormat: string = customizationController?.dateTimeFormat ?? defaultDateFormat;
46
46
 
47
- const [userToBeDeleted, setUserToBeDeleted] = useState<UserWithRoles | undefined>(undefined);
47
+ const [userToBeDeleted, setUserToBeDeleted] = useState<User | undefined>(undefined);
48
48
  const [deleteInProgress, setDeleteInProgress] = useState<boolean>(false);
49
49
 
50
50
  return (
@@ -3,19 +3,19 @@ import { AddIcon, Button, Container, Typography } from "@firecms/ui";
3
3
  import { UsersTable } from "./UsersTable";
4
4
  import { UserDetailsForm } from "./UserDetailsForm";
5
5
  import React, { useCallback, useState } from "react";
6
- import { UserWithRoles } from "../../types";
7
6
  import { useUserManagement } from "../../hooks/useUserManagement";
7
+ import { User } from "@firecms/core";
8
8
 
9
9
  export const UsersView = function UsersView({ children }: { children?: React.ReactNode }) {
10
10
 
11
11
  const [dialogOpen, setDialogOpen] = useState<boolean>();
12
- const [selectedUser, setSelectedUser] = useState<UserWithRoles | undefined>();
12
+ const [selectedUser, setSelectedUser] = useState<User | undefined>();
13
13
 
14
14
  const { users, usersLimit } = useUserManagement();
15
15
 
16
16
  const reachedUsersLimit = usersLimit !== undefined && (users && users.length >= usersLimit);
17
17
 
18
- const onUserClicked = useCallback((user: UserWithRoles) => {
18
+ const onUserClicked = useCallback((user: User) => {
19
19
  setSelectedUser(user);
20
20
  setDialogOpen(true);
21
21
  }, []);
@@ -1,2 +1,2 @@
1
- export * from "./useBuildFirestoreUserManagement";
1
+ export * from "./useFirestoreUserManagement";
2
2
  export * from "./useUserManagement";
@@ -1,17 +1,17 @@
1
- import React, { useCallback, useEffect, useRef } from "react";
1
+ import React, { useCallback, useEffect } from "react";
2
2
  import {
3
+ addDoc,
3
4
  collection,
4
5
  deleteDoc,
5
6
  doc,
6
7
  DocumentSnapshot,
7
- Firestore,
8
8
  getFirestore,
9
9
  onSnapshot,
10
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";
11
+ } from "@firebase/firestore";
12
+ import { FirebaseApp } from "@firebase/app";
13
+ import { UserManagement } from "../types";
14
+ import { Authenticator, PermissionsBuilder, Role, User } from "@firecms/core";
15
15
  import { resolveUserRolePermissions } from "../utils";
16
16
 
17
17
  type UserWithRoleIds = User & { roles: string[] };
@@ -37,12 +37,16 @@ export interface UserManagementParams {
37
37
  */
38
38
  rolesPath?: string;
39
39
 
40
+ /**
41
+ * Maximum number of users that can be created.
42
+ */
40
43
  usersLimit?: number;
41
44
 
45
+ /**
46
+ * Can the logged user edit roles
47
+ */
42
48
  canEditRoles?: boolean;
43
49
 
44
- authController: AuthController;
45
-
46
50
  /**
47
51
  * If there are no roles in the database, provide a button to create the default roles.
48
52
  */
@@ -64,18 +68,15 @@ export interface UserManagementParams {
64
68
  * @param usersLimit
65
69
  * @param canEditRoles
66
70
  */
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>();
71
+ export function useFirestoreUserManagement({
72
+ firebaseApp,
73
+ usersPath = "__FIRECMS/config/users",
74
+ rolesPath = "__FIRECMS/config/roles",
75
+ usersLimit,
76
+ canEditRoles = true,
77
+ allowDefaultRolesCreation,
78
+ includeCollectionConfigPermissions
79
+ }: UserManagementParams): UserManagement {
79
80
 
80
81
  const [rolesLoading, setRolesLoading] = React.useState<boolean>(true);
81
82
  const [usersLoading, setUsersLoading] = React.useState<boolean>(true);
@@ -85,27 +86,13 @@ export function useBuildFirestoreUserManagement({
85
86
  const users = usersWithRoleIds.map(u => ({
86
87
  ...u,
87
88
  roles: roles.filter(r => u.roles?.includes(r.id))
88
- }) as UserWithRoles);
89
+ }) as User);
89
90
 
90
91
  const [rolesError, setRolesError] = React.useState<Error | undefined>();
91
92
  const [usersError, setUsersError] = React.useState<Error | undefined>();
92
93
 
93
94
  const loading = rolesLoading || usersLoading;
94
95
 
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
96
  useEffect(() => {
110
97
  if (!firebaseApp || !rolesPath) return;
111
98
  const firestore = getFirestore(firebaseApp);
@@ -118,12 +105,13 @@ export function useBuildFirestoreUserManagement({
118
105
  const newRoles = docsToRoles(snapshot.docs);
119
106
  setRoles(newRoles);
120
107
  } catch (e) {
121
- // console.error(e);
108
+ console.error("Error loading roles", e);
122
109
  setRolesError(e as Error);
123
110
  }
124
111
  setRolesLoading(false);
125
112
  },
126
113
  error: (e) => {
114
+ console.error("Error loading roles", e);
127
115
  setRolesError(e);
128
116
  setRolesLoading(false);
129
117
  }
@@ -143,11 +131,13 @@ export function useBuildFirestoreUserManagement({
143
131
  const newUsers = docsToUsers(snapshot.docs);
144
132
  setUsersWithRoleIds(newUsers);
145
133
  } catch (e) {
134
+ console.error("Error loading users", e);
146
135
  setUsersError(e as Error);
147
136
  }
148
137
  setUsersLoading(false);
149
138
  },
150
139
  error: (e) => {
140
+ console.error("Error loading users", e);
151
141
  setUsersError(e);
152
142
  setUsersLoading(false);
153
143
  }
@@ -155,21 +145,31 @@ export function useBuildFirestoreUserManagement({
155
145
  );
156
146
  }, [firebaseApp, usersPath]);
157
147
 
158
- const saveUser = useCallback(async (user: UserWithRoles): Promise<UserWithRoles> => {
159
- const firestore = firestoreRef.current;
160
- if (!firestore || !usersPath) throw Error("useFirestoreConfigurationPersistence Firestore not initialised");
148
+ const saveUser = useCallback(async (user: User): Promise<User> => {
149
+ if (!firebaseApp) throw Error("useFirestoreUserManagement Firebase not initialised");
150
+ const firestore = getFirestore(firebaseApp);
151
+ if (!firestore || !usersPath) throw Error("useFirestoreUserManagement Firestore not initialised");
161
152
  console.debug("Persisting user", user);
162
- const roleIds = user.roles.map(r => r.id);
153
+ const roleIds = user.roles?.map(r => r.id);
163
154
  const {
164
155
  uid,
165
156
  ...userData
166
157
  } = user;
167
- return setDoc(doc(firestore, usersPath, uid), { ...userData, roles: roleIds }, { merge: true }).then(() => user);
168
- }, [usersPath]);
158
+ const data = {
159
+ ...userData,
160
+ roles: roleIds
161
+ };
162
+ if (uid) {
163
+ return setDoc(doc(firestore, usersPath, uid), data, { merge: true }).then(() => user);
164
+ } else {
165
+ return addDoc(collection(firestore, usersPath), data).then(() => user);
166
+ }
167
+ }, [usersPath, firebaseApp]);
169
168
 
170
169
  const saveRole = useCallback((role: Role): Promise<void> => {
171
- const firestore = firestoreRef.current;
172
- if (!firestore || !rolesPath) throw Error("useFirestoreConfigurationPersistence Firestore not initialised");
170
+ if (!firebaseApp) throw Error("useFirestoreUserManagement Firebase not initialised");
171
+ const firestore = getFirestore(firebaseApp);
172
+ if (!firestore || !rolesPath) throw Error("useFirestoreUserManagement Firestore not initialised");
173
173
  console.debug("Persisting role", role);
174
174
  const {
175
175
  id,
@@ -177,46 +177,82 @@ export function useBuildFirestoreUserManagement({
177
177
  } = role;
178
178
  const ref = doc(firestore, rolesPath, id);
179
179
  return setDoc(ref, roleData, { merge: true });
180
- }, [rolesPath]);
180
+ }, [rolesPath, firebaseApp]);
181
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");
182
+ const deleteUser = useCallback(async (user: User): Promise<void> => {
183
+ if (!firebaseApp) throw Error("useFirestoreUserManagement Firebase not initialised");
184
+ const firestore = getFirestore(firebaseApp);
185
+ if (!firestore || !usersPath) throw Error("useFirestoreUserManagement Firestore not initialised");
185
186
  console.debug("Deleting", user);
186
187
  const { uid } = user;
187
188
  return deleteDoc(doc(firestore, usersPath, uid));
188
- }, [usersPath]);
189
+ }, [usersPath, firebaseApp]);
189
190
 
190
191
  const deleteRole = useCallback((role: Role): Promise<void> => {
191
- const firestore = firestoreRef.current;
192
- if (!firestore || !rolesPath) throw Error("useFirestoreConfigurationPersistence Firestore not initialised");
192
+ if (!firebaseApp) throw Error("useFirestoreUserManagement Firebase not initialised");
193
+ const firestore = getFirestore(firebaseApp);
194
+ if (!firestore || !rolesPath) throw Error("useFirestoreUserManagement Firestore not initialised");
193
195
  console.debug("Deleting", role);
194
196
  const { id } = role;
195
197
  const ref = doc(firestore, rolesPath, id);
196
198
  return deleteDoc(ref);
197
- }, [rolesPath]);
199
+ }, [rolesPath, firebaseApp]);
198
200
 
199
201
  const collectionPermissions: PermissionsBuilder = useCallback(({
200
202
  collection,
203
+ user
201
204
  }) => resolveUserRolePermissions({
202
205
  collection,
203
- user: loggedInUser ?? null
204
- }), [loggedInUser?.uid]);
206
+ user
207
+ }), []);
208
+
209
+ const defineRolesFor: ((user: User) => Role[] | undefined) = useCallback((user) => {
210
+ if (!users) throw Error("Users not loaded");
211
+ const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
212
+ return mgmtUser?.roles;
213
+ }, [users]);
214
+
215
+ const authenticator: Authenticator = useCallback(({ user }) => {
216
+ console.debug("Authenticating user", user);
217
+
218
+ if (loading) {
219
+ console.warn("User management is still loading");
220
+ return false;
221
+ }
222
+
223
+ // This is an example of how you can link the access system to the user management plugin
224
+ if (users.length === 0) {
225
+ return true; // If there are no users created yet, we allow access to every user
226
+ }
227
+
228
+ const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
229
+ if (mgmtUser) {
230
+ return true;
231
+ }
232
+
233
+ throw Error("Could not find a user with the provided email in the user management system.");
234
+ }, [loading, users]);
235
+
236
+ const isAdmin = roles.some(r => r.id === "admin");
205
237
 
206
238
  return {
207
239
  loading,
208
- loggedInUser,
209
240
  roles,
210
241
  users,
211
242
  saveUser,
212
243
  saveRole,
244
+ rolesError,
213
245
  deleteUser,
214
246
  deleteRole,
215
247
  usersLimit,
248
+ usersError,
249
+ isAdmin,
216
250
  canEditRoles: canEditRoles === undefined ? true : canEditRoles,
217
251
  allowDefaultRolesCreation: allowDefaultRolesCreation === undefined ? true : allowDefaultRolesCreation,
218
252
  includeCollectionConfigPermissions: Boolean(includeCollectionConfigPermissions),
219
- collectionPermissions
253
+ collectionPermissions,
254
+ defineRolesFor,
255
+ authenticator
220
256
  }
221
257
  }
222
258
 
@@ -1,5 +1,5 @@
1
1
  import { useContext } from "react";
2
- import { UserManagement } from "../types/user_management";
2
+ import { UserManagement } from "../types";
3
3
  import { UserManagementContext } from "../UserManagementProvider";
4
-
5
- export const useUserManagement = () => useContext<UserManagement>(UserManagementContext);
4
+ import { User } from "@firecms/core";
5
+ export const useUserManagement = <USER extends User>() => useContext<UserManagement<USER>>(UserManagementContext);
@@ -1,3 +1,2 @@
1
- export * from "./firecms_user";
2
- export * from "./roles";
3
1
  export * from "./user_management";
2
+ export * from "./persisted_user";
@@ -0,0 +1,6 @@
1
+ import { User } from "@firecms/core";
2
+
3
+ export type PersistedUser = User & {
4
+ updated_on?: Date;
5
+ created_on?: Date;
6
+ }
@@ -1,18 +1,9 @@
1
- import { UserWithRoles } from "./firecms_user";
2
- import { Role } from "./roles";
3
- import { PermissionsBuilder } from "@firecms/core";
1
+ import { Authenticator, PermissionsBuilder, Role, User } from "@firecms/core";
4
2
 
5
- export type UserManagement<USER extends UserWithRoles = UserWithRoles> = {
3
+ export type UserManagement<USER extends User = User> = {
6
4
 
7
5
  loading: boolean;
8
6
 
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
7
  users: USER[];
17
8
  saveUser: (user: USER) => Promise<USER>;
18
9
  deleteUser: (user: USER) => Promise<void>;
@@ -31,6 +22,11 @@ export type UserManagement<USER extends UserWithRoles = UserWithRoles> = {
31
22
  */
32
23
  canEditRoles?: boolean;
33
24
 
25
+ /**
26
+ * Is the logged user Admin?
27
+ */
28
+ isAdmin?: boolean;
29
+
34
30
  /**
35
31
  * Include a button to create default roles, in case there are no roles in the system.
36
32
  */
@@ -47,4 +43,19 @@ export type UserManagement<USER extends UserWithRoles = UserWithRoles> = {
47
43
  */
48
44
  collectionPermissions: PermissionsBuilder;
49
45
 
46
+ /**
47
+ * Define the roles for a given user. You will typically want to plug this into your auth controller.
48
+ * @param user
49
+ */
50
+ defineRolesFor: (user: User) => Promise<Role[] | undefined> | Role[] | undefined;
51
+
52
+ /**
53
+ * You can build an authenticator callback from the current configuration of the user management.
54
+ * It will only allow access to users with the required roles.
55
+ */
56
+ authenticator?: Authenticator;
57
+
58
+ rolesError?: Error;
59
+ usersError?: Error;
60
+
50
61
  };