Added ACLs for User Management Preventing Users from Making Changes, Only Admins

This commit is contained in:
2025-09-24 14:11:38 -06:00
parent 3d3337d1b2
commit 930a897e0b
4 changed files with 46 additions and 9 deletions

View File

@@ -1,11 +1,12 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Paper, Box, Typography } from "@mui/material"; import { Paper, Box, Typography } from "@mui/material";
export default function ServerInfo() { export default function ServerInfo({ isAdmin = false }) {
const [serverTime, setServerTime] = useState(null); const [serverTime, setServerTime] = useState(null);
const [error, setError] = useState(null); const [error, setError] = useState(null);
useEffect(() => { useEffect(() => {
if (!isAdmin) return;
let isMounted = true; let isMounted = true;
const fetchTime = async () => { const fetchTime = async () => {
try { try {
@@ -23,7 +24,9 @@ export default function ServerInfo() {
fetchTime(); fetchTime();
const id = setInterval(fetchTime, 60000); // update once per minute const id = setInterval(fetchTime, 60000); // update once per minute
return () => { isMounted = false; clearInterval(id); }; return () => { isMounted = false; clearInterval(id); };
}, []); }, [isAdmin]);
if (!isAdmin) return null;
return ( return (
<Paper sx={{ m: 2, p: 0, bgcolor: "#1e1e1e" }} elevation={2}> <Paper sx={{ m: 2, p: 0, bgcolor: "#1e1e1e" }} elevation={2}>

View File

@@ -68,7 +68,7 @@ async function sha512(text) {
return arr.map((b) => b.toString(16).padStart(2, "0")).join(""); return arr.map((b) => b.toString(16).padStart(2, "0")).join("");
} }
export default function UserManagement() { export default function UserManagement({ isAdmin = false }) {
const [rows, setRows] = useState([]); // {username, display_name, role, last_login} const [rows, setRows] = useState([]); // {username, display_name, role, last_login}
const [orderBy, setOrderBy] = useState("username"); const [orderBy, setOrderBy] = useState("username");
const [order, setOrder] = useState("asc"); const [order, setOrder] = useState("asc");
@@ -113,7 +113,7 @@ export default function UserManagement() {
}, []); }, []);
useEffect(() => { useEffect(() => {
fetchUsers(); if (!isAdmin) return;
(async () => { (async () => {
try { try {
const resp = await fetch("/api/auth/me", { credentials: "include" }); const resp = await fetch("/api/auth/me", { credentials: "include" });
@@ -123,7 +123,8 @@ export default function UserManagement() {
} }
} catch {} } catch {}
})(); })();
}, [fetchUsers]); fetchUsers();
}, [fetchUsers, isAdmin]);
const handleSort = (col) => { const handleSort = (col) => {
if (orderBy === col) setOrder(order === "asc" ? "desc" : "asc"); if (orderBy === col) setOrder(order === "asc" ? "desc" : "asc");
@@ -291,6 +292,8 @@ export default function UserManagement() {
} }
}; };
if (!isAdmin) return null;
return ( return (
<> <>
<Paper sx={tablePaperSx} elevation={2}> <Paper sx={tablePaperSx} elevation={2}>

View File

@@ -5,7 +5,7 @@ import React, { useState, useEffect, useCallback, useRef } from "react";
import { ReactFlowProvider } from "reactflow"; import { ReactFlowProvider } from "reactflow";
import "reactflow/dist/style.css"; import "reactflow/dist/style.css";
import { import {
CloseAllDialog, CreditsDialog, RenameTabDialog, TabContextMenu CloseAllDialog, CreditsDialog, RenameTabDialog, TabContextMenu, NotAuthorizedDialog
} from "./Dialogs"; } from "./Dialogs";
import NavigationSidebar from "./Navigation_Sidebar"; import NavigationSidebar from "./Navigation_Sidebar";
@@ -104,6 +104,7 @@ export default function App() {
const [userRole, setUserRole] = useState(null); const [userRole, setUserRole] = useState(null);
const [editingJob, setEditingJob] = useState(null); const [editingJob, setEditingJob] = useState(null);
const [jobsRefreshToken, setJobsRefreshToken] = useState(0); const [jobsRefreshToken, setJobsRefreshToken] = useState(0);
const [notAuthorizedOpen, setNotAuthorizedOpen] = useState(false);
// Build breadcrumb items for current view // Build breadcrumb items for current view
const breadcrumbs = React.useMemo(() => { const breadcrumbs = React.useMemo(() => {
@@ -383,6 +384,15 @@ export default function App() {
[tabs, activeTabId] [tabs, activeTabId]
); );
const isAdmin = (String(userRole || '').toLowerCase() === 'admin');
useEffect(() => {
if (!isAdmin && (currentPage === 'admin_users' || currentPage === 'server_info')) {
setNotAuthorizedOpen(true);
setCurrentPage('devices');
}
}, [currentPage, isAdmin]);
const renderMainContent = () => { const renderMainContent = () => {
switch (currentPage) { switch (currentPage) {
case "sites": case "sites":
@@ -496,10 +506,10 @@ export default function App() {
return <ScriptEditor />; return <ScriptEditor />;
case "admin_users": case "admin_users":
return <UserManagement />; return <UserManagement isAdmin={isAdmin} />;
case "server_info": case "server_info":
return <ServerInfo />; return <ServerInfo isAdmin={isAdmin} />;
case "workflow-editor": case "workflow-editor":
return ( return (
@@ -647,7 +657,7 @@ export default function App() {
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Box sx={{ display: "flex", flexGrow: 1, overflow: "hidden" }}> <Box sx={{ display: "flex", flexGrow: 1, overflow: "hidden" }}>
<NavigationSidebar currentPage={currentPage} onNavigate={setCurrentPage} isAdmin={(String(userRole||'').toLowerCase()==='admin')} /> <NavigationSidebar currentPage={currentPage} onNavigate={setCurrentPage} isAdmin={isAdmin} />
<Box sx={{ flexGrow: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}> <Box sx={{ flexGrow: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
{renderMainContent()} {renderMainContent()}
</Box> </Box>
@@ -668,6 +678,7 @@ export default function App() {
onRename={handleRenameTab} onRename={handleRenameTab}
onCloseTab={handleCloseTab} onCloseTab={handleCloseTab}
/> />
<NotAuthorizedDialog open={notAuthorizedOpen} onClose={() => setNotAuthorizedOpen(false)} />
</ThemeProvider> </ThemeProvider>
); );
} }

View File

@@ -30,6 +30,26 @@ export function CloseAllDialog({ open, onClose, onConfirm }) {
); );
} }
export function NotAuthorizedDialog({ open, onClose }) {
return (
<Dialog
open={open}
onClose={onClose}
PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}
>
<DialogTitle>Not Authorized</DialogTitle>
<DialogContent>
<DialogContentText sx={{ color: "#ccc" }}>
You are not authorized to access this section.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose} sx={{ color: "#58a6ff" }}>OK</Button>
</DialogActions>
</Dialog>
);
}
export function CreditsDialog({ open, onClose }) { export function CreditsDialog({ open, onClose }) {
return ( return (
<Dialog open={open} onClose={onClose} PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}> <Dialog open={open} onClose={onClose} PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}>