@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.
- package/LICENSE +113 -21
- package/dist/UserManagementProvider.d.ts +4 -3
- package/dist/components/roles/RoleChip.d.ts +1 -1
- package/dist/components/roles/RolesDetailsForm.d.ts +1 -2
- package/dist/components/roles/RolesTable.d.ts +1 -1
- package/dist/components/roles/default_roles.d.ts +1 -1
- package/dist/components/users/UserDetailsForm.d.ts +2 -2
- package/dist/components/users/UsersTable.d.ts +2 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/{useBuildFirestoreUserManagement.d.ts → useFirestoreUserManagement.d.ts} +8 -4
- package/dist/hooks/useUserManagement.d.ts +3 -2
- package/dist/index.es.js +569 -493
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/types/index.d.ts +1 -2
- package/dist/types/persisted_user.d.ts +5 -0
- package/dist/types/user_management.d.ts +18 -10
- package/dist/useUserManagementPlugin.d.ts +6 -1
- package/dist/utils/permissions.d.ts +3 -4
- package/package.json +14 -31
- package/src/UserManagementProvider.tsx +4 -3
- package/src/components/roles/RoleChip.tsx +1 -1
- package/src/components/roles/RolesDetailsForm.tsx +1 -2
- package/src/components/roles/RolesTable.tsx +1 -2
- package/src/components/roles/RolesView.tsx +1 -2
- package/src/components/roles/default_roles.tsx +1 -1
- package/src/components/users/UserDetailsForm.tsx +14 -13
- package/src/components/users/UsersTable.tsx +6 -6
- package/src/components/users/UsersView.tsx +3 -3
- package/src/hooks/index.ts +1 -1
- package/src/hooks/{useBuildFirestoreUserManagement.tsx → useFirestoreUserManagement.tsx} +92 -56
- package/src/hooks/useUserManagement.tsx +3 -3
- package/src/types/index.ts +1 -2
- package/src/types/persisted_user.ts +6 -0
- package/src/types/user_management.tsx +22 -11
- package/src/useUserManagementPlugin.tsx +89 -3
- package/src/utils/permissions.ts +7 -5
- package/dist/types/firecms_user.d.ts +0 -7
- package/dist/types/roles.d.ts +0 -31
- package/src/types/firecms_user.ts +0 -8
- package/src/types/roles.ts +0 -41
- 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.
|
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.
|
35
|
-
"@firecms/formex": "^3.0.0-canary.
|
36
|
-
"@firecms/ui": "^3.0.0-canary.
|
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.
|
41
|
-
"react": "^18.
|
42
|
-
"react-dom": "^18.
|
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.
|
46
|
-
"@types/react": "^18.
|
47
|
-
"@types/react-dom": "^18.
|
48
|
-
"
|
49
|
-
"
|
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
|
-
"
|
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
|
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
|
7
|
+
export interface UserManagementProviderProps<U extends User = User> {
|
7
8
|
userManagement: UserManagement<U>
|
8
9
|
}
|
9
10
|
|
10
|
-
export function UserManagementProvider<U extends
|
11
|
+
export function UserManagementProvider<U extends User = User>({
|
11
12
|
children,
|
12
13
|
userManagement
|
13
14
|
}: PropsWithChildren<UserManagementProviderProps<U>>) {
|
@@ -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 }) {
|
@@ -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:
|
30
|
-
const admins = users.filter(u => u.roles
|
31
|
-
const loggedUserIsAdmin = loggedUser.roles
|
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
|
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?:
|
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:
|
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
|
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:
|
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
|
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:
|
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<
|
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<
|
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:
|
18
|
+
const onUserClicked = useCallback((user: User) => {
|
19
19
|
setSelectedUser(user);
|
20
20
|
setDialogOpen(true);
|
21
21
|
}, []);
|
package/src/hooks/index.ts
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
export * from "./
|
1
|
+
export * from "./useFirestoreUserManagement";
|
2
2
|
export * from "./useUserManagement";
|
@@ -1,17 +1,17 @@
|
|
1
|
-
import React, { useCallback, useEffect
|
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 {
|
14
|
-
import {
|
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
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
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
|
-
|
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:
|
159
|
-
|
160
|
-
|
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
|
153
|
+
const roleIds = user.roles?.map(r => r.id);
|
163
154
|
const {
|
164
155
|
uid,
|
165
156
|
...userData
|
166
157
|
} = user;
|
167
|
-
|
168
|
-
|
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
|
-
|
172
|
-
|
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:
|
183
|
-
|
184
|
-
|
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
|
-
|
192
|
-
|
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
|
204
|
-
}), [
|
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
|
2
|
+
import { UserManagement } from "../types";
|
3
3
|
import { UserManagementContext } from "../UserManagementProvider";
|
4
|
-
|
5
|
-
export const useUserManagement = () => useContext<UserManagement
|
4
|
+
import { User } from "@firecms/core";
|
5
|
+
export const useUserManagement = <USER extends User>() => useContext<UserManagement<USER>>(UserManagementContext);
|
package/src/types/index.ts
CHANGED
@@ -1,18 +1,9 @@
|
|
1
|
-
import {
|
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
|
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
|
};
|