@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.
Files changed (110) hide show
  1. package/.eslintrc.js +21 -0
  2. package/.vercelignore +4 -0
  3. package/CHANGELOG.md +56 -0
  4. package/LICENSE +21 -0
  5. package/README.md +50 -0
  6. package/index.html +125 -0
  7. package/package.json +61 -0
  8. package/prettier.config.js +1 -0
  9. package/public/favicon.ico +0 -0
  10. package/public/manifest.json +15 -0
  11. package/src/App.spec.tsx +42 -0
  12. package/src/App.tsx +232 -0
  13. package/src/AuthCallback.tsx +138 -0
  14. package/src/Layout.tsx +12 -0
  15. package/src/TenantsApp.tsx +115 -0
  16. package/src/auth0DataProvider.ts +1242 -0
  17. package/src/authProvider.ts +521 -0
  18. package/src/components/CertificateErrorDialog.tsx +116 -0
  19. package/src/components/DomainSelector.tsx +401 -0
  20. package/src/components/TenantAppBar.tsx +83 -0
  21. package/src/components/TenantLayout.tsx +25 -0
  22. package/src/components/TenantsAppBar.tsx +21 -0
  23. package/src/components/TenantsLayout.tsx +28 -0
  24. package/src/components/activity/ActivityDashboard.tsx +381 -0
  25. package/src/components/activity/index.ts +1 -0
  26. package/src/components/branding/BrandingList.tsx +0 -0
  27. package/src/components/branding/BrandingShow.tsx +0 -0
  28. package/src/components/branding/ThemesTab.tsx +286 -0
  29. package/src/components/branding/edit.tsx +149 -0
  30. package/src/components/branding/hooks/useThemesData.ts +123 -0
  31. package/src/components/branding/index.ts +2 -0
  32. package/src/components/branding/list.tsx +12 -0
  33. package/src/components/clients/create.tsx +12 -0
  34. package/src/components/clients/edit.tsx +1285 -0
  35. package/src/components/clients/index.ts +3 -0
  36. package/src/components/clients/list.tsx +37 -0
  37. package/src/components/common/DateAgo.tsx +6 -0
  38. package/src/components/common/JsonOutput.tsx +26 -0
  39. package/src/components/common/index.ts +1 -0
  40. package/src/components/connections/create.tsx +35 -0
  41. package/src/components/connections/edit.tsx +212 -0
  42. package/src/components/connections/index.ts +3 -0
  43. package/src/components/connections/list.tsx +15 -0
  44. package/src/components/custom-domains/create.tsx +26 -0
  45. package/src/components/custom-domains/edit.tsx +101 -0
  46. package/src/components/custom-domains/index.ts +3 -0
  47. package/src/components/custom-domains/list.tsx +16 -0
  48. package/src/components/flows/create.tsx +30 -0
  49. package/src/components/flows/edit.tsx +238 -0
  50. package/src/components/flows/index.ts +3 -0
  51. package/src/components/flows/list.tsx +15 -0
  52. package/src/components/forms/FlowEditor.tsx +1363 -0
  53. package/src/components/forms/NodeEditor.tsx +1119 -0
  54. package/src/components/forms/RichTextEditor.tsx +145 -0
  55. package/src/components/forms/create.tsx +30 -0
  56. package/src/components/forms/edit.tsx +256 -0
  57. package/src/components/forms/index.ts +3 -0
  58. package/src/components/forms/list.tsx +16 -0
  59. package/src/components/hooks/create.tsx +96 -0
  60. package/src/components/hooks/edit.tsx +114 -0
  61. package/src/components/hooks/index.ts +3 -0
  62. package/src/components/hooks/list.tsx +17 -0
  63. package/src/components/listActions/PostListActions.tsx +10 -0
  64. package/src/components/logs/LogIcon.tsx +32 -0
  65. package/src/components/logs/LogShow.tsx +82 -0
  66. package/src/components/logs/LogType.tsx +38 -0
  67. package/src/components/logs/index.ts +4 -0
  68. package/src/components/logs/list.tsx +41 -0
  69. package/src/components/organizations/create.tsx +13 -0
  70. package/src/components/organizations/edit.tsx +682 -0
  71. package/src/components/organizations/index.ts +3 -0
  72. package/src/components/organizations/list.tsx +21 -0
  73. package/src/components/resource-servers/create.tsx +87 -0
  74. package/src/components/resource-servers/edit.tsx +121 -0
  75. package/src/components/resource-servers/index.ts +3 -0
  76. package/src/components/resource-servers/list.tsx +47 -0
  77. package/src/components/roles/create.tsx +12 -0
  78. package/src/components/roles/edit.tsx +426 -0
  79. package/src/components/roles/index.ts +3 -0
  80. package/src/components/roles/list.tsx +24 -0
  81. package/src/components/sessions/edit.tsx +101 -0
  82. package/src/components/sessions/index.ts +3 -0
  83. package/src/components/sessions/list.tsx +20 -0
  84. package/src/components/sessions/show.tsx +113 -0
  85. package/src/components/settings/edit.tsx +236 -0
  86. package/src/components/settings/index.ts +2 -0
  87. package/src/components/settings/list.tsx +14 -0
  88. package/src/components/tenants/create.tsx +20 -0
  89. package/src/components/tenants/edit.tsx +54 -0
  90. package/src/components/tenants/index.ts +2 -0
  91. package/src/components/tenants/list.tsx +67 -0
  92. package/src/components/themes/edit.tsx +200 -0
  93. package/src/components/themes/index.ts +2 -0
  94. package/src/components/themes/list.tsx +12 -0
  95. package/src/components/users/create.tsx +144 -0
  96. package/src/components/users/edit.tsx +1711 -0
  97. package/src/components/users/index.ts +3 -0
  98. package/src/components/users/list.tsx +35 -0
  99. package/src/data.json +121 -0
  100. package/src/dataProvider.ts +97 -0
  101. package/src/index.tsx +106 -0
  102. package/src/lib/logs.ts +21 -0
  103. package/src/types/reactflow.d.ts +86 -0
  104. package/src/utils/domainUtils.ts +169 -0
  105. package/src/utils/tokenUtils.ts +75 -0
  106. package/src/vite-env.d.ts +1 -0
  107. package/tsconfig.json +37 -0
  108. package/tsconfig.node.json +10 -0
  109. package/vercel.json +17 -0
  110. 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
+ }