@rebasepro/auth 0.2.1 → 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 +96 -74
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +96 -74
- package/dist/index.umd.js.map +1 -1
- package/dist/types.d.ts +10 -1
- package/package.json +35 -4
- package/src/api.ts +5 -1
- package/src/hooks/useBackendUserManagement.ts +121 -86
- package/src/hooks/useRebaseAuthController.ts +14 -10
- package/src/index.ts +1 -1
- package/src/types.ts +8 -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 {
|
|
@@ -228,7 +224,8 @@ function convertToUser(userInfo) {
|
|
|
228
224
|
photoURL: userInfo.photoURL || null,
|
|
229
225
|
providerId: "custom",
|
|
230
226
|
isAnonymous: false,
|
|
231
|
-
roles: userInfo.roles || []
|
|
227
|
+
roles: userInfo.roles || [],
|
|
228
|
+
metadata: userInfo.metadata
|
|
232
229
|
};
|
|
233
230
|
}
|
|
234
231
|
function saveAuthToStorage(tokens, user) {
|
|
@@ -275,12 +272,11 @@ function useRebaseAuthController(props = {}) {
|
|
|
275
272
|
const refreshPromiseRef = useRef(null);
|
|
276
273
|
const isMountedRef = useRef(true);
|
|
277
274
|
useEffect(() => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
setApiUrl(apiUrl);
|
|
275
|
+
const url = client?.baseUrl || apiUrl;
|
|
276
|
+
if (url) {
|
|
277
|
+
setApiUrl(url);
|
|
282
278
|
}
|
|
283
|
-
}, [client, apiUrl]);
|
|
279
|
+
}, [client, client?.baseUrl, apiUrl]);
|
|
284
280
|
const clearError = useCallback(() => {
|
|
285
281
|
setAuthProviderError(null);
|
|
286
282
|
}, []);
|
|
@@ -396,7 +392,7 @@ function useRebaseAuthController(props = {}) {
|
|
|
396
392
|
}, [initialLoading, refreshAccessToken$1, clearSessionAndSignOut]);
|
|
397
393
|
useEffect(() => {
|
|
398
394
|
if (client) {
|
|
399
|
-
client.setAuthTokenGetter(async () => {
|
|
395
|
+
client.setAuthTokenGetter?.(async () => {
|
|
400
396
|
try {
|
|
401
397
|
return await getAuthToken();
|
|
402
398
|
} catch {
|
|
@@ -672,6 +668,10 @@ function useRebaseAuthController(props = {}) {
|
|
|
672
668
|
}
|
|
673
669
|
} catch (meError) {
|
|
674
670
|
if (!isMountedRef.current) return;
|
|
671
|
+
if (meError instanceof AuthApiError && (meError.code === "NOT_FOUND" || meError.code === "UNAUTHORIZED")) {
|
|
672
|
+
clearSessionAndSignOut();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
675
|
userToSet = convertToUser(stored.user);
|
|
676
676
|
}
|
|
677
677
|
if (!isMountedRef.current) return;
|
|
@@ -771,10 +771,10 @@ function useRebaseAuthController(props = {}) {
|
|
|
771
771
|
emailPasswordLogin: true,
|
|
772
772
|
googleLogin: !!props.googleClientId,
|
|
773
773
|
registration: authConfig?.registrationEnabled ?? false,
|
|
774
|
-
passwordReset:
|
|
774
|
+
passwordReset: authConfig?.passwordReset ?? false,
|
|
775
775
|
sessionManagement: true,
|
|
776
776
|
profileUpdate: true,
|
|
777
|
-
emailVerification: false,
|
|
777
|
+
emailVerification: authConfig?.emailVerification ?? false,
|
|
778
778
|
enabledProviders: authConfig?.enabledProviders ?? []
|
|
779
779
|
}
|
|
780
780
|
};
|
|
@@ -795,18 +795,34 @@ function convertRole(apiRole) {
|
|
|
795
795
|
return {
|
|
796
796
|
id: apiRole.id,
|
|
797
797
|
name: apiRole.name,
|
|
798
|
-
isAdmin: apiRole.isAdmin ?? false
|
|
799
|
-
config: apiRole.config ?? void 0
|
|
798
|
+
isAdmin: apiRole.isAdmin ?? false
|
|
800
799
|
};
|
|
801
800
|
}
|
|
802
801
|
function useBackendUserManagement(config) {
|
|
803
802
|
const { client, apiUrl, getAuthToken, currentUser } = config;
|
|
804
|
-
const [
|
|
803
|
+
const [userCache, setUserCache] = useState(/* @__PURE__ */ new Map());
|
|
804
|
+
const [hasAdminUsers, setHasAdminUsers] = useState(false);
|
|
805
805
|
const [roles, setRoles] = useState([]);
|
|
806
|
-
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
|
+
});
|
|
807
813
|
const [usersError, setUsersError] = useState();
|
|
808
814
|
const [rolesError, setRolesError] = useState();
|
|
809
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
|
+
}, []);
|
|
810
826
|
const apiRequestRef = useRef(null);
|
|
811
827
|
const apiRequest = useCallback(async (endpoint, method = "GET", body, retryCount = 6, signal) => {
|
|
812
828
|
let lastError = null;
|
|
@@ -817,7 +833,7 @@ function useBackendUserManagement(config) {
|
|
|
817
833
|
throw error;
|
|
818
834
|
}
|
|
819
835
|
try {
|
|
820
|
-
const token = getAuthToken ? await getAuthToken() : client ? await client.resolveToken() : null;
|
|
836
|
+
const token = getAuthToken ? await getAuthToken() : client?.resolveToken ? await client.resolveToken() : null;
|
|
821
837
|
const baseUrl = apiUrl || (client?.baseUrl ? client.baseUrl : "");
|
|
822
838
|
const response = await fetch(`${baseUrl}/api/admin${endpoint}`, {
|
|
823
839
|
method,
|
|
@@ -887,26 +903,29 @@ function useBackendUserManagement(config) {
|
|
|
887
903
|
setRolesError(error instanceof Error ? error : new Error(String(error)));
|
|
888
904
|
}
|
|
889
905
|
}, [apiRequest]);
|
|
890
|
-
const
|
|
906
|
+
const checkAdminExists = useCallback(async (signal) => {
|
|
891
907
|
try {
|
|
892
|
-
const data = await apiRequest("/users", "GET", void 0, 6, signal);
|
|
893
|
-
const
|
|
894
|
-
|
|
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
|
+
}
|
|
895
914
|
setUsersError(void 0);
|
|
896
915
|
} catch (error) {
|
|
897
916
|
if (error instanceof Error && error.name === "AbortError") return;
|
|
898
|
-
console.error("Failed to
|
|
917
|
+
console.error("Failed to check admin users:", error);
|
|
899
918
|
setUsersError(error instanceof Error ? error : new Error(String(error)));
|
|
900
919
|
}
|
|
901
|
-
}, [apiRequest]);
|
|
920
|
+
}, [apiRequest, mergeIntoCache]);
|
|
902
921
|
useEffect(() => {
|
|
903
922
|
if (!currentUser) {
|
|
904
923
|
setLoading(false);
|
|
905
924
|
return;
|
|
906
925
|
}
|
|
907
|
-
const
|
|
908
|
-
const
|
|
909
|
-
if (!
|
|
926
|
+
const userRoles2 = currentUser.roles ?? [];
|
|
927
|
+
const isUserAdmin2 = userRoles2.some((r) => r === "admin" || r === "schema-admin");
|
|
928
|
+
if (!isUserAdmin2) {
|
|
910
929
|
setLoading(false);
|
|
911
930
|
return;
|
|
912
931
|
}
|
|
@@ -935,13 +954,16 @@ function useBackendUserManagement(config) {
|
|
|
935
954
|
}
|
|
936
955
|
if (!abortController.signal.aborted) {
|
|
937
956
|
try {
|
|
938
|
-
const data = await request("/users", "GET", void 0, 6, abortController.signal);
|
|
939
|
-
const
|
|
940
|
-
|
|
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
|
+
}
|
|
941
963
|
setUsersError(void 0);
|
|
942
964
|
} catch (error) {
|
|
943
965
|
if (error instanceof Error && error.name === "AbortError") return;
|
|
944
|
-
console.error("Failed to
|
|
966
|
+
console.error("Failed to check admin users:", error);
|
|
945
967
|
setUsersError(error instanceof Error ? error : new Error(String(error)));
|
|
946
968
|
}
|
|
947
969
|
}
|
|
@@ -965,34 +987,24 @@ function useBackendUserManagement(config) {
|
|
|
965
987
|
if (options.roleId) params.set("role", options.roleId);
|
|
966
988
|
const qs = params.toString();
|
|
967
989
|
const data = await apiRequest("/users" + (qs ? "?" + qs : ""), "GET");
|
|
990
|
+
const converted = data.users.map((u) => convertUser(u));
|
|
991
|
+
mergeIntoCache(converted);
|
|
968
992
|
return {
|
|
969
|
-
users:
|
|
993
|
+
users: converted,
|
|
970
994
|
total: data.total
|
|
971
995
|
};
|
|
972
|
-
}, [apiRequest]);
|
|
996
|
+
}, [apiRequest, mergeIntoCache]);
|
|
973
997
|
const saveUser = useCallback(async (user) => {
|
|
974
998
|
const roleIds = user.roles ?? [];
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
return updated;
|
|
985
|
-
} else {
|
|
986
|
-
const data = await apiRequest("/users", "POST", {
|
|
987
|
-
email: user.email,
|
|
988
|
-
displayName: user.displayName,
|
|
989
|
-
roles: roleIds
|
|
990
|
-
});
|
|
991
|
-
const created = convertUser(data.user);
|
|
992
|
-
setUsers((prev) => [...prev, created]);
|
|
993
|
-
return created;
|
|
994
|
-
}
|
|
995
|
-
}, [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]);
|
|
996
1008
|
const createUser = useCallback(async (user) => {
|
|
997
1009
|
const roleIds = user.roles ?? [];
|
|
998
1010
|
const data = await apiRequest("/users", "POST", {
|
|
@@ -1001,34 +1013,37 @@ function useBackendUserManagement(config) {
|
|
|
1001
1013
|
roles: roleIds
|
|
1002
1014
|
});
|
|
1003
1015
|
const created = convertUser(data.user);
|
|
1004
|
-
|
|
1016
|
+
mergeIntoCache([created]);
|
|
1005
1017
|
return {
|
|
1006
1018
|
user: created,
|
|
1007
1019
|
invitationSent: data.invitationSent ?? false,
|
|
1008
1020
|
temporaryPassword: data.temporaryPassword
|
|
1009
1021
|
};
|
|
1010
|
-
}, [apiRequest,
|
|
1022
|
+
}, [apiRequest, mergeIntoCache]);
|
|
1011
1023
|
const resetPassword2 = useCallback(async (user) => {
|
|
1012
1024
|
const data = await apiRequest(`/users/${user.uid}/reset-password`, "POST");
|
|
1013
1025
|
const updatedUser = convertUser(data.user);
|
|
1014
|
-
|
|
1026
|
+
mergeIntoCache([updatedUser]);
|
|
1015
1027
|
return {
|
|
1016
1028
|
user: updatedUser,
|
|
1017
1029
|
invitationSent: data.invitationSent ?? false,
|
|
1018
1030
|
temporaryPassword: data.temporaryPassword
|
|
1019
1031
|
};
|
|
1020
|
-
}, [apiRequest]);
|
|
1032
|
+
}, [apiRequest, mergeIntoCache]);
|
|
1021
1033
|
const deleteUser = useCallback(async (user) => {
|
|
1022
1034
|
await apiRequest(`/users/${user.uid}`, "DELETE");
|
|
1023
|
-
|
|
1035
|
+
setUserCache((prev) => {
|
|
1036
|
+
const next = new Map(prev);
|
|
1037
|
+
next.delete(user.uid);
|
|
1038
|
+
return next;
|
|
1039
|
+
});
|
|
1024
1040
|
}, [apiRequest]);
|
|
1025
1041
|
const saveRole = useCallback(async (role) => {
|
|
1026
1042
|
const existingRole = roles.find((r) => r.id === role.id);
|
|
1027
1043
|
if (existingRole) {
|
|
1028
1044
|
const data = await apiRequest(`/roles/${role.id}`, "PUT", {
|
|
1029
1045
|
name: role.name,
|
|
1030
|
-
isAdmin: role.isAdmin
|
|
1031
|
-
config: role.config
|
|
1046
|
+
isAdmin: role.isAdmin
|
|
1032
1047
|
});
|
|
1033
1048
|
const updated = convertRole(data.role);
|
|
1034
1049
|
setRoles((prev) => prev.map((r) => r.id === updated.id ? updated : r));
|
|
@@ -1036,8 +1051,7 @@ function useBackendUserManagement(config) {
|
|
|
1036
1051
|
const data = await apiRequest("/roles", "POST", {
|
|
1037
1052
|
id: role.id,
|
|
1038
1053
|
name: role.name,
|
|
1039
|
-
isAdmin: role.isAdmin ?? false
|
|
1040
|
-
config: role.config
|
|
1054
|
+
isAdmin: role.isAdmin ?? false
|
|
1041
1055
|
});
|
|
1042
1056
|
const created = convertRole(data.role);
|
|
1043
1057
|
setRoles((prev) => [...prev, created]);
|
|
@@ -1048,14 +1062,22 @@ function useBackendUserManagement(config) {
|
|
|
1048
1062
|
setRoles((prev) => prev.filter((r) => r.id !== role.id));
|
|
1049
1063
|
}, [apiRequest]);
|
|
1050
1064
|
const getUser = useCallback((uid) => {
|
|
1051
|
-
return
|
|
1052
|
-
}, [
|
|
1065
|
+
return userCache.get(uid) ?? null;
|
|
1066
|
+
}, [userCache]);
|
|
1053
1067
|
const defineRolesFor = useCallback(async (user) => {
|
|
1054
|
-
|
|
1055
|
-
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
|
+
}
|
|
1056
1078
|
const userRoleIds = existingUser.roles ?? [];
|
|
1057
1079
|
return roles.filter((r) => userRoleIds.includes(r.id));
|
|
1058
|
-
}, [
|
|
1080
|
+
}, [userCache, roles, apiRequest, mergeIntoCache]);
|
|
1059
1081
|
const isAdmin = currentUser?.roles?.includes("admin") ?? false;
|
|
1060
1082
|
const bootstrapAdmin = useCallback(async () => {
|
|
1061
1083
|
try {
|
|
@@ -1063,15 +1085,17 @@ function useBackendUserManagement(config) {
|
|
|
1063
1085
|
const data = await apiRequest("/roles");
|
|
1064
1086
|
const loadedRoles = data.roles.map(convertRole);
|
|
1065
1087
|
setRoles(loadedRoles);
|
|
1066
|
-
await
|
|
1088
|
+
await checkAdminExists();
|
|
1067
1089
|
} catch (error) {
|
|
1068
1090
|
console.error("Failed to bootstrap admin:", error);
|
|
1069
1091
|
throw error;
|
|
1070
1092
|
}
|
|
1071
|
-
}, [apiRequest,
|
|
1093
|
+
}, [apiRequest, checkAdminExists]);
|
|
1094
|
+
const users = Array.from(userCache.values());
|
|
1072
1095
|
return {
|
|
1073
|
-
loading,
|
|
1096
|
+
loading: effectiveLoading,
|
|
1074
1097
|
users,
|
|
1098
|
+
hasAdminUsers,
|
|
1075
1099
|
saveUser,
|
|
1076
1100
|
createUser,
|
|
1077
1101
|
resetPassword: resetPassword2,
|
|
@@ -1081,7 +1105,6 @@ function useBackendUserManagement(config) {
|
|
|
1081
1105
|
deleteRole,
|
|
1082
1106
|
isAdmin,
|
|
1083
1107
|
allowDefaultRolesCreation: isAdmin,
|
|
1084
|
-
includeCollectionConfigPermissions: true,
|
|
1085
1108
|
defineRolesFor,
|
|
1086
1109
|
getUser,
|
|
1087
1110
|
searchUsers,
|
|
@@ -1092,7 +1115,6 @@ function useBackendUserManagement(config) {
|
|
|
1092
1115
|
}
|
|
1093
1116
|
export {
|
|
1094
1117
|
AuthApiError,
|
|
1095
|
-
clearAuthConfigCache,
|
|
1096
1118
|
fetchAuthConfig,
|
|
1097
1119
|
getApiUrl,
|
|
1098
1120
|
setApiUrl,
|