feat(auth): registration
This commit is contained in:
parent
f588f69f66
commit
4364294e75
|
@ -14,6 +14,7 @@
|
||||||
"@bem-react/classnames": "^1.3.10",
|
"@bem-react/classnames": "^1.3.10",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@mui/icons-material": "^6.4.1",
|
||||||
"@mui/material": "^6.4.1",
|
"@mui/material": "^6.4.1",
|
||||||
"keycloak-js": "^26.1.0",
|
"keycloak-js": "^26.1.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
@ -674,6 +675,32 @@
|
||||||
"url": "https://opencollective.com/mui-org"
|
"url": "https://opencollective.com/mui-org"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/icons-material": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-wsxFcUTQxt4s+7Bg4GgobqRjyaHLmZGNOs+HJpbwrwmLbT6mhIJxhpqsKzzWq9aDY8xIe7HCjhpH7XI5UD6teA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@mui/material": "^6.4.1",
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mui/material": {
|
"node_modules/@mui/material": {
|
||||||
"version": "6.4.1",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.1.tgz",
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"@bem-react/classnames": "^1.3.10",
|
"@bem-react/classnames": "^1.3.10",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@mui/icons-material": "^6.4.1",
|
||||||
"@mui/material": "^6.4.1",
|
"@mui/material": "^6.4.1",
|
||||||
"keycloak-js": "^26.1.0",
|
"keycloak-js": "^26.1.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||||
import { ThemeProvider } from '@mui/material';
|
import { ThemeProvider } from '@mui/material';
|
||||||
import { Root } from '../pages/root';
|
import { Root } from '../pages/root';
|
||||||
import { Search } from '../pages/search';
|
import { Search } from '../pages/search';
|
||||||
|
import { Header } from '../features/auth';
|
||||||
import theme from '../features/theme';
|
import theme from '../features/theme';
|
||||||
|
|
||||||
import './normalize.css';
|
import './normalize.css';
|
||||||
|
@ -12,6 +13,7 @@ export const App: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
<Header />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/search' element={<Search />} />
|
<Route path='/search' element={<Search />} />
|
||||||
<Route path='/' element={<Root />} />
|
<Route path='/' element={<Root />} />
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
.Header {
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid #444444;
|
||||||
|
|
||||||
|
&-Auth {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-Bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { cn } from '@bem-react/classname';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Button, IconButton } from '@mui/material';
|
||||||
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
|
import './header.scss';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import useKeycloakAuth from './keycloak';
|
||||||
|
|
||||||
|
const name = cn('Header');
|
||||||
|
|
||||||
|
export const Header: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const {authenticated, keycloak} = useKeycloakAuth();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className={name()}>
|
||||||
|
{authenticated ? <div className={name('Bar')}>
|
||||||
|
{!window.location.pathname.includes('/search') ?
|
||||||
|
<Button
|
||||||
|
variant='contained'
|
||||||
|
onClick={() => navigate('/search')}
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button> : <Button
|
||||||
|
variant='contained'
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>}
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
variant='contained'
|
||||||
|
onClick={() => keycloak.logout()}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
<IconButton
|
||||||
|
sx={{alignSelf: 'center', ml: 2}}
|
||||||
|
onClick={() => document.location = `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}/account/`}
|
||||||
|
>
|
||||||
|
<SettingsIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</div> : <div className={name('Auth')}>
|
||||||
|
<Button
|
||||||
|
variant='contained'
|
||||||
|
onClick={() => keycloak.login()}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{ ml: 2 }}
|
||||||
|
variant='contained'
|
||||||
|
onClick={() => keycloak.register()}
|
||||||
|
>
|
||||||
|
Registration
|
||||||
|
</Button>
|
||||||
|
</div>}
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './header';
|
||||||
|
export * from './keycloak';
|
|
@ -1,4 +1,5 @@
|
||||||
import Keycloak from 'keycloak-js';
|
import Keycloak from 'keycloak-js';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
const kc = new Keycloak({
|
const kc = new Keycloak({
|
||||||
url: process.env.KEYCLOAK_URL as string,
|
url: process.env.KEYCLOAK_URL as string,
|
||||||
|
@ -6,15 +7,33 @@ const kc = new Keycloak({
|
||||||
clientId: process.env.KEYCLOAK_CLIENT as string,
|
clientId: process.env.KEYCLOAK_CLIENT as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
kc.init({
|
const initKeycloak = () => {
|
||||||
|
return kc.init({
|
||||||
onLoad: 'check-sso',
|
onLoad: 'check-sso',
|
||||||
silentCheckSsoRedirectUri: window.location.origin + "/silent-check-sso.html",
|
silentCheckSsoRedirectUri: window.location.origin + "/silent-check-sso.html",
|
||||||
checkLoginIframe: false,
|
checkLoginIframe: false,
|
||||||
pkceMethod: 'S256'
|
pkceMethod: 'S256',
|
||||||
}).then(() => {
|
});
|
||||||
if (kc.onTokenExpired) {
|
};
|
||||||
kc.onTokenExpired = () => kc.updateToken();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default kc;
|
export default function useKeycloakAuth() {
|
||||||
|
const [authenticated, setAuthenticated] = useState<boolean | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initKeycloak().then(auth => {
|
||||||
|
setAuthenticated(auth);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle token expiration
|
||||||
|
if (kc.onTokenExpired) {
|
||||||
|
kc.onTokenExpired = () => kc.updateToken().then(() => setAuthenticated(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for authentication events
|
||||||
|
kc.onAuthSuccess = () => setAuthenticated(true);
|
||||||
|
kc.onAuthError = () => setAuthenticated(false);
|
||||||
|
kc.onAuthLogout = () => setAuthenticated(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { authenticated, keycloak: kc };
|
||||||
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
.Page {
|
.Page {
|
||||||
background-color: $background-secondary;
|
background-color: $background-secondary;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
Loading…
Reference in New Issue