@firecms/user_management 3.0.0-beta.10 → 3.0.0-beta.11

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.
@@ -1,5 +1,6 @@
1
1
  import { Authenticator, PermissionsBuilder, Role, User } from "@firecms/core";
2
2
  export type UserManagement<USER extends User = User> = {
3
+ authenticator?: Authenticator<USER>;
3
4
  loading: boolean;
4
5
  users: USER[];
5
6
  saveUser: (user: USER) => Promise<USER>;
@@ -37,11 +38,6 @@ export type UserManagement<USER extends User = User> = {
37
38
  * @param user
38
39
  */
39
40
  defineRolesFor: (user: User) => Promise<Role[] | undefined> | Role[] | undefined;
40
- /**
41
- * You can build an authenticator callback from the current configuration of the user management.
42
- * It will only allow access to users with the required roles.
43
- */
44
- authenticator?: Authenticator;
45
41
  rolesError?: Error;
46
42
  usersError?: Error;
47
43
  };
@@ -1,10 +1,10 @@
1
- import { FireCMSPlugin } from "@firecms/core";
2
- import { PersistedUser, UserManagement } from "./types";
3
- export declare function useUserManagementPlugin({ userManagement }: {
4
- userManagement: UserManagement;
1
+ import { FireCMSPlugin, User } from "@firecms/core";
2
+ import { UserManagement } from "./types";
3
+ export declare function useUserManagementPlugin<USER extends User = any>({ userManagement }: {
4
+ userManagement: UserManagement<USER>;
5
5
  }): FireCMSPlugin;
6
6
  export declare function IntroWidget({ noUsers, noRoles, userManagement }: {
7
7
  noUsers: boolean;
8
8
  noRoles: boolean;
9
- userManagement: UserManagement<PersistedUser>;
9
+ userManagement: UserManagement<any>;
10
10
  }): import("react/jsx-runtime").JSX.Element;
@@ -1,8 +1,8 @@
1
1
  import { EntityCollection, Permissions, Role, User } from "@firecms/core";
2
2
  export declare const RESERVED_GROUPS: string[];
3
- export declare function resolveUserRolePermissions<UserType extends User>({ collection, user }: {
3
+ export declare function resolveUserRolePermissions<USER extends User>({ collection, user }: {
4
4
  collection: EntityCollection<any>;
5
- user: UserType | null;
5
+ user: USER | null;
6
6
  }): Permissions;
7
7
  export declare function getUserRoles(roles: Role[], fireCMSUser: User): Role[] | undefined;
8
8
  export declare const areRolesEqual: (rolesA: Role[], rolesB: Role[]) => boolean;
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-beta.10",
4
+ "version": "3.0.0-beta.11",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -29,9 +29,9 @@
29
29
  "types": "dist/index.d.ts",
30
30
  "source": "src/index.ts",
31
31
  "dependencies": {
32
- "@firecms/core": "^3.0.0-beta.10",
33
- "@firecms/formex": "^3.0.0-beta.10",
34
- "@firecms/ui": "^3.0.0-beta.10",
32
+ "@firecms/core": "^3.0.0-beta.11",
33
+ "@firecms/formex": "^3.0.0-beta.11",
34
+ "@firecms/ui": "^3.0.0-beta.11",
35
35
  "date-fns": "^3.6.0"
36
36
  },
37
37
  "peerDependencies": {
@@ -39,11 +39,13 @@
39
39
  "react-dom": "^18.3.1"
40
40
  },
41
41
  "devDependencies": {
42
- "@types/node": "^20.16.11",
42
+ "@types/node": "^20.17.9",
43
43
  "@types/react": "^18.3.11",
44
- "@types/react-dom": "^18.3.0",
45
- "typescript": "^5.6.3",
46
- "vite": "^5.4.8"
44
+ "@types/react-dom": "^18.3.1",
45
+ "babel-plugin-react-compiler": "beta",
46
+ "eslint-plugin-react-compiler": "beta",
47
+ "typescript": "^5.7.2",
48
+ "vite": "^5.4.11"
47
49
  },
48
50
  "scripts": {
49
51
  "dev": "vite",
@@ -55,5 +57,5 @@
55
57
  "src",
56
58
  "bin"
57
59
  ],
58
- "gitHead": "f844c3f86094efec6c33313c3f106f30cdcd309f"
60
+ "gitHead": "cb6d419faf2664cfd6cb2603a834e4bf499a92bb"
59
61
  }
@@ -5,10 +5,11 @@ import { EntityCollection, FieldCaption, Role, toSnakeCase, useAuthController, U
5
5
  import {
6
6
  Button,
7
7
  Checkbox,
8
+ CheckIcon,
8
9
  Dialog,
9
10
  DialogActions,
10
11
  DialogContent,
11
- DoneIcon,
12
+ DialogTitle,
12
13
  LoadingButton,
13
14
  Paper,
14
15
  Select,
@@ -32,6 +33,7 @@ export const RoleYupSchema = Yup.object().shape({
32
33
 
33
34
  function canRoleBeEdited(loggedUser: User) {
34
35
  const loggedUserIsAdmin = loggedUser.roles?.map(r => r.id).includes("admin");
36
+ console.log("loggedUserIsAdmin", loggedUser);
35
37
  if (!loggedUserIsAdmin) {
36
38
  throw new Error("Only admins can edit roles");
37
39
  }
@@ -142,14 +144,10 @@ export function RolesDetailsForm({
142
144
  position: "relative",
143
145
  height: "100%"
144
146
  }}>
147
+ <DialogTitle variant={"h4"} gutterBottom={false}>
148
+ Role
149
+ </DialogTitle>
145
150
  <DialogContent className="flex-grow">
146
- <div
147
- className="flex flex-row pt-12 pb-8">
148
- <Typography variant={"h4"}
149
- className="flex-grow">
150
- Role
151
- </Typography>
152
- </div>
153
151
 
154
152
  <div className={"grid grid-cols-12 gap-8"}>
155
153
 
@@ -341,6 +339,8 @@ export function RolesDetailsForm({
341
339
  <div className={"col-span-12 md:col-span-4"}>
342
340
  <Select
343
341
  error={touched.config && Boolean(errors.config)}
342
+ size={"large"}
343
+ fullWidth={true}
344
344
  id="createCollections"
345
345
  name="createCollections"
346
346
  label="Create collections"
@@ -363,6 +363,8 @@ export function RolesDetailsForm({
363
363
 
364
364
  <div className={"col-span-12 md:col-span-4"}>
365
365
  <Select
366
+ size={"large"}
367
+ fullWidth={true}
366
368
  error={touched.config && Boolean(errors.config)}
367
369
  id="editCollections"
368
370
  name="editCollections"
@@ -389,6 +391,8 @@ export function RolesDetailsForm({
389
391
 
390
392
  <div className={"col-span-12 md:col-span-4"}>
391
393
  <Select
394
+ size={"large"}
395
+ fullWidth={true}
392
396
  error={touched.config && Boolean(errors.config)}
393
397
  id="deleteCollections"
394
398
  name="deleteCollections"
@@ -433,7 +437,7 @@ export function RolesDetailsForm({
433
437
  type="submit"
434
438
  disabled={!dirty}
435
439
  loading={isSubmitting}
436
- startIcon={<DoneIcon/>}
440
+ startIcon={<CheckIcon/>}
437
441
  >
438
442
  {isNewRole ? "Create role" : "Update"}
439
443
  </LoadingButton>
@@ -2,15 +2,15 @@ import React, { useCallback } from "react";
2
2
  import * as Yup from "yup";
3
3
  import {
4
4
  Button,
5
+ CheckIcon,
5
6
  Dialog,
6
7
  DialogActions,
7
8
  DialogContent,
8
- DoneIcon,
9
+ DialogTitle,
9
10
  LoadingButton,
10
11
  MultiSelect,
11
12
  MultiSelectItem,
12
13
  TextField,
13
- Typography,
14
14
  } from "@firecms/ui";
15
15
  import { FieldCaption, Role, useAuthController, User, useSnackbarController } from "@firecms/core";
16
16
  import { Formex, useCreateFormex } from "@firecms/formex";
@@ -140,14 +140,11 @@ export function UserDetailsForm({
140
140
  position: "relative",
141
141
  height: "100%"
142
142
  }}>
143
+
144
+ <DialogTitle variant={"h4"} gutterBottom={false}>
145
+ User
146
+ </DialogTitle>
143
147
  <DialogContent className="h-full flex-grow">
144
- <div
145
- className="flex flex-row pt-4 pb-4">
146
- <Typography variant={"h4"}
147
- className="flex-grow">
148
- User
149
- </Typography>
150
- </div>
151
148
 
152
149
  <div className={"grid grid-cols-12 gap-8"}>
153
150
 
@@ -220,7 +217,7 @@ export function UserDetailsForm({
220
217
  type="submit"
221
218
  disabled={!dirty}
222
219
  loading={isSubmitting}
223
- startIcon={<DoneIcon/>}
220
+ startIcon={<CheckIcon/>}
224
221
  >
225
222
  {isNewUser ? "Create user" : "Update"}
226
223
  </LoadingButton>
@@ -54,7 +54,6 @@ export function UsersTable({ onUserClicked }: {
54
54
 
55
55
  <TableHeader>
56
56
  <TableCell className="truncate w-16"></TableCell>
57
- <TableCell>ID</TableCell>
58
57
  <TableCell>Email</TableCell>
59
58
  <TableCell>Name</TableCell>
60
59
  <TableCell>Roles</TableCell>
@@ -88,7 +87,6 @@ export function UsersTable({ onUserClicked }: {
88
87
  </IconButton>
89
88
  </Tooltip>
90
89
  </TableCell>
91
- <TableCell>{user.uid}</TableCell>
92
90
  <TableCell>{user.email}</TableCell>
93
91
  <TableCell className={"font-medium align-left"}>{user.displayName}</TableCell>
94
92
  <TableCell className="align-left">
@@ -3,6 +3,7 @@ import equal from "react-fast-compare"
3
3
 
4
4
  import { UserManagement } from "../types";
5
5
  import {
6
+ AuthController,
6
7
  Authenticator,
7
8
  DataSourceDelegate,
8
9
  Entity,
@@ -13,9 +14,11 @@ import {
13
14
  } from "@firecms/core";
14
15
  import { resolveUserRolePermissions } from "../utils";
15
16
 
16
- type UserWithRoleIds = Omit<User, "roles"> & { roles: string[] };
17
+ type UserWithRoleIds<USER extends User = any> = Omit<USER, "roles"> & { roles: string[] };
17
18
 
18
- export interface UserManagementParams {
19
+ export interface UserManagementParams<CONTROLLER extends AuthController<any> = AuthController<any>> {
20
+
21
+ authController: CONTROLLER;
19
22
 
20
23
  /**
21
24
  * The delegate in charge of persisting the data.
@@ -62,6 +65,7 @@ export interface UserManagementParams {
62
65
  /**
63
66
  * This hook is used to build a user management object that can be used to
64
67
  * manage users and roles in a Firestore backend.
68
+ * @param authController
65
69
  * @param dataSourceDelegate
66
70
  * @param usersPath
67
71
  * @param rolesPath
@@ -70,38 +74,49 @@ export interface UserManagementParams {
70
74
  * @param allowDefaultRolesCreation
71
75
  * @param includeCollectionConfigPermissions
72
76
  */
73
- export function useBuildUserManagement({
74
- dataSourceDelegate,
75
- usersPath = "__FIRECMS/config/users",
76
- rolesPath = "__FIRECMS/config/roles",
77
- usersLimit,
78
- canEditRoles = true,
79
- allowDefaultRolesCreation,
80
- includeCollectionConfigPermissions
81
- }: UserManagementParams): UserManagement {
77
+ export function useBuildUserManagement<CONTROLLER extends AuthController<any> = AuthController<any>,
78
+ USER extends User = CONTROLLER extends AuthController<infer U> ? U : any>
79
+ ({
80
+ authController,
81
+ dataSourceDelegate,
82
+ usersPath = "__FIRECMS/config/users",
83
+ rolesPath = "__FIRECMS/config/roles",
84
+ usersLimit,
85
+ canEditRoles = true,
86
+ allowDefaultRolesCreation,
87
+ includeCollectionConfigPermissions
88
+ }: UserManagementParams<CONTROLLER>): UserManagement<USER> & CONTROLLER {
89
+
90
+ if (!authController) {
91
+ throw Error("useBuildUserManagement: You need to provide an authController since version 3.0.0-beta.11. Check https://firecms.co/docs/pro/migrating_from_v3_beta");
92
+ }
82
93
 
83
94
  const [rolesLoading, setRolesLoading] = React.useState<boolean>(true);
84
95
  const [usersLoading, setUsersLoading] = React.useState<boolean>(true);
85
96
  const [roles, setRoles] = React.useState<Role[]>([]);
86
- const [usersWithRoleIds, setUsersWithRoleIds] = React.useState<UserWithRoleIds[]>([]);
97
+ const [usersWithRoleIds, setUsersWithRoleIds] = React.useState<UserWithRoleIds<USER>[]>([]);
87
98
 
88
99
  const users = usersWithRoleIds.map(u => ({
89
100
  ...u,
90
101
  roles: roles.filter(r => u.roles?.includes(r.id))
91
- }) as User);
102
+ }) as USER);
92
103
 
93
104
  const [rolesError, setRolesError] = React.useState<Error | undefined>();
94
105
  const [usersError, setUsersError] = React.useState<Error | undefined>();
95
106
 
96
- const loading = rolesLoading || usersLoading;
107
+ const _usersLoading = usersLoading;
108
+ const _rolesLoading = rolesLoading;
109
+
110
+ const loading = _rolesLoading || _usersLoading;
97
111
 
98
112
  useEffect(() => {
99
113
  if (!dataSourceDelegate || !rolesPath) return;
100
114
  if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) return;
101
- if (dataSourceDelegate.authenticated !== undefined && !dataSourceDelegate.authenticated) {
102
- setRolesLoading(false);
103
- return;
104
- }
115
+ if (authController?.initialLoading) return;
116
+ // if (authController.user === null) {
117
+ // setRolesLoading(false);
118
+ // return;
119
+ // }
105
120
 
106
121
  setRolesLoading(true);
107
122
  return dataSourceDelegate.listenCollection?.({
@@ -110,8 +125,9 @@ export function useBuildUserManagement({
110
125
  setRolesError(undefined);
111
126
  try {
112
127
  const newRoles = entityToRoles(entities);
113
- if (!equal(newRoles, roles))
128
+ if (!equal(newRoles, roles)) {
114
129
  setRoles(newRoles);
130
+ }
115
131
  } catch (e) {
116
132
  setRoles([]);
117
133
  console.error("Error loading roles", e);
@@ -127,13 +143,14 @@ export function useBuildUserManagement({
127
143
  }
128
144
  });
129
145
 
130
- }, [dataSourceDelegate?.initialised, dataSourceDelegate?.authenticated, rolesPath]);
146
+ }, [dataSourceDelegate?.initialised, authController?.initialLoading, authController?.user?.uid, rolesPath]);
131
147
 
132
148
  useEffect(() => {
133
149
  if (!dataSourceDelegate || !usersPath) return;
134
- if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) return;
135
- if (dataSourceDelegate.authenticated !== undefined && !dataSourceDelegate.authenticated) {
136
- setUsersLoading(false);
150
+ if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) {
151
+ return;
152
+ }
153
+ if (authController?.initialLoading) {
137
154
  return;
138
155
  }
139
156
 
@@ -141,11 +158,12 @@ export function useBuildUserManagement({
141
158
  return dataSourceDelegate.listenCollection?.({
142
159
  path: usersPath,
143
160
  onUpdate(entities: Entity<any>[]): void {
161
+ console.debug("Updating users", entities);
144
162
  setUsersError(undefined);
145
163
  try {
146
164
  const newUsers = entitiesToUsers(entities);
147
- if (!equal(newUsers, usersWithRoleIds))
148
- setUsersWithRoleIds(newUsers);
165
+ // if (!equal(newUsers, usersWithRoleIds))
166
+ setUsersWithRoleIds(newUsers);
149
167
  } catch (e) {
150
168
  setUsersWithRoleIds([]);
151
169
  console.error("Error loading users", e);
@@ -154,16 +172,16 @@ export function useBuildUserManagement({
154
172
  setUsersLoading(false);
155
173
  },
156
174
  onError(e: any): void {
157
- setUsersWithRoleIds([]);
158
175
  console.error("Error loading users", e);
176
+ setUsersWithRoleIds([]);
159
177
  setUsersError(e);
160
178
  setUsersLoading(false);
161
179
  }
162
180
  });
163
181
 
164
- }, [dataSourceDelegate?.initialised, dataSourceDelegate?.authenticated, usersPath]);
182
+ }, [dataSourceDelegate?.initialised, authController?.initialLoading, authController?.user?.uid, usersPath]);
165
183
 
166
- const saveUser = useCallback(async (user: User): Promise<User> => {
184
+ const saveUser = useCallback(async (user: USER): Promise<USER> => {
167
185
  if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
168
186
  if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
169
187
 
@@ -238,39 +256,49 @@ export function useBuildUserManagement({
238
256
  const collectionPermissions: PermissionsBuilder = useCallback(({
239
257
  collection,
240
258
  user
241
- }) => resolveUserRolePermissions({
242
- collection,
243
- user
244
- }), []);
259
+ }) =>
260
+ resolveUserRolePermissions({
261
+ collection,
262
+ user
263
+ }), []);
245
264
 
246
265
  const defineRolesFor: ((user: User) => Role[] | undefined) = useCallback((user) => {
247
- if (!users) throw Error("Users not loaded");
266
+ if (!usersWithRoleIds) throw Error("Users not loaded");
267
+ const users = usersWithRoleIds.map(u => ({
268
+ ...u,
269
+ roles: roles.filter(r => u.roles?.includes(r.id))
270
+ }) as User);
248
271
  const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
249
272
  return mgmtUser?.roles;
250
- }, [users]);
251
-
252
- const authenticator: Authenticator = useCallback(({ user }) => {
253
- console.debug("Authenticating user", user);
273
+ }, [roles, usersWithRoleIds]);
254
274
 
275
+ const authenticator: Authenticator<USER> = useCallback(({ user }) => {
255
276
  if (loading) {
256
- console.warn("User management is still loading");
257
277
  return false;
258
278
  }
259
279
 
260
- // This is an example of how you can link the access system to the user management plugin
261
280
  if (users.length === 0) {
281
+ console.warn("No users created yet");
262
282
  return true; // If there are no users created yet, we allow access to every user
263
283
  }
264
284
 
265
285
  const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
266
286
  if (mgmtUser) {
287
+ console.debug("User found in user management system", mgmtUser);
267
288
  return true;
268
289
  }
269
290
 
270
291
  throw Error("Could not find a user with the provided email in the user management system.");
271
- }, [loading, users, usersError, rolesError]);
292
+ }, [loading, users]);
293
+
294
+ const userRoles = authController.user ? defineRolesFor(authController.user) : undefined;
295
+ const isAdmin = (userRoles ?? []).some(r => r.id === "admin");
272
296
 
273
- const isAdmin = roles.some(r => r.id === "admin");
297
+ const userRoleIds = userRoles?.map(r => r.id);
298
+ useEffect(() => {
299
+ console.debug("Setting roles", userRoles);
300
+ authController.setUserRoles?.(userRoles ?? []);
301
+ }, [userRoleIds]);
274
302
 
275
303
  return {
276
304
  loading,
@@ -289,7 +317,14 @@ export function useBuildUserManagement({
289
317
  includeCollectionConfigPermissions: Boolean(includeCollectionConfigPermissions),
290
318
  collectionPermissions,
291
319
  defineRolesFor,
292
- authenticator
320
+ authenticator,
321
+ ...authController,
322
+ initialLoading: authController.initialLoading || loading,
323
+ userRoles: userRoles,
324
+ user: authController.user ? {
325
+ ...authController.user,
326
+ roles: userRoles
327
+ } : null
293
328
  }
294
329
  }
295
330
 
@@ -2,6 +2,8 @@ import { Authenticator, PermissionsBuilder, Role, User } from "@firecms/core";
2
2
 
3
3
  export type UserManagement<USER extends User = User> = {
4
4
 
5
+ authenticator?: Authenticator<USER>;
6
+
5
7
  loading: boolean;
6
8
 
7
9
  users: USER[];
@@ -49,12 +51,6 @@ export type UserManagement<USER extends User = User> = {
49
51
  */
50
52
  defineRolesFor: (user: User) => Promise<Role[] | undefined> | Role[] | undefined;
51
53
 
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
54
  rolesError?: Error;
59
55
  usersError?: Error;
60
56
 
@@ -1,11 +1,11 @@
1
- import { FireCMSPlugin, useAuthController, useSnackbarController } from "@firecms/core";
1
+ import { FireCMSPlugin, useAuthController, User, useSnackbarController } from "@firecms/core";
2
2
  import { UserManagementProvider } from "./UserManagementProvider";
3
- import { PersistedUser, UserManagement } from "./types";
3
+ import { UserManagement } from "./types";
4
4
  import { AddIcon, Button, Paper, Typography } from "@firecms/ui";
5
5
  import { DEFAULT_ROLES } from "./components/roles/default_roles";
6
6
 
7
- export function useUserManagementPlugin({ userManagement }: {
8
- userManagement: UserManagement,
7
+ export function useUserManagementPlugin<USER extends User = any>({ userManagement }: {
8
+ userManagement: UserManagement<USER>,
9
9
  }): FireCMSPlugin {
10
10
 
11
11
  const noUsers = userManagement.users.length === 0;
@@ -39,7 +39,7 @@ export function IntroWidget({
39
39
  }: {
40
40
  noUsers: boolean;
41
41
  noRoles: boolean;
42
- userManagement: UserManagement<PersistedUser>;
42
+ userManagement: UserManagement<any>;
43
43
  }) {
44
44
 
45
45
  const authController = useAuthController();
@@ -53,7 +53,7 @@ export function IntroWidget({
53
53
 
54
54
  return (
55
55
  <Paper
56
- className={"my-4 flex flex-col px-4 py-6 bg-white dark:bg-slate-800 gap-2"}>
56
+ className={"my-4 flex flex-col px-4 py-6 bg-white dark:bg-surface-accent-800 gap-2"}>
57
57
  <Typography variant={"subtitle2"} className={"uppercase"}>Create your users and roles</Typography>
58
58
  <Typography>
59
59
  You have no users or roles defined. You can create default roles and add the current user as admin.
@@ -9,13 +9,13 @@ const DEFAULT_PERMISSIONS = {
9
9
  delete: false
10
10
  };
11
11
 
12
- export function resolveUserRolePermissions<UserType extends User>
12
+ export function resolveUserRolePermissions<USER extends User>
13
13
  ({
14
14
  collection,
15
15
  user
16
16
  }: {
17
17
  collection: EntityCollection<any>,
18
- user: UserType | null
18
+ user: USER | null
19
19
  }): Permissions {
20
20
 
21
21
  const roles = user?.roles;