diff --git a/README.md b/README.md deleted file mode 100644 index 690480f..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Join my discord for Instructions, support and feedback : - -https://discord.gg/S7RXUHwg diff --git a/backend/env.example b/backend/env.example index e3c1066..e0d713c 100644 --- a/backend/env.example +++ b/backend/env.example @@ -20,3 +20,6 @@ AGENT_RATE_LIMIT_MAX=1000 # Logging LOG_LEVEL=info ENABLE_LOGGING=true + +# User Registration +DEFAULT_USER_ROLE=user diff --git a/backend/prisma/migrations/20250924000102_add_default_user_role_to_settings/migration.sql b/backend/prisma/migrations/20250924000102_add_default_user_role_to_settings/migration.sql new file mode 100644 index 0000000..92cf369 --- /dev/null +++ b/backend/prisma/migrations/20250924000102_add_default_user_role_to_settings/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "settings" ADD COLUMN "default_user_role" TEXT NOT NULL DEFAULT 'user'; diff --git a/backend/prisma/migrations/20250924012341_init_default_dashboard_preferences/migration.sql b/backend/prisma/migrations/20250924012341_init_default_dashboard_preferences/migration.sql new file mode 100644 index 0000000..5a5c8b8 --- /dev/null +++ b/backend/prisma/migrations/20250924012341_init_default_dashboard_preferences/migration.sql @@ -0,0 +1,65 @@ +-- Initialize default dashboard preferences for all existing users +-- This migration ensures that all users have proper role-based dashboard preferences + +-- Function to create default dashboard preferences for a user +CREATE OR REPLACE FUNCTION init_user_dashboard_preferences(user_id TEXT, user_role TEXT) +RETURNS VOID AS $$ +DECLARE + pref_record RECORD; +BEGIN + -- Delete any existing preferences for this user + DELETE FROM dashboard_preferences WHERE dashboard_preferences.user_id = init_user_dashboard_preferences.user_id; + + -- Insert role-based preferences + IF user_role = 'admin' THEN + -- Admin gets full access to all cards (iby's preferred layout) + INSERT INTO dashboard_preferences (id, user_id, card_id, enabled, "order", created_at, updated_at) + VALUES + (gen_random_uuid(), user_id, 'totalHosts', true, 0, NOW(), NOW()), + (gen_random_uuid(), user_id, 'hostsNeedingUpdates', true, 1, NOW(), NOW()), + (gen_random_uuid(), user_id, 'totalOutdatedPackages', true, 2, NOW(), NOW()), + (gen_random_uuid(), user_id, 'securityUpdates', true, 3, NOW(), NOW()), + (gen_random_uuid(), user_id, 'totalHostGroups', true, 4, NOW(), NOW()), + (gen_random_uuid(), user_id, 'upToDateHosts', true, 5, NOW(), NOW()), + (gen_random_uuid(), user_id, 'totalRepos', true, 6, NOW(), NOW()), + (gen_random_uuid(), user_id, 'totalUsers', true, 7, NOW(), NOW()), + (gen_random_uuid(), user_id, 'osDistribution', true, 8, NOW(), NOW()), + (gen_random_uuid(), user_id, 'osDistributionBar', true, 9, NOW(), NOW()), + (gen_random_uuid(), user_id, 'recentCollection', true, 10, NOW(), NOW()), + (gen_random_uuid(), user_id, 'updateStatus', true, 11, NOW(), NOW()), + (gen_random_uuid(), user_id, 'packagePriority', true, 12, NOW(), NOW()), + (gen_random_uuid(), user_id, 'recentUsers', true, 13, NOW(), NOW()), + (gen_random_uuid(), user_id, 'quickStats', true, 14, NOW(), NOW()); + ELSE + -- Regular users get comprehensive layout but without user management cards + INSERT INTO dashboard_preferences (id, user_id, card_id, enabled, "order", created_at, updated_at) + VALUES + (gen_random_uuid(), user_id, 'totalHosts', true, 0, NOW(), NOW()), + (gen_random_uuid(), user_id, 'hostsNeedingUpdates', true, 1, NOW(), NOW()), + (gen_random_uuid(), user_id, 'totalOutdatedPackages', true, 2, NOW(), NOW()), + (gen_random_uuid(), user_id, 'securityUpdates', true, 3, NOW(), NOW()), + (gen_random_uuid(), user_id, 'totalHostGroups', true, 4, NOW(), NOW()), + (gen_random_uuid(), user_id, 'upToDateHosts', true, 5, NOW(), NOW()), + (gen_random_uuid(), user_id, 'totalRepos', true, 6, NOW(), NOW()), + (gen_random_uuid(), user_id, 'osDistribution', true, 7, NOW(), NOW()), + (gen_random_uuid(), user_id, 'osDistributionBar', true, 8, NOW(), NOW()), + (gen_random_uuid(), user_id, 'recentCollection', true, 9, NOW(), NOW()), + (gen_random_uuid(), user_id, 'updateStatus', true, 10, NOW(), NOW()), + (gen_random_uuid(), user_id, 'packagePriority', true, 11, NOW(), NOW()), + (gen_random_uuid(), user_id, 'quickStats', true, 12, NOW(), NOW()); + END IF; +END; +$$ LANGUAGE plpgsql; + +-- Apply default preferences to all existing users +DO $$ +DECLARE + user_record RECORD; +BEGIN + FOR user_record IN SELECT id, role FROM users LOOP + PERFORM init_user_dashboard_preferences(user_record.id, user_record.role); + END LOOP; +END $$; + +-- Drop the temporary function +DROP FUNCTION init_user_dashboard_preferences(TEXT, TEXT); diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 8881b63..4f1b859 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -168,6 +168,7 @@ model settings { latest_version String? update_available Boolean @default(false) signup_enabled Boolean @default(false) + default_user_role String @default("user") } model update_history { diff --git a/backend/src/routes/authRoutes.js b/backend/src/routes/authRoutes.js index 7ee1807..137fc8b 100644 --- a/backend/src/routes/authRoutes.js +++ b/backend/src/routes/authRoutes.js @@ -6,6 +6,7 @@ const { body, validationResult } = require('express-validator'); const { authenticateToken, requireAdmin } = require('../middleware/auth'); const { requireViewUsers, requireManageUsers } = require('../middleware/permissions'); const { v4: uuidv4 } = require('uuid'); +const { createDefaultDashboardPreferences } = require('./dashboardPreferencesRoutes'); const router = express.Router(); const prisma = new PrismaClient(); @@ -32,6 +33,8 @@ router.get('/check-admin-users', async (req, res) => { // Create first admin user (for first-time setup) router.post('/setup-admin', [ + body('firstName').isLength({ min: 1 }).withMessage('First name is required'), + body('lastName').isLength({ min: 1 }).withMessage('Last name is required'), body('username').isLength({ min: 1 }).withMessage('Username is required'), body('email').isEmail().withMessage('Valid email is required'), body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 characters for security') @@ -45,7 +48,7 @@ router.post('/setup-admin', [ }); } - const { username, email, password } = req.body; + const { firstName, lastName, username, email, password } = req.body; // Check if any admin users already exist const adminCount = await prisma.users.count({ @@ -84,6 +87,8 @@ router.post('/setup-admin', [ username: username.trim(), email: email.trim(), password_hash: passwordHash, + first_name: firstName.trim(), + last_name: lastName.trim(), role: 'admin', is_active: true, created_at: new Date(), @@ -98,6 +103,9 @@ router.post('/setup-admin', [ } }); + // Create default dashboard preferences for the new admin user + await createDefaultDashboardPreferences(user.id, 'admin'); + res.status(201).json({ message: 'Admin user created successfully', user: user @@ -173,7 +181,14 @@ router.post('/admin/users', authenticateToken, requireManageUsers, [ return res.status(400).json({ errors: errors.array() }); } - const { username, email, password, first_name, last_name, role = 'user' } = req.body; + const { username, email, password, first_name, last_name, role } = req.body; + + // Get default user role from settings if no role specified + let userRole = role; + if (!userRole) { + const settings = await prisma.settings.findFirst(); + userRole = settings?.default_user_role || 'user'; + } // Check if user already exists const existingUser = await prisma.users.findFirst({ @@ -201,7 +216,7 @@ router.post('/admin/users', authenticateToken, requireManageUsers, [ password_hash: passwordHash, first_name: first_name || null, last_name: last_name || null, - role, + role: userRole, updated_at: new Date() }, select: { @@ -216,6 +231,9 @@ router.post('/admin/users', authenticateToken, requireManageUsers, [ } }); + // Create default dashboard preferences for the new user + await createDefaultDashboardPreferences(user.id, userRole); + res.status(201).json({ message: 'User created successfully', user @@ -449,6 +467,8 @@ router.get('/signup-enabled', async (req, res) => { // Public signup endpoint router.post('/signup', [ + body('firstName').isLength({ min: 1 }).withMessage('First name is required'), + body('lastName').isLength({ min: 1 }).withMessage('Last name is required'), body('username').isLength({ min: 3 }).withMessage('Username must be at least 3 characters'), body('email').isEmail().withMessage('Valid email is required'), body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters') @@ -465,7 +485,7 @@ router.post('/signup', [ return res.status(400).json({ errors: errors.array() }); } - const { username, email, password } = req.body; + const { firstName, lastName, username, email, password } = req.body; // Check if user already exists const existingUser = await prisma.users.findFirst({ @@ -484,14 +504,19 @@ router.post('/signup', [ // Hash password const passwordHash = await bcrypt.hash(password, 12); - // Create user with default 'user' role + // Get default user role from settings or environment variable + const defaultRole = settings?.default_user_role || process.env.DEFAULT_USER_ROLE || 'user'; + + // Create user with default role from settings const user = await prisma.users.create({ data: { id: uuidv4(), username, email, password_hash: passwordHash, - role: 'user', + first_name: firstName.trim(), + last_name: lastName.trim(), + role: defaultRole, updated_at: new Date() }, select: { @@ -504,6 +529,9 @@ router.post('/signup', [ } }); + // Create default dashboard preferences for the new user + await createDefaultDashboardPreferences(user.id, defaultRole); + console.log(`New user registered: ${user.username} (${user.email})`); // Generate token for immediate login diff --git a/backend/src/routes/dashboardPreferencesRoutes.js b/backend/src/routes/dashboardPreferencesRoutes.js index 4179629..2fe04b4 100644 --- a/backend/src/routes/dashboardPreferencesRoutes.js +++ b/backend/src/routes/dashboardPreferencesRoutes.js @@ -2,10 +2,115 @@ const express = require('express'); const { body, validationResult } = require('express-validator'); const { PrismaClient } = require('@prisma/client'); const { authenticateToken } = require('../middleware/auth'); +const { v4: uuidv4 } = require('uuid'); const router = express.Router(); const prisma = new PrismaClient(); +// Helper function to get user permissions based on role +async function getUserPermissions(userRole) { + try { + const permissions = await prisma.role_permissions.findUnique({ + where: { role: userRole } + }); + + // If no specific permissions found, return default admin permissions (for backward compatibility) + if (!permissions) { + console.warn(`No permissions found for role: ${userRole}, defaulting to admin access`); + return { + can_view_dashboard: true, + can_view_hosts: true, + can_manage_hosts: true, + can_view_packages: true, + can_manage_packages: true, + can_view_users: true, + can_manage_users: true, + can_view_reports: true, + can_export_data: true, + can_manage_settings: true + }; + } + + return permissions; + } catch (error) { + console.error('Error fetching user permissions:', error); + // Return admin permissions as fallback + return { + can_view_dashboard: true, + can_view_hosts: true, + can_manage_hosts: true, + can_view_packages: true, + can_manage_packages: true, + can_view_users: true, + can_manage_users: true, + can_view_reports: true, + can_export_data: true, + can_manage_settings: true + }; + } +} + +// Helper function to create permission-based dashboard preferences for a new user +async function createDefaultDashboardPreferences(userId, userRole = 'user') { + try { + // Get user's actual permissions + const permissions = await getUserPermissions(userRole); + + // Define all possible dashboard cards with their required permissions + const allCards = [ + // Host-related cards + { cardId: 'totalHosts', requiredPermission: 'can_view_hosts', order: 0 }, + { cardId: 'hostsNeedingUpdates', requiredPermission: 'can_view_hosts', order: 1 }, + { cardId: 'upToDateHosts', requiredPermission: 'can_view_hosts', order: 2 }, + { cardId: 'totalHostGroups', requiredPermission: 'can_view_hosts', order: 3 }, + + // Package-related cards + { cardId: 'totalOutdatedPackages', requiredPermission: 'can_view_packages', order: 4 }, + { cardId: 'securityUpdates', requiredPermission: 'can_view_packages', order: 5 }, + { cardId: 'packagePriority', requiredPermission: 'can_view_packages', order: 6 }, + + // Repository-related cards + { cardId: 'totalRepos', requiredPermission: 'can_view_hosts', order: 7 }, // Repos are host-related + + // User management cards (admin only) + { cardId: 'totalUsers', requiredPermission: 'can_view_users', order: 8 }, + { cardId: 'recentUsers', requiredPermission: 'can_view_users', order: 9 }, + + // System/Report cards + { cardId: 'osDistribution', requiredPermission: 'can_view_reports', order: 10 }, + { cardId: 'osDistributionBar', requiredPermission: 'can_view_reports', order: 11 }, + { cardId: 'updateStatus', requiredPermission: 'can_view_reports', order: 12 }, + { cardId: 'recentCollection', requiredPermission: 'can_view_hosts', order: 13 }, // Collection is host-related + { cardId: 'quickStats', requiredPermission: 'can_view_dashboard', order: 14 } + ]; + + // Filter cards based on user's permissions + const allowedCards = allCards.filter(card => { + return permissions[card.requiredPermission] === true; + }); + + // Create preferences data + const preferencesData = allowedCards.map((card) => ({ + id: uuidv4(), + user_id: userId, + card_id: card.cardId, + enabled: true, + order: card.order, // Preserve original order from allCards + created_at: new Date(), + updated_at: new Date() + })); + + await prisma.dashboard_preferences.createMany({ + data: preferencesData + }); + + console.log(`Permission-based dashboard preferences created for user ${userId} with role ${userRole}: ${allowedCards.length} cards`); + } catch (error) { + console.error('Error creating default dashboard preferences:', error); + // Don't throw error - this shouldn't break user creation + } +} + // Get user's dashboard preferences router.get('/', authenticateToken, async (req, res) => { try { @@ -69,22 +174,24 @@ router.put('/', authenticateToken, [ // Get default dashboard card configuration router.get('/defaults', authenticateToken, async (req, res) => { try { + // Default configuration based on iby's (Muhammad Ibrahim) preferred layout + // This provides a comprehensive dashboard view for all new users const defaultCards = [ { cardId: 'totalHosts', title: 'Total Hosts', icon: 'Server', enabled: true, order: 0 }, { cardId: 'hostsNeedingUpdates', title: 'Needs Updating', icon: 'AlertTriangle', enabled: true, order: 1 }, { cardId: 'totalOutdatedPackages', title: 'Outdated Packages', icon: 'Package', enabled: true, order: 2 }, { cardId: 'securityUpdates', title: 'Security Updates', icon: 'Shield', enabled: true, order: 3 }, - { cardId: 'upToDateHosts', title: 'Up to date', icon: 'CheckCircle', enabled: true, order: 4 }, - { cardId: 'totalHostGroups', title: 'Host Groups', icon: 'Folder', enabled: false, order: 5 }, - { cardId: 'totalUsers', title: 'Users', icon: 'Users', enabled: false, order: 6 }, - { cardId: 'totalRepos', title: 'Repositories', icon: 'GitBranch', enabled: false, order: 7 }, + { cardId: 'totalHostGroups', title: 'Host Groups', icon: 'Folder', enabled: true, order: 4 }, + { cardId: 'upToDateHosts', title: 'Up to date', icon: 'CheckCircle', enabled: true, order: 5 }, + { cardId: 'totalRepos', title: 'Repositories', icon: 'GitBranch', enabled: true, order: 6 }, + { cardId: 'totalUsers', title: 'Users', icon: 'Users', enabled: true, order: 7 }, { cardId: 'osDistribution', title: 'OS Distribution', icon: 'BarChart3', enabled: true, order: 8 }, - { cardId: 'osDistributionBar', title: 'OS Distribution (Bar)', icon: 'BarChart3', enabled: false, order: 9 }, - { cardId: 'updateStatus', title: 'Update Status', icon: 'BarChart3', enabled: true, order: 10 }, - { cardId: 'packagePriority', title: 'Package Priority', icon: 'BarChart3', enabled: true, order: 11 }, - { cardId: 'quickStats', title: 'Quick Stats', icon: 'TrendingUp', enabled: true, order: 12 }, + { cardId: 'osDistributionBar', title: 'OS Distribution (Bar)', icon: 'BarChart3', enabled: true, order: 9 }, + { cardId: 'recentCollection', title: 'Recent Collection', icon: 'Server', enabled: true, order: 10 }, + { cardId: 'updateStatus', title: 'Update Status', icon: 'BarChart3', enabled: true, order: 11 }, + { cardId: 'packagePriority', title: 'Package Priority', icon: 'BarChart3', enabled: true, order: 12 }, { cardId: 'recentUsers', title: 'Recent Users Logged in', icon: 'Users', enabled: true, order: 13 }, - { cardId: 'recentCollection', title: 'Recent Collection', icon: 'Server', enabled: true, order: 14 } + { cardId: 'quickStats', title: 'Quick Stats', icon: 'TrendingUp', enabled: true, order: 14 } ]; res.json(defaultCards); @@ -94,4 +201,4 @@ router.get('/defaults', authenticateToken, async (req, res) => { } }); -module.exports = router; +module.exports = { router, createDefaultDashboardPreferences }; diff --git a/backend/src/routes/permissionsRoutes.js b/backend/src/routes/permissionsRoutes.js index 42a5c0e..7862032 100644 --- a/backend/src/routes/permissionsRoutes.js +++ b/backend/src/routes/permissionsRoutes.js @@ -59,9 +59,9 @@ router.put('/roles/:role', authenticateToken, requireManageSettings, async (req, can_manage_settings } = req.body; - // Prevent modifying admin role permissions (admin should always have full access) - if (role === 'admin') { - return res.status(400).json({ error: 'Cannot modify admin role permissions' }); + // Prevent modifying admin and user role permissions (built-in roles) + if (role === 'admin' || role === 'user') { + return res.status(400).json({ error: `Cannot modify ${role} role permissions - this is a built-in role` }); } const permissions = await prisma.role_permissions.upsert({ @@ -111,9 +111,9 @@ router.delete('/roles/:role', authenticateToken, requireManageSettings, async (r try { const { role } = req.params; - // Prevent deleting admin role - if (role === 'admin') { - return res.status(400).json({ error: 'Cannot delete admin role' }); + // Prevent deleting admin and user roles (built-in roles) + if (role === 'admin' || role === 'user') { + return res.status(400).json({ error: `Cannot delete ${role} role - this is a built-in role` }); } // Check if any users are using this role diff --git a/backend/src/routes/settingsRoutes.js b/backend/src/routes/settingsRoutes.js index a34eee4..8437c30 100644 --- a/backend/src/routes/settingsRoutes.js +++ b/backend/src/routes/settingsRoutes.js @@ -111,6 +111,7 @@ router.put('/', authenticateToken, requireManageSettings, [ body('updateInterval').isInt({ min: 5, max: 1440 }).withMessage('Update interval must be between 5 and 1440 minutes'), body('autoUpdate').isBoolean().withMessage('Auto update must be a boolean'), body('signupEnabled').isBoolean().withMessage('Signup enabled must be a boolean'), + body('defaultUserRole').optional().isLength({ min: 1 }).withMessage('Default user role must be a non-empty string'), body('githubRepoUrl').optional().isLength({ min: 1 }).withMessage('GitHub repo URL must be a non-empty string'), body('repositoryType').optional().isIn(['public', 'private']).withMessage('Repository type must be public or private'), body('sshKeyPath').optional().custom((value) => { @@ -130,7 +131,7 @@ router.put('/', authenticateToken, requireManageSettings, [ return res.status(400).json({ errors: errors.array() }); } - const { serverProtocol, serverHost, serverPort, updateInterval, autoUpdate, signupEnabled, githubRepoUrl, repositoryType, sshKeyPath } = req.body; + const { serverProtocol, serverHost, serverPort, updateInterval, autoUpdate, signupEnabled, defaultUserRole, githubRepoUrl, repositoryType, sshKeyPath } = req.body; // Get current settings to check for update interval changes const currentSettings = await getSettings(); @@ -144,6 +145,7 @@ router.put('/', authenticateToken, requireManageSettings, [ update_interval: updateInterval || 60, auto_update: autoUpdate || false, signup_enabled: signupEnabled || false, + default_user_role: defaultUserRole || process.env.DEFAULT_USER_ROLE || 'user', github_repo_url: githubRepoUrl !== undefined ? githubRepoUrl : 'git@github.com:9technologygroup/patchmon.net.git', repository_type: repositoryType || 'public', ssh_key_path: sshKeyPath || null, diff --git a/backend/src/server.js b/backend/src/server.js index 30e2ef0..b44253e 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -14,7 +14,7 @@ const packageRoutes = require('./routes/packageRoutes'); const dashboardRoutes = require('./routes/dashboardRoutes'); const permissionsRoutes = require('./routes/permissionsRoutes'); const settingsRoutes = require('./routes/settingsRoutes'); -const dashboardPreferencesRoutes = require('./routes/dashboardPreferencesRoutes'); +const { router: dashboardPreferencesRoutes, createDefaultDashboardPreferences } = require('./routes/dashboardPreferencesRoutes'); const repositoryRoutes = require('./routes/repositoryRoutes'); const versionRoutes = require('./routes/versionRoutes'); const tfaRoutes = require('./routes/tfaRoutes'); @@ -188,6 +188,111 @@ async function checkAndImportAgentVersion() { } } +// Function to check and create default role permissions on startup +async function checkAndCreateRolePermissions() { + console.log('šŸ” Starting role permissions auto-creation check...'); + + // Skip if auto-creation is disabled + if (process.env.AUTO_CREATE_ROLE_PERMISSIONS === 'false') { + console.log('āŒ Auto-creation of role permissions is disabled'); + if (process.env.ENABLE_LOGGING === 'true') { + logger.info('Auto-creation of role permissions is disabled'); + } + return; + } + + try { + const crypto = require('crypto'); + + // Define default roles and permissions + const defaultRoles = [ + { + id: crypto.randomUUID(), + role: 'admin', + can_view_dashboard: true, + can_view_hosts: true, + can_manage_hosts: true, + can_view_packages: true, + can_manage_packages: true, + can_view_users: true, + can_manage_users: true, + can_view_reports: true, + can_export_data: true, + can_manage_settings: true, + created_at: new Date(), + updated_at: new Date() + }, + { + id: crypto.randomUUID(), + role: 'user', + can_view_dashboard: true, + can_view_hosts: true, + can_manage_hosts: false, + can_view_packages: true, + can_manage_packages: false, + can_view_users: false, + can_manage_users: false, + can_view_reports: true, + can_export_data: false, + can_manage_settings: false, + created_at: new Date(), + updated_at: new Date() + } + ]; + + const createdRoles = []; + const existingRoles = []; + + for (const roleData of defaultRoles) { + // Check if role already exists + const existingRole = await prisma.role_permissions.findUnique({ + where: { role: roleData.role } + }); + + if (existingRole) { + console.log(`āœ… Role '${roleData.role}' already exists in database`); + existingRoles.push(existingRole); + if (process.env.ENABLE_LOGGING === 'true') { + logger.info(`Role '${roleData.role}' already exists in database`); + } + } else { + // Create new role permission + const permission = await prisma.role_permissions.create({ + data: roleData + }); + createdRoles.push(permission); + console.log(`šŸ†• Created role '${roleData.role}' with permissions`); + if (process.env.ENABLE_LOGGING === 'true') { + logger.info(`Created role '${roleData.role}' with permissions`); + } + } + } + + if (createdRoles.length > 0) { + console.log(`šŸŽ‰ Successfully auto-created ${createdRoles.length} role permissions on startup`); + console.log('šŸ“‹ Created roles:'); + createdRoles.forEach(role => { + console.log(` • ${role.role}: dashboard=${role.can_view_dashboard}, hosts=${role.can_manage_hosts}, packages=${role.can_manage_packages}, users=${role.can_manage_users}, settings=${role.can_manage_settings}`); + }); + + if (process.env.ENABLE_LOGGING === 'true') { + logger.info(`āœ… Auto-created ${createdRoles.length} role permissions on startup`); + } + } else { + console.log(`āœ… All default role permissions already exist (${existingRoles.length} roles verified)`); + if (process.env.ENABLE_LOGGING === 'true') { + logger.info(`All default role permissions already exist (${existingRoles.length} roles verified)`); + } + } + + } catch (error) { + console.error('āŒ Failed to check/create role permissions on startup:', error.message); + if (process.env.ENABLE_LOGGING === 'true') { + logger.error('Failed to check/create role permissions on startup:', error.message); + } + } +} + // Initialize logger - only if logging is enabled const logger = process.env.ENABLE_LOGGING === 'true' ? winston.createLogger({ level: process.env.LOG_LEVEL || 'info', @@ -405,6 +510,201 @@ process.on('SIGTERM', async () => { process.exit(0); }); +// Initialize dashboard preferences for all users +async function initializeDashboardPreferences() { + try { + console.log('šŸ”§ Initializing dashboard preferences for all users...'); + + // Get all users + const users = await prisma.users.findMany({ + select: { + id: true, + username: true, + email: true, + role: true, + dashboard_preferences: { + select: { + card_id: true + } + } + } + }); + + if (users.length === 0) { + console.log('ā„¹ļø No users found in database'); + return; + } + + console.log(`šŸ“Š Found ${users.length} users to initialize`); + + let initializedCount = 0; + let updatedCount = 0; + + for (const user of users) { + const hasPreferences = user.dashboard_preferences.length > 0; + + // Get permission-based preferences for this user's role + const expectedPreferences = await getPermissionBasedPreferences(user.role); + const expectedCardCount = expectedPreferences.length; + + if (!hasPreferences) { + // User has no preferences - create them + console.log(`āš™ļø Creating preferences for ${user.username} (${user.role})`); + + const preferencesData = expectedPreferences.map(pref => ({ + id: require('uuid').v4(), + user_id: user.id, + card_id: pref.cardId, + enabled: pref.enabled, + order: pref.order, + created_at: new Date(), + updated_at: new Date() + })); + + await prisma.dashboard_preferences.createMany({ + data: preferencesData + }); + + initializedCount++; + console.log(` āœ… Created ${expectedCardCount} cards based on permissions`); + } else { + // User already has preferences - check if they need updating + const currentCardCount = user.dashboard_preferences.length; + + if (currentCardCount !== expectedCardCount) { + console.log(`šŸ”„ Updating preferences for ${user.username} (${user.role}) - ${currentCardCount} → ${expectedCardCount} cards`); + + // Delete existing preferences + await prisma.dashboard_preferences.deleteMany({ + where: { user_id: user.id } + }); + + // Create new preferences based on permissions + const preferencesData = expectedPreferences.map(pref => ({ + id: require('uuid').v4(), + user_id: user.id, + card_id: pref.cardId, + enabled: pref.enabled, + order: pref.order, + created_at: new Date(), + updated_at: new Date() + })); + + await prisma.dashboard_preferences.createMany({ + data: preferencesData + }); + + updatedCount++; + console.log(` āœ… Updated to ${expectedCardCount} cards based on permissions`); + } else { + console.log(`āœ… ${user.username} already has correct preferences (${currentCardCount} cards)`); + } + } + } + + console.log(`\nšŸ“‹ Dashboard Preferences Initialization Complete:`); + console.log(` - New users initialized: ${initializedCount}`); + console.log(` - Existing users updated: ${updatedCount}`); + console.log(` - Users with correct preferences: ${users.length - initializedCount - updatedCount}`); + console.log(`\nšŸŽÆ Permission-based preferences:`); + console.log(` - Cards are now assigned based on actual user permissions`); + console.log(` - Each card requires specific permissions (can_view_hosts, can_view_users, etc.)`); + console.log(` - Users only see cards they have permission to access`); + + } catch (error) { + console.error('āŒ Error initializing dashboard preferences:', error); + throw error; + } +} + +// Helper function to get user permissions based on role +async function getUserPermissions(userRole) { + try { + const permissions = await prisma.role_permissions.findUnique({ + where: { role: userRole } + }); + + // If no specific permissions found, return default admin permissions (for backward compatibility) + if (!permissions) { + console.warn(`No permissions found for role: ${userRole}, defaulting to admin access`); + return { + can_view_dashboard: true, + can_view_hosts: true, + can_manage_hosts: true, + can_view_packages: true, + can_manage_packages: true, + can_view_users: true, + can_manage_users: true, + can_view_reports: true, + can_export_data: true, + can_manage_settings: true + }; + } + + return permissions; + } catch (error) { + console.error('Error fetching user permissions:', error); + // Return admin permissions as fallback + return { + can_view_dashboard: true, + can_view_hosts: true, + can_manage_hosts: true, + can_view_packages: true, + can_manage_packages: true, + can_view_users: true, + can_manage_users: true, + can_view_reports: true, + can_export_data: true, + can_manage_settings: true + }; + } +} + +// Helper function to get permission-based dashboard preferences for a role +async function getPermissionBasedPreferences(userRole) { + // Get user's actual permissions + const permissions = await getUserPermissions(userRole); + + // Define all possible dashboard cards with their required permissions + const allCards = [ + // Host-related cards + { cardId: 'totalHosts', requiredPermission: 'can_view_hosts', order: 0 }, + { cardId: 'hostsNeedingUpdates', requiredPermission: 'can_view_hosts', order: 1 }, + { cardId: 'upToDateHosts', requiredPermission: 'can_view_hosts', order: 2 }, + { cardId: 'totalHostGroups', requiredPermission: 'can_view_hosts', order: 3 }, + + // Package-related cards + { cardId: 'totalOutdatedPackages', requiredPermission: 'can_view_packages', order: 4 }, + { cardId: 'securityUpdates', requiredPermission: 'can_view_packages', order: 5 }, + { cardId: 'packagePriority', requiredPermission: 'can_view_packages', order: 6 }, + + // Repository-related cards + { cardId: 'totalRepos', requiredPermission: 'can_view_hosts', order: 7 }, // Repos are host-related + + // User management cards (admin only) + { cardId: 'totalUsers', requiredPermission: 'can_view_users', order: 8 }, + { cardId: 'recentUsers', requiredPermission: 'can_view_users', order: 9 }, + + // System/Report cards + { cardId: 'osDistribution', requiredPermission: 'can_view_reports', order: 10 }, + { cardId: 'osDistributionBar', requiredPermission: 'can_view_reports', order: 11 }, + { cardId: 'updateStatus', requiredPermission: 'can_view_reports', order: 12 }, + { cardId: 'recentCollection', requiredPermission: 'can_view_hosts', order: 13 }, // Collection is host-related + { cardId: 'quickStats', requiredPermission: 'can_view_dashboard', order: 14 } + ]; + + // Filter cards based on user's permissions + const allowedCards = allCards.filter(card => { + return permissions[card.requiredPermission] === true; + }); + + return allowedCards.map((card) => ({ + cardId: card.cardId, + enabled: true, + order: card.order // Preserve original order from allCards + })); +} + // Start server with database health check async function startServer() { try { @@ -430,7 +730,12 @@ async function startServer() { // Check and import agent version on startup await checkAndImportAgentVersion(); - + + // Check and create default role permissions on startup + await checkAndCreateRolePermissions(); + + // Initialize dashboard preferences for all users + await initializeDashboardPreferences(); app.listen(PORT, () => { if (process.env.ENABLE_LOGGING === 'true') { logger.info(`Server running on port ${PORT}`); diff --git a/frontend/src/components/FirstTimeAdminSetup.jsx b/frontend/src/components/FirstTimeAdminSetup.jsx index 435b317..c0bfa86 100644 --- a/frontend/src/components/FirstTimeAdminSetup.jsx +++ b/frontend/src/components/FirstTimeAdminSetup.jsx @@ -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 = () => { )}
+
+
+ + +
+
+ + +
+
+