diff --git a/src/constants/languageColors.ts b/src/constants/languageColors.ts index bdbbead73..f2038c89f 100644 --- a/src/constants/languageColors.ts +++ b/src/constants/languageColors.ts @@ -1,5 +1,3 @@ -// src/constants/languageColors.ts - export const languageColors: Record = { '1C Enterprise': '#814CCC', '2-Dimensional Array': '#38761D', diff --git a/src/routes.tsx b/src/routes.tsx index 41a9b7123..51ae327a6 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -25,9 +25,11 @@ import User from './ui/views/User/User'; import UserList from './ui/views/UserList/UserList'; import RepoDetails from './ui/views/RepoDetails/RepoDetails'; import RepoList from './ui/views/RepoList/RepoList'; +import SettingsView from './ui/views/Settings/Settings'; import { RepoIcon } from '@primer/octicons-react'; -import { Group, AccountCircle, Dashboard } from '@material-ui/icons'; +import { Group, AccountCircle, Dashboard, Settings } from '@material-ui/icons'; + import { Route } from './types/models'; const dashboardRoutes: Route[] = [ @@ -97,6 +99,18 @@ const dashboardRoutes: Route[] = [ layout: '/dashboard', visible: false, }, + { + path: '/admin/settings', + name: 'Settings', + icon: Settings, + component: (props) => + , + layout: '/dashboard', + visible: true, + }, ]; export default dashboardRoutes; diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index 32c81304d..9ebfa2bcb 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -17,10 +17,6 @@ const jwtAuthHandler = (overrideConfig = null) => { return next(); } - if (req.isAuthenticated()) { - return next(); - } - const token = req.header("Authorization"); if (!token) { return res.status(401).send("No token provided\n"); @@ -30,11 +26,15 @@ const jwtAuthHandler = (overrideConfig = null) => { const audience = expectedAudience || clientID; if (!authorityURL) { - return res.status(500).send("OIDC authority URL is not configured\n"); + return res.status(500).send({ + message: "JWT handler: authority URL is not configured\n" + }); } if (!clientID) { - return res.status(500).send("OIDC client ID is not configured\n"); + return res.status(500).send({ + message: "JWT handler: client ID is not configured\n" + }); } const tokenParts = token.split(" "); diff --git a/src/ui/components/Navbars/DashboardNavbarLinks.tsx b/src/ui/components/Navbars/DashboardNavbarLinks.tsx index d08245147..91ab3eed4 100644 --- a/src/ui/components/Navbars/DashboardNavbarLinks.tsx +++ b/src/ui/components/Navbars/DashboardNavbarLinks.tsx @@ -15,7 +15,7 @@ import { useNavigate } from 'react-router-dom'; import { AccountCircle } from '@material-ui/icons'; import { getUser } from '../../services/user'; import axios from 'axios'; -import { getCookie } from '../../utils'; +import { getAxiosConfig } from '../../services/auth'; import { UserData } from '../../../types/models'; const useStyles = makeStyles(styles); @@ -51,21 +51,17 @@ const DashboardNavbarLinks: React.FC = () => { const logout = async () => { try { - const response = await axios.post( + await axios.post( `${process.env.VITE_API_URI || 'http://localhost:3000'}/api/auth/logout`, {}, - { - withCredentials: true, - headers: { - 'X-CSRF-TOKEN': getCookie('csrf'), - }, - }, - ); - - if (!response.data.isAuth && !response.data.user) { - setAuth(false); - navigate(0); - } + getAxiosConfig(), + ) + .then((res) => { + if (!res.data.isAuth && !res.data.user) { + setAuth(false); + navigate(0); + } + }); } catch (error) { console.error('Logout failed:', error); } diff --git a/src/ui/components/Navbars/Navbar.tsx b/src/ui/components/Navbars/Navbar.tsx index dd1a931d9..4ae4474b9 100644 --- a/src/ui/components/Navbars/Navbar.tsx +++ b/src/ui/components/Navbars/Navbar.tsx @@ -38,7 +38,7 @@ const Header: React.FC = (props) => { }); return ( - +
{/* Here we create navbar brand, based on route name */} diff --git a/src/ui/components/Typography/Danger.jsx b/src/ui/components/Typography/Danger.jsx index aee271323..12693f660 100644 --- a/src/ui/components/Typography/Danger.jsx +++ b/src/ui/components/Typography/Danger.jsx @@ -8,7 +8,7 @@ const useStyles = makeStyles(styles); export default function Danger(props) { const classes = useStyles(); const { children } = props; - return
{children}
; + return
{children}
; } Danger.propTypes = { diff --git a/src/ui/services/auth.js b/src/ui/services/auth.js index e1155e9f5..bb2533d9a 100644 --- a/src/ui/services/auth.js +++ b/src/ui/services/auth.js @@ -1,3 +1,5 @@ +import { getCookie } from '../utils'; + const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}` : `${location.origin}`; @@ -20,3 +22,34 @@ export const getUserInfo = async () => { return null; } }; + +/** + * Gets the Axios config for the UI + * @return {Object} The Axios config + */ +export const getAxiosConfig = () => { + console.log('getAxiosConfig', getCookie('csrf'), localStorage.getItem('ui_jwt_token')); + const jwtToken = localStorage.getItem('ui_jwt_token'); + return { + withCredentials: true, + headers: { + 'X-CSRF-TOKEN': getCookie('csrf'), + Authorization: jwtToken ? `Bearer ${jwtToken}` : undefined, + }, + }; +}; + +/** + * Processes authentication errors and returns a user-friendly error message + * @param {Object} error - The error object + * @return {string} The error message + */ +export const processAuthError = (error) => { + let errorMessage = `Failed to authorize user: ${error.response.data.trim()}. `; + if (!localStorage.getItem('ui_jwt_token')) { + errorMessage += 'Set your JWT token in the settings page or disable JWT auth in your app configuration.' + } else { + errorMessage += 'Check your JWT token or disable JWT auth in your app configuration.' + } + return errorMessage; +}; diff --git a/src/ui/services/git-push.js b/src/ui/services/git-push.js index 483275a37..b3c88b17d 100644 --- a/src/ui/services/git-push.js +++ b/src/ui/services/git-push.js @@ -1,17 +1,13 @@ import axios from 'axios'; -import { getCookie } from '../utils.tsx'; +import { getAxiosConfig, processAuthError } from './auth.js'; const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}/api/v1` : `${location.origin}/api/v1`; -const config = { - withCredentials: true, -}; - const getPush = async (id, setIsLoading, setData, setAuth, setIsError) => { const url = `${baseUrl}/push/${id}`; - await axios(url, config) + await axios(url, getAxiosConfig()) .then((response) => { const data = response.data; data.diff = data.steps.find((x) => x.stepName === 'diff'); @@ -42,7 +38,7 @@ const getPushes = async ( url.search = new URLSearchParams(query); setIsLoading(true); - await axios(url.toString(), { withCredentials: true }) + await axios(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; setData(data); @@ -51,7 +47,7 @@ const getPushes = async ( setIsError(true); if (error.response && error.response.status === 401) { setAuth(false); - setErrorMessage('Failed to authorize user. If JWT auth is enabled, please check your configuration or disable it.'); + setErrorMessage(processAuthError(error)); } else { setErrorMessage(`Error fetching pushes: ${error.response.data.message}`); } @@ -72,7 +68,7 @@ const authorisePush = async (id, setMessage, setUserAllowedToApprove, attestatio attestation, }, }, - { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }, + getAxiosConfig(), ) .catch((error) => { if (error.response && error.response.status === 401) { @@ -89,7 +85,7 @@ const rejectPush = async (id, setMessage, setUserAllowedToReject) => { let errorMsg = ''; let isUserAllowedToReject = true; await axios - .post(url, {}, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .post(url, {}, getAxiosConfig()) .catch((error) => { if (error.response && error.response.status === 401) { errorMsg = 'You are not authorised to reject...'; @@ -103,7 +99,7 @@ const rejectPush = async (id, setMessage, setUserAllowedToReject) => { const cancelPush = async (id, setAuth, setIsError) => { const url = `${baseUrl}/push/${id}/cancel`; await axios - .post(url, {}, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .post(url, {}, getAxiosConfig()) .catch((error) => { if (error.response && error.response.status === 401) { setAuth(false); diff --git a/src/ui/services/repo.js b/src/ui/services/repo.js index 6bf9e9357..24f956dd2 100644 --- a/src/ui/services/repo.js +++ b/src/ui/services/repo.js @@ -1,18 +1,14 @@ import axios from 'axios'; -import { getCookie } from '../utils.tsx'; +import { getAxiosConfig, processAuthError } from './auth.js'; const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}/api/v1` : `${location.origin}/api/v1`; -const config = { - withCredentials: true, -}; - const canAddUser = (repoName, user, action) => { const url = new URL(`${baseUrl}/repo/${repoName}`); return axios - .get(url.toString(), config) + .get(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; if (action === 'authorise') { @@ -44,7 +40,7 @@ const getRepos = async ( const url = new URL(`${baseUrl}/repo`); url.search = new URLSearchParams(query); setIsLoading(true); - await axios(url.toString(), config) + await axios(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; setData(data); @@ -53,9 +49,9 @@ const getRepos = async ( setIsError(true); if (error.response && error.response.status === 401) { setAuth(false); - setErrorMessage('Failed to authorize user. If JWT auth is enabled, please check your configuration or disable it.'); + setErrorMessage(processAuthError(error)); } else { - setErrorMessage(`Error fetching repositories: ${error.response.data.message}`); + setErrorMessage(`Error fetching repos: ${error.response.data.message}`); } }).finally(() => { setIsLoading(false); @@ -65,7 +61,7 @@ const getRepos = async ( const getRepo = async (setIsLoading, setData, setAuth, setIsError, id) => { const url = new URL(`${baseUrl}/repo/${id}`); setIsLoading(true); - await axios(url.toString(), config) + await axios(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; setData(data); @@ -84,7 +80,7 @@ const getRepo = async (setIsLoading, setData, setAuth, setIsError, id) => { const addRepo = async (onClose, setError, data) => { const url = new URL(`${baseUrl}/repo`); axios - .post(url, data, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .post(url, data, getAxiosConfig()) .then(() => { onClose(); }) @@ -100,7 +96,7 @@ const addUser = async (repoName, user, action) => { const url = new URL(`${baseUrl}/repo/${repoName}/user/${action}`); const data = { username: user }; await axios - .patch(url, data, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .patch(url, data, getAxiosConfig()) .catch((error) => { console.log(error.response.data.message); throw error; @@ -115,7 +111,7 @@ const deleteUser = async (user, repoName, action) => { const url = new URL(`${baseUrl}/repo/${repoName}/user/${action}/${user}`); await axios - .delete(url, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .delete(url, getAxiosConfig()) .catch((error) => { console.log(error.response.data.message); throw error; @@ -126,7 +122,7 @@ const deleteRepo = async (repoName) => { const url = new URL(`${baseUrl}/repo/${repoName}/delete`); await axios - .delete(url, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .delete(url, getAxiosConfig()) .catch((error) => { console.log(error.response.data.message); throw error; diff --git a/src/ui/services/user.ts b/src/ui/services/user.ts index f7be1ea87..18b2d36cf 100644 --- a/src/ui/services/user.ts +++ b/src/ui/services/user.ts @@ -1,13 +1,10 @@ import axios, { AxiosError, AxiosResponse } from 'axios'; -import { getCookie } from '../utils'; +import { getAxiosConfig, processAuthError } from './auth'; import { UserData } from '../../types/models'; type SetStateCallback = (value: T | ((prevValue: T) => T)) => void; const baseUrl = process.env.VITE_API_URI || location.origin; -const config = { - withCredentials: true, -}; const getUser = async ( setIsLoading?: SetStateCallback, @@ -23,7 +20,7 @@ const getUser = async ( console.log(url); try { - const response: AxiosResponse = await axios(url, config); + const response: AxiosResponse = await axios(url, getAxiosConfig()); const data = response.data; setData?.(data); @@ -43,7 +40,6 @@ const getUsers = async ( setIsLoading: SetStateCallback, setData: SetStateCallback, setAuth: SetStateCallback, - setIsError: SetStateCallback, setErrorMessage: SetStateCallback, query: Record = {}, ): Promise => { @@ -53,17 +49,13 @@ const getUsers = async ( setIsLoading(true); try { - const response: AxiosResponse = await axios(url.toString(), { - withCredentials: true, - }); + const response: AxiosResponse = await axios(url.toString(), getAxiosConfig()); setData(response.data); } catch (error) { if (axios.isAxiosError(error)) { if (error.response?.status === 401) { setAuth(false); - setErrorMessage( - 'Failed to authorize user. If JWT auth is enabled, please check your configuration or disable it.', - ); + setErrorMessage(processAuthError(error)); } else { const msg = (error.response?.data as any)?.message ?? error.message; setErrorMessage(`Error fetching users: ${msg}`); @@ -81,10 +73,7 @@ const updateUser = async (data: UserData): Promise => { const url = new URL(`${baseUrl}/api/auth/gitAccount`); try { - await axios.post(url.toString(), data, { - withCredentials: true, - headers: { 'X-CSRF-TOKEN': getCookie('csrf') }, - }); + await axios.post(url.toString(), data, getAxiosConfig()); } catch (error) { const axiosError = error as AxiosError; if (axiosError.response) { @@ -103,9 +92,7 @@ const getUserLoggedIn = async ( const url = new URL(`${baseUrl}/api/auth/me`); try { - const response: AxiosResponse = await axios(url.toString(), { - withCredentials: true, - }); + const response: AxiosResponse = await axios(url.toString(), getAxiosConfig()); const data = response.data; setIsLoading(false); setIsAdmin(data.admin || false); diff --git a/src/ui/utils.tsx b/src/ui/utils.tsx index be15a19ef..16d08f083 100644 --- a/src/ui/utils.tsx +++ b/src/ui/utils.tsx @@ -5,13 +5,13 @@ */ export const getCookie = (name: string): string | null => { if (!document.cookie) return null; - + const cookies = document.cookie .split(';') .map((c) => c.trim()) .filter((c) => c.startsWith(name + '=')); - + if (!cookies.length) return null; - + return decodeURIComponent(cookies[0].split('=')[1]); -}; \ No newline at end of file +}; diff --git a/src/ui/views/OpenPushRequests/OpenPushRequests.tsx b/src/ui/views/OpenPushRequests/OpenPushRequests.tsx index bd332237e..256453a96 100644 --- a/src/ui/views/OpenPushRequests/OpenPushRequests.tsx +++ b/src/ui/views/OpenPushRequests/OpenPushRequests.tsx @@ -1,8 +1,9 @@ -import React from 'react'; +import React, { useState } from 'react'; import GridItem from '../../components/Grid/GridItem'; import GridContainer from '../../components/Grid/GridContainer'; import PushesTable from './components/PushesTable'; import CustomTabs from '../../components/CustomTabs/CustomTabs'; +import Danger from '../../components/Typography/Danger'; import { Visibility, CheckCircle, Cancel, Block } from '@material-ui/icons'; import { SvgIconProps } from '@material-ui/core'; @@ -13,6 +14,12 @@ interface TabConfig { } const Dashboard: React.FC = () => { + const [errorMessage, setErrorMessage] = useState(null); + + const handlePushTableError = (errorMessage: string) => { + setErrorMessage(errorMessage); + } + const tabs: TabConfig[] = [ { tabName: 'Pending', @@ -29,30 +36,47 @@ const Dashboard: React.FC = () => { { tabName: 'Approved', tabIcon: CheckCircle, - tabContent: , + tabContent: , }, { tabName: 'Canceled', tabIcon: Cancel, - tabContent: , + tabContent: ( + + ), }, { tabName: 'Rejected', tabIcon: Block, - tabContent: , + tabContent: ( + + ), }, ]; return (
- - - - - + {errorMessage && {errorMessage}} + {!errorMessage && ( + + + + + + )}
); }; diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.tsx b/src/ui/views/OpenPushRequests/components/PushesTable.tsx index 4fe5fcad9..fb957f282 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.tsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.tsx @@ -29,8 +29,7 @@ const PushesTable: React.FC = (props) => { const [data, setData] = useState([]); const [filteredData, setFilteredData] = useState([]); const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); + const [, setIsError] = useState(false); const navigate = useNavigate(); const [, setAuth] = useState(true); const [currentPage, setCurrentPage] = useState(1); @@ -46,7 +45,7 @@ const PushesTable: React.FC = (props) => { authorised: props.authorised ?? false, rejected: props.rejected ?? false, }; - getPushes(setIsLoading, setData, setAuth, setIsError, setErrorMessage, query); + getPushes(setIsLoading, setData, setAuth, setIsError, props.handleError, query); }, [props]); useEffect(() => { @@ -78,7 +77,6 @@ const PushesTable: React.FC = (props) => { const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem); if (isLoading) return
Loading...
; - if (isError) return
{errorMessage}
; return (
diff --git a/src/ui/views/RepoDetails/Components/AddUser.tsx b/src/ui/views/RepoDetails/Components/AddUser.tsx index bf59676e6..523b28f0c 100644 --- a/src/ui/views/RepoDetails/Components/AddUser.tsx +++ b/src/ui/views/RepoDetails/Components/AddUser.tsx @@ -17,6 +17,7 @@ import { addUser } from '../../../services/repo'; import { getUsers } from '../../../services/user'; import { PersonAdd } from '@material-ui/icons'; import { UserData } from '../../../../types/models'; +import Danger from '../../../components/Typography/Danger'; interface AddUserDialogProps { repoName: string; @@ -37,7 +38,7 @@ const AddUserDialog: React.FC = ({ const [data, setData] = useState([]); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); const [error, setError] = useState(''); const [tip, setTip] = useState(false); @@ -76,10 +77,10 @@ const AddUserDialog: React.FC = ({ }; useEffect(() => { - getUsers(setIsLoading, setData, setAuth, setIsError, setError, {}); + getUsers(setIsLoading, setData, setAuth, setErrorMessage, {}); }, []); - if (isError) return
Something went wrong ...
; + if (errorMessage) return {errorMessage}; return ( <> diff --git a/src/ui/views/RepoList/Components/Repositories.tsx b/src/ui/views/RepoList/Components/Repositories.tsx index be110f532..9d6a2df4b 100644 --- a/src/ui/views/RepoList/Components/Repositories.tsx +++ b/src/ui/views/RepoList/Components/Repositories.tsx @@ -14,6 +14,7 @@ import { UserContext } from '../../../../context'; import Search from '../../../components/Search/Search'; import Pagination from '../../../components/Pagination/Pagination'; import Filtering, { FilterOption, SortOrder } from '../../../components/Filtering/Filtering'; +import Danger from '../../../components/Typography/Danger'; import { RepositoriesProps } from '../repositories.types'; interface GridContainerLayoutProps { @@ -125,7 +126,7 @@ export default function Repositories(props: RepositoriesProps): React.ReactEleme const paginatedData = filteredData.slice(startIdx, startIdx + itemsPerPage); if (isLoading) return
Loading...
; - if (isError) return
{errorMessage}
; + if (isError) return {errorMessage}; const addrepoButton = user.admin ? ( diff --git a/src/ui/views/Settings/Settings.jsx b/src/ui/views/Settings/Settings.jsx new file mode 100644 index 000000000..7649981cb --- /dev/null +++ b/src/ui/views/Settings/Settings.jsx @@ -0,0 +1,119 @@ +import React, { useState, useEffect } from 'react'; +import { + TextField, + IconButton, + InputAdornment, + FormLabel, + Snackbar, + Typography, +} from '@material-ui/core'; +import { Visibility, VisibilityOff, Save, Clear } from '@material-ui/icons'; +import { makeStyles } from '@material-ui/core/styles'; + +import GridContainer from '../../components/Grid/GridContainer'; +import GridItem from '../../components/Grid/GridItem'; +import Card from '../../components/Card/Card'; +import CardBody from '../../components/Card/CardBody'; +import Button from '../../components/CustomButtons/Button'; + +const useStyles = makeStyles((theme) => ({ + root: { + '& .MuiTextField-root': { + margin: theme.spacing(1), + width: '100%', + }, + }, + buttonRow: { + display: 'flex', + justifyContent: 'flex-end', + marginTop: theme.spacing(2), + gap: theme.spacing(1), + }, +})); + +export default function SettingsView() { + const classes = useStyles(); + + const [jwtToken, setJwtToken] = useState(''); + const [showToken, setShowToken] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(''); + const [snackbarOpen, setSnackbarOpen] = useState(false); + + useEffect(() => { + const savedToken = localStorage.getItem('ui_jwt_token'); + if (savedToken) setJwtToken(savedToken); + }, []); + + const handleSave = () => { + localStorage.setItem('ui_jwt_token', jwtToken); + setSnackbarMessage('JWT token saved'); + setSnackbarOpen(true); + }; + + const handleClear = () => { + setJwtToken(''); + localStorage.removeItem('ui_jwt_token'); + setSnackbarMessage('JWT token cleared'); + setSnackbarOpen(true); + }; + + const toggleShowToken = () => { + setShowToken(!showToken); + }; + + return ( +
+ + + + + {/* Title */} + JWT Token for UI Authentication + + Authenticates UI requests to the server when "apiAuthentication" is enabled in the config. + + setJwtToken(e.target.value)} + InputProps={{ + endAdornment: ( + + + {showToken ? : } + + + ), + style: { + marginTop: '10px', + marginLeft: '-8px', + marginRight: '8px', + }, + }} + /> +
+ + +
+
+
+
+
+ setSnackbarOpen(false)} + message={snackbarMessage} + /> + + ); +} diff --git a/src/ui/views/User/User.tsx b/src/ui/views/User/User.tsx index a3635a2e8..0c3b632d4 100644 --- a/src/ui/views/User/User.tsx +++ b/src/ui/views/User/User.tsx @@ -71,6 +71,7 @@ export default function UserProfile(): React.ReactElement { if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; + if (!auth && window.location.pathname === '/dashboard/profile') { return ; } diff --git a/src/ui/views/UserList/Components/UserList.tsx b/src/ui/views/UserList/Components/UserList.tsx index b343efcd0..c38ed026d 100644 --- a/src/ui/views/UserList/Components/UserList.tsx +++ b/src/ui/views/UserList/Components/UserList.tsx @@ -16,6 +16,7 @@ import { getUsers } from '../../../services/user'; import Pagination from '../../../components/Pagination/Pagination'; import { CloseRounded, Check, KeyboardArrowRight } from '@material-ui/icons'; import Search from '../../../components/Search/Search'; +import Danger from '../../../components/Typography/Danger'; import { UserData } from '../../../../types/models'; interface UserListProps { @@ -29,7 +30,6 @@ const UserList: React.FC = (props) => { const [data, setData] = useState([]); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const navigate = useNavigate(); const [currentPage, setCurrentPage] = useState(1); @@ -46,11 +46,11 @@ const UserList: React.FC = (props) => { if (!k) continue; query[k] = props[k]; } - getUsers(setIsLoading, setData, setAuth, setIsError, setErrorMessage, query); + getUsers(setIsLoading, setData, setAuth, setErrorMessage, query); }, [props]); if (isLoading) return
Loading...
; - if (isError) return
{errorMessage}
; + if (errorMessage) return {errorMessage}; const filteredUsers = data.filter( (user) => diff --git a/test/testJwtAuthHandler.test.js b/test/testJwtAuthHandler.test.js index af2bb1bb2..536d10d05 100644 --- a/test/testJwtAuthHandler.test.js +++ b/test/testJwtAuthHandler.test.js @@ -167,7 +167,7 @@ describe('jwtAuthHandler', () => { await jwtAuthHandler(jwtConfig)(req, res, next); expect(res.status.calledWith(500)).to.be.true; - expect(res.send.calledWith('OIDC authority URL is not configured\n')).to.be.true; + expect(res.send.calledWith({ message: 'JWT handler: authority URL is not configured\n' })).to.be.true; }); it('should return 500 if clientID not configured', async () => { @@ -178,7 +178,7 @@ describe('jwtAuthHandler', () => { await jwtAuthHandler(jwtConfig)(req, res, next); expect(res.status.calledWith(500)).to.be.true; - expect(res.send.calledWith('OIDC client ID is not configured\n')).to.be.true; + expect(res.send.calledWith({ message: 'JWT handler: client ID is not configured\n' })).to.be.true; }); it('should return 401 if JWT validation fails', async () => {