@authhero/react-admin 0.10.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/.eslintrc.js +21 -0
- package/.vercelignore +4 -0
- package/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/index.html +125 -0
- package/package.json +61 -0
- package/prettier.config.js +1 -0
- package/public/favicon.ico +0 -0
- package/public/manifest.json +15 -0
- package/src/App.spec.tsx +42 -0
- package/src/App.tsx +232 -0
- package/src/AuthCallback.tsx +138 -0
- package/src/Layout.tsx +12 -0
- package/src/TenantsApp.tsx +115 -0
- package/src/auth0DataProvider.ts +1242 -0
- package/src/authProvider.ts +521 -0
- package/src/components/CertificateErrorDialog.tsx +116 -0
- package/src/components/DomainSelector.tsx +401 -0
- package/src/components/TenantAppBar.tsx +83 -0
- package/src/components/TenantLayout.tsx +25 -0
- package/src/components/TenantsAppBar.tsx +21 -0
- package/src/components/TenantsLayout.tsx +28 -0
- package/src/components/activity/ActivityDashboard.tsx +381 -0
- package/src/components/activity/index.ts +1 -0
- package/src/components/branding/BrandingList.tsx +0 -0
- package/src/components/branding/BrandingShow.tsx +0 -0
- package/src/components/branding/ThemesTab.tsx +286 -0
- package/src/components/branding/edit.tsx +149 -0
- package/src/components/branding/hooks/useThemesData.ts +123 -0
- package/src/components/branding/index.ts +2 -0
- package/src/components/branding/list.tsx +12 -0
- package/src/components/clients/create.tsx +12 -0
- package/src/components/clients/edit.tsx +1285 -0
- package/src/components/clients/index.ts +3 -0
- package/src/components/clients/list.tsx +37 -0
- package/src/components/common/DateAgo.tsx +6 -0
- package/src/components/common/JsonOutput.tsx +26 -0
- package/src/components/common/index.ts +1 -0
- package/src/components/connections/create.tsx +35 -0
- package/src/components/connections/edit.tsx +212 -0
- package/src/components/connections/index.ts +3 -0
- package/src/components/connections/list.tsx +15 -0
- package/src/components/custom-domains/create.tsx +26 -0
- package/src/components/custom-domains/edit.tsx +101 -0
- package/src/components/custom-domains/index.ts +3 -0
- package/src/components/custom-domains/list.tsx +16 -0
- package/src/components/flows/create.tsx +30 -0
- package/src/components/flows/edit.tsx +238 -0
- package/src/components/flows/index.ts +3 -0
- package/src/components/flows/list.tsx +15 -0
- package/src/components/forms/FlowEditor.tsx +1363 -0
- package/src/components/forms/NodeEditor.tsx +1119 -0
- package/src/components/forms/RichTextEditor.tsx +145 -0
- package/src/components/forms/create.tsx +30 -0
- package/src/components/forms/edit.tsx +256 -0
- package/src/components/forms/index.ts +3 -0
- package/src/components/forms/list.tsx +16 -0
- package/src/components/hooks/create.tsx +96 -0
- package/src/components/hooks/edit.tsx +114 -0
- package/src/components/hooks/index.ts +3 -0
- package/src/components/hooks/list.tsx +17 -0
- package/src/components/listActions/PostListActions.tsx +10 -0
- package/src/components/logs/LogIcon.tsx +32 -0
- package/src/components/logs/LogShow.tsx +82 -0
- package/src/components/logs/LogType.tsx +38 -0
- package/src/components/logs/index.ts +4 -0
- package/src/components/logs/list.tsx +41 -0
- package/src/components/organizations/create.tsx +13 -0
- package/src/components/organizations/edit.tsx +682 -0
- package/src/components/organizations/index.ts +3 -0
- package/src/components/organizations/list.tsx +21 -0
- package/src/components/resource-servers/create.tsx +87 -0
- package/src/components/resource-servers/edit.tsx +121 -0
- package/src/components/resource-servers/index.ts +3 -0
- package/src/components/resource-servers/list.tsx +47 -0
- package/src/components/roles/create.tsx +12 -0
- package/src/components/roles/edit.tsx +426 -0
- package/src/components/roles/index.ts +3 -0
- package/src/components/roles/list.tsx +24 -0
- package/src/components/sessions/edit.tsx +101 -0
- package/src/components/sessions/index.ts +3 -0
- package/src/components/sessions/list.tsx +20 -0
- package/src/components/sessions/show.tsx +113 -0
- package/src/components/settings/edit.tsx +236 -0
- package/src/components/settings/index.ts +2 -0
- package/src/components/settings/list.tsx +14 -0
- package/src/components/tenants/create.tsx +20 -0
- package/src/components/tenants/edit.tsx +54 -0
- package/src/components/tenants/index.ts +2 -0
- package/src/components/tenants/list.tsx +67 -0
- package/src/components/themes/edit.tsx +200 -0
- package/src/components/themes/index.ts +2 -0
- package/src/components/themes/list.tsx +12 -0
- package/src/components/users/create.tsx +144 -0
- package/src/components/users/edit.tsx +1711 -0
- package/src/components/users/index.ts +3 -0
- package/src/components/users/list.tsx +35 -0
- package/src/data.json +121 -0
- package/src/dataProvider.ts +97 -0
- package/src/index.tsx +106 -0
- package/src/lib/logs.ts +21 -0
- package/src/types/reactflow.d.ts +86 -0
- package/src/utils/domainUtils.ts +169 -0
- package/src/utils/tokenUtils.ts +75 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +37 -0
- package/tsconfig.node.json +10 -0
- package/vercel.json +17 -0
- package/vite.config.ts +30 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useEffect, useState, useRef } from "react";
|
|
2
|
+
import { useNavigate, useLocation } from "react-router-dom";
|
|
3
|
+
import { CircularProgress, Box, Typography, Alert } from "@mui/material";
|
|
4
|
+
import { getSelectedDomainFromStorage } from "./utils/domainUtils";
|
|
5
|
+
import { createAuth0Client } from "./authProvider";
|
|
6
|
+
|
|
7
|
+
interface AuthCallbackProps {
|
|
8
|
+
onAuthComplete?: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function AuthCallback({ onAuthComplete }: AuthCallbackProps) {
|
|
12
|
+
const navigate = useNavigate();
|
|
13
|
+
const location = useLocation();
|
|
14
|
+
const [error, setError] = useState<string | null>(null);
|
|
15
|
+
const callbackProcessedRef = useRef(false);
|
|
16
|
+
const navigationAttemptedRef = useRef(false);
|
|
17
|
+
|
|
18
|
+
// Helper function to ensure navigation happens and prevent infinite loops
|
|
19
|
+
const forceNavigate = (path: string) => {
|
|
20
|
+
if (!navigationAttemptedRef.current) {
|
|
21
|
+
navigationAttemptedRef.current = true;
|
|
22
|
+
|
|
23
|
+
// Signal that auth flow is complete before navigation
|
|
24
|
+
if (onAuthComplete) onAuthComplete();
|
|
25
|
+
|
|
26
|
+
// Use window.location for a hard redirect instead of React Router's navigate
|
|
27
|
+
// This ensures we get a clean page load without any state issues
|
|
28
|
+
window.location.href = path;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const handleCallback = async () => {
|
|
34
|
+
// Skip if we've already processed this callback
|
|
35
|
+
if (callbackProcessedRef.current) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Get the currently selected domain from cookies
|
|
41
|
+
const selectedDomain = getSelectedDomainFromStorage();
|
|
42
|
+
|
|
43
|
+
if (!selectedDomain) {
|
|
44
|
+
// If no domain is selected, redirect to the home page to select one
|
|
45
|
+
forceNavigate("/");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check if we have the required query parameters
|
|
50
|
+
const hasAuthParams =
|
|
51
|
+
location.search.includes("code=") &&
|
|
52
|
+
location.search.includes("state=");
|
|
53
|
+
if (!hasAuthParams) {
|
|
54
|
+
forceNavigate("/tenants");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Get the auth0 client for the selected domain
|
|
59
|
+
const auth0 = createAuth0Client(selectedDomain);
|
|
60
|
+
|
|
61
|
+
// Mark that we've processed this callback to prevent duplicate processing
|
|
62
|
+
callbackProcessedRef.current = true;
|
|
63
|
+
|
|
64
|
+
// Process the redirect callback
|
|
65
|
+
// This is critical - it extracts the auth info from the URL and stores it
|
|
66
|
+
await auth0.handleRedirectCallback();
|
|
67
|
+
|
|
68
|
+
// We're being redirected back from the authentication provider
|
|
69
|
+
// Force redirect to the main application
|
|
70
|
+
forceNavigate("/tenants");
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// Only set error for real errors, not for "already processed" errors
|
|
73
|
+
if (err instanceof Error) {
|
|
74
|
+
const errorMessage = err.message || "";
|
|
75
|
+
|
|
76
|
+
// Don't show errors about invalid state or missing query params
|
|
77
|
+
// as these are expected in subsequent renders
|
|
78
|
+
if (
|
|
79
|
+
errorMessage.includes("Invalid state") ||
|
|
80
|
+
errorMessage.includes("no query params")
|
|
81
|
+
) {
|
|
82
|
+
forceNavigate("/tenants");
|
|
83
|
+
} else {
|
|
84
|
+
setError(err.message);
|
|
85
|
+
// Still try to navigate after a short delay even on error
|
|
86
|
+
setTimeout(() => forceNavigate("/tenants"), 3000);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
setError("Unknown error during authentication");
|
|
90
|
+
// Still try to navigate after a short delay even on error
|
|
91
|
+
setTimeout(() => forceNavigate("/tenants"), 3000);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Execute the callback handler
|
|
97
|
+
handleCallback();
|
|
98
|
+
|
|
99
|
+
// Failsafe - if we're still on this page after 5 seconds, force navigation
|
|
100
|
+
const timeoutId = setTimeout(() => {
|
|
101
|
+
if (!navigationAttemptedRef.current) {
|
|
102
|
+
forceNavigate("/tenants");
|
|
103
|
+
}
|
|
104
|
+
}, 5000);
|
|
105
|
+
|
|
106
|
+
return () => {
|
|
107
|
+
clearTimeout(timeoutId);
|
|
108
|
+
};
|
|
109
|
+
}, [navigate, onAuthComplete, location]);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Box
|
|
113
|
+
sx={{
|
|
114
|
+
display: "flex",
|
|
115
|
+
flexDirection: "column",
|
|
116
|
+
alignItems: "center",
|
|
117
|
+
justifyContent: "center",
|
|
118
|
+
height: "100vh",
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{error ? (
|
|
122
|
+
<Alert severity="error" sx={{ maxWidth: 600, mb: 2 }}>
|
|
123
|
+
{error}
|
|
124
|
+
<Typography variant="body2" sx={{ mt: 2 }}>
|
|
125
|
+
Redirecting to application...
|
|
126
|
+
</Typography>
|
|
127
|
+
</Alert>
|
|
128
|
+
) : (
|
|
129
|
+
<>
|
|
130
|
+
<CircularProgress />
|
|
131
|
+
<Typography variant="h6" sx={{ mt: 2 }}>
|
|
132
|
+
Completing authentication...
|
|
133
|
+
</Typography>
|
|
134
|
+
</>
|
|
135
|
+
)}
|
|
136
|
+
</Box>
|
|
137
|
+
);
|
|
138
|
+
}
|
package/src/Layout.tsx
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Layout as RALayout,
|
|
4
|
+
CheckForApplicationUpdate,
|
|
5
|
+
} from "react-admin";
|
|
6
|
+
|
|
7
|
+
export const Layout = ({ children }: { children: ReactNode }) => (
|
|
8
|
+
<RALayout>
|
|
9
|
+
{children}
|
|
10
|
+
<CheckForApplicationUpdate />
|
|
11
|
+
</RALayout>
|
|
12
|
+
);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Admin, Resource } from "react-admin";
|
|
2
|
+
import { getDataprovider } from "./dataProvider";
|
|
3
|
+
import { getAuthProvider } from "./authProvider";
|
|
4
|
+
import { TenantsList } from "./components/tenants/list";
|
|
5
|
+
import { TenantsCreate } from "./components/tenants/create";
|
|
6
|
+
import { useMemo, useState } from "react";
|
|
7
|
+
import { Button } from "@mui/material";
|
|
8
|
+
import { DomainSelector } from "./components/DomainSelector";
|
|
9
|
+
import { saveSelectedDomainToStorage } from "./utils/domainUtils";
|
|
10
|
+
import { tenantsLayout } from "./components/TenantsLayout";
|
|
11
|
+
import { CertificateErrorDialog } from "./components/CertificateErrorDialog";
|
|
12
|
+
|
|
13
|
+
interface TenantsAppProps {
|
|
14
|
+
initialDomain?: string;
|
|
15
|
+
onAuthComplete?: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function TenantsApp(props: TenantsAppProps = {}) {
|
|
19
|
+
const { initialDomain, onAuthComplete } = props;
|
|
20
|
+
|
|
21
|
+
// State for domains and domain selector dialog
|
|
22
|
+
const [selectedDomain, setSelectedDomain] = useState<string>(
|
|
23
|
+
initialDomain || "",
|
|
24
|
+
);
|
|
25
|
+
const [showDomainDialog, setShowDomainDialog] = useState<boolean>(false);
|
|
26
|
+
const [certErrorUrl, setCertErrorUrl] = useState<string | null>(null);
|
|
27
|
+
|
|
28
|
+
// Use useMemo to prevent recreating the auth provider on every render
|
|
29
|
+
const authProvider = useMemo(
|
|
30
|
+
() => getAuthProvider(selectedDomain, onAuthComplete),
|
|
31
|
+
[selectedDomain, onAuthComplete],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Get the dataProvider with the selected domain - also memoize this
|
|
35
|
+
// Wrap it to catch certificate errors
|
|
36
|
+
const dataProvider = useMemo(() => {
|
|
37
|
+
const baseProvider = getDataprovider(
|
|
38
|
+
selectedDomain || import.meta.env.VITE_AUTH0_DOMAIN || "",
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Wrap all methods to catch certificate errors
|
|
42
|
+
const wrappedProvider: typeof baseProvider = {} as typeof baseProvider;
|
|
43
|
+
for (const method of Object.keys(baseProvider) as Array<
|
|
44
|
+
keyof typeof baseProvider
|
|
45
|
+
>) {
|
|
46
|
+
const original = baseProvider[method];
|
|
47
|
+
if (typeof original === "function") {
|
|
48
|
+
(wrappedProvider as any)[method] = async (...args: any[]) => {
|
|
49
|
+
try {
|
|
50
|
+
return await (original as Function).apply(baseProvider, args);
|
|
51
|
+
} catch (error: any) {
|
|
52
|
+
if (error?.isCertificateError && error?.serverUrl) {
|
|
53
|
+
setCertErrorUrl(error.serverUrl);
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return wrappedProvider;
|
|
61
|
+
}, [selectedDomain]);
|
|
62
|
+
|
|
63
|
+
const openDomainManager = () => {
|
|
64
|
+
setShowDomainDialog(true);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleDomainSelected = (domain: string) => {
|
|
68
|
+
setSelectedDomain(domain);
|
|
69
|
+
setShowDomainDialog(false);
|
|
70
|
+
saveSelectedDomainToStorage(domain);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleCloseCertError = () => {
|
|
74
|
+
setCertErrorUrl(null);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Create the domain selector button that will be passed to the AppBar
|
|
78
|
+
const DomainSelectorButton = (
|
|
79
|
+
<Button
|
|
80
|
+
color="inherit"
|
|
81
|
+
onClick={openDomainManager}
|
|
82
|
+
sx={{ marginLeft: 1, textTransform: "none" }}
|
|
83
|
+
>
|
|
84
|
+
Domain: {selectedDomain}
|
|
85
|
+
</Button>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<>
|
|
90
|
+
{showDomainDialog && (
|
|
91
|
+
<DomainSelector onDomainSelected={handleDomainSelected} />
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
<CertificateErrorDialog
|
|
95
|
+
open={!!certErrorUrl}
|
|
96
|
+
serverUrl={certErrorUrl || ""}
|
|
97
|
+
onClose={handleCloseCertError}
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
<Admin
|
|
101
|
+
dataProvider={dataProvider}
|
|
102
|
+
authProvider={authProvider}
|
|
103
|
+
requireAuth={false}
|
|
104
|
+
layout={(props) =>
|
|
105
|
+
tenantsLayout({
|
|
106
|
+
...props,
|
|
107
|
+
domainSelectorButton: DomainSelectorButton,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
>
|
|
111
|
+
<Resource name="tenants" list={TenantsList} create={TenantsCreate} />
|
|
112
|
+
</Admin>
|
|
113
|
+
</>
|
|
114
|
+
);
|
|
115
|
+
}
|