@rebasepro/auth 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/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: boolean;
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
  }
@@ -1,10 +1,11 @@
1
- import { Role, User } from "@rebasepro/types";
1
+ import type { User } from "@rebasepro/types";
2
2
  /**
3
3
  * UserManagement interface - compatible with @rebasepro/user_management
4
4
  * Defined inline to avoid dependency on that package
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<{
@@ -18,13 +19,9 @@ export interface UserManagement<USER extends User = User> {
18
19
  temporaryPassword?: string;
19
20
  }>;
20
21
  deleteUser: (user: USER) => Promise<void>;
21
- roles: Role[];
22
- saveRole: (role: Role) => Promise<void>;
23
- deleteRole: (role: Role) => Promise<void>;
24
22
  isAdmin?: boolean;
25
23
  allowDefaultRolesCreation?: boolean;
26
- includeCollectionConfigPermissions?: boolean;
27
- defineRolesFor: (user: User) => Promise<Role[] | undefined> | Role[] | undefined;
24
+ defineRolesFor: (user: User) => Promise<string[] | undefined> | string[] | undefined;
28
25
  getUser: (uid: string) => User | null;
29
26
  /**
30
27
  * Search users with server-side pagination.
@@ -43,14 +40,16 @@ export interface UserManagement<USER extends User = User> {
43
40
  total: number;
44
41
  }>;
45
42
  usersError?: Error;
46
- rolesError?: Error;
47
43
  bootstrapAdmin?: () => Promise<void>;
48
44
  }
49
45
  export interface BackendUserManagementConfig {
50
46
  /**
51
47
  * The Rebase Client instance
52
48
  */
53
- client?: any;
49
+ client?: {
50
+ baseUrl?: string;
51
+ resolveToken?: () => Promise<string | null>;
52
+ };
54
53
  /**
55
54
  * Base API URL for the backend (optional, extracted from client if not provided)
56
55
  */
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, clearAuthConfigCache, AuthApiError } from "./api";
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
- function clearAuthConfigCache() {
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
- if (client) {
280
- setApiUrl(client.baseUrl);
281
- } else if (apiUrl) {
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 {
@@ -430,7 +425,7 @@ function useRebaseAuthController(props = {}) {
430
425
  if (defineRolesFor) {
431
426
  const customRoles = await defineRolesFor(convertedUser);
432
427
  if (customRoles) {
433
- convertedUser = { ...convertedUser, roles: customRoles.map((r) => r.id) };
428
+ convertedUser = { ...convertedUser, roles: customRoles };
434
429
  }
435
430
  }
436
431
  saveAuthToStorage(tokens, userInfo);
@@ -561,7 +556,7 @@ function useRebaseAuthController(props = {}) {
561
556
  if (customRoles) {
562
557
  convertedUser = {
563
558
  ...convertedUser,
564
- roles: customRoles.map((r) => r.id)
559
+ roles: customRoles
565
560
  };
566
561
  }
567
562
  }
@@ -635,7 +630,7 @@ function useRebaseAuthController(props = {}) {
635
630
  if (customRoles) {
636
631
  userToSet = {
637
632
  ...userToSet,
638
- roles: customRoles.map((r) => r.id)
633
+ roles: customRoles
639
634
  };
640
635
  }
641
636
  }
@@ -667,12 +662,16 @@ function useRebaseAuthController(props = {}) {
667
662
  if (customRoles) {
668
663
  userToSet = {
669
664
  ...userToSet,
670
- roles: customRoles.map((r) => r.id)
665
+ roles: customRoles
671
666
  };
672
667
  }
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: true,
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
  };
@@ -792,22 +791,29 @@ function convertUser(apiUser) {
792
791
  createdAt: apiUser.createdAt ? new Date(apiUser.createdAt) : null
793
792
  };
794
793
  }
795
- function convertRole(apiRole) {
796
- return {
797
- id: apiRole.id,
798
- name: apiRole.name,
799
- isAdmin: apiRole.isAdmin ?? false,
800
- config: apiRole.config ?? void 0
801
- };
802
- }
803
794
  function useBackendUserManagement(config) {
804
795
  const { client, apiUrl, getAuthToken, currentUser } = config;
805
- const [users, setUsers] = useState([]);
806
- const [roles, setRoles] = useState([]);
807
- const [loading, setLoading] = useState(true);
796
+ const [userCache, setUserCache] = useState(/* @__PURE__ */ new Map());
797
+ const [hasAdminUsers, setHasAdminUsers] = useState(false);
798
+ const userRoles = currentUser?.roles ?? [];
799
+ const isUserAdmin = userRoles.some((r) => r === "admin" || r === "schema-admin");
800
+ const [loading, setLoading] = useState(() => {
801
+ if (!currentUser) return false;
802
+ if (!isUserAdmin) return false;
803
+ return true;
804
+ });
808
805
  const [usersError, setUsersError] = useState();
809
- const [rolesError, setRolesError] = useState();
810
806
  const lastLoadedUidRef = useRef(null);
807
+ const effectiveLoading = loading || !!(currentUser && isUserAdmin && lastLoadedUidRef.current !== currentUser.uid);
808
+ const mergeIntoCache = useCallback((incoming) => {
809
+ setUserCache((prev) => {
810
+ const next = new Map(prev);
811
+ for (const u of incoming) {
812
+ next.set(u.uid, u);
813
+ }
814
+ return next;
815
+ });
816
+ }, []);
811
817
  const apiRequestRef = useRef(null);
812
818
  const apiRequest = useCallback(async (endpoint, method = "GET", body, retryCount = 6, signal) => {
813
819
  let lastError = null;
@@ -818,7 +824,7 @@ function useBackendUserManagement(config) {
818
824
  throw error;
819
825
  }
820
826
  try {
821
- const token = getAuthToken ? await getAuthToken() : client ? await client.resolveToken() : null;
827
+ const token = getAuthToken ? await getAuthToken() : client?.resolveToken ? await client.resolveToken() : null;
822
828
  const baseUrl = apiUrl || (client?.baseUrl ? client.baseUrl : "");
823
829
  const response = await fetch(`${baseUrl}/api/admin${endpoint}`, {
824
830
  method,
@@ -877,37 +883,29 @@ function useBackendUserManagement(config) {
877
883
  throw lastError;
878
884
  }, [apiUrl, getAuthToken]);
879
885
  apiRequestRef.current = apiRequest;
880
- useCallback(async (signal) => {
886
+ const checkAdminExists = useCallback(async (signal) => {
881
887
  try {
882
- const data = await apiRequest("/roles", "GET", void 0, 6, signal);
883
- setRoles(data.roles.map(convertRole));
884
- setRolesError(void 0);
885
- } catch (error) {
886
- if (error instanceof Error && error.name === "AbortError") return;
887
- console.error("Failed to load roles:", error);
888
- setRolesError(error instanceof Error ? error : new Error(String(error)));
889
- }
890
- }, [apiRequest]);
891
- const loadUsers = useCallback(async (signal) => {
892
- try {
893
- const data = await apiRequest("/users", "GET", void 0, 6, signal);
894
- const allUsers = data.users.map((u) => convertUser(u));
895
- setUsers(allUsers);
888
+ const data = await apiRequest("/users?role=admin&limit=1", "GET", void 0, 6, signal);
889
+ const adminUsers = data.users.map((u) => convertUser(u));
890
+ setHasAdminUsers(adminUsers.length > 0);
891
+ if (adminUsers.length > 0) {
892
+ mergeIntoCache(adminUsers);
893
+ }
896
894
  setUsersError(void 0);
897
895
  } catch (error) {
898
896
  if (error instanceof Error && error.name === "AbortError") return;
899
- console.error("Failed to load users:", error);
897
+ console.error("Failed to check admin users:", error);
900
898
  setUsersError(error instanceof Error ? error : new Error(String(error)));
901
899
  }
902
- }, [apiRequest]);
900
+ }, [apiRequest, mergeIntoCache]);
903
901
  useEffect(() => {
904
902
  if (!currentUser) {
905
903
  setLoading(false);
906
904
  return;
907
905
  }
908
- const userRoles = currentUser.roles ?? [];
909
- const isUserAdmin = userRoles.some((r) => r === "admin" || r === "schema-admin");
910
- if (!isUserAdmin) {
906
+ const userRoles2 = currentUser.roles ?? [];
907
+ const isUserAdmin2 = userRoles2.some((r) => r === "admin" || r === "schema-admin");
908
+ if (!isUserAdmin2) {
911
909
  setLoading(false);
912
910
  return;
913
911
  }
@@ -919,30 +917,18 @@ function useBackendUserManagement(config) {
919
917
  const load = async () => {
920
918
  setLoading(true);
921
919
  const request = apiRequestRef.current;
922
- try {
923
- const data = await request("/roles", "GET", void 0, 6, abortController.signal);
924
- setRoles(data.roles.map(convertRole));
925
- setRolesError(void 0);
926
- } catch (error) {
927
- if (error instanceof Error && error.name === "AbortError") return;
928
- console.error("Failed to load roles:", error);
929
- setRolesError(error instanceof Error ? error : new Error(String(error)));
930
- const status = error.status;
931
- if (status === 403 || status === 401) {
932
- setUsersError(error instanceof Error ? error : new Error(String(error)));
933
- setLoading(false);
934
- return;
935
- }
936
- }
937
920
  if (!abortController.signal.aborted) {
938
921
  try {
939
- const data = await request("/users", "GET", void 0, 6, abortController.signal);
940
- const allUsers = data.users.map((u) => convertUser(u));
941
- setUsers(allUsers);
922
+ const data = await request("/users?role=admin&limit=1", "GET", void 0, 6, abortController.signal);
923
+ const adminUsers = data.users.map((u) => convertUser(u));
924
+ setHasAdminUsers(adminUsers.length > 0);
925
+ if (adminUsers.length > 0) {
926
+ mergeIntoCache(adminUsers);
927
+ }
942
928
  setUsersError(void 0);
943
929
  } catch (error) {
944
930
  if (error instanceof Error && error.name === "AbortError") return;
945
- console.error("Failed to load users:", error);
931
+ console.error("Failed to check admin users:", error);
946
932
  setUsersError(error instanceof Error ? error : new Error(String(error)));
947
933
  }
948
934
  }
@@ -966,34 +952,24 @@ function useBackendUserManagement(config) {
966
952
  if (options.roleId) params.set("role", options.roleId);
967
953
  const qs = params.toString();
968
954
  const data = await apiRequest("/users" + (qs ? "?" + qs : ""), "GET");
955
+ const converted = data.users.map((u) => convertUser(u));
956
+ mergeIntoCache(converted);
969
957
  return {
970
- users: data.users.map((u) => convertUser(u)),
958
+ users: converted,
971
959
  total: data.total
972
960
  };
973
- }, [apiRequest]);
961
+ }, [apiRequest, mergeIntoCache]);
974
962
  const saveUser = useCallback(async (user) => {
975
963
  const roleIds = user.roles ?? [];
976
- const existingUser = users.find((u) => u.uid === user.uid);
977
- if (existingUser) {
978
- const data = await apiRequest(`/users/${user.uid}`, "PUT", {
979
- email: user.email,
980
- displayName: user.displayName,
981
- roles: roleIds
982
- });
983
- const updated = convertUser(data.user);
984
- setUsers((prev) => prev.map((u) => u.uid === updated.uid ? updated : u));
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]);
964
+ const data = await apiRequest(`/users/${user.uid}`, "PUT", {
965
+ email: user.email,
966
+ displayName: user.displayName,
967
+ roles: roleIds
968
+ });
969
+ const updated = convertUser(data.user);
970
+ mergeIntoCache([updated]);
971
+ return updated;
972
+ }, [apiRequest, mergeIntoCache]);
997
973
  const createUser = useCallback(async (user) => {
998
974
  const roleIds = user.roles ?? [];
999
975
  const data = await apiRequest("/users", "POST", {
@@ -1002,98 +978,78 @@ function useBackendUserManagement(config) {
1002
978
  roles: roleIds
1003
979
  });
1004
980
  const created = convertUser(data.user);
1005
- setUsers((prev) => [...prev, created]);
981
+ mergeIntoCache([created]);
1006
982
  return {
1007
983
  user: created,
1008
984
  invitationSent: data.invitationSent ?? false,
1009
985
  temporaryPassword: data.temporaryPassword
1010
986
  };
1011
- }, [apiRequest, roles]);
987
+ }, [apiRequest, mergeIntoCache]);
1012
988
  const resetPassword2 = useCallback(async (user) => {
1013
989
  const data = await apiRequest(`/users/${user.uid}/reset-password`, "POST");
1014
990
  const updatedUser = convertUser(data.user);
1015
- setUsers((prev) => prev.map((u) => u.uid === updatedUser.uid ? updatedUser : u));
991
+ mergeIntoCache([updatedUser]);
1016
992
  return {
1017
993
  user: updatedUser,
1018
994
  invitationSent: data.invitationSent ?? false,
1019
995
  temporaryPassword: data.temporaryPassword
1020
996
  };
1021
- }, [apiRequest]);
997
+ }, [apiRequest, mergeIntoCache]);
1022
998
  const deleteUser = useCallback(async (user) => {
1023
999
  await apiRequest(`/users/${user.uid}`, "DELETE");
1024
- setUsers((prev) => prev.filter((u) => u.uid !== user.uid));
1025
- }, [apiRequest]);
1026
- const saveRole = useCallback(async (role) => {
1027
- const existingRole = roles.find((r) => r.id === role.id);
1028
- if (existingRole) {
1029
- const data = await apiRequest(`/roles/${role.id}`, "PUT", {
1030
- name: role.name,
1031
- isAdmin: role.isAdmin,
1032
- config: role.config
1033
- });
1034
- const updated = convertRole(data.role);
1035
- setRoles((prev) => prev.map((r) => r.id === updated.id ? updated : r));
1036
- } else {
1037
- const data = await apiRequest("/roles", "POST", {
1038
- id: role.id,
1039
- name: role.name,
1040
- isAdmin: role.isAdmin ?? false,
1041
- config: role.config
1042
- });
1043
- const created = convertRole(data.role);
1044
- setRoles((prev) => [...prev, created]);
1045
- }
1046
- }, [apiRequest, roles]);
1047
- const deleteRole = useCallback(async (role) => {
1048
- await apiRequest(`/roles/${role.id}`, "DELETE");
1049
- setRoles((prev) => prev.filter((r) => r.id !== role.id));
1000
+ setUserCache((prev) => {
1001
+ const next = new Map(prev);
1002
+ next.delete(user.uid);
1003
+ return next;
1004
+ });
1050
1005
  }, [apiRequest]);
1051
1006
  const getUser = useCallback((uid) => {
1052
- return users.find((u) => u.uid === uid) ?? null;
1053
- }, [users]);
1007
+ return userCache.get(uid) ?? null;
1008
+ }, [userCache]);
1054
1009
  const defineRolesFor = useCallback(async (user) => {
1055
- const existingUser = users.find((u) => u.uid === user.uid || u.email === user.email);
1056
- if (!existingUser) return void 0;
1010
+ let existingUser = userCache.get(user.uid) ?? Array.from(userCache.values()).find((u) => u.email === user.email);
1011
+ if (!existingUser) {
1012
+ try {
1013
+ const data = await apiRequest(`/users/${user.uid}`, "GET");
1014
+ existingUser = convertUser(data.user);
1015
+ mergeIntoCache([existingUser]);
1016
+ } catch {
1017
+ return void 0;
1018
+ }
1019
+ }
1057
1020
  const userRoleIds = existingUser.roles ?? [];
1058
- return roles.filter((r) => userRoleIds.includes(r.id));
1059
- }, [users, roles]);
1021
+ return userRoleIds;
1022
+ }, [userCache, apiRequest, mergeIntoCache]);
1060
1023
  const isAdmin = currentUser?.roles?.includes("admin") ?? false;
1061
1024
  const bootstrapAdmin = useCallback(async () => {
1062
1025
  try {
1063
1026
  await apiRequest("/bootstrap", "POST");
1064
- const data = await apiRequest("/roles");
1065
- const loadedRoles = data.roles.map(convertRole);
1066
- setRoles(loadedRoles);
1067
- await loadUsers();
1027
+ await checkAdminExists();
1068
1028
  } catch (error) {
1069
1029
  console.error("Failed to bootstrap admin:", error);
1070
1030
  throw error;
1071
1031
  }
1072
- }, [apiRequest, loadUsers]);
1032
+ }, [apiRequest, checkAdminExists]);
1033
+ const users = Array.from(userCache.values());
1073
1034
  return {
1074
- loading,
1035
+ loading: effectiveLoading,
1075
1036
  users,
1037
+ hasAdminUsers,
1076
1038
  saveUser,
1077
1039
  createUser,
1078
1040
  resetPassword: resetPassword2,
1079
1041
  deleteUser,
1080
- roles,
1081
- saveRole,
1082
- deleteRole,
1083
1042
  isAdmin,
1084
1043
  allowDefaultRolesCreation: isAdmin,
1085
- includeCollectionConfigPermissions: true,
1086
1044
  defineRolesFor,
1087
1045
  getUser,
1088
1046
  searchUsers,
1089
1047
  usersError,
1090
- rolesError,
1091
1048
  bootstrapAdmin
1092
1049
  };
1093
1050
  }
1094
1051
  export {
1095
1052
  AuthApiError,
1096
- clearAuthConfigCache,
1097
1053
  fetchAuthConfig,
1098
1054
  getApiUrl,
1099
1055
  setApiUrl,