@rebasepro/auth 0.0.1-canary.eae7889 → 0.1.0

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/index.umd.js CHANGED
@@ -71,11 +71,12 @@
71
71
  });
72
72
  return handleResponse(response);
73
73
  }
74
- async function googleLogin(idToken) {
74
+ async function googleLogin(tokenOrPayload, tokenType = "idToken") {
75
+ const body = typeof tokenOrPayload === "string" ? { [tokenType]: tokenOrPayload } : tokenOrPayload;
75
76
  const response = await fetchWithHandling(`${baseApiUrl}/api/auth/google`, {
76
77
  method: "POST",
77
78
  headers: { "Content-Type": "application/json" },
78
- body: JSON.stringify({ idToken })
79
+ body: JSON.stringify(body)
79
80
  });
80
81
  return handleResponse(response);
81
82
  }
@@ -195,12 +196,23 @@
195
196
  });
196
197
  return handleResponse(response);
197
198
  }
199
+ let authConfigInflight = null;
198
200
  async function fetchAuthConfig() {
199
- const response = await fetchWithHandling(`${baseApiUrl}/api/auth/config`, {
200
- method: "GET",
201
- headers: { "Content-Type": "application/json" }
202
- });
203
- return handleResponse(response);
201
+ if (authConfigInflight) {
202
+ return authConfigInflight;
203
+ }
204
+ authConfigInflight = (async () => {
205
+ const response = await fetchWithHandling(`${baseApiUrl}/api/auth/config`, {
206
+ method: "GET",
207
+ headers: { "Content-Type": "application/json" }
208
+ });
209
+ return handleResponse(response);
210
+ })();
211
+ try {
212
+ return await authConfigInflight;
213
+ } finally {
214
+ authConfigInflight = null;
215
+ }
204
216
  }
205
217
  const STORAGE_KEY = "rebase_auth";
206
218
  const TOKEN_REFRESH_BUFFER_MS = 2 * 60 * 1e3;
@@ -411,6 +423,7 @@
411
423
  }
412
424
  }, [client, getAuthToken, refreshAccessToken$1, clearSessionAndSignOut]);
413
425
  const handleAuthSuccess = react.useCallback(async (userInfo, tokens) => {
426
+ console.log("[Auth] handleAuthSuccess called, user:", userInfo.email, "uid:", userInfo.uid);
414
427
  tokensRef.current = tokens;
415
428
  let convertedUser = convertToUser(userInfo);
416
429
  if (defineRolesFor) {
@@ -423,11 +436,13 @@
423
436
  }
424
437
  }
425
438
  saveAuthToStorage(tokens, userInfo);
439
+ console.log("[Auth] Calling setUser, roles:", convertedUser.roles);
426
440
  setUser(convertedUser);
427
441
  setAuthError(null);
428
442
  setAuthProviderError(null);
429
443
  setLoginSkipped(false);
430
444
  scheduleTokenRefresh(tokens);
445
+ console.log("[Auth] handleAuthSuccess completed");
431
446
  }, [scheduleTokenRefresh, defineRolesFor]);
432
447
  const emailPasswordLogin = react.useCallback(async (email, password) => {
433
448
  setAuthLoading(true);
@@ -455,11 +470,11 @@
455
470
  setAuthLoading(false);
456
471
  }
457
472
  }, [handleAuthSuccess]);
458
- const googleLogin$1 = react.useCallback(async (idToken) => {
473
+ const googleLogin$1 = react.useCallback(async (tokenOrPayload, tokenType) => {
459
474
  setAuthLoading(true);
460
475
  setAuthProviderError(null);
461
476
  try {
462
- const response = await googleLogin(idToken);
477
+ const response = typeof tokenOrPayload === "string" ? await googleLogin(tokenOrPayload, tokenType ?? "idToken") : await googleLogin(tokenOrPayload);
463
478
  await handleAuthSuccess(response.user, response.tokens);
464
479
  } catch (error) {
465
480
  setAuthProviderError(error);
@@ -794,6 +809,8 @@
794
809
  const [loading, setLoading] = react.useState(true);
795
810
  const [usersError, setUsersError] = react.useState();
796
811
  const [rolesError, setRolesError] = react.useState();
812
+ const lastLoadedUidRef = react.useRef(null);
813
+ const apiRequestRef = react.useRef(null);
797
814
  const apiRequest = react.useCallback(async (endpoint, method = "GET", body, retryCount = 6, signal) => {
798
815
  let lastError = null;
799
816
  for (let attempt = 0; attempt < retryCount; attempt++) {
@@ -861,6 +878,7 @@
861
878
  }
862
879
  throw lastError;
863
880
  }, [apiUrl, getAuthToken]);
881
+ apiRequestRef.current = apiRequest;
864
882
  react.useCallback(async (signal) => {
865
883
  try {
866
884
  const data = await apiRequest("/roles", "GET", void 0, 6, signal);
@@ -889,23 +907,43 @@
889
907
  setLoading(false);
890
908
  return;
891
909
  }
910
+ if (lastLoadedUidRef.current === currentUser.uid) {
911
+ setLoading(false);
912
+ return;
913
+ }
892
914
  const abortController = new AbortController();
893
915
  const load = async () => {
894
916
  setLoading(true);
917
+ const request = apiRequestRef.current;
895
918
  try {
896
- const data = await apiRequest("/roles", "GET", void 0, 6, abortController.signal);
919
+ const data = await request("/roles", "GET", void 0, 6, abortController.signal);
897
920
  setRoles(data.roles.map(convertRole));
898
921
  setRolesError(void 0);
899
922
  } catch (error) {
900
- if (error instanceof Error && error.name !== "AbortError") {
901
- console.error("Failed to load roles:", error);
902
- setRolesError(error);
923
+ if (error instanceof Error && error.name === "AbortError") return;
924
+ console.error("Failed to load roles:", error);
925
+ setRolesError(error instanceof Error ? error : new Error(String(error)));
926
+ const status = error.status;
927
+ if (status === 403 || status === 401) {
928
+ setUsersError(error instanceof Error ? error : new Error(String(error)));
929
+ setLoading(false);
930
+ return;
903
931
  }
904
932
  }
905
933
  if (!abortController.signal.aborted) {
906
- await loadUsers(abortController.signal);
934
+ try {
935
+ const data = await request("/users", "GET", void 0, 6, abortController.signal);
936
+ const allUsers = data.users.map((u) => convertUser(u));
937
+ setUsers(allUsers);
938
+ setUsersError(void 0);
939
+ } catch (error) {
940
+ if (error instanceof Error && error.name === "AbortError") return;
941
+ console.error("Failed to load users:", error);
942
+ setUsersError(error instanceof Error ? error : new Error(String(error)));
943
+ }
907
944
  }
908
945
  if (!abortController.signal.aborted) {
946
+ lastLoadedUidRef.current = currentUser.uid;
909
947
  setLoading(false);
910
948
  }
911
949
  };
@@ -913,7 +951,7 @@
913
951
  return () => {
914
952
  abortController.abort();
915
953
  };
916
- }, [currentUser, apiRequest, loadUsers]);
954
+ }, [currentUser?.uid]);
917
955
  const searchUsers = react.useCallback(async (options) => {
918
956
  const params = new URLSearchParams();
919
957
  if (options.limit !== void 0) params.set("limit", String(options.limit));
@@ -1114,7 +1152,7 @@
1114
1152
  "div",
1115
1153
  {
1116
1154
  className: ui.cls(
1117
- "relative flex items-center justify-center h-screen w-screen p-4 transition-opacity duration-500 bg-white dark:bg-surface-950",
1155
+ "relative flex items-center justify-center h-screen w-screen p-4 transition-opacity duration-500 bg-white dark:bg-surface-900",
1118
1156
  fadeIn ? "opacity-100" : "opacity-0"
1119
1157
  ),
1120
1158
  children: [
@@ -1268,74 +1306,79 @@
1268
1306
  }
1269
1307
  );
1270
1308
  }
1309
+ const GoogleIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", children: [
1310
+ /* @__PURE__ */ jsxRuntime.jsx(
1311
+ "path",
1312
+ {
1313
+ fill: "#4285F4",
1314
+ 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"
1315
+ }
1316
+ ),
1317
+ /* @__PURE__ */ jsxRuntime.jsx(
1318
+ "path",
1319
+ {
1320
+ fill: "#34A853",
1321
+ 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"
1322
+ }
1323
+ ),
1324
+ /* @__PURE__ */ jsxRuntime.jsx(
1325
+ "path",
1326
+ {
1327
+ fill: "#FBBC05",
1328
+ 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"
1329
+ }
1330
+ ),
1331
+ /* @__PURE__ */ jsxRuntime.jsx(
1332
+ "path",
1333
+ {
1334
+ fill: "#EA4335",
1335
+ 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"
1336
+ }
1337
+ )
1338
+ ] });
1271
1339
  function GoogleLoginButton({
1272
1340
  disabled,
1273
1341
  googleClientId,
1274
1342
  authController
1275
1343
  }) {
1276
- const handleGoogleLogin = async () => {
1277
- try {
1278
- const google = window.google;
1279
- if (!google) {
1280
- console.error("Google Sign-In not loaded");
1281
- return;
1282
- }
1283
- google.accounts.id.initialize({
1284
- client_id: googleClientId,
1285
- callback: async (response) => {
1286
- try {
1287
- await authController.googleLogin(response.credential);
1288
- } catch (err) {
1289
- console.error("Google login error:", err);
1290
- }
1344
+ const codeClientRef = react.useRef(null);
1345
+ react.useEffect(() => {
1346
+ const google = window.google;
1347
+ if (!google || codeClientRef.current) return;
1348
+ codeClientRef.current = google.accounts.oauth2.initCodeClient({
1349
+ client_id: googleClientId,
1350
+ scope: "openid email profile",
1351
+ ux_mode: "popup",
1352
+ callback: async (response) => {
1353
+ if (response.error || !response.code) {
1354
+ console.error("Google login error:", response.error);
1355
+ return;
1291
1356
  }
1292
- });
1293
- google.accounts.id.prompt();
1294
- } catch (err) {
1295
- console.error("Google login error:", err);
1357
+ try {
1358
+ await authController.googleLogin({
1359
+ code: response.code,
1360
+ redirectUri: "postmessage"
1361
+ });
1362
+ } catch (err) {
1363
+ console.error("Google login error:", err);
1364
+ }
1365
+ }
1366
+ });
1367
+ }, [googleClientId, authController]);
1368
+ const handleClick = () => {
1369
+ if (!codeClientRef.current) {
1370
+ console.error("Google Sign-In not loaded");
1371
+ return;
1296
1372
  }
1373
+ codeClientRef.current.requestCode();
1297
1374
  };
1298
1375
  return /* @__PURE__ */ jsxRuntime.jsx(
1299
- ui.Button,
1376
+ LoginButton,
1300
1377
  {
1301
1378
  disabled,
1302
- className: "w-full",
1303
- variant: "outlined",
1304
- size: "large",
1305
- onClick: handleGoogleLogin,
1306
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center w-full gap-3 py-1", children: [
1307
- /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", children: [
1308
- /* @__PURE__ */ jsxRuntime.jsx(
1309
- "path",
1310
- {
1311
- fill: "#4285F4",
1312
- 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"
1313
- }
1314
- ),
1315
- /* @__PURE__ */ jsxRuntime.jsx(
1316
- "path",
1317
- {
1318
- fill: "#34A853",
1319
- 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"
1320
- }
1321
- ),
1322
- /* @__PURE__ */ jsxRuntime.jsx(
1323
- "path",
1324
- {
1325
- fill: "#FBBC05",
1326
- 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"
1327
- }
1328
- ),
1329
- /* @__PURE__ */ jsxRuntime.jsx(
1330
- "path",
1331
- {
1332
- fill: "#EA4335",
1333
- 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"
1334
- }
1335
- )
1336
- ] }),
1337
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "button", children: "Continue with Google" })
1338
- ] })
1379
+ text: "Sign in with Google",
1380
+ icon: /* @__PURE__ */ jsxRuntime.jsx(GoogleIcon, {}),
1381
+ onClick: handleClick
1339
1382
  }
1340
1383
  );
1341
1384
  }
@@ -1456,6 +1499,7 @@
1456
1499
  {
1457
1500
  type: "submit",
1458
1501
  variant: "filled",
1502
+ color: "primary",
1459
1503
  className: "w-full mt-1",
1460
1504
  size: "large",
1461
1505
  loading: authController.authLoading,
@@ -1577,6 +1621,7 @@
1577
1621
  {
1578
1622
  type: "submit",
1579
1623
  variant: "filled",
1624
+ color: "primary",
1580
1625
  className: "w-full",
1581
1626
  size: "large",
1582
1627
  loading: authController.authLoading,
@@ -1604,14 +1649,12 @@
1604
1649
  {
1605
1650
  slug: "dev/users",
1606
1651
  name: "CMS Users",
1607
- group: "Admin",
1608
1652
  icon: "face",
1609
1653
  view: /* @__PURE__ */ jsxRuntime.jsx(UsersView, { userManagement, apiUrl, getAuthToken })
1610
1654
  },
1611
1655
  {
1612
1656
  slug: "dev/roles",
1613
1657
  name: "Roles",
1614
- group: "Admin",
1615
1658
  icon: "gpp_good",
1616
1659
  view: /* @__PURE__ */ jsxRuntime.jsx(RolesView, { userManagement, collections })
1617
1660
  }
@@ -1632,6 +1675,7 @@
1632
1675
  }
1633
1676
  function UsersView({ userManagement, apiUrl, getAuthToken }) {
1634
1677
  const { users, roles, saveUser, deleteUser, loading } = userManagement;
1678
+ const usersError = "usersError" in userManagement ? userManagement.usersError : void 0;
1635
1679
  const snackbarController = core.useSnackbarController();
1636
1680
  const { user: loggedInUser } = core.useAuthController();
1637
1681
  const [dialogOpen, setDialogOpen] = react.useState(false);
@@ -1751,7 +1795,10 @@
1751
1795
  return role ? /* @__PURE__ */ jsxRuntime.jsx(RoleChip, { role }, roleId) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: roleId }, roleId);
1752
1796
  }) }) })
1753
1797
  ] }, user.uid)),
1754
- users.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.TableRow, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { colspan: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ui.CenteredView, { className: "flex flex-col gap-4 my-8 items-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", children: "There are no users yet" }) }) }) })
1798
+ users.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.TableRow, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { colspan: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.CenteredView, { className: "flex flex-col gap-4 my-8 items-center", children: [
1799
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", children: usersError ? "You don't have permission to view users" : "There are no users yet" }),
1800
+ usersError && /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "caption", className: "text-surface-500", children: "Contact an administrator if you need access to this section." })
1801
+ ] }) }) })
1755
1802
  ] })
1756
1803
  ] }) }),
1757
1804
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1908,6 +1955,7 @@
1908
1955
  }
1909
1956
  function RolesView({ userManagement, collections = [] }) {
1910
1957
  const { roles, saveRole, deleteRole, loading, allowDefaultRolesCreation } = userManagement;
1958
+ const rolesError = "rolesError" in userManagement ? userManagement.rolesError : void 0;
1911
1959
  const snackbarController = core.useSnackbarController();
1912
1960
  const [dialogOpen, setDialogOpen] = react.useState(false);
1913
1961
  const [selectedRole, setSelectedRole] = react.useState();
@@ -2000,8 +2048,9 @@
2000
2048
  ] }, role.id);
2001
2049
  }),
2002
2050
  roles.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.TableRow, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.TableCell, { colspan: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.CenteredView, { className: "flex flex-col gap-4 my-8 items-center", children: [
2003
- /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", children: "You don't have any roles yet." }),
2004
- allowDefaultRolesCreation && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: createDefaultRoles, children: "Create default roles" })
2051
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "label", children: rolesError ? "You don't have permission to view roles" : "You don&apos;t have any roles yet." }),
2052
+ rolesError && /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { variant: "caption", className: "text-surface-500", children: "Contact an administrator if you need access to this section." }),
2053
+ !rolesError && allowDefaultRolesCreation && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: createDefaultRoles, children: "Create default roles" })
2005
2054
  ] }) }) })
2006
2055
  ] })
2007
2056
  ] }) }),