diff --git a/backend/webui_service/webui_init.go b/backend/webui_service/webui_init.go index e3183788..c326f9a6 100644 --- a/backend/webui_service/webui_init.go +++ b/backend/webui_service/webui_init.go @@ -137,7 +137,7 @@ func (a *WebuiApp) Start(tlsKeyLogPath string) { AllowMethods: []string{"GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE"}, AllowHeaders: []string{ "Origin", "Content-Length", "Content-Type", "User-Agent", - "Referrer", "Host", "Token", "X-Requested-With", + "Referrer", "Host", "Authorization", "Token", "X-Requested-With", }, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b18695bc..011f934d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import StatusList from "./pages/StatusList"; import StatusRead from "./pages/StatusRead"; @@ -19,10 +19,38 @@ import { ProtectedRoute } from "./ProtectedRoute"; import { LoginContext, User } from "./LoginContext"; export default function App() { - const [user, setUser] = useState(null); + const [user, setUser] = useState(() => { + // retrieve from local storage on initial load (if available) + const storedUser = localStorage.getItem('username'); + const storedToken = localStorage.getItem('token'); + if (storedUser && storedToken) { + return { username: storedUser, token: storedToken }; + } else { + console.warn('no user stored!'); + } + return null; + }); + + useEffect(() => { + if (user && user.token) { + console.log('setting user related state'); + localStorage.setItem('username', user.username); + localStorage.setItem('token', user.token); + } else { + console.log('deleting user related state'); + localStorage.removeItem('username'); + localStorage.removeItem('token'); + } + }, [user]) + + // performance optimization, skip re-rendering of children if user did not change + const contextValue = useMemo(() => ({ + user, + setUser + }), [user]); return ( - + } /> diff --git a/frontend/src/Dashboard.tsx b/frontend/src/Dashboard.tsx index 19788edc..348a2868 100644 --- a/frontend/src/Dashboard.tsx +++ b/frontend/src/Dashboard.tsx @@ -82,7 +82,12 @@ function Dashboard(props: DashboardProps) { const toggleDrawer = () => { setOpen(!open); }; - const { user, setUser } = useContext(LoginContext); + const context = useContext(LoginContext); + if (context === undefined) { + throw new Error("LoginContext must be used within a LoginContext.Provider"); + } + const { user } = context; + const navigation = useNavigate(); const [time, setTime] = useState(new Date()); @@ -115,7 +120,7 @@ function Dashboard(props: DashboardProps) { navigation("/password"); break; case 1: - setUser(null); + // setUser(null); navigation("/login"); break; default: @@ -151,7 +156,6 @@ function Dashboard(props: DashboardProps) { break; } }; - return ( diff --git a/frontend/src/LoginContext.tsx b/frontend/src/LoginContext.tsx index 21f8b443..3555a1f2 100644 --- a/frontend/src/LoginContext.tsx +++ b/frontend/src/LoginContext.tsx @@ -1,4 +1,4 @@ -import { createContext } from "react"; +import { Dispatch, SetStateAction, createContext } from "react"; export interface User { username: string; @@ -7,12 +7,7 @@ export interface User { export interface UserContext { user: User | null; - setUser: (user: User | null) => void; + setUser: Dispatch>; } -export const LoginContext = createContext({ - user: null, - setUser: (user: User | null) => { - console.log(user); - }, -}); +export const LoginContext = createContext(undefined); diff --git a/frontend/src/ProtectedRoute.tsx b/frontend/src/ProtectedRoute.tsx index 3032678c..1b37ae7d 100644 --- a/frontend/src/ProtectedRoute.tsx +++ b/frontend/src/ProtectedRoute.tsx @@ -1,10 +1,14 @@ -import React, { useContext } from "react"; +import { useContext } from "react"; import { Navigate } from "react-router-dom"; import { LoginContext } from "./LoginContext"; export const ProtectedRoute = (props: any) => { - const { user } = useContext(LoginContext); + const context = useContext(LoginContext); + if (context === undefined) { + throw new Error("LoginContext must be used within a LoginContext.Provider"); + } + const { user } = context; if (user === null) { return ; diff --git a/frontend/src/SimpleListMenu.tsx b/frontend/src/SimpleListMenu.tsx index 9b65f5fd..3d5365b8 100644 --- a/frontend/src/SimpleListMenu.tsx +++ b/frontend/src/SimpleListMenu.tsx @@ -1,9 +1,11 @@ -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import List from "@mui/material/List"; import ListItem from "@mui/material/ListItem"; import ListItemText from "@mui/material/ListItemText"; import MenuItem from "@mui/material/MenuItem"; import Menu from "@mui/material/Menu"; +import { useNavigate } from "react-router-dom"; +import { LoginContext } from "./LoginContext"; export interface SimpleListMenuProps { title: string | undefined; @@ -19,6 +21,37 @@ export default function SimpleListMenu(props: SimpleListMenuProps) { setAnchorEl(event.currentTarget); }; + const navigation = useNavigate(); + const context = useContext(LoginContext); + if (context === undefined) { + throw new Error("LoginContext must be used within a LoginContext.Provider"); + } + const { setUser } = context; + + function onChangePassword() { + navigation("/password"); + } + + function onLogout() { + setUser(null); + navigation("/login"); + } + + const handleMenuItemClick = (event: React.MouseEvent, index: number) => { + setSelectedIndex(index); + setAnchorEl(null); + switch (index) { + case 0: + onChangePassword(); + break; + case 1: + onLogout(); + break; + default: + break; + } + }; + const handleClose = () => { setAnchorEl(null); }; diff --git a/frontend/src/axios.tsx b/frontend/src/axios.tsx index e0c54385..5dd7a63a 100644 --- a/frontend/src/axios.tsx +++ b/frontend/src/axios.tsx @@ -9,7 +9,27 @@ if (process.env.NODE_ENV === "development") { } else { apiConfig.API_URL = process.env.REACT_APP_HTTP_API_URL ? process.env.REACT_APP_HTTP_API_URL : ""; } + const instance = axios.create({ baseURL: apiConfig.API_URL, }); + +// attach the token to every request +instance.interceptors.request.use( + config => { + const token = localStorage.getItem('token'); + if (token) { + // add the token to the header + console.log('adding token to axios header'); + config.headers.Token = `${token}`; + } else { + console.warn('no token in local storage!'); + } + return config; + }, + error => { + return Promise.reject(error); + } +); + export default instance; diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 720bfbfd..3516eb90 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -17,7 +17,11 @@ const theme = createTheme(); export default function SignIn() { const navigation = useNavigate(); const [error, setError] = useState(""); - const { setUser } = useContext(LoginContext); + const context = useContext(LoginContext); + if (context === undefined) { + throw new Error("LoginContext must be used within a LoginContext.Provider"); + } + const { setUser } = context; const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); @@ -28,12 +32,11 @@ export default function SignIn() { if (data.get("email") !== null) { setUser({ username: data.get("email")!.toString(), token: res.data.access_token }); } - axios.defaults.headers.common.Token = res.data.access_token; setError(""); navigation("/"); }) .catch((err) => { - console.log(err); + console.log(err.message); setError("Wrong credentials"); }); }; diff --git a/frontend/src/pages/Logout.tsx b/frontend/src/pages/Logout.tsx index 1ced4c87..5d8c3fbb 100644 --- a/frontend/src/pages/Logout.tsx +++ b/frontend/src/pages/Logout.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React, { useContext, useState } from "react"; import { useNavigate } from "react-router-dom"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemIcon from "@mui/material/ListItemIcon"; @@ -9,7 +9,11 @@ import { LoginContext } from "../LoginContext"; export const Logout = () => { const navigation = useNavigate(); - const { setUser } = useContext(LoginContext); + const context = useContext(LoginContext); + if (context === undefined) { + throw new Error("LoginContext must be used within a LoginContext.Provider"); + } + const { setUser } = context; function onLogout() { setUser(null);