@rebasepro/auth 0.2.3 → 0.2.4
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/api.d.ts +5 -1
- package/dist/hooks/useBackendUserManagement.d.ts +5 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +94 -73
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +94 -73
- package/dist/index.umd.js.map +1 -1
- package/dist/types.d.ts +9 -1
- package/package.json +30 -4
- package/src/api.ts +5 -1
- package/src/hooks/useBackendUserManagement.ts +121 -86
- package/src/hooks/useRebaseAuthController.ts +12 -9
- package/src/index.ts +1 -1
- package/src/types.ts +7 -1
package/dist/api.d.ts
CHANGED
|
@@ -132,7 +132,11 @@ export interface AuthConfigResponse {
|
|
|
132
132
|
/** Whether new user registration is enabled */
|
|
133
133
|
registrationEnabled: boolean;
|
|
134
134
|
/** Whether email service is configured */
|
|
135
|
-
emailServiceEnabled
|
|
135
|
+
emailServiceEnabled?: boolean;
|
|
136
|
+
/** Whether password reset is supported */
|
|
137
|
+
passwordReset?: boolean;
|
|
138
|
+
/** Whether email verification is supported */
|
|
139
|
+
emailVerification?: boolean;
|
|
136
140
|
/** Complete list of enabled OAuth provider IDs (e.g. ["google", "github", "discord"]) */
|
|
137
141
|
enabledProviders: string[];
|
|
138
142
|
}
|
|
@@ -5,6 +5,7 @@ import { Role, User } from "@rebasepro/types";
|
|
|
5
5
|
*/
|
|
6
6
|
export interface UserManagement<USER extends User = User> {
|
|
7
7
|
loading: boolean;
|
|
8
|
+
hasAdminUsers?: boolean;
|
|
8
9
|
users: USER[];
|
|
9
10
|
saveUser: (user: USER) => Promise<USER>;
|
|
10
11
|
createUser?: (user: USER) => Promise<{
|
|
@@ -23,7 +24,6 @@ export interface UserManagement<USER extends User = User> {
|
|
|
23
24
|
deleteRole: (role: Role) => Promise<void>;
|
|
24
25
|
isAdmin?: boolean;
|
|
25
26
|
allowDefaultRolesCreation?: boolean;
|
|
26
|
-
includeCollectionConfigPermissions?: boolean;
|
|
27
27
|
defineRolesFor: (user: User) => Promise<Role[] | undefined> | Role[] | undefined;
|
|
28
28
|
getUser: (uid: string) => User | null;
|
|
29
29
|
/**
|
|
@@ -50,7 +50,10 @@ export interface BackendUserManagementConfig {
|
|
|
50
50
|
/**
|
|
51
51
|
* The Rebase Client instance
|
|
52
52
|
*/
|
|
53
|
-
client?:
|
|
53
|
+
client?: {
|
|
54
|
+
baseUrl?: string;
|
|
55
|
+
resolveToken?: () => Promise<string | null>;
|
|
56
|
+
};
|
|
54
57
|
/**
|
|
55
58
|
* Base API URL for the backend (optional, extracted from client if not provided)
|
|
56
59
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -10,5 +10,5 @@ export type { RebaseAuthController, RebaseAuthControllerProps, AuthTokens, UserI
|
|
|
10
10
|
export { useRebaseAuthController } from "./hooks/useRebaseAuthController";
|
|
11
11
|
export { useBackendUserManagement } from "./hooks/useBackendUserManagement";
|
|
12
12
|
export type { BackendUserManagementConfig, UserManagement } from "./hooks/useBackendUserManagement";
|
|
13
|
-
export { setApiUrl, getApiUrl, fetchAuthConfig,
|
|
13
|
+
export { setApiUrl, getApiUrl, fetchAuthConfig, AuthApiError } from "./api";
|
|
14
14
|
export type { AuthConfigResponse } from "./api";
|
package/dist/index.es.js
CHANGED
|
@@ -214,11 +214,7 @@ async function fetchAuthConfig() {
|
|
|
214
214
|
authConfigInflight = null;
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
-
|
|
218
|
-
authConfigCached = null;
|
|
219
|
-
authConfigInflight = null;
|
|
220
|
-
}
|
|
221
|
-
const STORAGE_KEY = "rebase_auth";
|
|
217
|
+
const STORAGE_KEY = "rebase_react_auth";
|
|
222
218
|
const TOKEN_REFRESH_BUFFER_MS = 2 * 60 * 1e3;
|
|
223
219
|
function convertToUser(userInfo) {
|
|
224
220
|
return {
|
|
@@ -276,12 +272,11 @@ function useRebaseAuthController(props = {}) {
|
|
|
276
272
|
const refreshPromiseRef = useRef(null);
|
|
277
273
|
const isMountedRef = useRef(true);
|
|
278
274
|
useEffect(() => {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
setApiUrl(apiUrl);
|
|
275
|
+
const url = client?.baseUrl || apiUrl;
|
|
276
|
+
if (url) {
|
|
277
|
+
setApiUrl(url);
|
|
283
278
|
}
|
|
284
|
-
}, [client, apiUrl]);
|
|
279
|
+
}, [client, client?.baseUrl, apiUrl]);
|
|
285
280
|
const clearError = useCallback(() => {
|
|
286
281
|
setAuthProviderError(null);
|
|
287
282
|
}, []);
|
|
@@ -397,7 +392,7 @@ function useRebaseAuthController(props = {}) {
|
|
|
397
392
|
}, [initialLoading, refreshAccessToken$1, clearSessionAndSignOut]);
|
|
398
393
|
useEffect(() => {
|
|
399
394
|
if (client) {
|
|
400
|
-
client.setAuthTokenGetter(async () => {
|
|
395
|
+
client.setAuthTokenGetter?.(async () => {
|
|
401
396
|
try {
|
|
402
397
|
return await getAuthToken();
|
|
403
398
|
} catch {
|
|
@@ -673,6 +668,10 @@ function useRebaseAuthController(props = {}) {
|
|
|
673
668
|
}
|
|
674
669
|
} catch (meError) {
|
|
675
670
|
if (!isMountedRef.current) return;
|
|
671
|
+
if (meError instanceof AuthApiError && (meError.code === "NOT_FOUND" || meError.code === "UNAUTHORIZED")) {
|
|
672
|
+
clearSessionAndSignOut();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
676
675
|
userToSet = convertToUser(stored.user);
|
|
677
676
|
}
|
|
678
677
|
if (!isMountedRef.current) return;
|
|
@@ -772,10 +771,10 @@ function useRebaseAuthController(props = {}) {
|
|
|
772
771
|
emailPasswordLogin: true,
|
|
773
772
|
googleLogin: !!props.googleClientId,
|
|
774
773
|
registration: authConfig?.registrationEnabled ?? false,
|
|
775
|
-
passwordReset:
|
|
774
|
+
passwordReset: authConfig?.passwordReset ?? false,
|
|
776
775
|
sessionManagement: true,
|
|
777
776
|
profileUpdate: true,
|
|
778
|
-
emailVerification: false,
|
|
777
|
+
emailVerification: authConfig?.emailVerification ?? false,
|
|
779
778
|
enabledProviders: authConfig?.enabledProviders ?? []
|
|
780
779
|
}
|
|
781
780
|
};
|
|
@@ -796,18 +795,34 @@ function convertRole(apiRole) {
|
|
|
796
795
|
return {
|
|
797
796
|
id: apiRole.id,
|
|
798
797
|
name: apiRole.name,
|
|
799
|
-
isAdmin: apiRole.isAdmin ?? false
|
|
800
|
-
config: apiRole.config ?? void 0
|
|
798
|
+
isAdmin: apiRole.isAdmin ?? false
|
|
801
799
|
};
|
|
802
800
|
}
|
|
803
801
|
function useBackendUserManagement(config) {
|
|
804
802
|
const { client, apiUrl, getAuthToken, currentUser } = config;
|
|
805
|
-
const [
|
|
803
|
+
const [userCache, setUserCache] = useState(/* @__PURE__ */ new Map());
|
|
804
|
+
const [hasAdminUsers, setHasAdminUsers] = useState(false);
|
|
806
805
|
const [roles, setRoles] = useState([]);
|
|
807
|
-
const
|
|
806
|
+
const userRoles = currentUser?.roles ?? [];
|
|
807
|
+
const isUserAdmin = userRoles.some((r) => r === "admin" || r === "schema-admin");
|
|
808
|
+
const [loading, setLoading] = useState(() => {
|
|
809
|
+
if (!currentUser) return false;
|
|
810
|
+
if (!isUserAdmin) return false;
|
|
811
|
+
return true;
|
|
812
|
+
});
|
|
808
813
|
const [usersError, setUsersError] = useState();
|
|
809
814
|
const [rolesError, setRolesError] = useState();
|
|
810
815
|
const lastLoadedUidRef = useRef(null);
|
|
816
|
+
const effectiveLoading = loading || !!(currentUser && isUserAdmin && lastLoadedUidRef.current !== currentUser.uid);
|
|
817
|
+
const mergeIntoCache = useCallback((incoming) => {
|
|
818
|
+
setUserCache((prev) => {
|
|
819
|
+
const next = new Map(prev);
|
|
820
|
+
for (const u of incoming) {
|
|
821
|
+
next.set(u.uid, u);
|
|
822
|
+
}
|
|
823
|
+
return next;
|
|
824
|
+
});
|
|
825
|
+
}, []);
|
|
811
826
|
const apiRequestRef = useRef(null);
|
|
812
827
|
const apiRequest = useCallback(async (endpoint, method = "GET", body, retryCount = 6, signal) => {
|
|
813
828
|
let lastError = null;
|
|
@@ -818,7 +833,7 @@ function useBackendUserManagement(config) {
|
|
|
818
833
|
throw error;
|
|
819
834
|
}
|
|
820
835
|
try {
|
|
821
|
-
const token = getAuthToken ? await getAuthToken() : client ? await client.resolveToken() : null;
|
|
836
|
+
const token = getAuthToken ? await getAuthToken() : client?.resolveToken ? await client.resolveToken() : null;
|
|
822
837
|
const baseUrl = apiUrl || (client?.baseUrl ? client.baseUrl : "");
|
|
823
838
|
const response = await fetch(`${baseUrl}/api/admin${endpoint}`, {
|
|
824
839
|
method,
|
|
@@ -888,26 +903,29 @@ function useBackendUserManagement(config) {
|
|
|
888
903
|
setRolesError(error instanceof Error ? error : new Error(String(error)));
|
|
889
904
|
}
|
|
890
905
|
}, [apiRequest]);
|
|
891
|
-
const
|
|
906
|
+
const checkAdminExists = useCallback(async (signal) => {
|
|
892
907
|
try {
|
|
893
|
-
const data = await apiRequest("/users", "GET", void 0, 6, signal);
|
|
894
|
-
const
|
|
895
|
-
|
|
908
|
+
const data = await apiRequest("/users?role=admin&limit=1", "GET", void 0, 6, signal);
|
|
909
|
+
const adminUsers = data.users.map((u) => convertUser(u));
|
|
910
|
+
setHasAdminUsers(adminUsers.length > 0);
|
|
911
|
+
if (adminUsers.length > 0) {
|
|
912
|
+
mergeIntoCache(adminUsers);
|
|
913
|
+
}
|
|
896
914
|
setUsersError(void 0);
|
|
897
915
|
} catch (error) {
|
|
898
916
|
if (error instanceof Error && error.name === "AbortError") return;
|
|
899
|
-
console.error("Failed to
|
|
917
|
+
console.error("Failed to check admin users:", error);
|
|
900
918
|
setUsersError(error instanceof Error ? error : new Error(String(error)));
|
|
901
919
|
}
|
|
902
|
-
}, [apiRequest]);
|
|
920
|
+
}, [apiRequest, mergeIntoCache]);
|
|
903
921
|
useEffect(() => {
|
|
904
922
|
if (!currentUser) {
|
|
905
923
|
setLoading(false);
|
|
906
924
|
return;
|
|
907
925
|
}
|
|
908
|
-
const
|
|
909
|
-
const
|
|
910
|
-
if (!
|
|
926
|
+
const userRoles2 = currentUser.roles ?? [];
|
|
927
|
+
const isUserAdmin2 = userRoles2.some((r) => r === "admin" || r === "schema-admin");
|
|
928
|
+
if (!isUserAdmin2) {
|
|
911
929
|
setLoading(false);
|
|
912
930
|
return;
|
|
913
931
|
}
|
|
@@ -936,13 +954,16 @@ function useBackendUserManagement(config) {
|
|
|
936
954
|
}
|
|
937
955
|
if (!abortController.signal.aborted) {
|
|
938
956
|
try {
|
|
939
|
-
const data = await request("/users", "GET", void 0, 6, abortController.signal);
|
|
940
|
-
const
|
|
941
|
-
|
|
957
|
+
const data = await request("/users?role=admin&limit=1", "GET", void 0, 6, abortController.signal);
|
|
958
|
+
const adminUsers = data.users.map((u) => convertUser(u));
|
|
959
|
+
setHasAdminUsers(adminUsers.length > 0);
|
|
960
|
+
if (adminUsers.length > 0) {
|
|
961
|
+
mergeIntoCache(adminUsers);
|
|
962
|
+
}
|
|
942
963
|
setUsersError(void 0);
|
|
943
964
|
} catch (error) {
|
|
944
965
|
if (error instanceof Error && error.name === "AbortError") return;
|
|
945
|
-
console.error("Failed to
|
|
966
|
+
console.error("Failed to check admin users:", error);
|
|
946
967
|
setUsersError(error instanceof Error ? error : new Error(String(error)));
|
|
947
968
|
}
|
|
948
969
|
}
|
|
@@ -966,34 +987,24 @@ function useBackendUserManagement(config) {
|
|
|
966
987
|
if (options.roleId) params.set("role", options.roleId);
|
|
967
988
|
const qs = params.toString();
|
|
968
989
|
const data = await apiRequest("/users" + (qs ? "?" + qs : ""), "GET");
|
|
990
|
+
const converted = data.users.map((u) => convertUser(u));
|
|
991
|
+
mergeIntoCache(converted);
|
|
969
992
|
return {
|
|
970
|
-
users:
|
|
993
|
+
users: converted,
|
|
971
994
|
total: data.total
|
|
972
995
|
};
|
|
973
|
-
}, [apiRequest]);
|
|
996
|
+
}, [apiRequest, mergeIntoCache]);
|
|
974
997
|
const saveUser = useCallback(async (user) => {
|
|
975
998
|
const roleIds = user.roles ?? [];
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
return updated;
|
|
986
|
-
} else {
|
|
987
|
-
const data = await apiRequest("/users", "POST", {
|
|
988
|
-
email: user.email,
|
|
989
|
-
displayName: user.displayName,
|
|
990
|
-
roles: roleIds
|
|
991
|
-
});
|
|
992
|
-
const created = convertUser(data.user);
|
|
993
|
-
setUsers((prev) => [...prev, created]);
|
|
994
|
-
return created;
|
|
995
|
-
}
|
|
996
|
-
}, [apiRequest, users, roles]);
|
|
999
|
+
const data = await apiRequest(`/users/${user.uid}`, "PUT", {
|
|
1000
|
+
email: user.email,
|
|
1001
|
+
displayName: user.displayName,
|
|
1002
|
+
roles: roleIds
|
|
1003
|
+
});
|
|
1004
|
+
const updated = convertUser(data.user);
|
|
1005
|
+
mergeIntoCache([updated]);
|
|
1006
|
+
return updated;
|
|
1007
|
+
}, [apiRequest, mergeIntoCache]);
|
|
997
1008
|
const createUser = useCallback(async (user) => {
|
|
998
1009
|
const roleIds = user.roles ?? [];
|
|
999
1010
|
const data = await apiRequest("/users", "POST", {
|
|
@@ -1002,34 +1013,37 @@ function useBackendUserManagement(config) {
|
|
|
1002
1013
|
roles: roleIds
|
|
1003
1014
|
});
|
|
1004
1015
|
const created = convertUser(data.user);
|
|
1005
|
-
|
|
1016
|
+
mergeIntoCache([created]);
|
|
1006
1017
|
return {
|
|
1007
1018
|
user: created,
|
|
1008
1019
|
invitationSent: data.invitationSent ?? false,
|
|
1009
1020
|
temporaryPassword: data.temporaryPassword
|
|
1010
1021
|
};
|
|
1011
|
-
}, [apiRequest,
|
|
1022
|
+
}, [apiRequest, mergeIntoCache]);
|
|
1012
1023
|
const resetPassword2 = useCallback(async (user) => {
|
|
1013
1024
|
const data = await apiRequest(`/users/${user.uid}/reset-password`, "POST");
|
|
1014
1025
|
const updatedUser = convertUser(data.user);
|
|
1015
|
-
|
|
1026
|
+
mergeIntoCache([updatedUser]);
|
|
1016
1027
|
return {
|
|
1017
1028
|
user: updatedUser,
|
|
1018
1029
|
invitationSent: data.invitationSent ?? false,
|
|
1019
1030
|
temporaryPassword: data.temporaryPassword
|
|
1020
1031
|
};
|
|
1021
|
-
}, [apiRequest]);
|
|
1032
|
+
}, [apiRequest, mergeIntoCache]);
|
|
1022
1033
|
const deleteUser = useCallback(async (user) => {
|
|
1023
1034
|
await apiRequest(`/users/${user.uid}`, "DELETE");
|
|
1024
|
-
|
|
1035
|
+
setUserCache((prev) => {
|
|
1036
|
+
const next = new Map(prev);
|
|
1037
|
+
next.delete(user.uid);
|
|
1038
|
+
return next;
|
|
1039
|
+
});
|
|
1025
1040
|
}, [apiRequest]);
|
|
1026
1041
|
const saveRole = useCallback(async (role) => {
|
|
1027
1042
|
const existingRole = roles.find((r) => r.id === role.id);
|
|
1028
1043
|
if (existingRole) {
|
|
1029
1044
|
const data = await apiRequest(`/roles/${role.id}`, "PUT", {
|
|
1030
1045
|
name: role.name,
|
|
1031
|
-
isAdmin: role.isAdmin
|
|
1032
|
-
config: role.config
|
|
1046
|
+
isAdmin: role.isAdmin
|
|
1033
1047
|
});
|
|
1034
1048
|
const updated = convertRole(data.role);
|
|
1035
1049
|
setRoles((prev) => prev.map((r) => r.id === updated.id ? updated : r));
|
|
@@ -1037,8 +1051,7 @@ function useBackendUserManagement(config) {
|
|
|
1037
1051
|
const data = await apiRequest("/roles", "POST", {
|
|
1038
1052
|
id: role.id,
|
|
1039
1053
|
name: role.name,
|
|
1040
|
-
isAdmin: role.isAdmin ?? false
|
|
1041
|
-
config: role.config
|
|
1054
|
+
isAdmin: role.isAdmin ?? false
|
|
1042
1055
|
});
|
|
1043
1056
|
const created = convertRole(data.role);
|
|
1044
1057
|
setRoles((prev) => [...prev, created]);
|
|
@@ -1049,14 +1062,22 @@ function useBackendUserManagement(config) {
|
|
|
1049
1062
|
setRoles((prev) => prev.filter((r) => r.id !== role.id));
|
|
1050
1063
|
}, [apiRequest]);
|
|
1051
1064
|
const getUser = useCallback((uid) => {
|
|
1052
|
-
return
|
|
1053
|
-
}, [
|
|
1065
|
+
return userCache.get(uid) ?? null;
|
|
1066
|
+
}, [userCache]);
|
|
1054
1067
|
const defineRolesFor = useCallback(async (user) => {
|
|
1055
|
-
|
|
1056
|
-
if (!existingUser)
|
|
1068
|
+
let existingUser = userCache.get(user.uid) ?? Array.from(userCache.values()).find((u) => u.email === user.email);
|
|
1069
|
+
if (!existingUser) {
|
|
1070
|
+
try {
|
|
1071
|
+
const data = await apiRequest(`/users/${user.uid}`, "GET");
|
|
1072
|
+
existingUser = convertUser(data.user);
|
|
1073
|
+
mergeIntoCache([existingUser]);
|
|
1074
|
+
} catch {
|
|
1075
|
+
return void 0;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1057
1078
|
const userRoleIds = existingUser.roles ?? [];
|
|
1058
1079
|
return roles.filter((r) => userRoleIds.includes(r.id));
|
|
1059
|
-
}, [
|
|
1080
|
+
}, [userCache, roles, apiRequest, mergeIntoCache]);
|
|
1060
1081
|
const isAdmin = currentUser?.roles?.includes("admin") ?? false;
|
|
1061
1082
|
const bootstrapAdmin = useCallback(async () => {
|
|
1062
1083
|
try {
|
|
@@ -1064,15 +1085,17 @@ function useBackendUserManagement(config) {
|
|
|
1064
1085
|
const data = await apiRequest("/roles");
|
|
1065
1086
|
const loadedRoles = data.roles.map(convertRole);
|
|
1066
1087
|
setRoles(loadedRoles);
|
|
1067
|
-
await
|
|
1088
|
+
await checkAdminExists();
|
|
1068
1089
|
} catch (error) {
|
|
1069
1090
|
console.error("Failed to bootstrap admin:", error);
|
|
1070
1091
|
throw error;
|
|
1071
1092
|
}
|
|
1072
|
-
}, [apiRequest,
|
|
1093
|
+
}, [apiRequest, checkAdminExists]);
|
|
1094
|
+
const users = Array.from(userCache.values());
|
|
1073
1095
|
return {
|
|
1074
|
-
loading,
|
|
1096
|
+
loading: effectiveLoading,
|
|
1075
1097
|
users,
|
|
1098
|
+
hasAdminUsers,
|
|
1076
1099
|
saveUser,
|
|
1077
1100
|
createUser,
|
|
1078
1101
|
resetPassword: resetPassword2,
|
|
@@ -1082,7 +1105,6 @@ function useBackendUserManagement(config) {
|
|
|
1082
1105
|
deleteRole,
|
|
1083
1106
|
isAdmin,
|
|
1084
1107
|
allowDefaultRolesCreation: isAdmin,
|
|
1085
|
-
includeCollectionConfigPermissions: true,
|
|
1086
1108
|
defineRolesFor,
|
|
1087
1109
|
getUser,
|
|
1088
1110
|
searchUsers,
|
|
@@ -1093,7 +1115,6 @@ function useBackendUserManagement(config) {
|
|
|
1093
1115
|
}
|
|
1094
1116
|
export {
|
|
1095
1117
|
AuthApiError,
|
|
1096
|
-
clearAuthConfigCache,
|
|
1097
1118
|
fetchAuthConfig,
|
|
1098
1119
|
getApiUrl,
|
|
1099
1120
|
setApiUrl,
|