fix infinity auth redirecting

add validation oidp server
This commit is contained in:
NlightN22 2024-03-01 18:21:07 +07:00
parent 811bc5bac7
commit a5e11f067c
6 changed files with 61 additions and 22 deletions

View File

@ -14,7 +14,7 @@ services:
REACT_APP_OPENID_SERVER: https://server:port/realms/your-realm REACT_APP_OPENID_SERVER: https://server:port/realms/your-realm
REACT_APP_CLIENT_ID: frontend-client REACT_APP_CLIENT_ID: frontend-client
ports: ports:
- 5173:80 # set your port here - 80:80 # set your port here
``` ```
- run: - run:
```bash ```bash

View File

@ -10,4 +10,4 @@ services:
REACT_APP_OPENID_SERVER: https://server:port/realms/your-realm REACT_APP_OPENID_SERVER: https://server:port/realms/your-realm
REACT_APP_CLIENT_ID: frontend-client REACT_APP_CLIENT_ID: frontend-client
ports: ports:
- 5173:80 # set your port here - 80:80 # set your port here

View File

@ -4,11 +4,12 @@ import CenterLoader from './shared/components/loaders/CenterLoader';
import { ColorScheme, ColorSchemeProvider, MantineProvider } from '@mantine/core'; import { ColorScheme, ColorSchemeProvider, MantineProvider } from '@mantine/core';
import { useColorScheme } from '@mantine/hooks'; import { useColorScheme } from '@mantine/hooks';
import { getCookie, setCookie } from 'cookies-next'; import { getCookie, setCookie } from 'cookies-next';
import { BrowserRouter } from 'react-router-dom';
import AppBody from './AppBody'; import AppBody from './AppBody';
import Forbidden from './pages/403'; import Forbidden from './pages/403';
import { Notifications } from '@mantine/notifications'; import { Notifications } from '@mantine/notifications';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import RetryErrorPage from './pages/RetryErrorPage';
import { keycloakConfig } from '.';
const queryClient = new QueryClient({ const queryClient = new QueryClient({
@ -20,8 +21,10 @@ const queryClient = new QueryClient({
}) })
function App() { function App() {
const maxErrorAuthConts = 10
const systemColorScheme = useColorScheme() const systemColorScheme = useColorScheme()
const [colorScheme, setColorScheme] = useState<ColorScheme>(getCookie('mantine-color-scheme') as ColorScheme || systemColorScheme); const [colorScheme, setColorScheme] = useState<ColorScheme>(getCookie('mantine-color-scheme') as ColorScheme || systemColorScheme)
const [authErrorCounter, setAuthErrorCounter] = useState(0)
const toggleColorScheme = (value?: ColorScheme) => { const toggleColorScheme = (value?: ColorScheme) => {
const nextColorScheme = value || (colorScheme === 'dark' ? 'light' : 'dark'); const nextColorScheme = value || (colorScheme === 'dark' ? 'light' : 'dark');
setColorScheme(nextColorScheme) setColorScheme(nextColorScheme)
@ -33,15 +36,32 @@ function App() {
// automatically sign-in // automatically sign-in
useEffect(() => { useEffect(() => {
if (!hasAuthParams() && if (!hasAuthParams() &&
!auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading) { !auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading && authErrorCounter < maxErrorAuthConts) {
auth.signinRedirect(); console.log('not authenticated! redirect!')
auth.signinRedirect()
} }
}, [auth, auth.isAuthenticated, auth.activeNavigator, auth.isLoading, auth.signinRedirect]); }, [auth, auth.isAuthenticated, auth.activeNavigator, auth.isLoading, auth.signinRedirect])
if (auth.activeNavigator || auth.isLoading) { if (auth.activeNavigator || auth.isLoading) {
return <CenterLoader /> return <CenterLoader />
} }
if ((!auth.isAuthenticated && !auth.isLoading) || auth.error) {
if (authErrorCounter > maxErrorAuthConts) {
console.log('maxErrorAuthConts authority', keycloakConfig.authority)
console.log('maxErrorAuthConts client_id', keycloakConfig.client_id)
console.log('maxErrorAuthConts redirect_uri', keycloakConfig.redirect_uri)
return <RetryErrorPage backVisible={false} mainVisible={false} onRetry={() => auth.signinRedirect()} />
}
if (!auth.isAuthenticated && !auth.isLoading && authErrorCounter < maxErrorAuthConts) {
console.log('not authenticated! redirect!')
setAuthErrorCounter(prevCount => prevCount + 1);
auth.signinRedirect()
}
if ((!hasAuthParams() && !auth.isAuthenticated && !auth.isLoading) || auth.error) {
setAuthErrorCounter(prevCount => prevCount + 1);
console.error(`auth.error:`, auth.error) console.error(`auth.error:`, auth.error)
return <Forbidden /> return <Forbidden />
} }
@ -72,10 +92,8 @@ function App() {
} }
}} }}
> >
<BrowserRouter> <Notifications />
<Notifications /> <AppBody />
<AppBody />
</BrowserRouter>
</MantineProvider > </MantineProvider >
</ColorSchemeProvider> </ColorSchemeProvider>
</div> </div>

View File

@ -4,7 +4,8 @@ import App from './App';
import reportWebVitals from './reportWebVitals'; import reportWebVitals from './reportWebVitals';
import RootStore from './shared/stores/root.store'; import RootStore from './shared/stores/root.store';
import { AuthProvider, AuthProviderProps } from 'react-oidc-context'; import { AuthProvider, AuthProviderProps } from 'react-oidc-context';
import { oidpSettings } from './shared/env.const'; import { isProduction, oidpSettings } from './shared/env.const';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement document.getElementById('root') as HTMLElement
@ -21,14 +22,22 @@ export const keycloakConfig: AuthProviderProps = {
const rootStore = new RootStore() const rootStore = new RootStore()
export const Context = createContext<RootStore>(rootStore) export const Context = createContext<RootStore>(rootStore)
if (!isProduction) {
console.log('keycloakConfig.authority', keycloakConfig.authority)
console.log('keycloakConfig.client_id', keycloakConfig.client_id)
console.log('keycloakConfig.redirect_uri', keycloakConfig.redirect_uri)
}
root.render( root.render(
<Context.Provider value={rootStore}> <Context.Provider value={rootStore}>
<AuthProvider {...keycloakConfig}> <AuthProvider {...keycloakConfig}>
<BrowserRouter>
{/* <React.StrictMode> */} {/* <React.StrictMode> */}
<App /> <App />
{/* </React.StrictMode> */} {/* </React.StrictMode> */}
</AuthProvider> </BrowserRouter>
</Context.Provider> </AuthProvider>
</Context.Provider>
); );
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function

View File

@ -8,10 +8,18 @@ import { Context } from '..';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
interface RetryErrorPageProps { interface RetryErrorPageProps {
repeatVisible?: boolean
backVisible?: boolean
mainVisible?: boolean
onRetry?: () => void onRetry?: () => void
} }
const RetryErrorPage = ({ onRetry }: RetryErrorPageProps) => { const RetryErrorPage = ({
repeatVisible = true,
backVisible = true,
mainVisible = true,
onRetry
}: RetryErrorPageProps) => {
const executed = useRef(false) const executed = useRef(false)
const navigate = useNavigate() const navigate = useNavigate()
@ -45,9 +53,9 @@ const RetryErrorPage = ({ onRetry }: RetryErrorPageProps) => {
{ExclamationCogWheel} {ExclamationCogWheel}
<Text fz='lg' fw={700}>{strings.youCanRetryOrGoToMain}</Text> <Text fz='lg' fw={700}>{strings.youCanRetryOrGoToMain}</Text>
<Flex> <Flex>
<Button ml='1rem' onClick={handleRetry}>{strings.retry}</Button> {repeatVisible ? <Button ml='1rem' onClick={handleRetry}>{strings.retry}</Button> : null}
<Button ml='1rem' onClick={handleGoBack}>{strings.back}</Button> { backVisible ? <Button ml='1rem' onClick={handleGoBack}>{strings.back}</Button> : null }
<Button ml='1rem' onClick={handleGoToMain}>{strings.goToMainPage}</Button> { mainVisible ? <Button ml='1rem' onClick={handleGoToMain}>{strings.goToMainPage}</Button> : null }
</Flex> </Flex>
</Flex> </Flex>
); );

View File

@ -1,3 +1,5 @@
import { z } from "zod"
export const appMode = process.env.NODE_ENV export const appMode = process.env.NODE_ENV
export const isProduction = appMode === "production" export const isProduction = appMode === "production"
export const host = isProduction ? window.env?.REACT_APP_HOST : process.env.HOST export const host = isProduction ? window.env?.REACT_APP_HOST : process.env.HOST
@ -8,6 +10,8 @@ const proxy = isProduction ? window.env?.REACT_APP_FRIGATE_PROXY : process.env.
export const proxyURL = new URL(proxy || '') export const proxyURL = new URL(proxy || '')
const oidpServer = isProduction ? window.env?.REACT_APP_OPENID_SERVER : process.env.REACT_APP_OPENID_SERVER const oidpServer = isProduction ? window.env?.REACT_APP_OPENID_SERVER : process.env.REACT_APP_OPENID_SERVER
const oidpServerParsed= z.string().url().safeParse(oidpServer)
if (!oidpServerParsed.success) throw Error('REACT_APP_OPENID_SERVER must be string and URL')
const oidpClientId = isProduction ? window.env?.REACT_APP_CLIENT_ID : process.env.REACT_APP_CLIENT_ID const oidpClientId = isProduction ? window.env?.REACT_APP_CLIENT_ID : process.env.REACT_APP_CLIENT_ID
export const oidpSettings = { export const oidpSettings = {
server: oidpServer || '', server: oidpServer || '',