@rebasepro/auth 0.0.1-canary.f81da60 → 0.1.2
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 +20 -2
- package/dist/components/RebaseLoginView.d.ts +22 -0
- package/dist/index.es.js +82 -37
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +81 -36
- package/dist/index.umd.js.map +1 -1
- package/dist/types.d.ts +8 -2
- package/package.json +4 -4
- package/src/api.ts +49 -9
- package/src/components/AdminViews.tsx +1 -3
- package/src/components/RebaseLoginView.tsx +48 -18
- package/src/hooks/useBackendUserManagement.ts +53 -8
- package/src/hooks/useRebaseAuthController.ts +11 -3
- package/src/types.ts +5 -2
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
|
-
*
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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 (
|
|
474
|
+
const googleLogin$1 = useCallback(async (tokenOrPayload, tokenType) => {
|
|
460
475
|
setAuthLoading(true);
|
|
461
476
|
setAuthProviderError(null);
|
|
462
477
|
try {
|
|
463
|
-
const response = await googleLogin(
|
|
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
|
|
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
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
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
|
|
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));
|
|
@@ -1193,17 +1231,22 @@ function RebaseLoginView({
|
|
|
1193
1231
|
authController
|
|
1194
1232
|
}
|
|
1195
1233
|
),
|
|
1196
|
-
showRegistration && /* @__PURE__ */ jsx(
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
+
] }) })
|
|
1207
1250
|
] }),
|
|
1208
1251
|
mode === "login" && /* @__PURE__ */ jsx(
|
|
1209
1252
|
LoginForm,
|
|
@@ -1299,20 +1342,24 @@ function GoogleLoginButton({
|
|
|
1299
1342
|
googleClientId,
|
|
1300
1343
|
authController
|
|
1301
1344
|
}) {
|
|
1302
|
-
const
|
|
1345
|
+
const codeClientRef = useRef(null);
|
|
1303
1346
|
useEffect(() => {
|
|
1304
1347
|
const google = window.google;
|
|
1305
|
-
if (!google ||
|
|
1306
|
-
|
|
1348
|
+
if (!google || codeClientRef.current) return;
|
|
1349
|
+
codeClientRef.current = google.accounts.oauth2.initCodeClient({
|
|
1307
1350
|
client_id: googleClientId,
|
|
1308
1351
|
scope: "openid email profile",
|
|
1352
|
+
ux_mode: "popup",
|
|
1309
1353
|
callback: async (response) => {
|
|
1310
|
-
if (response.error || !response.
|
|
1354
|
+
if (response.error || !response.code) {
|
|
1311
1355
|
console.error("Google login error:", response.error);
|
|
1312
1356
|
return;
|
|
1313
1357
|
}
|
|
1314
1358
|
try {
|
|
1315
|
-
await authController.googleLogin(
|
|
1359
|
+
await authController.googleLogin({
|
|
1360
|
+
code: response.code,
|
|
1361
|
+
redirectUri: "postmessage"
|
|
1362
|
+
});
|
|
1316
1363
|
} catch (err) {
|
|
1317
1364
|
console.error("Google login error:", err);
|
|
1318
1365
|
}
|
|
@@ -1320,11 +1367,11 @@ function GoogleLoginButton({
|
|
|
1320
1367
|
});
|
|
1321
1368
|
}, [googleClientId, authController]);
|
|
1322
1369
|
const handleClick = () => {
|
|
1323
|
-
if (!
|
|
1370
|
+
if (!codeClientRef.current) {
|
|
1324
1371
|
console.error("Google Sign-In not loaded");
|
|
1325
1372
|
return;
|
|
1326
1373
|
}
|
|
1327
|
-
|
|
1374
|
+
codeClientRef.current.requestCode();
|
|
1328
1375
|
};
|
|
1329
1376
|
return /* @__PURE__ */ jsx(
|
|
1330
1377
|
LoginButton,
|
|
@@ -1603,14 +1650,12 @@ function createUserManagementAdminViews({ userManagement, apiUrl, getAuthToken,
|
|
|
1603
1650
|
{
|
|
1604
1651
|
slug: "dev/users",
|
|
1605
1652
|
name: "CMS Users",
|
|
1606
|
-
group: "Admin",
|
|
1607
1653
|
icon: "face",
|
|
1608
1654
|
view: /* @__PURE__ */ jsx(UsersView, { userManagement, apiUrl, getAuthToken })
|
|
1609
1655
|
},
|
|
1610
1656
|
{
|
|
1611
1657
|
slug: "dev/roles",
|
|
1612
1658
|
name: "Roles",
|
|
1613
|
-
group: "Admin",
|
|
1614
1659
|
icon: "gpp_good",
|
|
1615
1660
|
view: /* @__PURE__ */ jsx(RolesView, { userManagement, collections })
|
|
1616
1661
|
}
|
|
@@ -1708,7 +1753,7 @@ function UsersView({ userManagement, apiUrl, getAuthToken }) {
|
|
|
1708
1753
|
return /* @__PURE__ */ jsx(CenteredView, { children: /* @__PURE__ */ jsx(CircularProgress, {}) });
|
|
1709
1754
|
}
|
|
1710
1755
|
return /* @__PURE__ */ jsxs(Container, { className: "w-full flex flex-col py-4 gap-4", maxWidth: "6xl", children: [
|
|
1711
|
-
!hasAdmin &&
|
|
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: [
|
|
1712
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." }) }),
|
|
1713
1758
|
/* @__PURE__ */ jsx(
|
|
1714
1759
|
Button,
|