| @@ -0,0 +1,52 @@ | |||
| // src/components/Require2FA.jsx | |||
| import React, { useState, useEffect } from 'react'; | |||
| import { Navigate } from 'react-router-dom'; | |||
| import { Box, CircularProgress, Typography } from '@mui/material'; | |||
| import axios from 'axios'; | |||
| import { apiPath } from '../auth/utils'; | |||
| const Require2FA = ({ children }) => { | |||
| const [is2FAEnabled, setIs2FAEnabled] = useState(null); // null = loading | |||
| const [error, setError] = useState(false); | |||
| useEffect(() => { | |||
| const check2FA = async () => { | |||
| try { | |||
| const response = await axios.get(`${apiPath}/2fa/status`, { | |||
| headers: { Authorization: `Bearer ${localStorage.getItem('accessToken')}` } | |||
| }); | |||
| setIs2FAEnabled(response.data.enabled); | |||
| } catch (err) { | |||
| // Fallback to localStorage (same logic as in Profile page) | |||
| try { | |||
| const userData = JSON.parse(localStorage.getItem('userData') || '{}'); | |||
| setIs2FAEnabled(!!userData.twoFactorEnabled); | |||
| } catch { | |||
| setError(true); | |||
| } | |||
| } | |||
| }; | |||
| check2FA(); | |||
| }, []); | |||
| if (is2FAEnabled === null) { | |||
| // Still loading | |||
| return ( | |||
| <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}> | |||
| <CircularProgress /> | |||
| <Typography sx={{ ml: 2 }}>Checking security settings...</Typography> | |||
| </Box> | |||
| ); | |||
| } | |||
| if (!is2FAEnabled || error) { | |||
| // Redirect to profile page with a clear message | |||
| return <Navigate to="/profile" replace state={{ require2FA: true }} />; | |||
| } | |||
| // 2FA is enabled → allow access | |||
| return children; | |||
| }; | |||
| export default Require2FA; | |||
| @@ -1,6 +1,6 @@ | |||
| import {lazy, useContext} from 'react'; | |||
| // project import | |||
| import Require2FA from '../components/Require2FA'; | |||
| import Loadable from 'components/Loadable'; | |||
| import MainLayout from "../layout/MainLayout"; | |||
| import {handleRouteAbility} from "../utils/CommonFunction"; | |||
| @@ -26,64 +26,78 @@ const ClientRoutes =() => { | |||
| path: '/', | |||
| element: <MainLayout />, | |||
| children: [ | |||
| { | |||
| path: 'client', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'CLIENT'), | |||
| <ClientSearchPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'client/maintain/:id', | |||
| element: ( | |||
| <Require2FA> | |||
| { | |||
| handleRouteAbility( | |||
| ability.can('VIEW', 'CLIENT'), | |||
| <ClientMaintainPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: '/pdf/:id', | |||
| element: ( | |||
| handleRouteAbility( | |||
| ability.can('VIEW', 'CLIENT'), | |||
| <PdfSearchPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'CLIENT'), | |||
| <PdfSearchPage />, | |||
| <Navigate to="/" /> | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: '/pdf/maintain/:id', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'CLIENT'), | |||
| <PdfMaintainPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: '/template/', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'TEMPLATE'), | |||
| <TemplateSearchPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: '/pdf/form-up-down/:id', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'CLIENT'), | |||
| <PdfFormUpAndDown />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| @@ -1,6 +1,6 @@ | |||
| import {lazy, useContext} from 'react'; | |||
| // project import | |||
| import Require2FA from '../components/Require2FA'; | |||
| import Loadable from 'components/Loadable'; | |||
| import MainLayout from "../layout/MainLayout"; | |||
| import AbilityContext from "../components/AbilityProvider"; | |||
| @@ -102,102 +102,122 @@ const SettingRoutes = () => { | |||
| { | |||
| path: 'usergroupSearchview', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('MAINTAIN', 'USER_GROUP'), | |||
| <UserGroupSearchPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'userGroup/:id', | |||
| element:( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('MAINTAIN', 'USER_GROUP'), | |||
| <UserGroupDetailPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'user/:id', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'USER'), | |||
| <UserMaintainPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'userSearchview', | |||
| element:( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'USER'), | |||
| <UserSearchPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'consultant', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'USER'), | |||
| <ConsultantSearchPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'consultant/:id', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'USER'), | |||
| <ConsultantMaintainPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'setting', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('MANAGE', 'SYSTEM_CONFIGURATION'), | |||
| <SettingPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'auditLog', | |||
| element:( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'AUDIT_LOG'), | |||
| <AuditLogPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'userActionLog', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'USER'), | |||
| <UserActionLogPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||
| path: 'loginLog', | |||
| element: ( | |||
| handleRouteAbility( | |||
| <Require2FA> | |||
| {handleRouteAbility( | |||
| ability.can('VIEW', 'LOGIN_LOG'), | |||
| <LoginLogPage />, | |||
| <Navigate to="/" /> | |||
| ) | |||
| )} | |||
| </Require2FA> | |||
| ), | |||
| }, | |||
| { | |||