Fixed permissions issues

Created default user role
modified server.js to check if roles of admin/user is present
modified server.js to check dashboard cards
set up default dashboard cards to show
This commit is contained in:
Muhammad Ibrahim
2025-09-24 01:56:02 +01:00
parent db0ba201a4
commit 3a0b564a6f
16 changed files with 797 additions and 77 deletions

View File

@@ -8,7 +8,9 @@ const FirstTimeAdminSetup = () => {
username: '',
email: '',
password: '',
confirmPassword: ''
confirmPassword: '',
firstName: '',
lastName: ''
})
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')
@@ -25,6 +27,14 @@ const FirstTimeAdminSetup = () => {
}
const validateForm = () => {
if (!formData.firstName.trim()) {
setError('First name is required')
return false
}
if (!formData.lastName.trim()) {
setError('Last name is required')
return false
}
if (!formData.username.trim()) {
setError('Username is required')
return false
@@ -69,7 +79,9 @@ const FirstTimeAdminSetup = () => {
body: JSON.stringify({
username: formData.username.trim(),
email: formData.email.trim(),
password: formData.password
password: formData.password,
firstName: formData.firstName.trim(),
lastName: formData.lastName.trim()
})
})
@@ -145,6 +157,41 @@ const FirstTimeAdminSetup = () => {
)}
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label htmlFor="firstName" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
First Name
</label>
<input
type="text"
id="firstName"
name="firstName"
value={formData.firstName}
onChange={handleInputChange}
className="input w-full"
placeholder="Enter your first name"
required
disabled={isLoading}
/>
</div>
<div>
<label htmlFor="lastName" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
Last Name
</label>
<input
type="text"
id="lastName"
name="lastName"
value={formData.lastName}
onChange={handleInputChange}
className="input w-full"
placeholder="Enter your last name"
required
disabled={isLoading}
/>
</div>
</div>
<div>
<label htmlFor="username" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
Username

View File

@@ -47,7 +47,7 @@ const Layout = ({ children }) => {
const [userMenuOpen, setUserMenuOpen] = useState(false)
const [githubStars, setGithubStars] = useState(null)
const location = useLocation()
const { user, logout, canViewHosts, canManageHosts, canViewPackages, canViewUsers, canManageUsers, canManageSettings } = useAuth()
const { user, logout, canViewDashboard, canViewHosts, canManageHosts, canViewPackages, canViewUsers, canManageUsers, canViewReports, canExportData, canManageSettings } = useAuth()
const { updateAvailable } = useUpdateNotification()
const userMenuRef = useRef(null)
@@ -66,44 +66,103 @@ const Layout = ({ children }) => {
staleTime: 300000, // Consider data stale after 5 minutes
})
const navigation = [
{ name: 'Dashboard', href: '/', icon: Home },
{
section: 'Inventory',
items: [
...(canViewHosts() ? [{ name: 'Hosts', href: '/hosts', icon: Server }] : []),
...(canViewPackages() ? [{ name: 'Packages', href: '/packages', icon: Package }] : []),
...(canViewHosts() ? [{ name: 'Repos', href: '/repositories', icon: GitBranch }] : []),
{ name: 'Services', href: '/services', icon: Activity, comingSoon: true },
{ name: 'Docker', href: '/docker', icon: Container, comingSoon: true },
{ name: 'Reporting', href: '/reporting', icon: BarChart3, comingSoon: true },
]
},
...(canViewUsers() || canManageUsers() ? [{
section: 'PatchMon Users',
items: [
...(canViewUsers() ? [{ name: 'Users', href: '/users', icon: Users }] : []),
...(canManageSettings() ? [{ name: 'Permissions', href: '/permissions', icon: Shield }] : []),
]
}] : []),
{
section: 'Settings',
items: [
...(canManageHosts() ? [{
// Build navigation based on permissions
const buildNavigation = () => {
const nav = []
// Dashboard - only show if user can view dashboard
if (canViewDashboard()) {
nav.push({ name: 'Dashboard', href: '/', icon: Home })
}
// Inventory section - only show if user has any inventory permissions
if (canViewHosts() || canViewPackages() || canViewReports()) {
const inventoryItems = []
if (canViewHosts()) {
inventoryItems.push({ name: 'Hosts', href: '/hosts', icon: Server })
inventoryItems.push({ name: 'Repos', href: '/repositories', icon: GitBranch })
}
if (canViewPackages()) {
inventoryItems.push({ name: 'Packages', href: '/packages', icon: Package })
}
if (canViewReports()) {
inventoryItems.push(
{ name: 'Services', href: '/services', icon: Activity, comingSoon: true },
{ name: 'Docker', href: '/docker', icon: Container, comingSoon: true },
{ name: 'Reporting', href: '/reporting', icon: BarChart3, comingSoon: true }
)
}
if (inventoryItems.length > 0) {
nav.push({
section: 'Inventory',
items: inventoryItems
})
}
}
// PatchMon Users section - only show if user can view/manage users
if (canViewUsers() || canManageUsers()) {
const userItems = []
if (canViewUsers()) {
userItems.push({ name: 'Users', href: '/users', icon: Users })
}
if (canManageSettings()) {
userItems.push({ name: 'Permissions', href: '/permissions', icon: Shield })
}
if (userItems.length > 0) {
nav.push({
section: 'PatchMon Users',
items: userItems
})
}
}
// Settings section - only show if user has any settings permissions
if (canManageSettings() || canViewReports() || canExportData()) {
const settingsItems = []
if (canManageSettings()) {
settingsItems.push({
name: 'PatchMon Options',
href: '/options',
icon: Settings
}] : []),
{ name: 'Audit Log', href: '/audit-log', icon: FileText, comingSoon: true },
...(canManageSettings() ? [{
})
settingsItems.push({
name: 'Server Config',
href: '/settings',
icon: Wrench,
showUpgradeIcon: updateAvailable
}] : []),
]
})
}
if (canViewReports() || canExportData()) {
settingsItems.push({
name: 'Audit Log',
href: '/audit-log',
icon: FileText,
comingSoon: true
})
}
if (settingsItems.length > 0) {
nav.push({
section: 'Settings',
items: settingsItems
})
}
}
]
return nav
}
const navigation = buildNavigation()
const isActive = (path) => location.pathname === path
@@ -221,6 +280,15 @@ const Layout = ({ children }) => {
</div>
</div>
<nav className="mt-8 flex-1 space-y-6 px-2">
{/* Show message for users with very limited permissions */}
{navigation.length === 0 && (
<div className="px-2 py-4 text-center">
<div className="text-sm text-secondary-500 dark:text-secondary-400">
<p className="mb-2">Limited access</p>
<p className="text-xs">Contact your administrator for additional permissions</p>
</div>
</div>
)}
{navigation.map((item, index) => {
if (item.name) {
// Single item (Dashboard)
@@ -346,6 +414,15 @@ const Layout = ({ children }) => {
</div>
<nav className="flex flex-1 flex-col">
<ul className="flex flex-1 flex-col gap-y-6">
{/* Show message for users with very limited permissions */}
{navigation.length === 0 && (
<li className="px-2 py-4 text-center">
<div className="text-sm text-secondary-500 dark:text-secondary-400">
<p className="mb-2">Limited access</p>
<p className="text-xs">Contact your administrator for additional permissions</p>
</div>
</li>
)}
{navigation.map((item, index) => {
if (item.name) {
// Single item (Dashboard)

View File

@@ -9,7 +9,9 @@ const Login = () => {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
password: '',
firstName: '',
lastName: ''
})
const [tfaData, setTfaData] = useState({
token: ''
@@ -76,8 +78,7 @@ const Login = () => {
setError('')
try {
const response = await authAPI.signup(formData.username, formData.email, formData.password)
const response = await authAPI.signup(formData.username, formData.email, formData.password, formData.firstName, formData.lastName)
if (response.data && response.data.token) {
// Store token and user data
localStorage.setItem('token', response.data.token)
@@ -162,7 +163,9 @@ const Login = () => {
setFormData({
username: '',
email: '',
password: ''
password: '',
firstName: '',
lastName: ''
})
setError('')
}
@@ -211,10 +214,53 @@ const Login = () => {
</div>
{isSignupMode && (
<div>
<label htmlFor="email" className="block text-sm font-medium text-secondary-700">
Email
</label>
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label htmlFor="firstName" className="block text-sm font-medium text-secondary-700">
First Name
</label>
<div className="mt-1 relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<User className="h-5 w-5 text-secondary-400" />
</div>
<input
id="firstName"
name="firstName"
type="text"
required
value={formData.firstName}
onChange={handleInputChange}
className="appearance-none rounded-md relative block w-full pl-10 pr-3 py-2 border border-secondary-300 placeholder-secondary-500 text-secondary-900 focus:outline-none focus:ring-primary-500 focus:border-primary-500 focus:z-10 sm:text-sm"
placeholder="Enter your first name"
/>
</div>
</div>
<div>
<label htmlFor="lastName" className="block text-sm font-medium text-secondary-700">
Last Name
</label>
<div className="mt-1 relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<User className="h-5 w-5 text-secondary-400" />
</div>
<input
id="lastName"
name="lastName"
type="text"
required
value={formData.lastName}
onChange={handleInputChange}
className="appearance-none rounded-md relative block w-full pl-10 pr-3 py-2 border border-secondary-300 placeholder-secondary-500 text-secondary-900 focus:outline-none focus:ring-primary-500 focus:border-primary-500 focus:z-10 sm:text-sm"
placeholder="Enter your last name"
/>
</div>
</div>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-secondary-700">
Email
</label>
<div className="mt-1 relative">
<input
id="email"
@@ -235,6 +281,7 @@ const Login = () => {
</div>
</div>
</div>
</>
)}
<div>

View File

@@ -174,7 +174,7 @@ const RolePermissionsCard = ({ role, isEditing, onEdit, onCancel, onSave, onDele
onSave(role.role, permissions)
}
const isAdminRole = role.role === 'admin'
const isBuiltInRole = role.role === 'admin' || role.role === 'user'
return (
<div className="bg-white dark:bg-secondary-800 shadow rounded-lg">
@@ -183,9 +183,9 @@ const RolePermissionsCard = ({ role, isEditing, onEdit, onCancel, onSave, onDele
<div className="flex items-center">
<Shield className="h-5 w-5 text-primary-600 mr-3" />
<h3 className="text-lg font-medium text-secondary-900 dark:text-white capitalize">{role.role}</h3>
{isAdminRole && (
{isBuiltInRole && (
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-100 text-primary-800">
System Role
Built-in Role
</span>
)}
</div>
@@ -211,13 +211,13 @@ const RolePermissionsCard = ({ role, isEditing, onEdit, onCancel, onSave, onDele
<>
<button
onClick={onEdit}
disabled={isAdminRole}
disabled={isBuiltInRole}
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
<Edit className="h-4 w-4 mr-1" />
Edit
</button>
{!isAdminRole && (
{!isBuiltInRole && (
<button
onClick={() => onDelete(role.role)}
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700"
@@ -245,7 +245,7 @@ const RolePermissionsCard = ({ role, isEditing, onEdit, onCancel, onSave, onDele
type="checkbox"
checked={isChecked}
onChange={(e) => handlePermissionChange(field.key, e.target.checked)}
disabled={!isEditing || (isAdminRole && field.key === 'can_manage_users')}
disabled={!isEditing || (isBuiltInRole && field.key === 'can_manage_users')}
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-secondary-300 rounded disabled:opacity-50"
/>
</div>

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Save, Server, Shield, AlertCircle, CheckCircle, Code, Plus, Trash2, Star, Download, X, Settings as SettingsIcon, Clock } from 'lucide-react';
import { settingsAPI, agentVersionAPI, versionAPI } from '../utils/api';
import { settingsAPI, agentVersionAPI, versionAPI, permissionsAPI } from '../utils/api';
import { useUpdateNotification } from '../contexts/UpdateNotificationContext';
import UpgradeNotificationIcon from '../components/UpgradeNotificationIcon';
@@ -13,6 +13,7 @@ const Settings = () => {
updateInterval: 60,
autoUpdate: false,
signupEnabled: false,
defaultUserRole: 'user',
githubRepoUrl: 'git@github.com:9technologygroup/patchmon.net.git',
repositoryType: 'public',
sshKeyPath: '',
@@ -68,6 +69,12 @@ const Settings = () => {
queryFn: () => settingsAPI.get().then(res => res.data)
});
// Fetch available roles for default user role dropdown
const { data: roles, isLoading: rolesLoading } = useQuery({
queryKey: ['rolePermissions'],
queryFn: () => permissionsAPI.getRoles().then(res => res.data)
});
// Update form data when settings are loaded
useEffect(() => {
if (settings) {
@@ -78,6 +85,7 @@ const Settings = () => {
updateInterval: settings.update_interval || 60,
autoUpdate: settings.auto_update || false,
signupEnabled: settings.signup_enabled === true ? true : false, // Explicit boolean conversion
defaultUserRole: settings.default_user_role || 'user',
githubRepoUrl: settings.github_repo_url || 'git@github.com:9technologygroup/patchmon.net.git',
repositoryType: settings.repository_type || 'public',
sshKeyPath: settings.ssh_key_path || '',
@@ -560,6 +568,37 @@ const Settings = () => {
Enable User Self-Registration
</div>
</label>
{/* Default User Role Dropdown */}
{formData.signupEnabled && (
<div className="mt-3 ml-6">
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
Default Role for New Users
</label>
<select
value={formData.defaultUserRole}
onChange={(e) => handleInputChange('defaultUserRole', e.target.value)}
className="w-full max-w-xs border-secondary-300 dark:border-secondary-600 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500 bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white"
disabled={rolesLoading}
>
{rolesLoading ? (
<option>Loading roles...</option>
) : roles && Array.isArray(roles) ? (
roles.map((role) => (
<option key={role.role} value={role.role}>
{role.role.charAt(0).toUpperCase() + role.role.slice(1)}
</option>
))
) : (
<option value="user">User</option>
)}
</select>
<p className="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
New users will be assigned this role when they register.
</p>
</div>
)}
<p className="mt-1 text-sm text-secondary-500 dark:text-secondary-400">
When enabled, users can create their own accounts through the signup page. When disabled, only administrators can create user accounts.
</p>

View File

@@ -201,7 +201,7 @@ export const versionAPI = {
export const authAPI = {
login: (username, password) => api.post('/auth/login', { username, password }),
verifyTfa: (username, token) => api.post('/auth/verify-tfa', { username, token }),
signup: (username, email, password) => api.post('/auth/signup', { username, email, password }),
signup: (username, email, password, firstName, lastName) => api.post('/auth/signup', { username, email, password, firstName, lastName }),
}
// TFA API