diff --git a/app/controllers/auth.controller.js b/app/controllers/auth.controller.js index 85d005e..c2cafc7 100644 --- a/app/controllers/auth.controller.js +++ b/app/controllers/auth.controller.js @@ -47,7 +47,6 @@ const ALL_PERMISSIONS = [ 'Edit_Vehicle info_Repair Records', 'Add_New Vehicle', 'Archive_Vehicle', - 'Delete_Vehicle', 'Export_Vehicle Report', 'View_Transportation Schedule_Route Overview', 'Create&Edit_Transportation Schedule', @@ -145,4 +144,31 @@ exports.login = (req, res) => { } else { throw(Error('email or username is required')); } -} \ No newline at end of file +} + +exports.me = (req, res) => { + const condition = splitSite.splitSiteGet(req, { _id: req.userId }); + Employee.findOne(condition) + .then((employee) => { + if (!employee) { + return res.status(404).send({ message: "User not found" }); + } + if (!isEmployeeActive(employee)) { + return res.status(403).send({ message: "User is not activated" }); + } + return res.send({ + username: employee.username, + email: employee.email, + roles: employee.roles, + permissions: getEffectivePermissions(employee), + id: employee.id, + name: employee.name, + name_cn: employee.name_cn + }); + }) + .catch((err) => { + res.status(500).send({ + message: err.message || "Failed to fetch current user" + }); + }); +}; \ No newline at end of file diff --git a/app/routes/auth.routes.js b/app/routes/auth.routes.js index 93538f6..ba82823 100644 --- a/app/routes/auth.routes.js +++ b/app/routes/auth.routes.js @@ -1,5 +1,6 @@ module.exports = app => { const auth = require("../controllers/auth.controller"); + const { authJwt } = require("../middlewares"); app.use(function(req, res, next) { res.header( "Access-Control-Allow-Headers", @@ -8,4 +9,5 @@ module.exports = app => { next(); }); app.post('/api/auth/login', auth.login); + app.get('/api/auth/me', [authJwt.verifyToken], auth.me); }; \ No newline at end of file diff --git a/client/src/App.js b/client/src/App.js index a816d5f..270172e 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -89,6 +89,7 @@ import { AuthService } from "./services"; function App() { const [showMenu, setShowMenu] = useState(false); + const [isPermissionReady, setIsPermissionReady] = useState(false); // const getLogo = () => { // return (window.location.hostname.includes('worldshine2.mayo.llc') || window.location.hostname.includes('site2') || window.location.host.includes('ws2') ||window.location.hostname.includes('localhost')) ? "/images/logo2.png" : ((window.location.hostname.includes('worldshine3.mayo.llc') ||window.location.hostname.includes('site3') || window.location.hostname.includes('ws3')) ? "/images/logo3.png" : "/images/logo1.png"); // } @@ -158,6 +159,32 @@ function App() { }; }, []); + useEffect(() => { + let isMounted = true; + const recheckPermissions = async () => { + try { + if (localStorage.getItem('token')) { + await AuthService.refreshCurrentUserPermissions(); + } + } catch (error) { + // Token failures are handled by axios interceptor (logout + redirect). + console.log(error); + } finally { + if (isMounted) { + setIsPermissionReady(true); + } + } + }; + recheckPermissions(); + return () => { + isMounted = false; + }; + }, []); + + if (!isPermissionReady) { + return null; + } + return ( <> {/*
diff --git a/client/src/components/info-screen/InfoScreen.js b/client/src/components/info-screen/InfoScreen.js index 84ac7c9..8c5e528 100644 --- a/client/src/components/info-screen/InfoScreen.js +++ b/client/src/components/info-screen/InfoScreen.js @@ -7,6 +7,7 @@ import moment from 'moment'; const InfoScreen = () => { const navigate = useNavigate(); + const canEditInfoScreen = AuthService.canEditInfoScreen(); const canViewAppointmentCalendar = AuthService.hasPermission('View_Appointment Calendar'); const [isLoading, setIsLoading] = useState(true); const [medicalEvents, setMedicalEvents] = useState([]); @@ -793,14 +794,16 @@ const InfoScreen = () => { Worldshine Logo Worldshine
- + {canEditInfoScreen && ( + + )} {/* Content Layout */} @@ -848,14 +851,16 @@ const InfoScreen = () => { {/* Header with Title and Edit Button */}
Gallery
- + {canEditInfoScreen && ( + + )}
{/* Image Carousel */} @@ -1173,9 +1178,11 @@ const InfoScreen = () => { {!isFullScreen && (
- + {canEditInfoScreen && ( + + )} @@ -1185,7 +1192,7 @@ const InfoScreen = () => {
{/* Edit AttendanceNote Modal */} - + {canEditInfoScreen && Edit Attendance Note @@ -1264,10 +1271,10 @@ const InfoScreen = () => { Save Changes - + } {/* Gallery Edit Modal */} - + {canEditInfoScreen && Update Images @@ -1333,10 +1340,10 @@ const InfoScreen = () => { Save Changes - + } {/* Background Selection Modal */} - + {canEditInfoScreen && Choose Background Image @@ -1386,7 +1393,7 @@ const InfoScreen = () => { Save Background - + } {/* Full Screen Hint */} {isFullScreen && ( diff --git a/client/src/components/messages/MessageList.js b/client/src/components/messages/MessageList.js index a23080e..d09c1e1 100644 --- a/client/src/components/messages/MessageList.js +++ b/client/src/components/messages/MessageList.js @@ -11,6 +11,7 @@ import 'react-datepicker/dist/react-datepicker.css'; const MessageList = () => { const navigate = useNavigate(); + const canEditMessagingTemplate = AuthService.hasPermission('Create&Edit_Messaging Template'); const [messages, setMessages] = useState([]); const [activeTab, setActiveTab] = useState('allMessages'); @@ -90,6 +91,7 @@ const MessageList = () => { // ---- Custom Template CRUD ---- const openCreateTemplateModal = () => { + if (!canEditMessagingTemplate) return; setEditingTemplate(null); setTemplateTitle(''); setTemplateChinese(''); @@ -98,6 +100,7 @@ const MessageList = () => { }; const openEditTemplateModal = (template) => { + if (!canEditMessagingTemplate) return; setEditingTemplate(template); setTemplateTitle(template.title || ''); setTemplateChinese(template.chinese || ''); @@ -114,6 +117,7 @@ const MessageList = () => { }; const handleSaveTemplate = () => { + if (!canEditMessagingTemplate) return; if (!templateTitle.trim()) { window.alert('Template Title is required.'); return; } if (!templateChinese.trim()) { window.alert('Message Content (Chinese) is required.'); return; } if (!templateEnglish.trim()) { window.alert('Message Content (English) is required.'); return; } @@ -131,6 +135,7 @@ const MessageList = () => { }; const handleDeleteTemplate = (id) => { + if (!canEditMessagingTemplate) return; if (window.confirm('Are you sure you want to delete this template?')) { MessageService.deleteCustomTemplate(id).then(() => { fetchCustomTemplates(); }); } @@ -399,7 +404,7 @@ const MessageList = () => { Template Title Message Content (Chinese) Message Content (English) - Actions + {canEditMessagingTemplate && Actions} @@ -409,14 +414,14 @@ const MessageList = () => { {template?.title} {template?.chinese} {template?.english} - + {canEditMessagingTemplate && openEditTemplateModal(template)} /> handleDeleteTemplate(template.id)} /> - + } ))} {customTemplates.length === 0 && ( - No templates yet. Create one to get started. + No templates yet. Create one to get started. )} @@ -482,14 +487,14 @@ const MessageList = () => { )} {activeTab === 'messageTemplate' && ( - + canEditMessagingTemplate && )} {/* Create / Edit Message Template Modal */} - + {editingTemplate ? 'Edit Message Template' : 'Create New Message Template'} diff --git a/client/src/components/vehicles/UpdateVehicle.js b/client/src/components/vehicles/UpdateVehicle.js index c4edbc2..e982474 100644 --- a/client/src/components/vehicles/UpdateVehicle.js +++ b/client/src/components/vehicles/UpdateVehicle.js @@ -18,6 +18,7 @@ import { const UpdateVehicle = () => { const navigate = useNavigate(); const dispatch = useDispatch(); + const canArchiveVehicle = AuthService.canArchiveVehicle(); const params = useParams(); const [searchParams] = useSearchParams(); const validTabs = ['basicInfo', 'complianceDeadlines']; @@ -209,6 +210,7 @@ const UpdateVehicle = () => { } const triggerShowDeleteModal = () => { + if (!canArchiveVehicle) return; setShowDeleteModal(true); } @@ -217,6 +219,7 @@ const UpdateVehicle = () => { } const deactivateVehicle = () => { + if (!canArchiveVehicle) return; const data = buildVehicleData(); data.status = 'inactive'; dispatch(deleteVehicle({id: params.id, data})); @@ -451,7 +454,7 @@ const UpdateVehicle = () => {
- + {canArchiveVehicle && }
diff --git a/client/src/services/AuthService.js b/client/src/services/AuthService.js index d634c62..3b53447 100644 --- a/client/src/services/AuthService.js +++ b/client/src/services/AuthService.js @@ -4,6 +4,20 @@ const login = (data) => { return http.post('/auth/login', data); }; +const getCurrentUserFromServer = () => { + return http.get('/auth/me'); +}; + +const refreshCurrentUserPermissions = async () => { + const token = localStorage.getItem('token'); + if (!token) return null; + const { data } = await getCurrentUserFromServer(); + if (data) { + localStorage.setItem('user', JSON.stringify(data)); + } + return data; +}; + const logout = (data, shouldClearSession = false) => { if (shouldClearSession) { localStorage.removeItem('user'); @@ -162,10 +176,9 @@ const canViewVehicleRepairs = () => hasAnyPermission(['View_Vehicle info_Repair const canEditVehicleRepairs = () => hasPermission('Edit_Vehicle info_Repair Records'); const canAddVehicle = () => hasPermission('Add_New Vehicle'); const canArchiveVehicle = () => hasPermission('Archive_Vehicle'); -const canDeleteVehicle = () => hasPermission('Delete_Vehicle'); const canExportVehicleReport = () => hasPermission('Export_Vehicle Report'); -const canViewVehicleAny = () => canViewVehicleBasic() || canViewVehicleDocuments() || canViewVehicleRepairs() || canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canDeleteVehicle() || canExportVehicleReport(); -const canEditVehicleAny = () => canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canDeleteVehicle(); +const canViewVehicleAny = () => canViewVehicleBasic() || canViewVehicleDocuments() || canViewVehicleRepairs() || canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canExportVehicleReport(); +const canEditVehicleAny = () => canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle(); const canViewDashboard = () => { return hasPermission('Dashboard'); @@ -178,6 +191,10 @@ const canViewInfoScreen = () => { ]); } +const canEditInfoScreen = () => { + return hasPermission('Edit_Info Screen'); +} + const canViewCalendar = () => { return canViewAnyCalendarTab() || canEditAnyCalendarTab(); } @@ -395,10 +412,13 @@ const getLocalAccessToken = () => { export const AuthService = { login, + getCurrentUserFromServer, + refreshCurrentUserPermissions, logout, hasPermission, canViewDashboard, canViewInfoScreen, + canEditInfoScreen, canViewCalendarTab, canEditCalendarTab, canViewAnyCalendarTab, @@ -443,7 +463,6 @@ export const AuthService = { canEditVehicleRepairs, canAddVehicle, canArchiveVehicle, - canDeleteVehicle, canExportVehicleReport, canViewVehicleAny, canEditVehicleAny, diff --git a/client/src/shared/constants/employee.constant.js b/client/src/shared/constants/employee.constant.js index 3097116..a457e97 100644 --- a/client/src/shared/constants/employee.constant.js +++ b/client/src/shared/constants/employee.constant.js @@ -180,7 +180,6 @@ export const EMPLOYEE_PERMISSION_GROUPS = { 'Edit_Vehicle info_Repair Records', 'Add_New Vehicle', 'Archive_Vehicle', - 'Delete_Vehicle', 'Export_Vehicle Report' ], Transportation: [