Skip to content

Commit

Permalink
Merge pull request #22 from BTE-Germany/21-fix-authentication
Browse files Browse the repository at this point in the history
⚡ Migrate to new OIDC client to fix auth and improve performance
  • Loading branch information
Nachwahl authored Aug 18, 2024
2 parents 31b5620 + a11336b commit b8fef2e
Show file tree
Hide file tree
Showing 16 changed files with 142 additions and 84 deletions.
2 changes: 2 additions & 0 deletions frontend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ VITE_WS_HOST=https://map.bte-germany.de
VITE_SEARCH_URL=https://search.bte-germany.de
VITE_SEARCH_KEY=ac0f027dadb439dbf0d4fc7f3e00da433aa872dae2f75417ba01ad0d0e6b0035
VITE_SEARCH_INDEX=map
VITE_OIDC_ISSUER=https://auth.bte-germany.de/realms/btegermany
VITE_OIDC_CLIENT_ID=mapfrontend

WS_HOST=https://map.bte-germany.de
SEARCH_URL=https://search.bte-germany.de
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"mapbox-gl": "^2.12.0",
"mapbox-gl-style-switcher": "^1.0.11",
"meilisearch": "^0.30.0",
"oidc-spa": "^5.1.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-icons": "^4.7.1",
Expand Down
16 changes: 16 additions & 0 deletions frontend/public/silent-sso.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!--
~ silent-sso.htm
~
~ Copyright (c) 2024 Robin Ferch
~ https://robinferch.me
~ This project is released under the MIT license.
-->

<!doctype html>
<html>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>
14 changes: 6 additions & 8 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ App.jsx +
+ +
+ Copyright (c) 2022-2023 Robin Ferch +
+ Copyright (c) 2022-2024 Robin Ferch +
+ https://robinferch.me +
+ This project is released under the MIT license. +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
Expand All @@ -20,11 +20,10 @@ import Stats from "./pages/Stats";
import Admin from "./pages/Admin";
import User from "./pages/User";
import {ErrorBoundary} from "./components/ErrorBoundary";
import {OidcProvider} from "./oidc";

function App() {
const keycloak = new Keycloak({
"url": "https://auth.bte-germany.de", "realm": "btegermany", "clientId": "mapfrontend"
});

const [colorScheme, setColorScheme] = useState(window.localStorage.getItem("color-scheme") || "dark");
const toggleColorScheme = (value) => {

Expand All @@ -39,9 +38,8 @@ function App() {
<NotificationsProvider>
<ModalsProvider>
<ErrorBoundary>
<ReactKeycloakProvider
authClient={keycloak}
LoadingComponent={<div style={{
<OidcProvider
fallback={<div style={{
display: "absolute",
top: "0",
left: "0",
Expand All @@ -61,7 +59,7 @@ function App() {
<Route path="/stats/:username" element={<User />} exact />
</Routes>
</ProvideAuth>
</ReactKeycloakProvider>
</OidcProvider>
</ErrorBoundary>

</ModalsProvider>
Expand Down
32 changes: 14 additions & 18 deletions frontend/src/components/AccountButton.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ AccountButton.jsx +
+ +
+ Copyright (c) 2022-2023 Robin Ferch +
+ Copyright (c) 2022-2024 Robin Ferch +
+ https://robinferch.me +
+ This project is released under the MIT license. +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
Expand All @@ -19,23 +19,19 @@ import {useUser} from "../hooks/useUser";
import {showNotification} from "@mantine/notifications";
import axios from "axios";
import {BiDotsVerticalRounded} from "react-icons/bi";
import {useOidc} from "../oidc";

const AccountButton = props => {

const {keycloak} = useKeycloak()
const { isUserLoggedIn, login, logout, oidcTokens, initializationError } = useOidc();

const user = useUser();


const login = useCallback(() => {
keycloak?.login()
}, [keycloak])

const logout = useCallback(() => {
keycloak?.logout()
}, [keycloak])

const unlinkUser = async () => {
await axios.post(`/api/v1/user/unlink`, {}, {headers: {authorization: "Bearer " + keycloak.token}})
await axios.post(`/api/v1/user/unlink`, {}, {headers: {authorization: "Bearer " + oidcTokens.accessToken}})
await user.updateData();
showNotification({
title: 'Unlink successful', message: 'Your account was unlinked successfully.', color: "green"
Expand All @@ -50,17 +46,17 @@ const AccountButton = props => {

<Menu.Dropdown>
<Menu.Label>Account</Menu.Label>
{keycloak?.authenticated && <Menu.Item icon={<AiOutlineUser size={14}/>}
disabled>Hey, {keycloak?.tokenParsed.preferred_username}</Menu.Item>}
{keycloak?.authenticated ?
<Menu.Item icon={<FiLock size={14}/>} onClick={() => logout()}>Logout</Menu.Item> :
<Menu.Item icon={<FiLock size={14}/>} onClick={() => login()}>Login</Menu.Item>}
{isUserLoggedIn && <Menu.Item icon={<AiOutlineUser size={14}/>}
disabled>Hey, {oidcTokens.decodedIdToken.preferred_username}</Menu.Item>}
{isUserLoggedIn ?
<Menu.Item icon={<FiLock size={14}/>} onClick={() => logout({redirectTo: "current page"})}>Logout</Menu.Item> :
<Menu.Item icon={<FiLock size={14}/>} onClick={() => login({doesCurrentHrefRequiresAuth: false})}>Login</Menu.Item>}

{(keycloak?.authenticated && !user?.data?.minecraftUUID) &&
{(isUserLoggedIn && !user?.data?.minecraftUUID) &&
<Menu.Item icon={<FiLink2 size={14}/>} component={Link} to={"/link"}>Link Minecraft
Account</Menu.Item>}

{(keycloak?.authenticated && user?.data?.minecraftUUID) &&
{(isUserLoggedIn && user?.data?.minecraftUUID) &&
<Menu.Item icon={<FiLink2 size={14}/>} onClick={unlinkUser}>Unlink Minecraft
Account</Menu.Item>}

Expand All @@ -71,9 +67,9 @@ const AccountButton = props => {
Server</Menu.Item>
<Menu.Item component={"a"} href="https://bte-germany.de" target={"_blank"}
icon={<FiGlobe size={14}/>}>Website</Menu.Item>
<Menu.Item component={"a"} href="https://robinferch.me/legal" target={"_blank"}
<Menu.Item component={"a"} href="https://bte-germany.de/legal" target={"_blank"}
icon={<IoMdPaper size={14}/>}>Impressum</Menu.Item>
<Menu.Item component={"a"} href="https://robinferch.me/privacy" target={"_blank"}
<Menu.Item component={"a"} href="https://bte-germany.de/privacy" target={"_blank"}
icon={<MdOutlinePrivacyTip size={14}/>}>Privacy policy</Menu.Item>
</Menu.Dropdown>
</Menu>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/AdditionalBuildersDialog.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ AdditionalBuildersDialog.jsx +
+ +
+ Copyright (c) 2022 Robin Ferch +
+ Copyright (c) 2022-2024 Robin Ferch +
+ https://robinferch.me +
+ This project is released under the MIT license. +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
Expand All @@ -14,7 +14,7 @@ import {showNotification} from "@mantine/notifications";
import {useModals} from "@mantine/modals";
import {useDebouncedValue } from '@mantine/hooks';

const AdditionalBuildersDialog = ({regionId, keycloak, onUsers}) => {
const AdditionalBuildersDialog = ({regionId, onUsers}) => {
const modals = useModals();
const [loading, setLoading] = useState(false);
const [username, setUsername] = useState('');
Expand Down
15 changes: 8 additions & 7 deletions frontend/src/components/AdminGeneral.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ AdminGeneral.jsx +
+ +
+ Copyright (c) 2023 Robin Ferch +
+ Copyright (c) 2023-2024 Robin Ferch +
+ https://robinferch.me +
+ This project is released under the MIT license. +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
Expand All @@ -12,6 +12,7 @@ import axios from "axios";
import {useKeycloak} from "@react-keycloak-fork/web";
import {IoIosWarning} from "react-icons/io";
import {showNotification} from "@mantine/notifications";
import {useOidc} from "../oidc";

const AdminGeneral = props => {

Expand All @@ -22,7 +23,7 @@ const AdminGeneral = props => {
const [skipOld, setSkipOld] = useState(false);
const [skipOldOsm, setSkipOldOsm] = useState(false);

const {keycloak} = useKeycloak();
const { isUserLoggedIn, login, logout, oidcTokens } = useOidc();

useEffect(() => {
let interval;
Expand All @@ -31,9 +32,9 @@ const AdminGeneral = props => {
setAllBuildingsCount(statsData.totalBuildings)
interval = setInterval(async () => {
const {data: progress} = await axios.get(`/api/v1/admin/calculateProgress`,
{headers: {authorization: "Bearer " + keycloak.token}})
{headers: {authorization: "Bearer " + oidcTokens.accessToken}})
const {data: progressOsm} = await axios.get(`/api/v1/admin/osmDisplayNameProgress`,
{headers: {authorization: "Bearer " + keycloak.token}})
{headers: {authorization: "Bearer " + oidcTokens.accessToken}})

const {data: stats} = await axios.get(`/api/v1/stats/general`)
setAllBuildingsCount(stats.totalBuildings)
Expand All @@ -48,7 +49,7 @@ const AdminGeneral = props => {

const start = () => {
setProgress(0.0000000001);
axios.get(`/api/v1/admin/recalculateBuildings${skipOld ? "?skipOld=true" : ""}`, {headers: {authorization: "Bearer " + keycloak.token}}).then(({data}) => {
axios.get(`/api/v1/admin/recalculateBuildings${skipOld ? "?skipOld=true" : ""}`, {headers: {authorization: "Bearer " + oidcTokens.accessToken}}).then(({data}) => {
showNotification({
title: "Ok",
message: `${data.count} Regionen werden neu berechnet`
Expand All @@ -58,7 +59,7 @@ const AdminGeneral = props => {

const startOsm = () => {
setOsmProgress(0.0000000001);
axios.get(`/api/v1/admin/getOsmDisplayNames${skipOld ? "?skipOld=true" : ""}`, {headers: {authorization: "Bearer " + keycloak.token}}).then(({data}) => {
axios.get(`/api/v1/admin/getOsmDisplayNames${skipOld ? "?skipOld=true" : ""}`, {headers: {authorization: "Bearer " + oidcTokens.accessToken}}).then(({data}) => {
showNotification({
title: "Ok",
message: `Von ${data.count} Regionen werden die OSM Namen geholt.`,
Expand All @@ -73,7 +74,7 @@ const AdminGeneral = props => {
message: 'Synchronisiere Search-DB',
color: "green"
})
await axios.get(`/api/v1/admin/syncWithSearchDB`, {headers: {authorization: "Bearer " + keycloak.token}})
await axios.get(`/api/v1/admin/syncWithSearchDB`, {headers: {authorization: "Bearer " + oidcTokens.accessToken}})
showNotification({
title: 'Fertig',
message: 'Synchronisierung abgeschlossen',
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/components/AdminRegions.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ AdminRegions.jsx +
+ +
+ Copyright (c) 2024 Robin Ferch +
+ https://robinferch.me +
+ This project is released under the MIT license. +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

import React, {useEffect} from 'react';
import axios from "axios";
import {useKeycloak} from "@react-keycloak-fork/web";
Expand All @@ -9,10 +17,11 @@ import {BiEdit} from 'react-icons/bi';
import {MdDelete} from 'react-icons/md';
import {useModals} from "@mantine/modals";
import {showNotification} from "@mantine/notifications";
import {useOidc} from "../oidc";


const AdminRegions = () => {
const {keycloak} = useKeycloak();
const { isUserLoggedIn, login, logout, oidcTokens } = useOidc();
const modals = useModals();
const [regions, setRegions] = React.useState([]);
const [isLoading, setIsLoading] = React.useState(true);
Expand Down Expand Up @@ -55,7 +64,7 @@ const AdminRegions = () => {
//console.log('getRegions', {"sort": sort, "direction": direction, "page": currentPage, "pageSize": pageSize});

const {data} = await axios.get(`api/v1/region/all`, {
headers: {authorization: "Bearer " + keycloak.token},
headers: {authorization: "Bearer " + oidcTokens.accessToken},
params: {page: currentPage, size: pageSize, sort: sort, direction: direction},
});
console.log("response", data);
Expand All @@ -82,7 +91,7 @@ const AdminRegions = () => {
};

const deleteRegion = async (id) => {
await axios.delete(`/api/v1/region/${id}`, {headers: {authorization: "Bearer " + keycloak.token}});
await axios.delete(`/api/v1/region/${id}`, {headers: {authorization: "Bearer " + oidcTokens.accessToken}});
showNotification({
title: 'Region deleted!',
message: 'This region has been deleted.',
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/components/AdminUsers.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ AdminUsers.jsx +
+ +
+ Copyright (c) 2022 Robin Ferch +
+ Copyright (c) 2022-2024 Robin Ferch +
+ https://robinferch.me +
+ This project is released under the MIT license. +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
Expand All @@ -12,14 +12,16 @@ import {useKeycloak} from "@react-keycloak-fork/web";
import {ActionIcon, Badge, Box, Group, Loader, Select, Table, Tooltip, Pagination} from "@mantine/core";
import {BsFileEarmarkLock2} from "react-icons/bs";
import {BiLockOpen} from "react-icons/bi";
import {useOidc} from "../oidc";

const AdminUsers = (props) => {
const [users, setUsers] = React.useState([]);
const [isLoading, setIsLoading] = React.useState(true);
const [activePage, setPage] = React.useState(1);
const [currentPageSize, setPageSize] = React.useState(25);
const [totalPages, setTotalPages] = React.useState(12);
const {keycloak} = useKeycloak();
const { isUserLoggedIn, login, logout, oidcTokens } = useOidc();


useEffect(() => {
getUsers();
Expand All @@ -40,7 +42,7 @@ const AdminUsers = (props) => {
currentPage = currentPage === undefined ? activePage : currentPage;
pageSize = pageSize === undefined ? currentPageSize : pageSize;
const {data} = await axios.get(`api/v1/admin/user/@list`, {
headers: {authorization: "Bearer " + keycloak.token},
headers: {authorization: "Bearer " + oidcTokens.accessToken},
params: {page: currentPage, size: pageSize}
});
setTotalPages(data.totalPages);
Expand All @@ -51,7 +53,7 @@ const AdminUsers = (props) => {
await axios.post(
`api/v1/admin/user/@lock`,
{userId: user},
{headers: {authorization: "Bearer " + keycloak.token}}
{headers: {authorization: "Bearer " + oidcTokens.accessToken}}
);
getUsers();
};
Expand All @@ -60,7 +62,7 @@ const AdminUsers = (props) => {
await axios.post(
`api/v1/admin/user/@unlock`,
{userId: user},
{headers: {authorization: "Bearer " + keycloak.token}}
{headers: {authorization: "Bearer " + oidcTokens.accessToken}}
);
getUsers();
};
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/components/NavHeader.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ NavHeader.jsx +
+ +
+ Copyright (c) 2022 Robin Ferch +
+ Copyright (c) 2022-2024 Robin Ferch +
+ https://robinferch.me +
+ This project is released under the MIT license. +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
Expand All @@ -16,6 +16,7 @@ import {AiOutlineUser, AiOutlineSearch} from "react-icons/ai";
import {openSpotlight} from "@mantine/spotlight";
import {useUser} from "../hooks/useUser";
import {useDisclosure} from "@mantine/hooks";
import {useOidc} from "../oidc";


const HEADER_HEIGHT = 60;
Expand Down Expand Up @@ -113,7 +114,9 @@ const useStyles = createStyles((theme) => ({

const NavHeader = ({mapRef}) => {

const {keycloak} = useKeycloak();

const { isUserLoggedIn, login, logout, oidcTokens, initializationError } = useOidc();

const links = [
{
"link": "/",
Expand Down Expand Up @@ -159,7 +162,7 @@ const NavHeader = ({mapRef}) => {
<Group spacing={5} className={classes.links} mr={"md"}>
{items}
{
keycloak?.tokenParsed?.realm_access.roles.includes("mapadmin") && <Link
oidcTokens?.decodedIdToken?.realm_access.roles.includes("mapadmin") && <Link
to={"/admin"}
className={cx(classes.link, {[classes.linkActive]: active === "/admin"})}
onClick={() => {
Expand Down
Loading

0 comments on commit b8fef2e

Please sign in to comment.