@firecms/user_management 3.0.0-canary.144 → 3.0.0-canary.145

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,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-canary.144",
4
+ "version": "3.0.0-canary.145",
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-canary.144",
33
- "@firecms/formex": "^3.0.0-canary.144",
34
- "@firecms/ui": "^3.0.0-canary.144",
32
+ "@firecms/core": "^3.0.0-canary.145",
33
+ "@firecms/formex": "^3.0.0-canary.145",
34
+ "@firecms/ui": "^3.0.0-canary.145",
35
35
  "date-fns": "^3.6.0"
36
36
  },
37
37
  "peerDependencies": {
@@ -55,5 +55,5 @@
55
55
  "src",
56
56
  "bin"
57
57
  ],
58
- "gitHead": "8f862289db623ffd350e61e4ab4a06957ed97bdf"
58
+ "gitHead": "e5d660f00c5806e1b63839c013af3c973f3dd47e"
59
59
  }
@@ -32,6 +32,7 @@ export const RoleYupSchema = Yup.object().shape({
32
32
 
33
33
  function canRoleBeEdited(loggedUser: User) {
34
34
  const loggedUserIsAdmin = loggedUser.roles?.map(r => r.id).includes("admin");
35
+ console.log("loggedUserIsAdmin", loggedUser);
35
36
  if (!loggedUserIsAdmin) {
36
37
  throw new Error("Only admins can edit roles");
37
38
  }
@@ -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,13 @@ 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
+ USER extends User = CONTROLLER extends AuthController<infer U> ? U : any>
21
+ {
22
+
23
+ authController: CONTROLLER;
19
24
 
20
25
  /**
21
26
  * The delegate in charge of persisting the data.
@@ -62,6 +67,7 @@ export interface UserManagementParams {
62
67
  /**
63
68
  * This hook is used to build a user management object that can be used to
64
69
  * manage users and roles in a Firestore backend.
70
+ * @param authController
65
71
  * @param dataSourceDelegate
66
72
  * @param usersPath
67
73
  * @param rolesPath
@@ -70,38 +76,45 @@ export interface UserManagementParams {
70
76
  * @param allowDefaultRolesCreation
71
77
  * @param includeCollectionConfigPermissions
72
78
  */
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 {
79
+ export function useBuildUserManagement<CONTROLLER extends AuthController<any> = AuthController<any>,
80
+ USER extends User = CONTROLLER extends AuthController<infer U> ? U : any>
81
+ ({
82
+ authController,
83
+ dataSourceDelegate,
84
+ usersPath = "__FIRECMS/config/users",
85
+ rolesPath = "__FIRECMS/config/roles",
86
+ usersLimit,
87
+ canEditRoles = true,
88
+ allowDefaultRolesCreation,
89
+ includeCollectionConfigPermissions
90
+ }: UserManagementParams<CONTROLLER, USER>): UserManagement<USER> & CONTROLLER {
82
91
 
83
92
  const [rolesLoading, setRolesLoading] = React.useState<boolean>(true);
84
93
  const [usersLoading, setUsersLoading] = React.useState<boolean>(true);
85
94
  const [roles, setRoles] = React.useState<Role[]>([]);
86
- const [usersWithRoleIds, setUsersWithRoleIds] = React.useState<UserWithRoleIds[]>([]);
95
+ const [usersWithRoleIds, setUsersWithRoleIds] = React.useState<UserWithRoleIds<USER>[]>([]);
87
96
 
88
97
  const users = usersWithRoleIds.map(u => ({
89
98
  ...u,
90
99
  roles: roles.filter(r => u.roles?.includes(r.id))
91
- }) as User);
100
+ }) as USER);
92
101
 
93
102
  const [rolesError, setRolesError] = React.useState<Error | undefined>();
94
103
  const [usersError, setUsersError] = React.useState<Error | undefined>();
95
104
 
96
- const loading = rolesLoading || usersLoading;
105
+ const _usersLoading = usersLoading;
106
+ const _rolesLoading = rolesLoading;
107
+
108
+ const loading = _rolesLoading || _usersLoading;
97
109
 
98
110
  useEffect(() => {
99
111
  if (!dataSourceDelegate || !rolesPath) return;
100
112
  if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) return;
101
- if (dataSourceDelegate.authenticated !== undefined && !dataSourceDelegate.authenticated) {
102
- setRolesLoading(false);
103
- return;
104
- }
113
+ if (authController?.initialLoading) return;
114
+ // if (authController.user === null) {
115
+ // setRolesLoading(false);
116
+ // return;
117
+ // }
105
118
 
106
119
  setRolesLoading(true);
107
120
  return dataSourceDelegate.listenCollection?.({
@@ -110,8 +123,9 @@ export function useBuildUserManagement({
110
123
  setRolesError(undefined);
111
124
  try {
112
125
  const newRoles = entityToRoles(entities);
113
- if (!equal(newRoles, roles))
126
+ if (!equal(newRoles, roles)) {
114
127
  setRoles(newRoles);
128
+ }
115
129
  } catch (e) {
116
130
  setRoles([]);
117
131
  console.error("Error loading roles", e);
@@ -127,25 +141,31 @@ export function useBuildUserManagement({
127
141
  }
128
142
  });
129
143
 
130
- }, [dataSourceDelegate?.initialised, dataSourceDelegate?.authenticated, rolesPath]);
144
+ }, [dataSourceDelegate?.initialised, authController?.initialLoading, authController?.user?.uid, rolesPath]);
131
145
 
132
146
  useEffect(() => {
133
147
  if (!dataSourceDelegate || !usersPath) return;
134
- if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) return;
135
- if (dataSourceDelegate.authenticated !== undefined && !dataSourceDelegate.authenticated) {
136
- setUsersLoading(false);
148
+ if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) {
149
+ return;
150
+ }
151
+ if (authController?.initialLoading) {
137
152
  return;
138
153
  }
154
+ // if (authController.user === null) {
155
+ // setUsersLoading(false);
156
+ // return;
157
+ // }
139
158
 
140
159
  setUsersLoading(true);
141
160
  return dataSourceDelegate.listenCollection?.({
142
161
  path: usersPath,
143
162
  onUpdate(entities: Entity<any>[]): void {
163
+ console.debug("Updating users", entities);
144
164
  setUsersError(undefined);
145
165
  try {
146
166
  const newUsers = entitiesToUsers(entities);
147
- if (!equal(newUsers, usersWithRoleIds))
148
- setUsersWithRoleIds(newUsers);
167
+ // if (!equal(newUsers, usersWithRoleIds))
168
+ setUsersWithRoleIds(newUsers);
149
169
  } catch (e) {
150
170
  setUsersWithRoleIds([]);
151
171
  console.error("Error loading users", e);
@@ -154,16 +174,16 @@ export function useBuildUserManagement({
154
174
  setUsersLoading(false);
155
175
  },
156
176
  onError(e: any): void {
157
- setUsersWithRoleIds([]);
158
177
  console.error("Error loading users", e);
178
+ setUsersWithRoleIds([]);
159
179
  setUsersError(e);
160
180
  setUsersLoading(false);
161
181
  }
162
182
  });
163
183
 
164
- }, [dataSourceDelegate?.initialised, dataSourceDelegate?.authenticated, usersPath]);
184
+ }, [dataSourceDelegate?.initialised, authController?.initialLoading, authController?.user?.uid, usersPath]);
165
185
 
166
- const saveUser = useCallback(async (user: User): Promise<User> => {
186
+ const saveUser = useCallback(async (user: USER): Promise<USER> => {
167
187
  if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
168
188
  if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
169
189
 
@@ -238,39 +258,61 @@ export function useBuildUserManagement({
238
258
  const collectionPermissions: PermissionsBuilder = useCallback(({
239
259
  collection,
240
260
  user
241
- }) => resolveUserRolePermissions({
242
- collection,
243
- user
244
- }), []);
261
+ }) =>
262
+ resolveUserRolePermissions({
263
+ collection,
264
+ user
265
+ }), []);
245
266
 
246
267
  const defineRolesFor: ((user: User) => Role[] | undefined) = useCallback((user) => {
247
- if (!users) throw Error("Users not loaded");
268
+ if (!usersWithRoleIds) throw Error("Users not loaded");
269
+ const users = usersWithRoleIds.map(u => ({
270
+ ...u,
271
+ roles: roles.filter(r => u.roles?.includes(r.id))
272
+ }) as User);
248
273
  const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
249
274
  return mgmtUser?.roles;
250
- }, [users]);
275
+ }, [roles, usersWithRoleIds]);
251
276
 
252
- const authenticator: Authenticator = useCallback(({ user }) => {
253
- console.debug("Authenticating user", user);
277
+ console.debug({
278
+ loading,
279
+ users,
280
+ usersError
281
+ });
254
282
 
283
+ const authenticator: Authenticator<USER> = useCallback(({ user }) => {
255
284
  if (loading) {
256
- console.warn("User management is still loading");
257
285
  return false;
258
286
  }
259
287
 
260
- // This is an example of how you can link the access system to the user management plugin
261
288
  if (users.length === 0) {
289
+ console.warn("No users created yet");
262
290
  return true; // If there are no users created yet, we allow access to every user
263
291
  }
264
292
 
265
293
  const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
266
294
  if (mgmtUser) {
295
+ console.debug("User found in user management system", mgmtUser);
267
296
  return true;
268
297
  }
269
298
 
270
299
  throw Error("Could not find a user with the provided email in the user management system.");
271
- }, [loading, users, usersError, rolesError]);
272
-
273
- const isAdmin = roles.some(r => r.id === "admin");
300
+ }, [loading, users]);
301
+
302
+ const userRoles = authController.user ? defineRolesFor(authController.user) : undefined;
303
+ const isAdmin = (userRoles ?? []).some(r => r.id === "admin");
304
+
305
+ // console.log("Setting roles", {
306
+ // user: authController.user,
307
+ // userRoles
308
+ // });
309
+ // useEffect(() => {
310
+ // console.debug("Setting roles", {
311
+ // authController,
312
+ // userRoles
313
+ // });
314
+ // authController.setUserRoles?.(userRoles ?? []);
315
+ // }, [userRoles?.map(r => r.id)]);
274
316
 
275
317
  return {
276
318
  loading,
@@ -289,7 +331,14 @@ export function useBuildUserManagement({
289
331
  includeCollectionConfigPermissions: Boolean(includeCollectionConfigPermissions),
290
332
  collectionPermissions,
291
333
  defineRolesFor,
292
- authenticator
334
+ authenticator,
335
+ ...authController,
336
+ initialLoading: authController.initialLoading || loading,
337
+ userRoles: userRoles,
338
+ user: authController.user ? {
339
+ ...authController.user,
340
+ roles: userRoles
341
+ } : null
293
342
  }
294
343
  }
295
344
 
@@ -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;