@firecms/user_management 3.0.0-canary.12 → 3.0.0-canary.120

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/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.12",
4
+ "version": "3.0.0-canary.120",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -24,36 +24,26 @@
24
24
  },
25
25
  "./package.json": "./package.json"
26
26
  },
27
- "packageManager": "yarn@4.1.0",
28
27
  "main": "./dist/index.umd.js",
29
28
  "module": "./dist/index.es.js",
30
29
  "types": "dist/index.d.ts",
31
30
  "source": "src/index.ts",
32
31
  "dependencies": {
33
- "@firecms/core": "^3.0.0-canary.12",
34
- "@firecms/formex": "^3.0.0-canary.12",
35
- "@firecms/ui": "^3.0.0-canary.12",
32
+ "@firecms/core": "^3.0.0-canary.120",
33
+ "@firecms/formex": "^3.0.0-canary.120",
34
+ "@firecms/ui": "^3.0.0-canary.120",
36
35
  "date-fns": "^3.6.0"
37
36
  },
38
37
  "peerDependencies": {
39
- "firebase": "^10.7.1",
40
- "react": "^18.2.0",
41
- "react-dom": "^18.2.0"
38
+ "react": "^18.3.1",
39
+ "react-dom": "^18.3.1"
42
40
  },
43
41
  "devDependencies": {
44
- "@types/node": "^20.11.30",
45
- "@types/react": "^18.2.67",
46
- "@types/react-dom": "^18.2.22",
47
- "@typescript-eslint/parser": "^7.3.1",
48
- "eslint": "^8.57.0",
49
- "eslint-config-standard": "^17.1.0",
50
- "eslint-plugin-import": "^2.29.1",
51
- "eslint-plugin-n": "^16.6.2",
52
- "eslint-plugin-promise": "^6.1.1",
53
- "eslint-plugin-react": "^7.34.1",
54
- "eslint-plugin-react-hooks": "^4.6.0",
55
- "typescript": "^5.4.2",
56
- "vite": "^5.2.3"
42
+ "@types/node": "^20.14.12",
43
+ "@types/react": "^18.3.3",
44
+ "@types/react-dom": "^18.3.0",
45
+ "typescript": "^5.5.4",
46
+ "vite": "^5.3.4"
57
47
  },
58
48
  "scripts": {
59
49
  "dev": "vite",
@@ -65,11 +55,5 @@
65
55
  "src",
66
56
  "bin"
67
57
  ],
68
- "eslintConfig": {
69
- "extends": [
70
- "react-app",
71
- "react-app/jest"
72
- ]
73
- },
74
- "gitHead": "8bbe4916c2901f1cd63e6f507b844fc527c573c9"
58
+ "gitHead": "aa817730cb582b5724774a50ac8a512bf0643ef3"
75
59
  }
@@ -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, Role, toSnakeCase, } from "@firecms/core";
4
+ import { EntityCollection, FieldCaption, Role, toSnakeCase, useAuthController, User, } from "@firecms/core";
5
5
  import {
6
6
  Button,
7
7
  Checkbox,
@@ -30,6 +30,15 @@ export const RoleYupSchema = Yup.object().shape({
30
30
  name: Yup.string().required("Required")
31
31
  });
32
32
 
33
+ function canRoleBeEdited(loggedUser: User) {
34
+ const loggedUserIsAdmin = loggedUser.roles?.map(r => r.id).includes("admin");
35
+ if (!loggedUserIsAdmin) {
36
+ throw new Error("Only admins can edit roles");
37
+ }
38
+
39
+ return true;
40
+ }
41
+
33
42
  export function RolesDetailsForm({
34
43
  open,
35
44
  role,
@@ -46,27 +55,39 @@ export function RolesDetailsForm({
46
55
 
47
56
  const { saveRole } = useUserManagement();
48
57
  const isNewRole = !role;
58
+ const {
59
+ user: loggedInUser
60
+ } = useAuthController();
49
61
 
50
62
  const [savingError, setSavingError] = useState<Error | undefined>();
51
63
 
52
64
  const onRoleUpdated = useCallback((role: Role) => {
53
65
  setSavingError(undefined);
66
+ if (!loggedInUser) throw new Error("User not found");
67
+ canRoleBeEdited(loggedInUser);
54
68
  return saveRole(role);
55
- }, [saveRole]);
69
+ }, [saveRole, loggedInUser]);
56
70
 
57
71
  const formex = useCreateFormex({
58
72
  initialValues: role ?? {
59
73
  name: ""
60
74
  } as Role,
61
75
  onSubmit: (role: Role, formexController) => {
62
- return onRoleUpdated(role)
63
- .then(() => {
64
- formexController.resetForm({
65
- values: role
76
+ try {
77
+ return onRoleUpdated(role)
78
+ .then(() => {
79
+ formexController.resetForm({
80
+ values: role
81
+ });
82
+ handleClose();
83
+ })
84
+ .catch(e => {
85
+ setSavingError(e);
66
86
  });
67
- handleClose();
68
- })
69
- .catch(e => setSavingError(e));
87
+ } catch (e: any) {
88
+ setSavingError(e);
89
+ return Promise.resolve();
90
+ }
70
91
  },
71
92
  validation: (values) => {
72
93
  return RoleYupSchema.validate(values, { abortEarly: false })
@@ -168,11 +189,9 @@ export function RolesDetailsForm({
168
189
  </div>
169
190
 
170
191
  <div className={"col-span-12"}>
171
- <Paper
172
-
173
- className="bg-inherit">
174
- <Table>
175
- <TableHeader>
192
+ <Paper className="bg-inherit overflow-hidden">
193
+ <Table className={"w-full rounded-md"}>
194
+ <TableHeader className={"rounded-md"}>
176
195
  <TableCell></TableCell>
177
196
  <TableCell
178
197
  align="center">Create
@@ -190,6 +209,9 @@ export function RolesDetailsForm({
190
209
  align="center">Delete
191
210
  entities
192
211
  </TableCell>
212
+ <TableCell
213
+ align="center">
214
+ </TableCell>
193
215
  </TableHeader>
194
216
 
195
217
  <TableBody>
@@ -245,6 +267,9 @@ export function RolesDetailsForm({
245
267
 
246
268
  </Tooltip>
247
269
  </TableCell>
270
+ <TableCell
271
+ align="center">
272
+ </TableCell>
248
273
  </TableRow>
249
274
  {collections && collections.map((col) => (
250
275
  <TableRow key={col.name}>
@@ -256,29 +281,49 @@ export function RolesDetailsForm({
256
281
  align="center">
257
282
  <Checkbox
258
283
  disabled={isAdmin || defaultCreate || !editable}
259
- checked={(isAdmin || defaultCreate || getIn(values, `collectionPermissions.${col.path}.create`)) ?? false}
260
- onCheckedChange={(checked) => setFieldValue(`collectionPermissions.${col.path}.create`, checked)}/>
284
+ checked={(isAdmin || defaultCreate || getIn(values, `collectionPermissions.${col.id}.create`)) ?? false}
285
+ onCheckedChange={(checked) => setFieldValue(`collectionPermissions.${col.id}.create`, checked)}/>
261
286
  </TableCell>
262
287
  <TableCell
263
288
  align="center">
264
289
  <Checkbox
265
290
  disabled={isAdmin || defaultRead || !editable}
266
- checked={(isAdmin || defaultRead || getIn(values, `collectionPermissions.${col.path}.read`)) ?? false}
267
- onCheckedChange={(checked) => setFieldValue(`collectionPermissions.${col.path}.read`, checked)}/>
291
+ checked={(isAdmin || defaultRead || getIn(values, `collectionPermissions.${col.id}.read`)) ?? false}
292
+ onCheckedChange={(checked) => setFieldValue(`collectionPermissions.${col.id}.read`, checked)}/>
268
293
  </TableCell>
269
294
  <TableCell
270
295
  align="center">
271
296
  <Checkbox
272
297
  disabled={isAdmin || defaultEdit || !editable}
273
- checked={(isAdmin || defaultEdit || getIn(values, `collectionPermissions.${col.path}.edit`)) ?? false}
274
- onCheckedChange={(checked) => setFieldValue(`collectionPermissions.${col.path}.edit`, checked)}/>
298
+ checked={(isAdmin || defaultEdit || getIn(values, `collectionPermissions.${col.id}.edit`)) ?? false}
299
+ onCheckedChange={(checked) => setFieldValue(`collectionPermissions.${col.id}.edit`, checked)}/>
275
300
  </TableCell>
276
301
  <TableCell
277
302
  align="center">
278
303
  <Checkbox
279
304
  disabled={isAdmin || defaultDelete || !editable}
280
- checked={(isAdmin || defaultDelete || getIn(values, `collectionPermissions.${col.path}.delete`)) ?? false}
281
- onCheckedChange={(checked) => setFieldValue(`collectionPermissions.${col.path}.delete`, checked)}/>
305
+ checked={(isAdmin || defaultDelete || getIn(values, `collectionPermissions.${col.id}.delete`)) ?? false}
306
+ onCheckedChange={(checked) => setFieldValue(`collectionPermissions.${col.id}.delete`, checked)}/>
307
+ </TableCell>
308
+
309
+ <TableCell
310
+ align="center">
311
+ <Tooltip
312
+ title="Allow all permissions in this collections">
313
+ <Button
314
+ className={"color-inherit"}
315
+ onClick={() => {
316
+ setFieldValue(`collectionPermissions.${col.id}.create`, true);
317
+ setFieldValue(`collectionPermissions.${col.id}.read`, true);
318
+ setFieldValue(`collectionPermissions.${col.id}.edit`, true);
319
+ setFieldValue(`collectionPermissions.${col.id}.delete`, true);
320
+ }}
321
+ disabled={isAdmin || !editable}
322
+ variant={"text"}>
323
+ All
324
+ </Button>
325
+
326
+ </Tooltip>
282
327
  </TableCell>
283
328
  </TableRow>
284
329
  ))}
@@ -373,8 +418,8 @@ export function RolesDetailsForm({
373
418
  </DialogContent>
374
419
 
375
420
  <DialogActions position={"sticky"}>
376
- {savingError && <Typography className={"text-red-500"}>
377
- There was an error saving this role
421
+ {savingError && <Typography className={"text-red-500 dark:text-red-500"}>
422
+ {savingError.message ?? "There was an error saving this role"}
378
423
  </Typography>}
379
424
  <Button variant={"text"}
380
425
  onClick={() => {
@@ -38,7 +38,7 @@ export function RolesTable({
38
38
 
39
39
  return <div
40
40
  className="w-full overflow-auto">
41
- <Table>
41
+ <Table className={"w-full"}>
42
42
  <TableHeader>
43
43
  <TableCell header={true} className="w-16"></TableCell>
44
44
  <TableCell header={true}>Role</TableCell>
@@ -61,7 +61,9 @@ export function RolesTable({
61
61
  >
62
62
  <TableCell style={{ width: "64px" }}>
63
63
  {!role.isAdmin &&
64
- <Tooltip title={"Delete this role"}>
64
+ <Tooltip
65
+ asChild={true}
66
+ title={"Delete this role"}>
65
67
  <IconButton
66
68
  size={"small"}
67
69
  disabled={!editable}
@@ -36,7 +36,9 @@ export const RolesView = React.memo(
36
36
  component="h4">
37
37
  Roles
38
38
  </Typography>
39
- <Tooltip title={!canEditRoles ? "Update plans to customise roles" : undefined}>
39
+ <Tooltip
40
+ asChild={true}
41
+ title={!canEditRoles ? "Update plans to customise roles" : undefined}>
40
42
  <Button
41
43
  size={"large"}
42
44
  disabled={!canEditRoles}
@@ -50,7 +50,7 @@ export function UsersTable({ onUserClicked }: {
50
50
  return (
51
51
  <div className="overflow-auto">
52
52
 
53
- <Table>
53
+ <Table className={"w-full"}>
54
54
 
55
55
  <TableHeader>
56
56
  <TableCell className="truncate w-16"></TableCell>
@@ -75,7 +75,9 @@ export function UsersTable({ onUserClicked }: {
75
75
  }}
76
76
  >
77
77
  <TableCell className={"w-10"}>
78
- <Tooltip title={"Delete this user"}>
78
+ <Tooltip
79
+ asChild={true}
80
+ title={"Delete this user"}>
79
81
  <IconButton
80
82
  size={"small"}
81
83
  onClick={(event) => {
@@ -1,2 +1,2 @@
1
- export * from "./useBuildFirestoreUserManagement";
1
+ export * from "./useBuildUserManagement";
2
2
  export * from "./useUserManagement";
@@ -0,0 +1,314 @@
1
+ import React, { useCallback, useEffect } from "react";
2
+ import equal from "react-fast-compare"
3
+
4
+ import { UserManagement } from "../types";
5
+ import {
6
+ Authenticator,
7
+ DataSourceDelegate,
8
+ Entity,
9
+ PermissionsBuilder,
10
+ removeUndefined,
11
+ Role,
12
+ User
13
+ } from "@firecms/core";
14
+ import { resolveUserRolePermissions } from "../utils";
15
+
16
+ type UserWithRoleIds = Omit<User, "roles"> & { roles: string[] };
17
+
18
+ export interface UserManagementParams {
19
+
20
+ /**
21
+ * The delegate in charge of persisting the data.
22
+ */
23
+ dataSourceDelegate?: DataSourceDelegate;
24
+
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 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
+ /**
41
+ * Maximum number of users that can be created.
42
+ */
43
+ usersLimit?: number;
44
+
45
+ /**
46
+ * Can the logged user edit roles
47
+ */
48
+ canEditRoles?: boolean;
49
+
50
+ /**
51
+ * If there are no roles in the database, provide a button to create the default roles.
52
+ */
53
+ allowDefaultRolesCreation?: boolean;
54
+
55
+ /**
56
+ * Include the collection config permissions in the user management system.
57
+ */
58
+ includeCollectionConfigPermissions?: boolean;
59
+
60
+ }
61
+
62
+ /**
63
+ * This hook is used to build a user management object that can be used to
64
+ * manage users and roles in a Firestore backend.
65
+ * @param dataSourceDelegate
66
+ * @param usersPath
67
+ * @param rolesPath
68
+ * @param usersLimit
69
+ * @param canEditRoles
70
+ * @param allowDefaultRolesCreation
71
+ * @param includeCollectionConfigPermissions
72
+ */
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 {
82
+
83
+ const [rolesLoading, setRolesLoading] = React.useState<boolean>(true);
84
+ const [usersLoading, setUsersLoading] = React.useState<boolean>(true);
85
+ const [roles, setRoles] = React.useState<Role[]>([]);
86
+ const [usersWithRoleIds, setUsersWithRoleIds] = React.useState<UserWithRoleIds[]>([]);
87
+
88
+ const users = usersWithRoleIds.map(u => ({
89
+ ...u,
90
+ roles: roles.filter(r => u.roles?.includes(r.id))
91
+ }) as User);
92
+
93
+ const [rolesError, setRolesError] = React.useState<Error | undefined>();
94
+ const [usersError, setUsersError] = React.useState<Error | undefined>();
95
+
96
+ const loading = rolesLoading || usersLoading;
97
+
98
+ useEffect(() => {
99
+ if (!dataSourceDelegate || !rolesPath) return;
100
+ if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) return;
101
+ if (dataSourceDelegate.authenticated !== undefined && !dataSourceDelegate.authenticated) {
102
+ setRolesLoading(false);
103
+ return;
104
+ }
105
+
106
+ setRolesLoading(true);
107
+ return dataSourceDelegate.listenCollection?.({
108
+ path: rolesPath,
109
+ onUpdate(entities: Entity<any>[]): void {
110
+ setRolesError(undefined);
111
+ try {
112
+ const newRoles = entityToRoles(entities);
113
+ if (!equal(newRoles, roles))
114
+ setRoles(newRoles);
115
+ } catch (e) {
116
+ setRoles([]);
117
+ console.error("Error loading roles", e);
118
+ setRolesError(e as Error);
119
+ }
120
+ setRolesLoading(false);
121
+ },
122
+ onError(e: any): void {
123
+ setRoles([]);
124
+ console.error("Error loading roles", e);
125
+ setRolesError(e);
126
+ setRolesLoading(false);
127
+ }
128
+ });
129
+
130
+ }, [dataSourceDelegate?.initialised, dataSourceDelegate?.authenticated, rolesPath]);
131
+
132
+ useEffect(() => {
133
+ if (!dataSourceDelegate || !usersPath) return;
134
+ if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) return;
135
+ if (dataSourceDelegate.authenticated !== undefined && !dataSourceDelegate.authenticated) {
136
+ setUsersLoading(false);
137
+ return;
138
+ }
139
+
140
+ setUsersLoading(true);
141
+ return dataSourceDelegate.listenCollection?.({
142
+ path: usersPath,
143
+ onUpdate(entities: Entity<any>[]): void {
144
+ setUsersError(undefined);
145
+ try {
146
+ const newUsers = entitiesToUsers(entities);
147
+ if (!equal(newUsers, usersWithRoleIds))
148
+ setUsersWithRoleIds(newUsers);
149
+ } catch (e) {
150
+ setUsersWithRoleIds([]);
151
+ console.error("Error loading users", e);
152
+ setUsersError(e as Error);
153
+ }
154
+ setUsersLoading(false);
155
+ },
156
+ onError(e: any): void {
157
+ setUsersWithRoleIds([]);
158
+ console.error("Error loading users", e);
159
+ setUsersError(e);
160
+ setUsersLoading(false);
161
+ }
162
+ });
163
+
164
+ }, [dataSourceDelegate?.initialised, dataSourceDelegate?.authenticated, usersPath]);
165
+
166
+ const saveUser = useCallback(async (user: User): Promise<User> => {
167
+ if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
168
+ if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
169
+
170
+ console.debug("Persisting user", user);
171
+
172
+ const roleIds = user.roles?.map(r => r.id);
173
+ const email = user.email?.toLowerCase().trim();
174
+ if (!email) throw Error("Email is required");
175
+
176
+ const userExists = users.find(u => u.email?.toLowerCase() === email);
177
+ const data = {
178
+ ...user,
179
+ roles: roleIds ?? []
180
+ };
181
+ if (!userExists) {
182
+ // @ts-ignore
183
+ data.created_on = new Date();
184
+ }
185
+
186
+ return dataSourceDelegate.saveEntity({
187
+ status: "existing",
188
+ path: usersPath,
189
+ entityId: email,
190
+ values: removeUndefined(data)
191
+ }).then(() => user);
192
+ }, [usersPath, dataSourceDelegate?.initialised]);
193
+
194
+ const saveRole = useCallback((role: Role): Promise<void> => {
195
+ if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
196
+ if (!rolesPath) throw Error("useBuildUserManagement Firestore not initialised");
197
+ console.debug("Persisting role", role);
198
+ const {
199
+ id,
200
+ ...roleData
201
+ } = role;
202
+ return dataSourceDelegate.saveEntity({
203
+ status: "existing",
204
+ path: rolesPath,
205
+ entityId: id,
206
+ values: removeUndefined(roleData)
207
+ }).then(() => {
208
+ return;
209
+ });
210
+ }, [rolesPath, dataSourceDelegate?.initialised]);
211
+
212
+ const deleteUser = useCallback(async (user: User): Promise<void> => {
213
+ if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
214
+ if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
215
+ console.debug("Deleting", user);
216
+ const { uid } = user;
217
+ const entity: Entity<any> = {
218
+ path: usersPath,
219
+ id: uid,
220
+ values: {}
221
+ };
222
+ await dataSourceDelegate.deleteEntity({ entity })
223
+ }, [usersPath, dataSourceDelegate?.initialised]);
224
+
225
+ const deleteRole = useCallback(async (role: Role): Promise<void> => {
226
+ if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
227
+ if (!rolesPath) throw Error("useBuildUserManagement Firestore not initialised");
228
+ console.debug("Deleting", role);
229
+ const { id } = role;
230
+ const entity: Entity<any> = {
231
+ path: rolesPath,
232
+ id: id,
233
+ values: {}
234
+ };
235
+ await dataSourceDelegate.deleteEntity({ entity })
236
+ }, [rolesPath, dataSourceDelegate?.initialised]);
237
+
238
+ const collectionPermissions: PermissionsBuilder = useCallback(({
239
+ collection,
240
+ user
241
+ }) => resolveUserRolePermissions({
242
+ collection,
243
+ user
244
+ }), []);
245
+
246
+ const defineRolesFor: ((user: User) => Role[] | undefined) = useCallback((user) => {
247
+ if (!users) throw Error("Users not loaded");
248
+ const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
249
+ return mgmtUser?.roles;
250
+ }, [users]);
251
+
252
+ const authenticator: Authenticator = useCallback(({ user }) => {
253
+ console.debug("Authenticating user", user);
254
+
255
+ if (loading) {
256
+ console.warn("User management is still loading");
257
+ return false;
258
+ }
259
+
260
+ // This is an example of how you can link the access system to the user management plugin
261
+ if (users.length === 0) {
262
+ return true; // If there are no users created yet, we allow access to every user
263
+ }
264
+
265
+ const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
266
+ if (mgmtUser) {
267
+ return true;
268
+ }
269
+
270
+ 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");
274
+
275
+ return {
276
+ loading,
277
+ roles,
278
+ users,
279
+ saveUser,
280
+ saveRole,
281
+ rolesError,
282
+ deleteUser,
283
+ deleteRole,
284
+ usersLimit,
285
+ usersError,
286
+ isAdmin,
287
+ canEditRoles: canEditRoles === undefined ? true : canEditRoles,
288
+ allowDefaultRolesCreation: allowDefaultRolesCreation === undefined ? true : allowDefaultRolesCreation,
289
+ includeCollectionConfigPermissions: Boolean(includeCollectionConfigPermissions),
290
+ collectionPermissions,
291
+ defineRolesFor,
292
+ authenticator
293
+ }
294
+ }
295
+
296
+ const entitiesToUsers = (docs: Entity<Omit<UserWithRoleIds, "id">>[]): (UserWithRoleIds)[] => {
297
+ return docs.map((doc) => {
298
+ const data = doc.values as any;
299
+ const newVar = {
300
+ uid: doc.id,
301
+ ...data,
302
+ created_on: data?.created_on,
303
+ updated_on: data?.updated_on
304
+ };
305
+ return newVar as (UserWithRoleIds);
306
+ });
307
+ }
308
+
309
+ const entityToRoles = (entities: Entity<Omit<Role, "id">>[]): Role[] => {
310
+ return entities.map((doc) => ({
311
+ id: doc.id,
312
+ ...doc.values
313
+ } as Role));
314
+ }
@@ -1,4 +1,4 @@
1
- import { PermissionsBuilder, Role, User } from "@firecms/core";
1
+ import { Authenticator, PermissionsBuilder, Role, User } from "@firecms/core";
2
2
 
3
3
  export type UserManagement<USER extends User = User> = {
4
4
 
@@ -22,6 +22,11 @@ export type UserManagement<USER extends User = User> = {
22
22
  */
23
23
  canEditRoles?: boolean;
24
24
 
25
+ /**
26
+ * Is the logged user Admin?
27
+ */
28
+ isAdmin?: boolean;
29
+
25
30
  /**
26
31
  * Include a button to create default roles, in case there are no roles in the system.
27
32
  */
@@ -39,9 +44,18 @@ export type UserManagement<USER extends User = User> = {
39
44
  collectionPermissions: PermissionsBuilder;
40
45
 
41
46
  /**
42
- * Define the roles for a given user.
47
+ * Define the roles for a given user. You will typically want to plug this into your auth controller.
43
48
  * @param user
44
49
  */
45
- defineRolesFor: (user: User) => Promise<Role[]> | Role[] | undefined;
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;
46
60
 
47
61
  };