@firecms/user_management 3.0.0-beta.10 → 3.0.0-beta.12
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/dist/hooks/useBuildUserManagement.d.ts +5 -13
- package/dist/index.es.js +1786 -1166
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1784 -1165
- package/dist/index.umd.js.map +1 -1
- package/dist/types/user_management.d.ts +1 -13
- package/dist/useUserManagementPlugin.d.ts +5 -5
- package/dist/utils/permissions.d.ts +2 -2
- package/package.json +13 -11
- package/src/components/roles/RolesDetailsForm.tsx +12 -9
- package/src/components/roles/RolesView.tsx +9 -17
- package/src/components/users/UserDetailsForm.tsx +7 -10
- package/src/components/users/UsersTable.tsx +0 -2
- package/src/components/users/UsersView.tsx +1 -4
- package/src/hooks/useBuildUserManagement.tsx +75 -56
- package/src/types/user_management.tsx +2 -16
- package/src/useUserManagementPlugin.tsx +6 -6
- package/src/utils/permissions.ts +2 -2
@@ -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>;
|
@@ -7,14 +8,6 @@ export type UserManagement<USER extends User = User> = {
|
|
7
8
|
roles: Role[];
|
8
9
|
saveRole: (role: Role) => Promise<void>;
|
9
10
|
deleteRole: (role: Role) => Promise<void>;
|
10
|
-
/**
|
11
|
-
* Maximum number of users that can be created.
|
12
|
-
*/
|
13
|
-
usersLimit?: number;
|
14
|
-
/**
|
15
|
-
* Can the logged user edit roles?
|
16
|
-
*/
|
17
|
-
canEditRoles?: boolean;
|
18
11
|
/**
|
19
12
|
* Is the logged user Admin?
|
20
13
|
*/
|
@@ -37,11 +30,6 @@ export type UserManagement<USER extends User = User> = {
|
|
37
30
|
* @param user
|
38
31
|
*/
|
39
32
|
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
33
|
rolesError?: Error;
|
46
34
|
usersError?: Error;
|
47
35
|
};
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import { FireCMSPlugin } from "@firecms/core";
|
2
|
-
import {
|
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<
|
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<
|
3
|
+
export declare function resolveUserRolePermissions<USER extends User>({ collection, user }: {
|
4
4
|
collection: EntityCollection<any>;
|
5
|
-
user:
|
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.
|
4
|
+
"version": "3.0.0-beta.12",
|
5
5
|
"publishConfig": {
|
6
6
|
"access": "public"
|
7
7
|
},
|
@@ -29,21 +29,23 @@
|
|
29
29
|
"types": "dist/index.d.ts",
|
30
30
|
"source": "src/index.ts",
|
31
31
|
"dependencies": {
|
32
|
-
"@firecms/core": "^3.0.0-beta.
|
33
|
-
"@firecms/formex": "^3.0.0-beta.
|
34
|
-
"@firecms/ui": "^3.0.0-beta.
|
32
|
+
"@firecms/core": "^3.0.0-beta.12",
|
33
|
+
"@firecms/formex": "^3.0.0-beta.12",
|
34
|
+
"@firecms/ui": "^3.0.0-beta.12",
|
35
35
|
"date-fns": "^3.6.0"
|
36
36
|
},
|
37
37
|
"peerDependencies": {
|
38
|
-
"react": "
|
39
|
-
"react-dom": "
|
38
|
+
"react": ">=18.0.0",
|
39
|
+
"react-dom": ">=18.0.0"
|
40
40
|
},
|
41
41
|
"devDependencies": {
|
42
|
-
"@types/node": "^20.
|
43
|
-
"@types/react": "^18.3.
|
42
|
+
"@types/node": "^20.17.14",
|
43
|
+
"@types/react": "^18.3.18",
|
44
44
|
"@types/react-dom": "^18.3.0",
|
45
|
-
"
|
46
|
-
"
|
45
|
+
"babel-plugin-react-compiler": "beta",
|
46
|
+
"eslint-plugin-react-compiler": "beta",
|
47
|
+
"typescript": "^5.7.3",
|
48
|
+
"vite": "^5.4.14"
|
47
49
|
},
|
48
50
|
"scripts": {
|
49
51
|
"dev": "vite",
|
@@ -55,5 +57,5 @@
|
|
55
57
|
"src",
|
56
58
|
"bin"
|
57
59
|
],
|
58
|
-
"gitHead": "
|
60
|
+
"gitHead": "8de3edb4560643922fe44e9c357985f64c3951c1"
|
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
|
-
|
12
|
+
DialogTitle,
|
12
13
|
LoadingButton,
|
13
14
|
Paper,
|
14
15
|
Select,
|
@@ -142,14 +143,10 @@ export function RolesDetailsForm({
|
|
142
143
|
position: "relative",
|
143
144
|
height: "100%"
|
144
145
|
}}>
|
146
|
+
<DialogTitle variant={"h4"} gutterBottom={false}>
|
147
|
+
Role
|
148
|
+
</DialogTitle>
|
145
149
|
<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
150
|
|
154
151
|
<div className={"grid grid-cols-12 gap-8"}>
|
155
152
|
|
@@ -341,6 +338,8 @@ export function RolesDetailsForm({
|
|
341
338
|
<div className={"col-span-12 md:col-span-4"}>
|
342
339
|
<Select
|
343
340
|
error={touched.config && Boolean(errors.config)}
|
341
|
+
size={"large"}
|
342
|
+
fullWidth={true}
|
344
343
|
id="createCollections"
|
345
344
|
name="createCollections"
|
346
345
|
label="Create collections"
|
@@ -363,6 +362,8 @@ export function RolesDetailsForm({
|
|
363
362
|
|
364
363
|
<div className={"col-span-12 md:col-span-4"}>
|
365
364
|
<Select
|
365
|
+
size={"large"}
|
366
|
+
fullWidth={true}
|
366
367
|
error={touched.config && Boolean(errors.config)}
|
367
368
|
id="editCollections"
|
368
369
|
name="editCollections"
|
@@ -389,6 +390,8 @@ export function RolesDetailsForm({
|
|
389
390
|
|
390
391
|
<div className={"col-span-12 md:col-span-4"}>
|
391
392
|
<Select
|
393
|
+
size={"large"}
|
394
|
+
fullWidth={true}
|
392
395
|
error={touched.config && Boolean(errors.config)}
|
393
396
|
id="deleteCollections"
|
394
397
|
name="deleteCollections"
|
@@ -433,7 +436,7 @@ export function RolesDetailsForm({
|
|
433
436
|
type="submit"
|
434
437
|
disabled={!dirty}
|
435
438
|
loading={isSubmitting}
|
436
|
-
startIcon={<
|
439
|
+
startIcon={<CheckIcon/>}
|
437
440
|
>
|
438
441
|
{isNewRole ? "Create role" : "Update"}
|
439
442
|
</LoadingButton>
|
@@ -1,10 +1,9 @@
|
|
1
1
|
import React, { useCallback, useState } from "react";
|
2
2
|
|
3
3
|
import { Role, useNavigationController } from "@firecms/core";
|
4
|
-
import { AddIcon, Button, Container,
|
4
|
+
import { AddIcon, Button, Container, Typography } from "@firecms/ui";
|
5
5
|
import { RolesTable } from "./RolesTable";
|
6
6
|
import { RolesDetailsForm } from "./RolesDetailsForm";
|
7
|
-
import { useUserManagement } from "../../hooks";
|
8
7
|
|
9
8
|
export const RolesView = React.memo(
|
10
9
|
function RolesView({ children }: { children?: React.ReactNode }) {
|
@@ -13,8 +12,6 @@ export const RolesView = React.memo(
|
|
13
12
|
const [dialogOpen, setDialogOpen] = useState(false);
|
14
13
|
const [selectedRole, setSelectedRole] = useState<Role | undefined>();
|
15
14
|
|
16
|
-
const { canEditRoles } = useUserManagement();
|
17
|
-
|
18
15
|
const onRoleClicked = useCallback((user: Role) => {
|
19
16
|
setDialogOpen(true);
|
20
17
|
setSelectedRole(user);
|
@@ -36,26 +33,21 @@ export const RolesView = React.memo(
|
|
36
33
|
component="h4">
|
37
34
|
Roles
|
38
35
|
</Typography>
|
39
|
-
<
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
startIcon={<AddIcon/>}
|
46
|
-
onClick={() => setDialogOpen(true)}>
|
47
|
-
Add role
|
48
|
-
</Button>
|
49
|
-
</Tooltip>
|
36
|
+
<Button
|
37
|
+
size={"large"}
|
38
|
+
startIcon={<AddIcon/>}
|
39
|
+
onClick={() => setDialogOpen(true)}>
|
40
|
+
Add role
|
41
|
+
</Button>
|
50
42
|
</div>
|
51
43
|
|
52
|
-
<RolesTable onRoleClicked={onRoleClicked} editable={
|
44
|
+
<RolesTable onRoleClicked={onRoleClicked} editable={true}/>
|
53
45
|
|
54
46
|
<RolesDetailsForm
|
55
47
|
key={selectedRole?.id ?? "new"}
|
56
48
|
open={dialogOpen}
|
57
49
|
role={selectedRole}
|
58
|
-
editable={
|
50
|
+
editable={true}
|
59
51
|
collections={collections}
|
60
52
|
handleClose={handleClose}/>
|
61
53
|
|
@@ -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
|
-
|
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={<
|
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">
|
@@ -11,9 +11,7 @@ export const UsersView = function UsersView({ children }: { children?: React.Rea
|
|
11
11
|
const [dialogOpen, setDialogOpen] = useState<boolean>();
|
12
12
|
const [selectedUser, setSelectedUser] = useState<User | undefined>();
|
13
13
|
|
14
|
-
const { users
|
15
|
-
|
16
|
-
const reachedUsersLimit = usersLimit !== undefined && (users && users.length >= usersLimit);
|
14
|
+
const { users } = useUserManagement();
|
17
15
|
|
18
16
|
const onUserClicked = useCallback((user: User) => {
|
19
17
|
setSelectedUser(user);
|
@@ -39,7 +37,6 @@ export const UsersView = function UsersView({ children }: { children?: React.Rea
|
|
39
37
|
</Typography>
|
40
38
|
<Button
|
41
39
|
size={"large"}
|
42
|
-
disabled={reachedUsersLimit}
|
43
40
|
startIcon={<AddIcon/>}
|
44
41
|
onClick={() => setDialogOpen(true)}>
|
45
42
|
Add user
|
@@ -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<
|
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.
|
@@ -37,16 +40,6 @@ export interface UserManagementParams {
|
|
37
40
|
*/
|
38
41
|
rolesPath?: string;
|
39
42
|
|
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
43
|
/**
|
51
44
|
* If there are no roles in the database, provide a button to create the default roles.
|
52
45
|
*/
|
@@ -62,46 +55,54 @@ export interface UserManagementParams {
|
|
62
55
|
/**
|
63
56
|
* This hook is used to build a user management object that can be used to
|
64
57
|
* manage users and roles in a Firestore backend.
|
58
|
+
* @param authController
|
65
59
|
* @param dataSourceDelegate
|
66
60
|
* @param usersPath
|
67
61
|
* @param rolesPath
|
68
|
-
* @param usersLimit
|
69
|
-
* @param canEditRoles
|
70
62
|
* @param allowDefaultRolesCreation
|
71
63
|
* @param includeCollectionConfigPermissions
|
72
64
|
*/
|
73
|
-
export function useBuildUserManagement
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
65
|
+
export function useBuildUserManagement<CONTROLLER extends AuthController<any> = AuthController<any>,
|
66
|
+
USER extends User = CONTROLLER extends AuthController<infer U> ? U : any>
|
67
|
+
({
|
68
|
+
authController,
|
69
|
+
dataSourceDelegate,
|
70
|
+
usersPath = "__FIRECMS/config/users",
|
71
|
+
rolesPath = "__FIRECMS/config/roles",
|
72
|
+
allowDefaultRolesCreation,
|
73
|
+
includeCollectionConfigPermissions
|
74
|
+
}: UserManagementParams<CONTROLLER>): UserManagement<USER> & CONTROLLER {
|
75
|
+
|
76
|
+
if (!authController) {
|
77
|
+
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");
|
78
|
+
}
|
82
79
|
|
83
80
|
const [rolesLoading, setRolesLoading] = React.useState<boolean>(true);
|
84
81
|
const [usersLoading, setUsersLoading] = React.useState<boolean>(true);
|
85
82
|
const [roles, setRoles] = React.useState<Role[]>([]);
|
86
|
-
const [usersWithRoleIds, setUsersWithRoleIds] = React.useState<UserWithRoleIds[]>([]);
|
83
|
+
const [usersWithRoleIds, setUsersWithRoleIds] = React.useState<UserWithRoleIds<USER>[]>([]);
|
87
84
|
|
88
85
|
const users = usersWithRoleIds.map(u => ({
|
89
86
|
...u,
|
90
87
|
roles: roles.filter(r => u.roles?.includes(r.id))
|
91
|
-
}) as
|
88
|
+
}) as USER);
|
92
89
|
|
93
90
|
const [rolesError, setRolesError] = React.useState<Error | undefined>();
|
94
91
|
const [usersError, setUsersError] = React.useState<Error | undefined>();
|
95
92
|
|
96
|
-
const
|
93
|
+
const _usersLoading = usersLoading;
|
94
|
+
const _rolesLoading = rolesLoading;
|
95
|
+
|
96
|
+
const loading = _rolesLoading || _usersLoading;
|
97
97
|
|
98
98
|
useEffect(() => {
|
99
99
|
if (!dataSourceDelegate || !rolesPath) return;
|
100
100
|
if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) return;
|
101
|
-
if (
|
102
|
-
|
103
|
-
|
104
|
-
|
101
|
+
if (authController?.initialLoading) return;
|
102
|
+
// if (authController.user === null) {
|
103
|
+
// setRolesLoading(false);
|
104
|
+
// return;
|
105
|
+
// }
|
105
106
|
|
106
107
|
setRolesLoading(true);
|
107
108
|
return dataSourceDelegate.listenCollection?.({
|
@@ -110,8 +111,9 @@ export function useBuildUserManagement({
|
|
110
111
|
setRolesError(undefined);
|
111
112
|
try {
|
112
113
|
const newRoles = entityToRoles(entities);
|
113
|
-
if (!equal(newRoles, roles))
|
114
|
+
if (!equal(newRoles, roles)) {
|
114
115
|
setRoles(newRoles);
|
116
|
+
}
|
115
117
|
} catch (e) {
|
116
118
|
setRoles([]);
|
117
119
|
console.error("Error loading roles", e);
|
@@ -127,13 +129,14 @@ export function useBuildUserManagement({
|
|
127
129
|
}
|
128
130
|
});
|
129
131
|
|
130
|
-
}, [dataSourceDelegate?.initialised,
|
132
|
+
}, [dataSourceDelegate?.initialised, authController?.initialLoading, authController?.user?.uid, rolesPath]);
|
131
133
|
|
132
134
|
useEffect(() => {
|
133
135
|
if (!dataSourceDelegate || !usersPath) return;
|
134
|
-
if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised)
|
135
|
-
|
136
|
-
|
136
|
+
if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) {
|
137
|
+
return;
|
138
|
+
}
|
139
|
+
if (authController?.initialLoading) {
|
137
140
|
return;
|
138
141
|
}
|
139
142
|
|
@@ -141,11 +144,12 @@ export function useBuildUserManagement({
|
|
141
144
|
return dataSourceDelegate.listenCollection?.({
|
142
145
|
path: usersPath,
|
143
146
|
onUpdate(entities: Entity<any>[]): void {
|
147
|
+
console.debug("Updating users", entities);
|
144
148
|
setUsersError(undefined);
|
145
149
|
try {
|
146
150
|
const newUsers = entitiesToUsers(entities);
|
147
|
-
if (!equal(newUsers, usersWithRoleIds))
|
148
|
-
|
151
|
+
// if (!equal(newUsers, usersWithRoleIds))
|
152
|
+
setUsersWithRoleIds(newUsers);
|
149
153
|
} catch (e) {
|
150
154
|
setUsersWithRoleIds([]);
|
151
155
|
console.error("Error loading users", e);
|
@@ -154,16 +158,16 @@ export function useBuildUserManagement({
|
|
154
158
|
setUsersLoading(false);
|
155
159
|
},
|
156
160
|
onError(e: any): void {
|
157
|
-
setUsersWithRoleIds([]);
|
158
161
|
console.error("Error loading users", e);
|
162
|
+
setUsersWithRoleIds([]);
|
159
163
|
setUsersError(e);
|
160
164
|
setUsersLoading(false);
|
161
165
|
}
|
162
166
|
});
|
163
167
|
|
164
|
-
}, [dataSourceDelegate?.initialised,
|
168
|
+
}, [dataSourceDelegate?.initialised, authController?.initialLoading, authController?.user?.uid, usersPath]);
|
165
169
|
|
166
|
-
const saveUser = useCallback(async (user:
|
170
|
+
const saveUser = useCallback(async (user: USER): Promise<USER> => {
|
167
171
|
if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
|
168
172
|
if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
|
169
173
|
|
@@ -238,39 +242,49 @@ export function useBuildUserManagement({
|
|
238
242
|
const collectionPermissions: PermissionsBuilder = useCallback(({
|
239
243
|
collection,
|
240
244
|
user
|
241
|
-
}) =>
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
+
}) =>
|
246
|
+
resolveUserRolePermissions({
|
247
|
+
collection,
|
248
|
+
user
|
249
|
+
}), []);
|
245
250
|
|
246
251
|
const defineRolesFor: ((user: User) => Role[] | undefined) = useCallback((user) => {
|
247
|
-
if (!
|
252
|
+
if (!usersWithRoleIds) throw Error("Users not loaded");
|
253
|
+
const users = usersWithRoleIds.map(u => ({
|
254
|
+
...u,
|
255
|
+
roles: roles.filter(r => u.roles?.includes(r.id))
|
256
|
+
}) as User);
|
248
257
|
const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
|
249
258
|
return mgmtUser?.roles;
|
250
|
-
}, [
|
251
|
-
|
252
|
-
const authenticator: Authenticator = useCallback(({ user }) => {
|
253
|
-
console.debug("Authenticating user", user);
|
259
|
+
}, [roles, usersWithRoleIds]);
|
254
260
|
|
261
|
+
const authenticator: Authenticator<USER> = useCallback(({ user }) => {
|
255
262
|
if (loading) {
|
256
|
-
console.warn("User management is still loading");
|
257
263
|
return false;
|
258
264
|
}
|
259
265
|
|
260
|
-
// This is an example of how you can link the access system to the user management plugin
|
261
266
|
if (users.length === 0) {
|
267
|
+
console.warn("No users created yet");
|
262
268
|
return true; // If there are no users created yet, we allow access to every user
|
263
269
|
}
|
264
270
|
|
265
271
|
const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
|
266
272
|
if (mgmtUser) {
|
273
|
+
console.debug("User found in user management system", mgmtUser);
|
267
274
|
return true;
|
268
275
|
}
|
269
276
|
|
270
277
|
throw Error("Could not find a user with the provided email in the user management system.");
|
271
|
-
}, [loading, users
|
278
|
+
}, [loading, users]);
|
272
279
|
|
273
|
-
const
|
280
|
+
const userRoles = authController.user ? defineRolesFor(authController.user) : undefined;
|
281
|
+
const isAdmin = (userRoles ?? []).some(r => r.id === "admin");
|
282
|
+
|
283
|
+
const userRoleIds = userRoles?.map(r => r.id);
|
284
|
+
useEffect(() => {
|
285
|
+
console.debug("Setting roles", userRoles);
|
286
|
+
authController.setUserRoles?.(userRoles ?? []);
|
287
|
+
}, [userRoleIds]);
|
274
288
|
|
275
289
|
return {
|
276
290
|
loading,
|
@@ -281,15 +295,20 @@ export function useBuildUserManagement({
|
|
281
295
|
rolesError,
|
282
296
|
deleteUser,
|
283
297
|
deleteRole,
|
284
|
-
usersLimit,
|
285
298
|
usersError,
|
286
299
|
isAdmin,
|
287
|
-
canEditRoles: canEditRoles === undefined ? true : canEditRoles,
|
288
300
|
allowDefaultRolesCreation: allowDefaultRolesCreation === undefined ? true : allowDefaultRolesCreation,
|
289
301
|
includeCollectionConfigPermissions: Boolean(includeCollectionConfigPermissions),
|
290
302
|
collectionPermissions,
|
291
303
|
defineRolesFor,
|
292
|
-
authenticator
|
304
|
+
authenticator,
|
305
|
+
...authController,
|
306
|
+
initialLoading: authController.initialLoading || loading,
|
307
|
+
userRoles: userRoles,
|
308
|
+
user: authController.user ? {
|
309
|
+
...authController.user,
|
310
|
+
roles: userRoles
|
311
|
+
} : null
|
293
312
|
}
|
294
313
|
}
|
295
314
|
|
@@ -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[];
|
@@ -12,16 +14,6 @@ export type UserManagement<USER extends User = User> = {
|
|
12
14
|
saveRole: (role: Role) => Promise<void>;
|
13
15
|
deleteRole: (role: Role) => Promise<void>;
|
14
16
|
|
15
|
-
/**
|
16
|
-
* Maximum number of users that can be created.
|
17
|
-
*/
|
18
|
-
usersLimit?: number;
|
19
|
-
|
20
|
-
/**
|
21
|
-
* Can the logged user edit roles?
|
22
|
-
*/
|
23
|
-
canEditRoles?: boolean;
|
24
|
-
|
25
17
|
/**
|
26
18
|
* Is the logged user Admin?
|
27
19
|
*/
|
@@ -49,12 +41,6 @@ export type UserManagement<USER extends User = User> = {
|
|
49
41
|
*/
|
50
42
|
defineRolesFor: (user: User) => Promise<Role[] | undefined> | Role[] | undefined;
|
51
43
|
|
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
44
|
rolesError?: Error;
|
59
45
|
usersError?: Error;
|
60
46
|
|
@@ -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 {
|
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<
|
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-
|
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.
|
package/src/utils/permissions.ts
CHANGED
@@ -9,13 +9,13 @@ const DEFAULT_PERMISSIONS = {
|
|
9
9
|
delete: false
|
10
10
|
};
|
11
11
|
|
12
|
-
export function resolveUserRolePermissions<
|
12
|
+
export function resolveUserRolePermissions<USER extends User>
|
13
13
|
({
|
14
14
|
collection,
|
15
15
|
user
|
16
16
|
}: {
|
17
17
|
collection: EntityCollection<any>,
|
18
|
-
user:
|
18
|
+
user: USER | null
|
19
19
|
}): Permissions {
|
20
20
|
|
21
21
|
const roles = user?.roles;
|