@rebasepro/client-firebase 0.2.3 → 0.2.5
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/components/RebaseFirebaseApp.d.ts +1 -1
- package/dist/components/RebaseFirebaseAppProps.d.ts +5 -4
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useAppCheck.d.ts +1 -1
- package/dist/hooks/useBuildUserManagement.d.ts +8 -8
- package/dist/hooks/useFirebaseAccessGate.d.ts +44 -0
- package/dist/hooks/useFirebaseAuthController.d.ts +2 -2
- package/dist/hooks/useFirestoreDriver.d.ts +2 -2
- package/dist/index.es.js +165 -44
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +164 -43
- package/dist/index.umd.js.map +1 -1
- package/dist/types/auth.d.ts +2 -2
- package/dist/utils/algolia.d.ts +7 -1
- package/package.json +7 -7
- package/src/components/FirebaseLoginView.tsx +1 -1
- package/src/components/RebaseFirebaseApp.tsx +9 -9
- package/src/components/RebaseFirebaseAppProps.tsx +5 -4
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAppCheck.ts +4 -4
- package/src/hooks/useBuildUserManagement.tsx +25 -28
- package/src/hooks/useFirebaseAccessGate.tsx +145 -0
- package/src/hooks/useFirebaseAuthController.ts +6 -8
- package/src/hooks/useFirebaseRealTimeDBDelegate.ts +17 -12
- package/src/hooks/useFirebaseStorageSource.ts +4 -4
- package/src/hooks/useFirestoreDriver.ts +27 -23
- package/src/hooks/useInitialiseFirebase.ts +2 -2
- package/src/types/auth.tsx +2 -2
- package/src/utils/algolia.ts +4 -4
- package/src/utils/local_text_search_controller.ts +1 -1
- package/src/utils/rebase_search_controller.ts +14 -14
package/dist/types/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApplicationVerifier, ConfirmationResult, User as FirebaseUser } from "@firebase/auth";
|
|
2
|
-
import { AuthController,
|
|
2
|
+
import type { AuthController, User } from "@rebasepro/types";
|
|
3
3
|
/**
|
|
4
4
|
* @group Firebase
|
|
5
5
|
*/
|
|
@@ -37,5 +37,5 @@ export type FirebaseAuthController<USER extends User = FirebaseUserWrapper, Extr
|
|
|
37
37
|
*/
|
|
38
38
|
skipLogin: () => void;
|
|
39
39
|
setUser: (user: USER | null) => void;
|
|
40
|
-
setUserRoles: (roles:
|
|
40
|
+
setUserRoles: (roles: string[]) => void;
|
|
41
41
|
};
|
package/dist/utils/algolia.d.ts
CHANGED
|
@@ -6,4 +6,10 @@
|
|
|
6
6
|
* @param query
|
|
7
7
|
* @group Firebase
|
|
8
8
|
*/
|
|
9
|
-
export declare function performAlgoliaTextSearch(client:
|
|
9
|
+
export declare function performAlgoliaTextSearch(client: {
|
|
10
|
+
searchSingleIndex: (params: Record<string, unknown>) => Promise<{
|
|
11
|
+
hits: Array<{
|
|
12
|
+
objectID: string;
|
|
13
|
+
}>;
|
|
14
|
+
}>;
|
|
15
|
+
}, indexName: string, query: string): Promise<readonly string[]>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/client-firebase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.5",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"fast-equals": "6.0.0",
|
|
15
15
|
"fuse.js": "^7.3.0",
|
|
16
16
|
"react-router-dom": "^7.15.1",
|
|
17
|
-
"@rebasepro/admin": "0.2.
|
|
18
|
-
"@rebasepro/
|
|
19
|
-
"@rebasepro/
|
|
20
|
-
"@rebasepro/ui": "0.2.
|
|
21
|
-
"@rebasepro/utils": "0.2.
|
|
22
|
-
"@rebasepro/
|
|
17
|
+
"@rebasepro/admin": "0.2.5",
|
|
18
|
+
"@rebasepro/core": "0.2.5",
|
|
19
|
+
"@rebasepro/common": "0.2.5",
|
|
20
|
+
"@rebasepro/ui": "0.2.5",
|
|
21
|
+
"@rebasepro/utils": "0.2.5",
|
|
22
|
+
"@rebasepro/types": "0.2.5"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"firebase": "^10.12.2 || ^11.0.0 || ^12.0.0",
|
|
@@ -261,7 +261,7 @@ export function FirebaseLoginView({
|
|
|
261
261
|
} else if (notAllowedError instanceof Error) {
|
|
262
262
|
notAllowedMessage = notAllowedError.message;
|
|
263
263
|
} else {
|
|
264
|
-
notAllowedMessage = "It looks like you don't have access to the CMS, based on the specified
|
|
264
|
+
notAllowedMessage = "It looks like you don't have access to the CMS, based on the specified access gate configuration";
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
useBuildModeController,
|
|
11
11
|
useBuildAdminModeController,
|
|
12
12
|
AdminModeControllerProvider,
|
|
13
|
-
useValidateAuthenticator,
|
|
14
13
|
RebaseRoutes
|
|
15
14
|
} from "@rebasepro/core";
|
|
16
15
|
import {
|
|
@@ -27,7 +26,7 @@ import {
|
|
|
27
26
|
SideEntityProvider,
|
|
28
27
|
RebaseRoute
|
|
29
28
|
} from "@rebasepro/admin";
|
|
30
|
-
import { Entity, PropertyConfig, UserManagementDelegate } from "@rebasepro/types";
|
|
29
|
+
import { Entity, PropertyConfig, RebaseContext, UserManagementDelegate } from "@rebasepro/types";
|
|
31
30
|
import { CenteredView, CircularProgressCenter } from "@rebasepro/ui";
|
|
32
31
|
import { buildRebaseData } from "@rebasepro/common";
|
|
33
32
|
import { Route, Outlet, Navigate } from "react-router-dom";
|
|
@@ -40,7 +39,8 @@ import {
|
|
|
40
39
|
useFirebaseStorageSource,
|
|
41
40
|
useFirestoreDriver,
|
|
42
41
|
useInitialiseFirebase,
|
|
43
|
-
useBuildUserManagement
|
|
42
|
+
useBuildUserManagement,
|
|
43
|
+
useFirebaseAccessGate
|
|
44
44
|
} from "../hooks";
|
|
45
45
|
|
|
46
46
|
import { FirebaseAuthController } from "../types";
|
|
@@ -70,7 +70,7 @@ export function RebaseFirebaseApp({
|
|
|
70
70
|
name,
|
|
71
71
|
logo,
|
|
72
72
|
logoDark,
|
|
73
|
-
|
|
73
|
+
accessGate,
|
|
74
74
|
collections,
|
|
75
75
|
views,
|
|
76
76
|
adminViews,
|
|
@@ -158,15 +158,15 @@ export function RebaseFirebaseApp({
|
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
|
-
* Validate
|
|
161
|
+
* Validate access gate
|
|
162
162
|
*/
|
|
163
163
|
const {
|
|
164
164
|
authLoading,
|
|
165
165
|
canAccessMainView,
|
|
166
166
|
notAllowedError
|
|
167
|
-
} =
|
|
167
|
+
} = useFirebaseAccessGate({
|
|
168
168
|
authController,
|
|
169
|
-
|
|
169
|
+
accessGate,
|
|
170
170
|
data: buildRebaseData(firestoreDelegate),
|
|
171
171
|
storageSource
|
|
172
172
|
});
|
|
@@ -234,7 +234,7 @@ export function RebaseFirebaseApp({
|
|
|
234
234
|
{({
|
|
235
235
|
context,
|
|
236
236
|
loading
|
|
237
|
-
}: { context:
|
|
237
|
+
}: { context: RebaseContext, loading: boolean }) => {
|
|
238
238
|
|
|
239
239
|
let component;
|
|
240
240
|
if (loading || authLoading) {
|
|
@@ -250,7 +250,7 @@ export function RebaseFirebaseApp({
|
|
|
250
250
|
signInOptions={signInOptions ?? DEFAULT_SIGN_IN_OPTIONS}
|
|
251
251
|
firebaseApp={firebaseApp}
|
|
252
252
|
authController={authController}
|
|
253
|
-
notAllowedError={notAllowedError}/>
|
|
253
|
+
notAllowedError={notAllowedError instanceof Error ? notAllowedError : typeof notAllowedError === "string" ? notAllowedError : undefined}/>
|
|
254
254
|
);
|
|
255
255
|
} else {
|
|
256
256
|
const firstCollectionEntry = navigationStateController.topLevelNavigation?.navigationEntries.find(e => e.type === "collection");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { AnalyticsEvent, AppView, AppViewsBuilder, EntityCollection, EntityCollectionsBuilder, RebasePlugin, Locale, PropertyConfig } from "@rebasepro/types";
|
|
4
|
+
import { FirebaseAccessGate } from "../hooks/useFirebaseAccessGate";
|
|
4
5
|
import { UserManagementDelegate } from "@rebasepro/types";
|
|
5
6
|
import { FirebaseApp } from "@firebase/app";
|
|
6
7
|
import { FirebaseLoginViewProps } from "./FirebaseLoginView";
|
|
@@ -64,12 +65,12 @@ export type RebaseFirebaseAppProps = {
|
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
67
|
* Do the users need to log in to access the CMS.
|
|
67
|
-
* You can specify
|
|
68
|
-
* access the CMS or not.
|
|
68
|
+
* You can specify a {@link FirebaseAccessGate} function to discriminate
|
|
69
|
+
* which users can access the CMS or not.
|
|
69
70
|
* If not specified, authentication is enabled but no user restrictions
|
|
70
71
|
* apply
|
|
71
72
|
*/
|
|
72
|
-
|
|
73
|
+
accessGate?: boolean | FirebaseAccessGate<FirebaseUserWrapper>;
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
76
|
* List of sign in options that will be displayed in the login
|
package/src/hooks/index.ts
CHANGED
package/src/hooks/useAppCheck.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface InitializeAppCheckProps {
|
|
|
15
15
|
export interface InitializeAppCheckResult {
|
|
16
16
|
loading: boolean;
|
|
17
17
|
appCheckVerified?: boolean;
|
|
18
|
-
error?:
|
|
18
|
+
error?: unknown;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -35,7 +35,7 @@ export function useAppCheck({
|
|
|
35
35
|
|
|
36
36
|
const [appCheckLoading, setAppCheckLoading] = React.useState<boolean>(false);
|
|
37
37
|
const [appCheckVerified, setAppCheckVerified] = React.useState<boolean | undefined>(undefined);
|
|
38
|
-
const [error, setError] = React.useState<
|
|
38
|
+
const [error, setError] = React.useState<unknown>();
|
|
39
39
|
|
|
40
40
|
const initialCheck = useRef<boolean>(false);
|
|
41
41
|
|
|
@@ -51,9 +51,9 @@ export function useAppCheck({
|
|
|
51
51
|
setAppCheckVerified(true);
|
|
52
52
|
console.debug("App Check success.");
|
|
53
53
|
}
|
|
54
|
-
} catch (e:
|
|
54
|
+
} catch (e: unknown) {
|
|
55
55
|
console.error("App Check error:", e);
|
|
56
|
-
setError(e.message);
|
|
56
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
57
57
|
}
|
|
58
58
|
}, [options?.forceRefresh]);
|
|
59
59
|
|
|
@@ -4,17 +4,22 @@ import { deepEqual as equal } from "fast-equals";
|
|
|
4
4
|
import { removeUndefined } from "@rebasepro/utils";
|
|
5
5
|
import {
|
|
6
6
|
AuthController,
|
|
7
|
-
Authenticator,
|
|
8
7
|
DataDriver,
|
|
9
8
|
Entity,
|
|
10
|
-
Role,
|
|
11
9
|
User,
|
|
12
10
|
UserManagementDelegate
|
|
13
11
|
} from "@rebasepro/types";
|
|
12
|
+
import { FirebaseAccessGate } from "./useFirebaseAccessGate";
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
export interface Role {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
isAdmin?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type UserWithRoleIds<USER extends User = User> = Omit<USER, "roles"> & { roles: string[] };
|
|
16
21
|
|
|
17
|
-
export interface UserManagementDelegateParams<CONTROLLER extends AuthController<
|
|
22
|
+
export interface UserManagementDelegateParams<CONTROLLER extends AuthController<User> = AuthController<User>> {
|
|
18
23
|
|
|
19
24
|
authController: CONTROLLER;
|
|
20
25
|
|
|
@@ -49,11 +54,6 @@ export interface UserManagementDelegateParams<CONTROLLER extends AuthController<
|
|
|
49
54
|
*/
|
|
50
55
|
allowDefaultRolesCreation?: boolean;
|
|
51
56
|
|
|
52
|
-
/**
|
|
53
|
-
* Include the collection config permissions in the user management system.
|
|
54
|
-
*/
|
|
55
|
-
includeCollectionConfigPermissions?: boolean;
|
|
56
|
-
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -65,18 +65,16 @@ export interface UserManagementDelegateParams<CONTROLLER extends AuthController<
|
|
|
65
65
|
* @param rolesPath
|
|
66
66
|
* @param roles
|
|
67
67
|
* @param allowDefaultRolesCreation
|
|
68
|
-
* @param includeCollectionConfigPermissions
|
|
69
68
|
*/
|
|
70
|
-
export function useBuildUserManagement<CONTROLLER extends AuthController<
|
|
71
|
-
USER extends User = CONTROLLER extends AuthController<infer U> ? U :
|
|
69
|
+
export function useBuildUserManagement<CONTROLLER extends AuthController<User> = AuthController<User>,
|
|
70
|
+
USER extends User = CONTROLLER extends AuthController<infer U> ? U : User>
|
|
72
71
|
({
|
|
73
72
|
authController,
|
|
74
73
|
dataSourceDelegate,
|
|
75
74
|
roles: rolesProp,
|
|
76
75
|
usersPath = "__FIRECMS/config/users",
|
|
77
76
|
rolesPath = "__FIRECMS/config/roles",
|
|
78
|
-
allowDefaultRolesCreation
|
|
79
|
-
includeCollectionConfigPermissions
|
|
77
|
+
allowDefaultRolesCreation
|
|
80
78
|
}: UserManagementDelegateParams<CONTROLLER>): UserManagementDelegate<USER> & CONTROLLER {
|
|
81
79
|
|
|
82
80
|
if (!authController) {
|
|
@@ -125,10 +123,10 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
125
123
|
}
|
|
126
124
|
setRolesLoading(false);
|
|
127
125
|
},
|
|
128
|
-
onError(e:
|
|
126
|
+
onError(e: unknown): void {
|
|
129
127
|
setRoles([]);
|
|
130
128
|
console.error("Error loading roles", e);
|
|
131
|
-
setRolesError(e);
|
|
129
|
+
setRolesError(e instanceof Error ? e : new Error(String(e)));
|
|
132
130
|
setRolesLoading(false);
|
|
133
131
|
}
|
|
134
132
|
});
|
|
@@ -151,7 +149,7 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
151
149
|
console.debug("Updating users", entities);
|
|
152
150
|
setUsersError(undefined);
|
|
153
151
|
try {
|
|
154
|
-
const newUsers = entitiesToUsers(entities);
|
|
152
|
+
const newUsers = entitiesToUsers(entities) as UserWithRoleIds<USER>[];
|
|
155
153
|
// if (!equal(newUsers, usersWithRoleIds))
|
|
156
154
|
setUsersWithRoleIds(newUsers);
|
|
157
155
|
} catch (e) {
|
|
@@ -161,10 +159,10 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
161
159
|
}
|
|
162
160
|
setUsersLoading(false);
|
|
163
161
|
},
|
|
164
|
-
onError(e:
|
|
162
|
+
onError(e: unknown): void {
|
|
165
163
|
console.error("Error loading users", e);
|
|
166
164
|
setUsersWithRoleIds([]);
|
|
167
|
-
setUsersError(e);
|
|
165
|
+
setUsersError(e instanceof Error ? e : new Error(String(e)));
|
|
168
166
|
setUsersLoading(false);
|
|
169
167
|
}
|
|
170
168
|
});
|
|
@@ -261,14 +259,14 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
261
259
|
|
|
262
260
|
|
|
263
261
|
|
|
264
|
-
const defineRolesFor: ((user: User) =>
|
|
262
|
+
const defineRolesFor: ((user: User) => string[] | undefined) = useCallback((user) => {
|
|
265
263
|
if (!usersWithRoleIds) throw Error("Users not loaded");
|
|
266
264
|
const mgmtUser = usersWithRoleIds.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
|
|
267
265
|
if (!mgmtUser || !mgmtUser.roles) return undefined;
|
|
268
|
-
return
|
|
269
|
-
}, [
|
|
266
|
+
return mgmtUser.roles;
|
|
267
|
+
}, [usersWithRoleIds]);
|
|
270
268
|
|
|
271
|
-
const
|
|
269
|
+
const accessGate: FirebaseAccessGate<USER> = useCallback(({ user }) => {
|
|
272
270
|
|
|
273
271
|
if (loading) {
|
|
274
272
|
return false;
|
|
@@ -310,9 +308,9 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
310
308
|
}, [loading, users]);
|
|
311
309
|
|
|
312
310
|
const userRoles = authController.user ? defineRolesFor(authController.user) : undefined;
|
|
313
|
-
const isAdmin = (userRoles ?? []).some(r => r
|
|
311
|
+
const isAdmin = (userRoles ?? []).some(r => r === "admin");
|
|
314
312
|
|
|
315
|
-
const userRoleIds = userRoles
|
|
313
|
+
const userRoleIds = userRoles;
|
|
316
314
|
useEffect(() => {
|
|
317
315
|
console.debug("Setting user roles", {
|
|
318
316
|
userRoles,
|
|
@@ -339,9 +337,8 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
339
337
|
usersError,
|
|
340
338
|
isAdmin,
|
|
341
339
|
allowDefaultRolesCreation: allowDefaultRolesCreation === undefined ? true : allowDefaultRolesCreation,
|
|
342
|
-
includeCollectionConfigPermissions: Boolean(includeCollectionConfigPermissions),
|
|
343
340
|
defineRolesFor,
|
|
344
|
-
|
|
341
|
+
accessGate,
|
|
345
342
|
...authController,
|
|
346
343
|
initialLoading: authController.initialLoading || loading,
|
|
347
344
|
userRoles: userRoles,
|
|
@@ -358,8 +355,8 @@ const entitiesToUsers = (docs: Entity<Omit<UserWithRoleIds, "id">>[]): (UserWith
|
|
|
358
355
|
const data = doc.values;
|
|
359
356
|
const record = data as Record<string, unknown>;
|
|
360
357
|
const newVar = {
|
|
361
|
-
uid: doc.id,
|
|
362
358
|
...data,
|
|
359
|
+
uid: doc.id,
|
|
363
360
|
created_on: record.created_on as Date | undefined,
|
|
364
361
|
updated_on: record.updated_on as Date | undefined
|
|
365
362
|
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
|
|
2
|
+
import { useCallback, useEffect, useRef, useState, useMemo } from "react";
|
|
3
|
+
import { deepEqual as equal } from "fast-equals";
|
|
4
|
+
|
|
5
|
+
import { AuthController, RebaseData, StorageSource, User } from "@rebasepro/types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client-side gate that decides whether a Firebase-authenticated user
|
|
9
|
+
* is allowed to access the CMS UI.
|
|
10
|
+
*
|
|
11
|
+
* Return `true` to allow access or `false` / throw to deny.
|
|
12
|
+
* @group Firebase
|
|
13
|
+
*/
|
|
14
|
+
export type FirebaseAccessGate<USER extends User = User> = (props: {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Logged-in user or null
|
|
18
|
+
*/
|
|
19
|
+
user: USER | null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* AuthController
|
|
23
|
+
*/
|
|
24
|
+
authController: AuthController<USER>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Unified data access API
|
|
28
|
+
*/
|
|
29
|
+
data: RebaseData;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Used storage implementation
|
|
33
|
+
*/
|
|
34
|
+
storageSource: StorageSource;
|
|
35
|
+
|
|
36
|
+
}) => boolean | Promise<boolean>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hook that evaluates a {@link FirebaseAccessGate} callback after
|
|
40
|
+
* the user logs in and gates access to the main CMS view.
|
|
41
|
+
*
|
|
42
|
+
* @group Firebase
|
|
43
|
+
*/
|
|
44
|
+
export function useFirebaseAccessGate<USER extends User = User>
|
|
45
|
+
({
|
|
46
|
+
disabled,
|
|
47
|
+
authController,
|
|
48
|
+
accessGate,
|
|
49
|
+
storageSource,
|
|
50
|
+
data
|
|
51
|
+
}:
|
|
52
|
+
{
|
|
53
|
+
disabled?: boolean,
|
|
54
|
+
authController: AuthController<USER>,
|
|
55
|
+
accessGate?: boolean | FirebaseAccessGate<USER>,
|
|
56
|
+
data: RebaseData;
|
|
57
|
+
storageSource: StorageSource;
|
|
58
|
+
}): {
|
|
59
|
+
canAccessMainView: boolean,
|
|
60
|
+
authLoading: boolean,
|
|
61
|
+
notAllowedError: unknown,
|
|
62
|
+
authVerified: boolean,
|
|
63
|
+
} {
|
|
64
|
+
|
|
65
|
+
const gateEnabled = Boolean(accessGate);
|
|
66
|
+
|
|
67
|
+
const [authLoading, setAuthLoading] = useState<boolean>(gateEnabled);
|
|
68
|
+
const [notAllowedError, setNotAllowedError] = useState<unknown>(false);
|
|
69
|
+
const [authVerified, setAuthVerified] = useState<boolean>(!gateEnabled || Boolean(authController.loginSkipped));
|
|
70
|
+
|
|
71
|
+
const canAccessMainView = (authVerified) &&
|
|
72
|
+
(!gateEnabled || Boolean(authController.user) || Boolean(authController.loginSkipped)) &&
|
|
73
|
+
!notAllowedError;
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (authController.loginSkipped)
|
|
77
|
+
setAuthVerified(true);
|
|
78
|
+
}, [authController.loginSkipped]);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* We use this ref to check the authentication only if the user has
|
|
82
|
+
* changed.
|
|
83
|
+
*/
|
|
84
|
+
const checkedUserRef = useRef<User | undefined>(undefined);
|
|
85
|
+
|
|
86
|
+
const checkAccess = useCallback(async () => {
|
|
87
|
+
|
|
88
|
+
if (disabled) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (authController.initialLoading) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!authController.user && !authController.loginSkipped) {
|
|
97
|
+
checkedUserRef.current = undefined;
|
|
98
|
+
setAuthLoading(false);
|
|
99
|
+
setAuthVerified(false);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const delegateUser = authController.user;
|
|
104
|
+
|
|
105
|
+
if (accessGate instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
|
|
106
|
+
setAuthLoading(true);
|
|
107
|
+
try {
|
|
108
|
+
const allowed = await accessGate({
|
|
109
|
+
user: delegateUser,
|
|
110
|
+
authController,
|
|
111
|
+
data,
|
|
112
|
+
storageSource
|
|
113
|
+
});
|
|
114
|
+
if (!allowed) {
|
|
115
|
+
authController.signOut();
|
|
116
|
+
setNotAllowedError(true);
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
setNotAllowedError(e);
|
|
120
|
+
authController.signOut();
|
|
121
|
+
}
|
|
122
|
+
setAuthLoading(false);
|
|
123
|
+
setAuthVerified(true);
|
|
124
|
+
checkedUserRef.current = delegateUser;
|
|
125
|
+
} else {
|
|
126
|
+
setAuthLoading(false);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!authController.initialLoading && !delegateUser) {
|
|
130
|
+
setAuthVerified(true);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
}, [disabled, authController, accessGate, data, storageSource]);
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
checkAccess();
|
|
137
|
+
}, [checkAccess]);
|
|
138
|
+
|
|
139
|
+
return useMemo(() => ({
|
|
140
|
+
canAccessMainView,
|
|
141
|
+
authLoading: gateEnabled && authLoading,
|
|
142
|
+
notAllowedError,
|
|
143
|
+
authVerified
|
|
144
|
+
}), [canAccessMainView, gateEnabled, authLoading, notAllowedError, authVerified]);
|
|
145
|
+
}
|
|
@@ -24,14 +24,14 @@ import {
|
|
|
24
24
|
} from "@firebase/auth";
|
|
25
25
|
import { FirebaseApp } from "@firebase/app";
|
|
26
26
|
import { FirebaseAuthController, FirebaseSignInOption, FirebaseSignInProvider, FirebaseUserWrapper } from "../types";
|
|
27
|
-
import {
|
|
27
|
+
import type { User } from "@rebasepro/types";
|
|
28
28
|
|
|
29
29
|
export interface FirebaseAuthControllerProps {
|
|
30
30
|
loading?: boolean;
|
|
31
31
|
firebaseApp?: FirebaseApp;
|
|
32
32
|
signInOptions?: Array<FirebaseSignInProvider | FirebaseSignInOption>;
|
|
33
33
|
onSignOut?: () => void;
|
|
34
|
-
defineRolesFor?: (user: User) => Promise<
|
|
34
|
+
defineRolesFor?: (user: User) => Promise<string[] | undefined> | string[] | undefined;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -53,13 +53,11 @@ export const useFirebaseAuthController = <USER extends FirebaseUserWrapper = any
|
|
|
53
53
|
const [authLoading, setAuthLoading] = useState(true);
|
|
54
54
|
const [loginSkipped, setLoginSkipped] = useState<boolean>(false);
|
|
55
55
|
const [confirmationResult, setConfirmationResult] = useState<undefined | ConfirmationResult>();
|
|
56
|
-
const [userRoles, _setUserRoles] = useState<
|
|
56
|
+
const [userRoles, _setUserRoles] = useState<string[] | undefined>();
|
|
57
57
|
const [extra, setExtra] = useState<any>();
|
|
58
58
|
|
|
59
|
-
const setUserRoles = useCallback((roles:
|
|
60
|
-
|
|
61
|
-
const newRoleIds = roles?.map(r => r.id);
|
|
62
|
-
if (!equal(currentRoleIds, newRoleIds)) {
|
|
59
|
+
const setUserRoles = useCallback((roles: string[] | undefined) => {
|
|
60
|
+
if (!equal(userRoles, roles)) {
|
|
63
61
|
_setUserRoles(roles);
|
|
64
62
|
}
|
|
65
63
|
}, [userRoles]);
|
|
@@ -299,7 +297,7 @@ export const useFirebaseAuthController = <USER extends FirebaseUserWrapper = any
|
|
|
299
297
|
const firebaseUserWrapper: FirebaseUserWrapper | null = loggedUser
|
|
300
298
|
? {
|
|
301
299
|
...loggedUser,
|
|
302
|
-
roles: userRoles
|
|
300
|
+
roles: userRoles,
|
|
303
301
|
firebaseUser: loggedUser
|
|
304
302
|
}
|
|
305
303
|
: null;
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
startAt
|
|
16
16
|
} from "@firebase/database";
|
|
17
17
|
import { useCallback } from "react";
|
|
18
|
-
import { DataDriver, DeleteEntityProps, Entity, FetchCollectionProps, FetchEntityProps, ListenCollectionProps, ListenEntityProps, SaveEntityProps } from "@rebasepro/types";
|
|
18
|
+
import { DataDriver, DeleteEntityProps, Entity, EntityCollection, FetchCollectionProps, FetchEntityProps, FilterValues, ListenCollectionProps, ListenEntityProps, SaveEntityProps } from "@rebasepro/types";
|
|
19
19
|
|
|
20
20
|
export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: FirebaseApp }): DataDriver {
|
|
21
21
|
|
|
@@ -167,14 +167,14 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
167
167
|
}, [firebaseApp]);
|
|
168
168
|
|
|
169
169
|
// Implementing additional methods required by DataDriver
|
|
170
|
-
const checkUniqueField = useCallback(async (slug: string, name: string, value:
|
|
170
|
+
const checkUniqueField = useCallback(async (slug: string, name: string, value: unknown, entityId?: string | number): Promise<boolean> => {
|
|
171
171
|
if (!firebaseApp) {
|
|
172
172
|
throw new Error("Firebase app not provided");
|
|
173
173
|
}
|
|
174
174
|
const database = getDatabase(firebaseApp);
|
|
175
175
|
|
|
176
176
|
// Simplified example; the Realtime Database does not support querying with "not equal" conditions
|
|
177
|
-
const dbRef = query(ref(database, slug), orderByChild(name), startAt(value), limitToFirst(1));
|
|
177
|
+
const dbRef = query(ref(database, slug), orderByChild(name), startAt(value as string | number | boolean | null), limitToFirst(1));
|
|
178
178
|
const snapshot = await get(dbRef);
|
|
179
179
|
|
|
180
180
|
if (!snapshot.exists()) {
|
|
@@ -194,7 +194,11 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
194
194
|
path,
|
|
195
195
|
filter,
|
|
196
196
|
sortBy
|
|
197
|
-
}:
|
|
197
|
+
}: {
|
|
198
|
+
path: string;
|
|
199
|
+
filter?: FilterValues<string>;
|
|
200
|
+
sortBy?: [string, "asc" | "desc"];
|
|
201
|
+
}): boolean => {
|
|
198
202
|
return false;
|
|
199
203
|
}, []);
|
|
200
204
|
|
|
@@ -217,7 +221,7 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
217
221
|
* Transform data from RTDB format back to CMS format
|
|
218
222
|
* This is used internally when fetching/listening to data
|
|
219
223
|
*/
|
|
220
|
-
function delegateToCMSModel(data:
|
|
224
|
+
function delegateToCMSModel(data: unknown): unknown {
|
|
221
225
|
if (data === null || data === undefined) return null;
|
|
222
226
|
|
|
223
227
|
if (Array.isArray(data)) {
|
|
@@ -225,9 +229,9 @@ function delegateToCMSModel(data: any): any {
|
|
|
225
229
|
}
|
|
226
230
|
|
|
227
231
|
if (typeof data === "object") {
|
|
228
|
-
const result: Record<string,
|
|
229
|
-
for (const key of Object.keys(data)) {
|
|
230
|
-
const childValue = delegateToCMSModel(data[key]);
|
|
232
|
+
const result: Record<string, unknown> = {};
|
|
233
|
+
for (const key of Object.keys(data as Record<string, unknown>)) {
|
|
234
|
+
const childValue = delegateToCMSModel((data as Record<string, unknown>)[key]);
|
|
231
235
|
if (childValue !== undefined)
|
|
232
236
|
result[key] = childValue;
|
|
233
237
|
}
|
|
@@ -241,20 +245,21 @@ function delegateToCMSModel(data: any): any {
|
|
|
241
245
|
* Transform data from CMS format to RTDB format
|
|
242
246
|
* This is used internally when saving data
|
|
243
247
|
*/
|
|
244
|
-
function cmsToRTDBModel(data:
|
|
248
|
+
function cmsToRTDBModel(data: unknown, database: Database): unknown {
|
|
245
249
|
if (data === undefined) {
|
|
246
250
|
return null;
|
|
247
251
|
} else if (data === null) {
|
|
248
252
|
return null;
|
|
249
253
|
} else if (Array.isArray(data)) {
|
|
250
254
|
return data.filter(v => v !== undefined).map(v => cmsToRTDBModel(v, database));
|
|
251
|
-
} else if (data.isEntityReference && data.isEntityReference()) {
|
|
252
|
-
|
|
255
|
+
} else if (typeof data === "object" && data !== null && "isEntityReference" in data && typeof (data as Record<string, unknown>).isEntityReference === "function" && (data as { isEntityReference: () => boolean }).isEntityReference()) {
|
|
256
|
+
const entityRef = data as unknown as { slug: string; id: string };
|
|
257
|
+
return ref(database, `${entityRef.slug}/${entityRef.id}`);
|
|
253
258
|
} else if (data instanceof Date) {
|
|
254
259
|
// For dates, convert to ISO string or timestamp.
|
|
255
260
|
return data.toISOString();
|
|
256
261
|
} else if (data && typeof data === "object") {
|
|
257
|
-
return Object.entries(data)
|
|
262
|
+
return Object.entries(data as Record<string, unknown>)
|
|
258
263
|
.map(([key, v]) => {
|
|
259
264
|
const rtdbModel = cmsToRTDBModel(v, database);
|
|
260
265
|
if (rtdbModel !== undefined)
|
|
@@ -132,8 +132,8 @@ export function useFirebaseStorageSource({
|
|
|
132
132
|
const response = await fetch(url);
|
|
133
133
|
const blob = await response.blob();
|
|
134
134
|
return new File([blob], path);
|
|
135
|
-
} catch (e:
|
|
136
|
-
if (e
|
|
135
|
+
} catch (e: unknown) {
|
|
136
|
+
if (typeof e === "object" && e !== null && "code" in e && (e as { code: string }).code === "storage/object-not-found") return null;
|
|
137
137
|
throw e;
|
|
138
138
|
}
|
|
139
139
|
},
|
|
@@ -170,8 +170,8 @@ export function useFirebaseStorageSource({
|
|
|
170
170
|
}
|
|
171
171
|
urlsCache[storagePathOrUrl] = result;
|
|
172
172
|
return result;
|
|
173
|
-
} catch (e:
|
|
174
|
-
if (e
|
|
173
|
+
} catch (e: unknown) {
|
|
174
|
+
if (typeof e === "object" && e !== null && "code" in e && (e as { code: string }).code === "storage/object-not-found") return {
|
|
175
175
|
url: null,
|
|
176
176
|
fileNotFound: true
|
|
177
177
|
};
|