@rebasepro/auth 0.0.1-canary.892f711 → 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,8 +20,23 @@ 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 token (ID token or access token)
23
+ * Google login payload one of the three supported flows.
24
24
  */
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>;
25
40
  export declare function googleLogin(token: string, tokenType?: "idToken" | "accessToken"): Promise<AuthResponse>;
26
41
  /**
27
42
  * Login with LinkedIn OAuth code
@@ -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
@@ -1,6 +1,6 @@
1
1
  import { useState, useRef, useEffect, useCallback, useLayoutEffect } from "react";
2
2
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
3
- import { Menu, MenuItem, iconSize, IconButton, Button, cls, Typography, TextField, LoadingButton, CenteredView, CircularProgress, Container, Table, TableHeader, TableCell, TableBody, TableRow, Tooltip, Checkbox, getColorSchemeForSeed, Chip, Dialog, DialogTitle, DialogContent, DialogActions, MultiSelect, MultiSelectItem } from "@rebasepro/ui";
3
+ import { Menu, MenuItem, iconSize, IconButton, Typography, cls, TextField, LoadingButton, Button, CenteredView, CircularProgress, Container, Table, TableHeader, TableCell, TableBody, TableRow, Tooltip, Checkbox, getColorSchemeForSeed, Chip, Dialog, DialogTitle, DialogContent, DialogActions, MultiSelect, MultiSelectItem } from "@rebasepro/ui";
4
4
  import { MoonIcon, SunIcon, SunMoonIcon, MailIcon, ArrowLeftIcon, PlusIcon, Trash2Icon } from "lucide-react";
5
5
  import { useModeController, useTranslation, LanguageToggle, ErrorView, RebaseLogo, useRebaseRegistryDispatch, useSnackbarController, ConfirmationDialog, useAuthController } from "@rebasepro/core";
6
6
  let baseApiUrl = "";
@@ -72,11 +72,12 @@ async function login(email, password) {
72
72
  });
73
73
  return handleResponse(response);
74
74
  }
75
- async function googleLogin(token, tokenType = "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({ [tokenType]: token })
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 (token, tokenType = "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(token, tokenType);
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,7 @@ 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);
798
814
  const apiRequestRef = useRef(null);
799
815
  const apiRequest = useCallback(async (endpoint, method = "GET", body, retryCount = 6, signal) => {
800
816
  let lastError = null;
@@ -892,6 +908,10 @@ function useBackendUserManagement(config) {
892
908
  setLoading(false);
893
909
  return;
894
910
  }
911
+ if (lastLoadedUidRef.current === currentUser.uid) {
912
+ setLoading(false);
913
+ return;
914
+ }
895
915
  const abortController = new AbortController();
896
916
  const load = async () => {
897
917
  setLoading(true);
@@ -924,6 +944,7 @@ function useBackendUserManagement(config) {
924
944
  }
925
945
  }
926
946
  if (!abortController.signal.aborted) {
947
+ lastLoadedUidRef.current = currentUser.uid;
927
948
  setLoading(false);
928
949
  }
929
950
  };
@@ -1210,17 +1231,22 @@ function RebaseLoginView({
1210
1231
  authController
1211
1232
  }
1212
1233
  ),
1213
- showRegistration && /* @__PURE__ */ jsx(
1214
- Button,
1215
- {
1216
- className: "w-full",
1217
- variant: "filled",
1218
- color: "primary",
1219
- size: "large",
1220
- onClick: () => switchMode("register"),
1221
- children: "Create an account"
1222
- }
1223
- )
1234
+ showRegistration && /* @__PURE__ */ jsx("div", { className: "mt-2 text-center", children: /* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "secondary", children: [
1235
+ "Don't have an account?",
1236
+ " ",
1237
+ /* @__PURE__ */ jsx(
1238
+ "button",
1239
+ {
1240
+ type: "button",
1241
+ className: cls(
1242
+ "font-semibold hover:underline cursor-pointer",
1243
+ "text-primary-600 dark:text-primary-400"
1244
+ ),
1245
+ onClick: () => switchMode("register"),
1246
+ children: "Create one"
1247
+ }
1248
+ )
1249
+ ] }) })
1224
1250
  ] }),
1225
1251
  mode === "login" && /* @__PURE__ */ jsx(
1226
1252
  LoginForm,
@@ -1316,20 +1342,24 @@ function GoogleLoginButton({
1316
1342
  googleClientId,
1317
1343
  authController
1318
1344
  }) {
1319
- const tokenClientRef = useRef(null);
1345
+ const codeClientRef = useRef(null);
1320
1346
  useEffect(() => {
1321
1347
  const google = window.google;
1322
- if (!google || tokenClientRef.current) return;
1323
- tokenClientRef.current = google.accounts.oauth2.initTokenClient({
1348
+ if (!google || codeClientRef.current) return;
1349
+ codeClientRef.current = google.accounts.oauth2.initCodeClient({
1324
1350
  client_id: googleClientId,
1325
1351
  scope: "openid email profile",
1352
+ ux_mode: "popup",
1326
1353
  callback: async (response) => {
1327
- if (response.error || !response.access_token) {
1354
+ if (response.error || !response.code) {
1328
1355
  console.error("Google login error:", response.error);
1329
1356
  return;
1330
1357
  }
1331
1358
  try {
1332
- await authController.googleLogin(response.access_token, "accessToken");
1359
+ await authController.googleLogin({
1360
+ code: response.code,
1361
+ redirectUri: "postmessage"
1362
+ });
1333
1363
  } catch (err) {
1334
1364
  console.error("Google login error:", err);
1335
1365
  }
@@ -1337,11 +1367,11 @@ function GoogleLoginButton({
1337
1367
  });
1338
1368
  }, [googleClientId, authController]);
1339
1369
  const handleClick = () => {
1340
- if (!tokenClientRef.current) {
1370
+ if (!codeClientRef.current) {
1341
1371
  console.error("Google Sign-In not loaded");
1342
1372
  return;
1343
1373
  }
1344
- tokenClientRef.current.requestAccessToken();
1374
+ codeClientRef.current.requestCode();
1345
1375
  };
1346
1376
  return /* @__PURE__ */ jsx(
1347
1377
  LoginButton,
@@ -1620,14 +1650,12 @@ function createUserManagementAdminViews({ userManagement, apiUrl, getAuthToken,
1620
1650
  {
1621
1651
  slug: "dev/users",
1622
1652
  name: "CMS Users",
1623
- group: "Admin",
1624
1653
  icon: "face",
1625
1654
  view: /* @__PURE__ */ jsx(UsersView, { userManagement, apiUrl, getAuthToken })
1626
1655
  },
1627
1656
  {
1628
1657
  slug: "dev/roles",
1629
1658
  name: "Roles",
1630
- group: "Admin",
1631
1659
  icon: "gpp_good",
1632
1660
  view: /* @__PURE__ */ jsx(RolesView, { userManagement, collections })
1633
1661
  }
@@ -1725,7 +1753,7 @@ function UsersView({ userManagement, apiUrl, getAuthToken }) {
1725
1753
  return /* @__PURE__ */ jsx(CenteredView, { children: /* @__PURE__ */ jsx(CircularProgress, {}) });
1726
1754
  }
1727
1755
  return /* @__PURE__ */ jsxs(Container, { className: "w-full flex flex-col py-4 gap-4", maxWidth: "6xl", children: [
1728
- !hasAdmin && !usersError && loggedInUser && /* @__PURE__ */ jsxs("div", { className: "bg-yellow-100 dark:bg-yellow-900 border border-yellow-400 dark:border-yellow-700 rounded p-4 flex items-center justify-between", children: [
1756
+ !hasAdmin && loggedInUser && /* @__PURE__ */ jsxs("div", { className: "bg-yellow-100 dark:bg-yellow-900 border border-yellow-400 dark:border-yellow-700 rounded p-4 flex items-center justify-between", children: [
1729
1757
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Typography, { variant: "label", className: "text-yellow-800 dark:text-yellow-200", children: "No admin users exist. You can make yourself an admin." }) }),
1730
1758
  /* @__PURE__ */ jsx(
1731
1759
  Button,