@rebasepro/auth 0.0.1-canary.94dff14 → 0.0.1-canary.a6becfb

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
@@ -20,9 +20,24 @@ export declare function register(email: string, password: string, displayName?:
20
20
  */
21
21
  export declare function login(email: string, password: string): Promise<AuthResponse>;
22
22
  /**
23
- * Login with Google ID token
23
+ * Google login payload one of the three supported flows.
24
24
  */
25
- export declare function googleLogin(idToken: string): Promise<AuthResponse>;
25
+ export type GoogleLoginPayload = {
26
+ idToken: string;
27
+ } | {
28
+ accessToken: string;
29
+ } | {
30
+ code: string;
31
+ redirectUri: string;
32
+ };
33
+ /**
34
+ * Login with Google.
35
+ *
36
+ * Overload 1 (legacy): `googleLogin("token", "idToken" | "accessToken")`
37
+ * Overload 2 (code flow): `googleLogin({ code, redirectUri })`
38
+ */
39
+ export declare function googleLogin(payload: GoogleLoginPayload): Promise<AuthResponse>;
40
+ export declare function googleLogin(token: string, tokenType?: "idToken" | "accessToken"): Promise<AuthResponse>;
26
41
  /**
27
42
  * Login with LinkedIn OAuth code
28
43
  */
@@ -126,7 +141,10 @@ export interface AuthConfigResponse {
126
141
  }
127
142
  /**
128
143
  * Fetch auth configuration / status from the backend
129
- * This is an unauthenticated endpoint used to detect bootstrap mode
144
+ * This is an unauthenticated endpoint used to detect bootstrap mode.
145
+ *
146
+ * Concurrent calls are deduplicated: only one network request is made
147
+ * and all callers share the same promise.
130
148
  */
131
149
  export declare function fetchAuthConfig(): Promise<AuthConfigResponse>;
132
150
  export { AuthApiError };
@@ -49,3 +49,25 @@ export interface RebaseLoginViewProps {
49
49
  * Login view component for custom JWT authentication
50
50
  */
51
51
  export declare function RebaseLoginView({ logo, authController, noUserComponent, disableSignupScreen, disabled, notAllowedError, googleEnabled, googleClientId }: RebaseLoginViewProps): import("react/jsx-runtime").JSX.Element;
52
+ /** Google Identity Services SDK — injected by the GIS <script> tag. */
53
+ declare global {
54
+ interface Window {
55
+ google?: {
56
+ accounts: {
57
+ oauth2: {
58
+ initCodeClient(config: {
59
+ client_id: string;
60
+ scope: string;
61
+ ux_mode: "popup" | "redirect";
62
+ callback: (response: {
63
+ code?: string;
64
+ error?: string;
65
+ }) => void;
66
+ }): {
67
+ requestCode(): void;
68
+ };
69
+ };
70
+ };
71
+ };
72
+ }
73
+ }
package/dist/index.es.js CHANGED
@@ -72,11 +72,12 @@ async function login(email, password) {
72
72
  });
73
73
  return handleResponse(response);
74
74
  }
75
- async function googleLogin(idToken) {
75
+ async function googleLogin(tokenOrPayload, tokenType = "idToken") {
76
+ const body = typeof tokenOrPayload === "string" ? { [tokenType]: tokenOrPayload } : tokenOrPayload;
76
77
  const response = await fetchWithHandling(`${baseApiUrl}/api/auth/google`, {
77
78
  method: "POST",
78
79
  headers: { "Content-Type": "application/json" },
79
- body: JSON.stringify({ idToken })
80
+ body: JSON.stringify(body)
80
81
  });
81
82
  return handleResponse(response);
82
83
  }
@@ -196,12 +197,23 @@ async function revokeAllSessions(accessToken) {
196
197
  });
197
198
  return handleResponse(response);
198
199
  }
200
+ let authConfigInflight = null;
199
201
  async function fetchAuthConfig() {
200
- const response = await fetchWithHandling(`${baseApiUrl}/api/auth/config`, {
201
- method: "GET",
202
- headers: { "Content-Type": "application/json" }
203
- });
204
- return handleResponse(response);
202
+ if (authConfigInflight) {
203
+ return authConfigInflight;
204
+ }
205
+ authConfigInflight = (async () => {
206
+ const response = await fetchWithHandling(`${baseApiUrl}/api/auth/config`, {
207
+ method: "GET",
208
+ headers: { "Content-Type": "application/json" }
209
+ });
210
+ return handleResponse(response);
211
+ })();
212
+ try {
213
+ return await authConfigInflight;
214
+ } finally {
215
+ authConfigInflight = null;
216
+ }
205
217
  }
206
218
  const STORAGE_KEY = "rebase_auth";
207
219
  const TOKEN_REFRESH_BUFFER_MS = 2 * 60 * 1e3;
@@ -412,6 +424,7 @@ function useRebaseAuthController(props = {}) {
412
424
  }
413
425
  }, [client, getAuthToken, refreshAccessToken$1, clearSessionAndSignOut]);
414
426
  const handleAuthSuccess = useCallback(async (userInfo, tokens) => {
427
+ console.log("[Auth] handleAuthSuccess called, user:", userInfo.email, "uid:", userInfo.uid);
415
428
  tokensRef.current = tokens;
416
429
  let convertedUser = convertToUser(userInfo);
417
430
  if (defineRolesFor) {
@@ -424,11 +437,13 @@ function useRebaseAuthController(props = {}) {
424
437
  }
425
438
  }
426
439
  saveAuthToStorage(tokens, userInfo);
440
+ console.log("[Auth] Calling setUser, roles:", convertedUser.roles);
427
441
  setUser(convertedUser);
428
442
  setAuthError(null);
429
443
  setAuthProviderError(null);
430
444
  setLoginSkipped(false);
431
445
  scheduleTokenRefresh(tokens);
446
+ console.log("[Auth] handleAuthSuccess completed");
432
447
  }, [scheduleTokenRefresh, defineRolesFor]);
433
448
  const emailPasswordLogin = useCallback(async (email, password) => {
434
449
  setAuthLoading(true);
@@ -456,11 +471,11 @@ function useRebaseAuthController(props = {}) {
456
471
  setAuthLoading(false);
457
472
  }
458
473
  }, [handleAuthSuccess]);
459
- const googleLogin$1 = useCallback(async (idToken) => {
474
+ const googleLogin$1 = useCallback(async (tokenOrPayload, tokenType) => {
460
475
  setAuthLoading(true);
461
476
  setAuthProviderError(null);
462
477
  try {
463
- const response = await googleLogin(idToken);
478
+ const response = typeof tokenOrPayload === "string" ? await googleLogin(tokenOrPayload, tokenType ?? "idToken") : await googleLogin(tokenOrPayload);
464
479
  await handleAuthSuccess(response.user, response.tokens);
465
480
  } catch (error) {
466
481
  setAuthProviderError(error);
@@ -795,6 +810,8 @@ function useBackendUserManagement(config) {
795
810
  const [loading, setLoading] = useState(true);
796
811
  const [usersError, setUsersError] = useState();
797
812
  const [rolesError, setRolesError] = useState();
813
+ const lastLoadedUidRef = useRef(null);
814
+ const apiRequestRef = useRef(null);
798
815
  const apiRequest = useCallback(async (endpoint, method = "GET", body, retryCount = 6, signal) => {
799
816
  let lastError = null;
800
817
  for (let attempt = 0; attempt < retryCount; attempt++) {
@@ -862,6 +879,7 @@ function useBackendUserManagement(config) {
862
879
  }
863
880
  throw lastError;
864
881
  }, [apiUrl, getAuthToken]);
882
+ apiRequestRef.current = apiRequest;
865
883
  useCallback(async (signal) => {
866
884
  try {
867
885
  const data = await apiRequest("/roles", "GET", void 0, 6, signal);
@@ -890,23 +908,43 @@ function useBackendUserManagement(config) {
890
908
  setLoading(false);
891
909
  return;
892
910
  }
911
+ if (lastLoadedUidRef.current === currentUser.uid) {
912
+ setLoading(false);
913
+ return;
914
+ }
893
915
  const abortController = new AbortController();
894
916
  const load = async () => {
895
917
  setLoading(true);
918
+ const request = apiRequestRef.current;
896
919
  try {
897
- const data = await apiRequest("/roles", "GET", void 0, 6, abortController.signal);
920
+ const data = await request("/roles", "GET", void 0, 6, abortController.signal);
898
921
  setRoles(data.roles.map(convertRole));
899
922
  setRolesError(void 0);
900
923
  } catch (error) {
901
- if (error instanceof Error && error.name !== "AbortError") {
902
- console.error("Failed to load roles:", error);
903
- setRolesError(error);
924
+ if (error instanceof Error && error.name === "AbortError") return;
925
+ console.error("Failed to load roles:", error);
926
+ setRolesError(error instanceof Error ? error : new Error(String(error)));
927
+ const status = error.status;
928
+ if (status === 403 || status === 401) {
929
+ setUsersError(error instanceof Error ? error : new Error(String(error)));
930
+ setLoading(false);
931
+ return;
904
932
  }
905
933
  }
906
934
  if (!abortController.signal.aborted) {
907
- await loadUsers(abortController.signal);
935
+ try {
936
+ const data = await request("/users", "GET", void 0, 6, abortController.signal);
937
+ const allUsers = data.users.map((u) => convertUser(u));
938
+ setUsers(allUsers);
939
+ setUsersError(void 0);
940
+ } catch (error) {
941
+ if (error instanceof Error && error.name === "AbortError") return;
942
+ console.error("Failed to load users:", error);
943
+ setUsersError(error instanceof Error ? error : new Error(String(error)));
944
+ }
908
945
  }
909
946
  if (!abortController.signal.aborted) {
947
+ lastLoadedUidRef.current = currentUser.uid;
910
948
  setLoading(false);
911
949
  }
912
950
  };
@@ -914,7 +952,7 @@ function useBackendUserManagement(config) {
914
952
  return () => {
915
953
  abortController.abort();
916
954
  };
917
- }, [currentUser, apiRequest, loadUsers]);
955
+ }, [currentUser?.uid]);
918
956
  const searchUsers = useCallback(async (options) => {
919
957
  const params = new URLSearchParams();
920
958
  if (options.limit !== void 0) params.set("limit", String(options.limit));
@@ -1115,7 +1153,7 @@ function RebaseLoginView({
1115
1153
  "div",
1116
1154
  {
1117
1155
  className: cls(
1118
- "relative flex items-center justify-center h-screen w-screen p-4 transition-opacity duration-500 bg-white dark:bg-surface-950",
1156
+ "relative flex items-center justify-center h-screen w-screen p-4 transition-opacity duration-500 bg-white dark:bg-surface-900",
1119
1157
  fadeIn ? "opacity-100" : "opacity-0"
1120
1158
  ),
1121
1159
  children: [
@@ -1269,74 +1307,79 @@ function LoginButton({
1269
1307
  }
1270
1308
  );
1271
1309
  }
1310
+ const GoogleIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", children: [
1311
+ /* @__PURE__ */ jsx(
1312
+ "path",
1313
+ {
1314
+ fill: "#4285F4",
1315
+ d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
1316
+ }
1317
+ ),
1318
+ /* @__PURE__ */ jsx(
1319
+ "path",
1320
+ {
1321
+ fill: "#34A853",
1322
+ d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
1323
+ }
1324
+ ),
1325
+ /* @__PURE__ */ jsx(
1326
+ "path",
1327
+ {
1328
+ fill: "#FBBC05",
1329
+ d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
1330
+ }
1331
+ ),
1332
+ /* @__PURE__ */ jsx(
1333
+ "path",
1334
+ {
1335
+ fill: "#EA4335",
1336
+ d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
1337
+ }
1338
+ )
1339
+ ] });
1272
1340
  function GoogleLoginButton({
1273
1341
  disabled,
1274
1342
  googleClientId,
1275
1343
  authController
1276
1344
  }) {
1277
- const handleGoogleLogin = async () => {
1278
- try {
1279
- const google = window.google;
1280
- if (!google) {
1281
- console.error("Google Sign-In not loaded");
1282
- return;
1283
- }
1284
- google.accounts.id.initialize({
1285
- client_id: googleClientId,
1286
- callback: async (response) => {
1287
- try {
1288
- await authController.googleLogin(response.credential);
1289
- } catch (err) {
1290
- console.error("Google login error:", err);
1291
- }
1345
+ const codeClientRef = useRef(null);
1346
+ useEffect(() => {
1347
+ const google = window.google;
1348
+ if (!google || codeClientRef.current) return;
1349
+ codeClientRef.current = google.accounts.oauth2.initCodeClient({
1350
+ client_id: googleClientId,
1351
+ scope: "openid email profile",
1352
+ ux_mode: "popup",
1353
+ callback: async (response) => {
1354
+ if (response.error || !response.code) {
1355
+ console.error("Google login error:", response.error);
1356
+ return;
1292
1357
  }
1293
- });
1294
- google.accounts.id.prompt();
1295
- } catch (err) {
1296
- console.error("Google login error:", err);
1358
+ try {
1359
+ await authController.googleLogin({
1360
+ code: response.code,
1361
+ redirectUri: "postmessage"
1362
+ });
1363
+ } catch (err) {
1364
+ console.error("Google login error:", err);
1365
+ }
1366
+ }
1367
+ });
1368
+ }, [googleClientId, authController]);
1369
+ const handleClick = () => {
1370
+ if (!codeClientRef.current) {
1371
+ console.error("Google Sign-In not loaded");
1372
+ return;
1297
1373
  }
1374
+ codeClientRef.current.requestCode();
1298
1375
  };
1299
1376
  return /* @__PURE__ */ jsx(
1300
- Button,
1377
+ LoginButton,
1301
1378
  {
1302
1379
  disabled,
1303
- className: "w-full",
1304
- variant: "outlined",
1305
- size: "large",
1306
- onClick: handleGoogleLogin,
1307
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center w-full gap-3 py-1", children: [
1308
- /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", children: [
1309
- /* @__PURE__ */ jsx(
1310
- "path",
1311
- {
1312
- fill: "#4285F4",
1313
- d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
1314
- }
1315
- ),
1316
- /* @__PURE__ */ jsx(
1317
- "path",
1318
- {
1319
- fill: "#34A853",
1320
- d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
1321
- }
1322
- ),
1323
- /* @__PURE__ */ jsx(
1324
- "path",
1325
- {
1326
- fill: "#FBBC05",
1327
- d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
1328
- }
1329
- ),
1330
- /* @__PURE__ */ jsx(
1331
- "path",
1332
- {
1333
- fill: "#EA4335",
1334
- d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
1335
- }
1336
- )
1337
- ] }),
1338
- /* @__PURE__ */ jsx(Typography, { variant: "button", children: "Continue with Google" })
1339
- ] })
1380
+ text: "Sign in with Google",
1381
+ icon: /* @__PURE__ */ jsx(GoogleIcon, {}),
1382
+ onClick: handleClick
1340
1383
  }
1341
1384
  );
1342
1385
  }
@@ -1457,6 +1500,7 @@ function LoginForm({
1457
1500
  {
1458
1501
  type: "submit",
1459
1502
  variant: "filled",
1503
+ color: "primary",
1460
1504
  className: "w-full mt-1",
1461
1505
  size: "large",
1462
1506
  loading: authController.authLoading,
@@ -1578,6 +1622,7 @@ function ForgotPasswordForm({
1578
1622
  {
1579
1623
  type: "submit",
1580
1624
  variant: "filled",
1625
+ color: "primary",
1581
1626
  className: "w-full",
1582
1627
  size: "large",
1583
1628
  loading: authController.authLoading,
@@ -1605,14 +1650,12 @@ function createUserManagementAdminViews({ userManagement, apiUrl, getAuthToken,
1605
1650
  {
1606
1651
  slug: "dev/users",
1607
1652
  name: "CMS Users",
1608
- group: "Admin",
1609
1653
  icon: "face",
1610
1654
  view: /* @__PURE__ */ jsx(UsersView, { userManagement, apiUrl, getAuthToken })
1611
1655
  },
1612
1656
  {
1613
1657
  slug: "dev/roles",
1614
1658
  name: "Roles",
1615
- group: "Admin",
1616
1659
  icon: "gpp_good",
1617
1660
  view: /* @__PURE__ */ jsx(RolesView, { userManagement, collections })
1618
1661
  }
@@ -1633,6 +1676,7 @@ function RoleChip({ role }) {
1633
1676
  }
1634
1677
  function UsersView({ userManagement, apiUrl, getAuthToken }) {
1635
1678
  const { users, roles, saveUser, deleteUser, loading } = userManagement;
1679
+ const usersError = "usersError" in userManagement ? userManagement.usersError : void 0;
1636
1680
  const snackbarController = useSnackbarController();
1637
1681
  const { user: loggedInUser } = useAuthController();
1638
1682
  const [dialogOpen, setDialogOpen] = useState(false);
@@ -1752,7 +1796,10 @@ function UsersView({ userManagement, apiUrl, getAuthToken }) {
1752
1796
  return role ? /* @__PURE__ */ jsx(RoleChip, { role }, roleId) : /* @__PURE__ */ jsx("span", { children: roleId }, roleId);
1753
1797
  }) }) })
1754
1798
  ] }, user.uid)),
1755
- users.length === 0 && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colspan: 4, children: /* @__PURE__ */ jsx(CenteredView, { className: "flex flex-col gap-4 my-8 items-center", children: /* @__PURE__ */ jsx(Typography, { variant: "label", children: "There are no users yet" }) }) }) })
1799
+ users.length === 0 && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colspan: 4, children: /* @__PURE__ */ jsxs(CenteredView, { className: "flex flex-col gap-4 my-8 items-center", children: [
1800
+ /* @__PURE__ */ jsx(Typography, { variant: "label", children: usersError ? "You don't have permission to view users" : "There are no users yet" }),
1801
+ usersError && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-500", children: "Contact an administrator if you need access to this section." })
1802
+ ] }) }) })
1756
1803
  ] })
1757
1804
  ] }) }),
1758
1805
  /* @__PURE__ */ jsx(
@@ -1909,6 +1956,7 @@ function UserDetailsForm({
1909
1956
  }
1910
1957
  function RolesView({ userManagement, collections = [] }) {
1911
1958
  const { roles, saveRole, deleteRole, loading, allowDefaultRolesCreation } = userManagement;
1959
+ const rolesError = "rolesError" in userManagement ? userManagement.rolesError : void 0;
1912
1960
  const snackbarController = useSnackbarController();
1913
1961
  const [dialogOpen, setDialogOpen] = useState(false);
1914
1962
  const [selectedRole, setSelectedRole] = useState();
@@ -2001,8 +2049,9 @@ function RolesView({ userManagement, collections = [] }) {
2001
2049
  ] }, role.id);
2002
2050
  }),
2003
2051
  roles.length === 0 && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colspan: 4, children: /* @__PURE__ */ jsxs(CenteredView, { className: "flex flex-col gap-4 my-8 items-center", children: [
2004
- /* @__PURE__ */ jsx(Typography, { variant: "label", children: "You don't have any roles yet." }),
2005
- allowDefaultRolesCreation && /* @__PURE__ */ jsx(Button, { onClick: createDefaultRoles, children: "Create default roles" })
2052
+ /* @__PURE__ */ jsx(Typography, { variant: "label", children: rolesError ? "You don't have permission to view roles" : "You don&apos;t have any roles yet." }),
2053
+ rolesError && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-500", children: "Contact an administrator if you need access to this section." }),
2054
+ !rolesError && allowDefaultRolesCreation && /* @__PURE__ */ jsx(Button, { onClick: createDefaultRoles, children: "Create default roles" })
2006
2055
  ] }) }) })
2007
2056
  ] })
2008
2057
  ] }) }),