Multi-User Role Management System in HTML CSS AND JAVASCRIPT WITH PHP AND MY SQL

Project Introduction

A comprehensive role-based access control (RBAC) system that manages different user types, permissions, and access levels across the entire blog platform. This system integrates with all previous modules (Blog, Contact Form, URL Shortener, Gallery, and Freelance Marketplace) to provide granular control over what each user can see and do.


✨ Features

User Types & Roles

  • Super Admin: Full system control, can manage all aspects
  • Admin: Platform management, user moderation, content approval
  • Moderator: Content moderation, user reports, dispute resolution
  • Editor: Content creation and management (blog posts, gallery)
  • Client: Post projects, hire freelancers, make payments
  • Freelancer: Bid on projects, submit work, receive payments
  • Premium User: Enhanced features, priority support
  • Regular User: Basic access (commenting, contact forms)
  • Guest: View-only access to public content

Core Features

  • Role Hierarchy: Inherited permissions based on role levels
  • Permission Matrix: Granular control over 200+ permissions
  • Dynamic Access Control: Permissions can be assigned/revoked in real-time
  • Audit Logging: Track all user actions and access attempts
  • IP Restrictions: Limit access by IP address ranges
  • Time-based Access: Set active hours for specific roles
  • Two-Factor Authentication: Enhanced security for admin roles
  • Session Management: View and terminate active sessions

Permission Categories

  1. User Management: Create, edit, delete, suspend users
  2. Content Management: Blog posts, comments, gallery images
  3. Marketplace: Projects, proposals, contracts, payments
  4. System Settings: Platform configuration, security settings
  5. Financial: Payment processing, withdrawals, refunds
  6. Reports: Analytics, user statistics, financial reports
  7. API Access: Rate limits, endpoint permissions

📁 File Structure

blog-website/
│
├── admin/                                      # Admin panel
│   ├── dashboard.php                             # Admin dashboard
│   ├── roles.php                                 # Role management
│   ├── permissions.php                           # Permission matrix
│   ├── users.php                                 # User management
│   ├── activity-logs.php                         # Audit logs
│   ├── security-settings.php                     # Security config
│   └── api-keys.php                              # API key management
│
├── includes/
│   ├── role-config.php                           # Role system configuration
│   ├── role-functions.php                        # Core role functions
│   ├── auth.php                                  # Authentication
│   ├── middleware.php                             # Access control middleware
│   └── activity-logger.php                        # Activity logging
│
├── profile/
│   ├── index.php                                 # User profile
│   ├── security.php                              # Security settings
│   ├── sessions.php                              # Session management
│   └── api-keys.php                              # API keys
│
├── login.php                                      # Login page
├── register.php                                   # Registration
├── logout.php                                     # Logout
├── forgot-password.php                            # Password reset
├── verify-email.php                               # Email verification
├── two-factor.php                                 # 2FA setup
│
├── css/
│   └── auth.css                                   # Authentication styles
│
├── js/
│   └── auth.js                                    # Authentication JS
│
├── .env                                           # Environment variables
├── composer.json                                  # Dependencies
│
└── database/
└── roles.sql                                  # Database schema

🗄️ Database Schema (database/roles.sql)

-- Create roles database (extends existing blog database)
USE blog_db;
-- Roles table
CREATE TABLE IF NOT EXISTS roles (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
slug VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
level INT DEFAULT 0, -- Higher level = more privileges
is_system BOOLEAN DEFAULT FALSE, -- System roles cannot be deleted
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_level (level)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Permissions table
CREATE TABLE IF NOT EXISTS permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
slug VARCHAR(255) NOT NULL UNIQUE,
category VARCHAR(100),
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_category (category)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Role permissions junction table
CREATE TABLE IF NOT EXISTS role_permissions (
role_id INT NOT NULL,
permission_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- User roles junction table (users can have multiple roles)
CREATE TABLE IF NOT EXISTS user_roles (
user_id INT NOT NULL,
role_id INT NOT NULL,
assigned_by INT,
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NULL,
is_active BOOLEAN DEFAULT TRUE,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
FOREIGN KEY (assigned_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_expires (expires_at),
INDEX idx_active (is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- User permissions override table (for individual exceptions)
CREATE TABLE IF NOT EXISTS user_permissions (
user_id INT NOT NULL,
permission_id INT NOT NULL,
grant_type ENUM('allow', 'deny') DEFAULT 'allow',
granted_by INT,
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NULL,
PRIMARY KEY (user_id, permission_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
FOREIGN KEY (granted_by) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Activity logs
CREATE TABLE IF NOT EXISTS activity_logs (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
action VARCHAR(255) NOT NULL,
description TEXT,
ip_address VARCHAR(45),
user_agent TEXT,
request_method VARCHAR(10),
request_url TEXT,
request_data JSON,
response_status INT,
execution_time INT, -- in milliseconds
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_action (action),
INDEX idx_created (created_at),
INDEX idx_ip (ip_address),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Sessions table
CREATE TABLE IF NOT EXISTS user_sessions (
id VARCHAR(128) PRIMARY KEY,
user_id INT NOT NULL,
ip_address VARCHAR(45),
user_agent TEXT,
payload TEXT,
last_activity INT,
expires_at TIMESTAMP NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_last_activity (last_activity),
INDEX idx_expires (expires_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Two-factor authentication
CREATE TABLE IF NOT EXISTS two_factor_auth (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL UNIQUE,
secret VARCHAR(255),
backup_codes JSON,
is_enabled BOOLEAN DEFAULT FALSE,
last_verified_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Login attempts (for brute force protection)
CREATE TABLE IF NOT EXISTS login_attempts (
id INT AUTO_INCREMENT PRIMARY KEY,
ip_address VARCHAR(45) NOT NULL,
user_email VARCHAR(255),
attempts INT DEFAULT 1,
last_attempt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
blocked_until TIMESTAMP NULL,
INDEX idx_ip (ip_address),
INDEX idx_blocked (blocked_until)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- API keys
CREATE TABLE IF NOT EXISTS api_keys (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
api_key VARCHAR(64) UNIQUE NOT NULL,
permissions JSON,
rate_limit INT DEFAULT 60, -- requests per minute
last_used_at TIMESTAMP NULL,
expires_at TIMESTAMP NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_api_key (api_key),
INDEX idx_expires (expires_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- IP whitelist for admin access
CREATE TABLE IF NOT EXISTS ip_whitelist (
id INT AUTO_INCREMENT PRIMARY KEY,
ip_address VARCHAR(45) NOT NULL UNIQUE,
description TEXT,
created_by INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ip (ip_address),
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Insert default roles
INSERT INTO roles (name, slug, description, level, is_system) VALUES
('Super Admin', 'super-admin', 'Full system access with all permissions', 100, TRUE),
('Admin', 'admin', 'Platform administration and management', 90, TRUE),
('Moderator', 'moderator', 'Content moderation and user support', 70, TRUE),
('Editor', 'editor', 'Content creation and management', 50, TRUE),
('Client', 'client', 'Can post projects and hire freelancers', 30, TRUE),
('Freelancer', 'freelancer', 'Can bid on projects and earn money', 30, TRUE),
('Premium User', 'premium-user', 'Enhanced features and priority support', 20, TRUE),
('Regular User', 'regular-user', 'Basic platform access', 10, TRUE),
('Guest', 'guest', 'View-only access', 0, TRUE);
-- Insert default permissions
INSERT INTO permissions (name, slug, category, description) VALUES
-- User Management
('View Users', 'view-users', 'users', 'Can view user list and profiles'),
('Create Users', 'create-users', 'users', 'Can create new user accounts'),
('Edit Users', 'edit-users', 'users', 'Can edit user details'),
('Delete Users', 'delete-users', 'users', 'Can delete user accounts'),
('Suspend Users', 'suspend-users', 'users', 'Can suspend/ban users'),
('Assign Roles', 'assign-roles', 'users', 'Can assign roles to users'),
('Manage Roles', 'manage-roles', 'users', 'Can create/edit/delete roles'),
('Manage Permissions', 'manage-permissions', 'users', 'Can modify permission assignments'),
-- Content Management
('View Posts', 'view-posts', 'content', 'Can view blog posts'),
('Create Posts', 'create-posts', 'content', 'Can create new blog posts'),
('Edit Posts', 'edit-posts', 'content', 'Can edit any blog post'),
('Delete Posts', 'delete-posts', 'content', 'Can delete blog posts'),
('Publish Posts', 'publish-posts', 'content', 'Can publish/unpublish posts'),
('Moderate Comments', 'moderate-comments', 'content', 'Can approve/reject comments'),
('View Gallery', 'view-gallery', 'content', 'Can view image gallery'),
('Upload Images', 'upload-images', 'content', 'Can upload images to gallery'),
('Manage Albums', 'manage-albums', 'content', 'Can create/edit/delete albums'),
-- Marketplace
('View Projects', 'view-projects', 'marketplace', 'Can view project listings'),
('Create Projects', 'create-projects', 'marketplace', 'Can post new projects'),
('Edit Projects', 'edit-projects', 'marketplace', 'Can edit any project'),
('Delete Projects', 'delete-projects', 'marketplace', 'Can delete projects'),
('Submit Proposals', 'submit-proposals', 'marketplace', 'Can bid on projects'),
('Manage Contracts', 'manage-contracts', 'marketplace', 'Can manage active contracts'),
('Process Payments', 'process-payments', 'marketplace', 'Can process escrow payments'),
('View Financial Data', 'view-financial', 'marketplace', 'Can view financial reports'),
-- System Settings
('Access Admin Panel', 'access-admin', 'system', 'Can access admin dashboard'),
('View System Logs', 'view-logs', 'system', 'Can view activity logs'),
('Configure Settings', 'configure-settings', 'system', 'Can modify system settings'),
('Manage Security', 'manage-security', 'system', 'Can configure security settings'),
('View Reports', 'view-reports', 'system', 'Can view analytics reports'),
('Export Data', 'export-data', 'system', 'Can export system data'),
-- API Access
('Use API', 'use-api', 'api', 'Can access API endpoints'),
('Create API Keys', 'create-api-keys', 'api', 'Can generate API keys'),
('Manage API Keys', 'manage-api-keys', 'api', 'Can manage API keys');
-- Assign permissions to roles (simplified - in practice you'd have many more)
-- Super Admin gets all permissions
INSERT INTO role_permissions (role_id, permission_id)
SELECT 1, id FROM permissions;
-- Admin gets most permissions
INSERT INTO role_permissions (role_id, permission_id)
SELECT 2, id FROM permissions 
WHERE category IN ('users', 'content', 'system') 
AND slug NOT IN ('manage-roles', 'manage-permissions');
-- Moderator gets content moderation permissions
INSERT INTO role_permissions (role_id, permission_id)
SELECT 3, id FROM permissions 
WHERE slug IN ('view-users', 'view-posts', 'edit-posts', 'moderate-comments', 
'view-gallery', 'view-projects');
-- Editor gets content creation permissions
INSERT INTO role_permissions (role_id, permission_id)
SELECT 4, id FROM permissions 
WHERE slug IN ('view-posts', 'create-posts', 'edit-posts', 'view-gallery', 
'upload-images', 'manage-albums');
-- Client gets project-related permissions
INSERT INTO role_permissions (role_id, permission_id)
SELECT 5, id FROM permissions 
WHERE slug IN ('view-projects', 'create-projects', 'edit-projects', 'manage-contracts');
-- Freelancer gets proposal and contract permissions
INSERT INTO role_permissions (role_id, permission_id)
SELECT 6, id FROM permissions 
WHERE slug IN ('view-projects', 'submit-proposals', 'manage-contracts');
-- Regular User gets basic permissions
INSERT INTO role_permissions (role_id, permission_id)
SELECT 8, id FROM permissions 
WHERE slug IN ('view-posts', 'view-gallery', 'view-projects');

🔧 Core Files

1. includes/role-config.php

<?php
/**
* Role Management System Configuration
*/
require_once __DIR__ . '/../vendor/autoload.php';
use Dotenv\Dotenv;
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
// Session configuration
ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']));
// Security settings
define('MAX_LOGIN_ATTEMPTS', 5);
define('LOCKOUT_TIME', 15); // minutes
define('SESSION_LIFETIME', 7200); // 2 hours
define('REMEMBER_ME_LIFETIME', 2592000); // 30 days
define('PASSWORD_MIN_LENGTH', 8);
define('REQUIRE_EMAIL_VERIFICATION', true);
define('ENABLE_2FA', true);
// Role levels (for hierarchy)
define('ROLE_LEVEL_SUPER_ADMIN', 100);
define('ROLE_LEVEL_ADMIN', 90);
define('ROLE_LEVEL_MODERATOR', 70);
define('ROLE_LEVEL_EDITOR', 50);
define('ROLE_LEVEL_PREMIUM', 20);
define('ROLE_LEVEL_REGULAR', 10);
define('ROLE_LEVEL_GUEST', 0);
// Cache permissions for performance
define('CACHE_PERMISSIONS', true);
define('PERMISSION_CACHE_TTL', 3600); // 1 hour
// Start session with secure settings
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Database connection function
function getDB() {
static $pdo = null;
if ($pdo === null) {
try {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]);
} catch (PDOException $e) {
error_log("Database connection failed: " . $e->getMessage());
die("Database connection failed. Please try again later.");
}
}
return $pdo;
}

2. includes/role-functions.php

<?php
/**
* Core Role Management Functions
*/
require_once __DIR__ . '/role-config.php';
/**
* Get all roles
*/
function getRoles() {
$pdo = getDB();
$stmt = $pdo->query("SELECT * FROM roles ORDER BY level DESC, name");
return $stmt->fetchAll();
}
/**
* Get role by ID
*/
function getRole($roleId) {
$pdo = getDB();
$stmt = $pdo->prepare("SELECT * FROM roles WHERE id = ?");
$stmt->execute([$roleId]);
return $stmt->fetch();
}
/**
* Get all permissions
*/
function getPermissions($category = null) {
$pdo = getDB();
if ($category) {
$stmt = $pdo->prepare("SELECT * FROM permissions WHERE category = ? ORDER BY name");
$stmt->execute([$category]);
} else {
$stmt = $pdo->query("SELECT * FROM permissions ORDER BY category, name");
}
return $stmt->fetchAll();
}
/**
* Get permission by ID
*/
function getPermission($permissionId) {
$pdo = getDB();
$stmt = $pdo->prepare("SELECT * FROM permissions WHERE id = ?");
$stmt->execute([$permissionId]);
return $stmt->fetch();
}
/**
* Get permissions for a role
*/
function getRolePermissions($roleId) {
$pdo = getDB();
$stmt = $pdo->prepare("
SELECT p.* 
FROM permissions p
JOIN role_permissions rp ON p.id = rp.permission_id
WHERE rp.role_id = ?
ORDER BY p.category, p.name
");
$stmt->execute([$roleId]);
return $stmt->fetchAll();
}
/**
* Get roles for a user
*/
function getUserRoles($userId) {
$pdo = getDB();
$stmt = $pdo->prepare("
SELECT r.* 
FROM roles r
JOIN user_roles ur ON r.id = ur.role_id
WHERE ur.user_id = ? AND ur.is_active = 1 
AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
ORDER BY r.level DESC
");
$stmt->execute([$userId]);
return $stmt->fetchAll();
}
/**
* Get user's primary role (highest level)
*/
function getPrimaryRole($userId) {
$roles = getUserRoles($userId);
return !empty($roles) ? $roles[0] : getRoleBySlug('regular-user');
}
/**
* Get role by slug
*/
function getRoleBySlug($slug) {
$pdo = getDB();
$stmt = $pdo->prepare("SELECT * FROM roles WHERE slug = ?");
$stmt->execute([$slug]);
return $stmt->fetch();
}
/**
* Check if user has permission
*/
function hasPermission($userId, $permissionSlug) {
// Super admin always has all permissions
if (isSuperAdmin($userId)) {
return true;
}
// Check cache first
static $permissionCache = [];
$cacheKey = $userId . '_' . $permissionSlug;
if (CACHE_PERMISSIONS && isset($permissionCache[$cacheKey])) {
return $permissionCache[$cacheKey];
}
$pdo = getDB();
// Check user-specific permissions first (overrides)
$stmt = $pdo->prepare("
SELECT grant_type 
FROM user_permissions up
JOIN permissions p ON up.permission_id = p.id
WHERE up.user_id = ? AND p.slug = ?
AND (up.expires_at IS NULL OR up.expires_at > NOW())
");
$stmt->execute([$userId, $permissionSlug]);
$userPerm = $stmt->fetch();
if ($userPerm) {
$result = $userPerm['grant_type'] === 'allow';
if (CACHE_PERMISSIONS) {
$permissionCache[$cacheKey] = $result;
}
return $result;
}
// Check role-based permissions
$stmt = $pdo->prepare("
SELECT COUNT(*) 
FROM user_roles ur
JOIN role_permissions rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE ur.user_id = ? AND p.slug = ?
AND ur.is_active = 1 AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
");
$stmt->execute([$userId, $permissionSlug]);
$count = $stmt->fetchColumn();
$result = $count > 0;
if (CACHE_PERMISSIONS) {
$permissionCache[$cacheKey] = $result;
}
return $result;
}
/**
* Check if user has multiple permissions (ALL required)
*/
function hasAllPermissions($userId, $permissionSlugs) {
foreach ($permissionSlugs as $slug) {
if (!hasPermission($userId, $slug)) {
return false;
}
}
return true;
}
/**
* Check if user has any of the permissions
*/
function hasAnyPermission($userId, $permissionSlugs) {
foreach ($permissionSlugs as $slug) {
if (hasPermission($userId, $slug)) {
return true;
}
}
return false;
}
/**
* Check if user is super admin
*/
function isSuperAdmin($userId) {
static $superAdminCache = [];
if (isset($superAdminCache[$userId])) {
return $superAdminCache[$userId];
}
$pdo = getDB();
$stmt = $pdo->prepare("
SELECT COUNT(*)
FROM user_roles ur
JOIN roles r ON ur.role_id = r.id
WHERE ur.user_id = ? AND r.slug = 'super-admin'
AND ur.is_active = 1
");
$stmt->execute([$userId]);
$result = $stmt->fetchColumn() > 0;
$superAdminCache[$userId] = $result;
return $result;
}
/**
* Check if user is admin (or higher)
*/
function isAdmin($userId) {
return hasPermission($userId, 'access-admin') || isSuperAdmin($userId);
}
/**
* Assign role to user
*/
function assignRoleToUser($userId, $roleId, $assignedBy = null, $expiresAt = null) {
$pdo = getDB();
// Check if already assigned
$stmt = $pdo->prepare("SELECT * FROM user_roles WHERE user_id = ? AND role_id = ?");
$stmt->execute([$userId, $roleId]);
$existing = $stmt->fetch();
if ($existing) {
// Update existing
$stmt = $pdo->prepare("
UPDATE user_roles 
SET is_active = 1, expires_at = ?, assigned_by = ?
WHERE user_id = ? AND role_id = ?
");
return $stmt->execute([$expiresAt, $assignedBy, $userId, $roleId]);
} else {
// Insert new
$stmt = $pdo->prepare("
INSERT INTO user_roles (user_id, role_id, assigned_by, expires_at)
VALUES (?, ?, ?, ?)
");
return $stmt->execute([$userId, $roleId, $assignedBy, $expiresAt]);
}
}
/**
* Remove role from user
*/
function removeRoleFromUser($userId, $roleId) {
$pdo = getDB();
$stmt = $pdo->prepare("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?");
return $stmt->execute([$userId, $roleId]);
}
/**
* Grant direct permission to user
*/
function grantUserPermission($userId, $permissionId, $grantedBy = null, $expiresAt = null) {
$pdo = getDB();
$stmt = $pdo->prepare("
INSERT INTO user_permissions (user_id, permission_id, grant_type, granted_by, expires_at)
VALUES (?, ?, 'allow', ?, ?)
ON DUPLICATE KEY UPDATE 
grant_type = 'allow', granted_by = ?, expires_at = ?
");
return $stmt->execute([$userId, $permissionId, $grantedBy, $expiresAt, $grantedBy, $expiresAt]);
}
/**
* Deny permission to user (override)
*/
function denyUserPermission($userId, $permissionId, $grantedBy = null, $expiresAt = null) {
$pdo = getDB();
$stmt = $pdo->prepare("
INSERT INTO user_permissions (user_id, permission_id, grant_type, granted_by, expires_at)
VALUES (?, ?, 'deny', ?, ?)
ON DUPLICATE KEY UPDATE 
grant_type = 'deny', granted_by = ?, expires_at = ?
");
return $stmt->execute([$userId, $permissionId, $grantedBy, $expiresAt, $grantedBy, $expiresAt]);
}
/**
* Remove user permission override
*/
function removeUserPermission($userId, $permissionId) {
$pdo = getDB();
$stmt = $pdo->prepare("DELETE FROM user_permissions WHERE user_id = ? AND permission_id = ?");
return $stmt->execute([$userId, $permissionId]);
}
/**
* Get all users with their roles
*/
function getUsersWithRoles($filters = [], $page = 1, $perPage = 20) {
$pdo = getDB();
$where = ["1=1"];
$params = [];
if (!empty($filters['role'])) {
$where[] = "EXISTS (SELECT 1 FROM user_roles ur WHERE ur.user_id = u.id AND ur.role_id = ?)";
$params[] = $filters['role'];
}
if (!empty($filters['search'])) {
$where[] = "(u.email LIKE ? OR u.full_name LIKE ? OR u.username LIKE ?)";
$search = '%' . $filters['search'] . '%';
$params[] = $search;
$params[] = $search;
$params[] = $search;
}
if (!empty($filters['status'])) {
$where[] = "u.account_status = ?";
$params[] = $filters['status'];
}
// Count total
$countSql = "SELECT COUNT(*) FROM users u WHERE " . implode(' AND ', $where);
$countStmt = $pdo->prepare($countSql);
$countStmt->execute($params);
$total = $countStmt->fetchColumn();
// Get users
$offset = ($page - 1) * $perPage;
$sql = "SELECT u.* FROM users u 
WHERE " . implode(' AND ', $where) . "
ORDER BY u.created_at DESC
LIMIT ? OFFSET ?";
$params[] = $perPage;
$params[] = $offset;
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$users = $stmt->fetchAll();
// Get roles for each user
foreach ($users as &$user) {
$user['roles'] = getUserRoles($user['id']);
$user['primary_role'] = !empty($user['roles']) ? $user['roles'][0] : null;
}
return [
'users' => $users,
'total' => $total,
'page' => $page,
'perPage' => $perPage,
'totalPages' => ceil($total / $perPage)
];
}
/**
* Log user activity
*/
function logActivity($userId, $action, $description = '', $data = null) {
$pdo = getDB();
$stmt = $pdo->prepare("
INSERT INTO activity_logs (
user_id, action, description, ip_address, user_agent, 
request_method, request_url, request_data
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
return $stmt->execute([
$userId,
$action,
$description,
$_SERVER['REMOTE_ADDR'] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null,
$_SERVER['REQUEST_METHOD'] ?? null,
$_SERVER['REQUEST_URI'] ?? null,
$data ? json_encode($data) : null
]);
}
/**
* Get activity logs
*/
function getActivityLogs($filters = [], $page = 1, $perPage = 50) {
$pdo = getDB();
$where = ["1=1"];
$params = [];
if (!empty($filters['user_id'])) {
$where[] = "user_id = ?";
$params[] = $filters['user_id'];
}
if (!empty($filters['action'])) {
$where[] = "action = ?";
$params[] = $filters['action'];
}
if (!empty($filters['from_date'])) {
$where[] = "created_at >= ?";
$params[] = $filters['from_date'];
}
if (!empty($filters['to_date'])) {
$where[] = "created_at <= ?";
$params[] = $filters['to_date'];
}
$offset = ($page - 1) * $perPage;
$sql = "SELECT al.*, u.email, u.full_name 
FROM activity_logs al
LEFT JOIN users u ON al.user_id = u.id
WHERE " . implode(' AND ', $where) . "
ORDER BY al.created_at DESC
LIMIT ? OFFSET ?";
$params[] = $perPage;
$params[] = $offset;
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
/**
* Check login attempts (brute force protection)
*/
function checkLoginAttempts($ip, $email = null) {
$pdo = getDB();
// Clean old attempts
$pdo->exec("DELETE FROM login_attempts WHERE last_attempt < DATE_SUB(NOW(), INTERVAL 1 HOUR)");
// Check if IP is blocked
$stmt = $pdo->prepare("SELECT * FROM login_attempts WHERE ip_address = ?");
$stmt->execute([$ip]);
$record = $stmt->fetch();
if ($record) {
if ($record['blocked_until'] && strtotime($record['blocked_until']) > time()) {
return false;
}
if ($record['attempts'] >= MAX_LOGIN_ATTEMPTS) {
// Block IP
$blockUntil = date('Y-m-d H:i:s', strtotime('+' . LOCKOUT_TIME . ' minutes'));
$stmt = $pdo->prepare("UPDATE login_attempts SET blocked_until = ? WHERE ip_address = ?");
$stmt->execute([$blockUntil, $ip]);
return false;
}
}
return true;
}
/**
* Record login attempt
*/
function recordLoginAttempt($ip, $email = null, $success = false) {
$pdo = getDB();
if ($success) {
// Clear attempts on successful login
$pdo->prepare("DELETE FROM login_attempts WHERE ip_address = ?")->execute([$ip]);
return;
}
// Increment attempts
$stmt = $pdo->prepare("
INSERT INTO login_attempts (ip_address, user_email, attempts) 
VALUES (?, ?, 1) 
ON DUPLICATE KEY UPDATE 
attempts = attempts + 1, last_attempt = CURRENT_TIMESTAMP
");
$stmt->execute([$ip, $email]);
}

3. includes/middleware.php

<?php
/**
* Access Control Middleware
*/
require_once __DIR__ . '/role-functions.php';
/**
* Check if user is authenticated
*/
function requireAuth() {
if (!isset($_SESSION['user_id'])) {
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
header('Location: /blog-website/login.php');
exit;
}
// Check session expiration
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > SESSION_LIFETIME) {
session_destroy();
header('Location: /blog-website/login.php?expired=1');
exit;
}
$_SESSION['last_activity'] = time();
}
/**
* Require specific permission
*/
function requirePermission($permissionSlug) {
requireAuth();
if (!hasPermission($_SESSION['user_id'], $permissionSlug)) {
logActivity($_SESSION['user_id'], 'permission_denied', "Required: $permissionSlug");
if (isAjaxRequest()) {
http_response_code(403);
echo json_encode(['error' => 'Access denied. Insufficient permissions.']);
exit;
} else {
$_SESSION['error'] = 'Access denied. You do not have permission to view this page.';
header('Location: /blog-website/index.php');
exit;
}
}
}
/**
* Require any of the given permissions
*/
function requireAnyPermission($permissionSlugs) {
requireAuth();
if (!hasAnyPermission($_SESSION['user_id'], $permissionSlugs)) {
logActivity($_SESSION['user_id'], 'permission_denied', "Required any of: " . implode(', ', $permissionSlugs));
if (isAjaxRequest()) {
http_response_code(403);
echo json_encode(['error' => 'Access denied. Insufficient permissions.']);
exit;
} else {
$_SESSION['error'] = 'Access denied. You do not have permission to view this page.';
header('Location: /blog-website/index.php');
exit;
}
}
}
/**
* Require all of the given permissions
*/
function requireAllPermissions($permissionSlugs) {
requireAuth();
if (!hasAllPermissions($_SESSION['user_id'], $permissionSlugs)) {
logActivity($_SESSION['user_id'], 'permission_denied', "Required all: " . implode(', ', $permissionSlugs));
if (isAjaxRequest()) {
http_response_code(403);
echo json_encode(['error' => 'Access denied. Insufficient permissions.']);
exit;
} else {
$_SESSION['error'] = 'Access denied. You do not have permission to view this page.';
header('Location: /blog-website/index.php');
exit;
}
}
}
/**
* Require specific role
*/
function requireRole($roleSlug) {
requireAuth();
$pdo = getDB();
$stmt = $pdo->prepare("
SELECT COUNT(*) 
FROM user_roles ur
JOIN roles r ON ur.role_id = r.id
WHERE ur.user_id = ? AND r.slug = ? AND ur.is_active = 1
");
$stmt->execute([$_SESSION['user_id'], $roleSlug]);
$hasRole = $stmt->fetchColumn() > 0;
if (!$hasRole) {
logActivity($_SESSION['user_id'], 'role_denied', "Required role: $roleSlug");
if (isAjaxRequest()) {
http_response_code(403);
echo json_encode(['error' => 'Access denied. Required role not assigned.']);
exit;
} else {
$_SESSION['error'] = 'Access denied. You do not have the required role.';
header('Location: /blog-website/index.php');
exit;
}
}
}
/**
* Require minimum role level
*/
function requireMinLevel($minLevel) {
requireAuth();
$roles = getUserRoles($_SESSION['user_id']);
$maxLevel = 0;
foreach ($roles as $role) {
if ($role['level'] > $maxLevel) {
$maxLevel = $role['level'];
}
}
if ($maxLevel < $minLevel) {
logActivity($_SESSION['user_id'], 'level_denied', "Required level: $minLevel, User level: $maxLevel");
if (isAjaxRequest()) {
http_response_code(403);
echo json_encode(['error' => 'Access denied. Insufficient privilege level.']);
exit;
} else {
$_SESSION['error'] = 'Access denied. You do not have sufficient privileges.';
header('Location: /blog-website/index.php');
exit;
}
}
}
/**
* Require admin access
*/
function requireAdmin() {
requireAuth();
if (!isAdmin($_SESSION['user_id'])) {
logActivity($_SESSION['user_id'], 'admin_access_denied');
if (isAjaxRequest()) {
http_response_code(403);
echo json_encode(['error' => 'Admin access required.']);
exit;
} else {
$_SESSION['error'] = 'Access denied. Admin privileges required.';
header('Location: /blog-website/index.php');
exit;
}
}
}
/**
* Check if request is AJAX
*/
function isAjaxRequest() {
return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && 
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
}
/**
* Check resource ownership
*/
function checkOwnership($resourceUserId, $allowAdmins = true) {
if ($_SESSION['user_id'] == $resourceUserId) {
return true;
}
if ($allowAdmins && isAdmin($_SESSION['user_id'])) {
return true;
}
return false;
}
/**
* Require resource ownership
*/
function requireOwnership($resourceUserId, $allowAdmins = true) {
requireAuth();
if (!checkOwnership($resourceUserId, $allowAdmins)) {
logActivity($_SESSION['user_id'], 'ownership_denied', "Resource owner: $resourceUserId");
if (isAjaxRequest()) {
http_response_code(403);
echo json_encode(['error' => 'Access denied. You do not own this resource.']);
exit;
} else {
$_SESSION['error'] = 'Access denied. You do not have permission to modify this resource.';
header('Location: /blog-website/index.php');
exit;
}
}
}
/**
* Rate limiting middleware
*/
function checkRateLimit($maxRequests = 60, $timeWindow = 60) {
$ip = $_SERVER['REMOTE_ADDR'];
$key = "rate_limit:{$ip}:" . floor(time() / $timeWindow);
// Simple file-based rate limiting (replace with Redis in production)
$cacheDir = __DIR__ . '/../cache/ratelimit/';
if (!file_exists($cacheDir)) {
mkdir($cacheDir, 0777, true);
}
$cacheFile = $cacheDir . md5($key) . '.cache';
$count = 1;
if (file_exists($cacheFile)) {
$data = json_decode(file_get_contents($cacheFile), true);
if ($data['expires'] > time()) {
$count = $data['count'] + 1;
}
}
file_put_contents($cacheFile, json_encode([
'count' => $count,
'expires' => time() + $timeWindow
]));
if ($count > $maxRequests) {
http_response_code(429);
die('Rate limit exceeded. Please try again later.');
}
}
/**
* CSRF protection
*/
function generateCSRFToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function verifyCSRFToken($token) {
if (empty($_SESSION['csrf_token']) || $token !== $_SESSION['csrf_token']) {
die('Invalid CSRF token');
}
return true;
}
/**
* Check IP whitelist for admin access
*/
function checkIPWhitelist() {
if (!ENABLE_IP_WHITELIST) {
return true;
}
$ip = $_SERVER['REMOTE_ADDR'];
$pdo = getDB();
$stmt = $pdo->prepare("SELECT COUNT(*) FROM ip_whitelist WHERE ip_address = ?");
$stmt->execute([$ip]);
if ($stmt->fetchColumn() == 0) {
logActivity($_SESSION['user_id'] ?? null, 'ip_blocked', "IP: $ip");
die('Access denied. Your IP is not whitelisted.');
}
return true;
}

4. admin/roles.php (Role Management)

<?php
require_once '../includes/role-config.php';
require_once '../includes/middleware.php';
// Only super admin can manage roles
requirePermission('manage-roles');
$pdo = getDB();
$page_title = 'Role Management';
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'create_role':
$name = $_POST['name'];
$slug = strtolower(preg_replace('/[^a-z0-9]+/', '-', $name));
$description = $_POST['description'];
$level = (int)$_POST['level'];
$stmt = $pdo->prepare("INSERT INTO roles (name, slug, description, level) VALUES (?, ?, ?, ?)");
$stmt->execute([$name, $slug, $description, $level]);
logActivity($_SESSION['user_id'], 'role_created', "Created role: $name");
$_SESSION['success'] = 'Role created successfully';
break;
case 'update_role':
$roleId = $_POST['role_id'];
$name = $_POST['name'];
$description = $_POST['description'];
$level = (int)$_POST['level'];
// Don't allow changing system role names
$role = getRole($roleId);
if ($role && !$role['is_system']) {
$stmt = $pdo->prepare("UPDATE roles SET name = ?, description = ?, level = ? WHERE id = ?");
$stmt->execute([$name, $description, $level, $roleId]);
logActivity($_SESSION['user_id'], 'role_updated', "Updated role: $name");
$_SESSION['success'] = 'Role updated successfully';
}
break;
case 'delete_role':
$roleId = $_POST['role_id'];
$role = getRole($roleId);
if ($role && !$role['is_system']) {
$stmt = $pdo->prepare("DELETE FROM roles WHERE id = ?");
$stmt->execute([$roleId]);
logActivity($_SESSION['user_id'], 'role_deleted', "Deleted role: {$role['name']}");
$_SESSION['success'] = 'Role deleted successfully';
}
break;
case 'update_permissions':
$roleId = $_POST['role_id'];
$permissions = $_POST['permissions'] ?? [];
// Clear existing permissions
$pdo->prepare("DELETE FROM role_permissions WHERE role_id = ?")->execute([$roleId]);
// Insert new permissions
$stmt = $pdo->prepare("INSERT INTO role_permissions (role_id, permission_id) VALUES (?, ?)");
foreach ($permissions as $permId) {
$stmt->execute([$roleId, $permId]);
}
logActivity($_SESSION['user_id'], 'permissions_updated', "Updated permissions for role ID: $roleId");
$_SESSION['success'] = 'Permissions updated successfully';
break;
}
header('Location: roles.php');
exit;
}
}
// Get all roles
$roles = getRoles();
// Get all permissions grouped by category
$permissions = getPermissions();
$permissionsByCategory = [];
foreach ($permissions as $perm) {
$permissionsByCategory[$perm['category']][] = $perm;
}
include '../includes/header.php';
?>
<div class="admin-container">
<h1>Role Management</h1>
<?php if (isset($_SESSION['success'])): ?>
<div class="alert success"><?php echo $_SESSION['success']; unset($_SESSION['success']); ?></div>
<?php endif; ?>
<div class="roles-grid">
<!-- Role List -->
<div class="roles-list">
<div class="card">
<div class="card-header">
<h2>Roles</h2>
<button class="btn-primary" onclick="showCreateRoleModal()">Create New Role</button>
</div>
<table class="data-table">
<thead>
<tr>
<th>Role</th>
<th>Level</th>
<th>Users</th>
<th>System</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($roles as $role): ?>
<?php
$userCount = $pdo->prepare("SELECT COUNT(*) FROM user_roles WHERE role_id = ?");
$userCount->execute([$role['id']]);
$count = $userCount->fetchColumn();
?>
<tr>
<td>
<strong><?php echo htmlspecialchars($role['name']); ?></strong>
<small><?php echo htmlspecialchars($role['description']); ?></small>
</td>
<td><?php echo $role['level']; ?></td>
<td><?php echo $count; ?></td>
<td><?php echo $role['is_system'] ? 'Yes' : 'No'; ?></td>
<td>
<button class="btn-small" onclick="editRole(<?php echo $role['id']; ?>)">Edit</button>
<button class="btn-small" onclick="managePermissions(<?php echo $role['id']; ?>)">Permissions</button>
<?php if (!$role['is_system']): ?>
<button class="btn-small btn-danger" onclick="deleteRole(<?php echo $role['id']; ?>)">Delete</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Create/Edit Role Modal -->
<div id="roleModal" class="modal" style="display: none;">
<div class="modal-content">
<span class="close">&times;</span>
<h2 id="modalTitle">Create New Role</h2>
<form method="POST" id="roleForm">
<input type="hidden" name="action" id="roleAction" value="create_role">
<input type="hidden" name="role_id" id="roleId">
<div class="form-group">
<label for="roleName">Role Name</label>
<input type="text" id="roleName" name="name" class="form-control" required>
</div>
<div class="form-group">
<label for="roleDescription">Description</label>
<textarea id="roleDescription" name="description" class="form-control" rows="3"></textarea>
</div>
<div class="form-group">
<label for="roleLevel">Level (higher = more privileges)</label>
<input type="number" id="roleLevel" name="level" class="form-control" value="10" min="0" max="100">
</div>
<button type="submit" class="btn-primary">Save Role</button>
</form>
</div>
</div>
<!-- Permissions Modal -->
<div id="permissionsModal" class="modal" style="display: none;">
<div class="modal-content modal-lg">
<span class="close">&times;</span>
<h2>Manage Permissions</h2>
<form method="POST" id="permissionsForm">
<input type="hidden" name="action" value="update_permissions">
<input type="hidden" name="role_id" id="permRoleId">
<div class="permissions-grid">
<?php foreach ($permissionsByCategory as $category => $perms): ?>
<div class="permission-category">
<h3><?php echo ucfirst($category); ?></h3>
<?php foreach ($perms as $perm): ?>
<label class="permission-checkbox">
<input type="checkbox" name="permissions[]" value="<?php echo $perm['id']; ?>"
class="perm-checkbox" data-perm-id="<?php echo $perm['id']; ?>">
<span class="perm-name"><?php echo htmlspecialchars($perm['name']); ?></span>
<small class="perm-slug">(<?php echo $perm['slug']; ?>)</small>
</label>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
<div class="form-actions">
<button type="button" class="btn-secondary" onclick="selectAllPermissions()">Select All</button>
<button type="button" class="btn-secondary" onclick="deselectAllPermissions()">Deselect All</button>
<button type="submit" class="btn-primary">Save Permissions</button>
</div>
</form>
</div>
</div>
<script>
let currentRolePermissions = [];
function showCreateRoleModal() {
document.getElementById('modalTitle').textContent = 'Create New Role';
document.getElementById('roleAction').value = 'create_role';
document.getElementById('roleId').value = '';
document.getElementById('roleName').value = '';
document.getElementById('roleDescription').value = '';
document.getElementById('roleLevel').value = '10';
document.getElementById('roleModal').style.display = 'block';
}
function editRole(roleId) {
fetch(`get-role.php?id=${roleId}`)
.then(response => response.json())
.then(role => {
document.getElementById('modalTitle').textContent = 'Edit Role';
document.getElementById('roleAction').value = 'update_role';
document.getElementById('roleId').value = role.id;
document.getElementById('roleName').value = role.name;
document.getElementById('roleDescription').value = role.description;
document.getElementById('roleLevel').value = role.level;
document.getElementById('roleModal').style.display = 'block';
});
}
function deleteRole(roleId) {
if (confirm('Are you sure you want to delete this role? Users with this role will lose its permissions.')) {
const form = document.createElement('form');
form.method = 'POST';
form.innerHTML = `
<input type="hidden" name="action" value="delete_role">
<input type="hidden" name="role_id" value="${roleId}">
`;
document.body.appendChild(form);
form.submit();
}
}
function managePermissions(roleId) {
document.getElementById('permRoleId').value = roleId;
// Load current permissions
fetch(`get-role-permissions.php?role_id=${roleId}`)
.then(response => response.json())
.then(permissions => {
currentRolePermissions = permissions;
// Check checkboxes
document.querySelectorAll('.perm-checkbox').forEach(cb => {
cb.checked = permissions.includes(parseInt(cb.value));
});
document.getElementById('permissionsModal').style.display = 'block';
});
}
function selectAllPermissions() {
document.querySelectorAll('.perm-checkbox').forEach(cb => cb.checked = true);
}
function deselectAllPermissions() {
document.querySelectorAll('.perm-checkbox').forEach(cb => cb.checked = false);
}
// Close modals
document.querySelectorAll('.modal .close').forEach(btn => {
btn.onclick = function() {
this.closest('.modal').style.display = 'none';
}
});
window.onclick = function(event) {
if (event.target.classList.contains('modal')) {
event.target.style.display = 'none';
}
}
</script>
<style>
.admin-container {
max-width: 1200px;
margin: 2rem auto;
padding: 0 1rem;
}
.card {
background: white;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
overflow: hidden;
}
.card-header {
padding: 1.5rem;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th {
background: #f8f9fa;
padding: 1rem;
text-align: left;
font-weight: 500;
color: #666;
}
.data-table td {
padding: 1rem;
border-bottom: 1px solid #eee;
}
.data-table tr:hover td {
background: #f8f9fa;
}
.modal-lg {
max-width: 900px;
}
.permissions-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
max-height: 500px;
overflow-y: auto;
padding: 1rem;
}
.permission-category {
background: #f8f9fa;
padding: 1rem;
border-radius: 5px;
}
.permission-category h3 {
margin-bottom: 1rem;
color: #333;
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.permission-checkbox {
display: block;
margin-bottom: 0.5rem;
cursor: pointer;
}
.permission-checkbox input {
margin-right: 0.5rem;
}
.perm-name {
font-weight: 500;
}
.perm-slug {
color: #888;
font-size: 0.8rem;
margin-left: 0.5rem;
}
.form-actions {
padding: 1rem;
border-top: 1px solid #eee;
display: flex;
gap: 1rem;
justify-content: flex-end;
}
.btn-small {
padding: 0.3rem 0.8rem;
font-size: 0.9rem;
border: none;
border-radius: 3px;
cursor: pointer;
margin: 0 0.2rem;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
}
.btn-secondary {
background: #6c757d;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
}
.btn-danger {
background: #dc3545;
color: white;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 10px;
max-width: 500px;
width: 90%;
position: relative;
max-height: 90vh;
overflow-y: auto;
}
.close {
position: absolute;
top: 1rem;
right: 1rem;
font-size: 1.5rem;
cursor: pointer;
color: #999;
}
.close:hover {
color: #333;
}
@media (max-width: 768px) {
.data-table {
display: block;
overflow-x: auto;
}
.permissions-grid {
grid-template-columns: 1fr;
}
}
</style>
<?php include '../includes/footer.php'; ?>

5. admin/users.php (User Management)

<?php
require_once '../includes/role-config.php';
require_once '../includes/middleware.php';
requirePermission('view-users');
$pdo = getDB();
$page_title = 'User Management';
// Handle user actions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'update_status':
requirePermission('suspend-users');
$userId = $_POST['user_id'];
$status = $_POST['status'];
$stmt = $pdo->prepare("UPDATE users SET account_status = ? WHERE id = ?");
$stmt->execute([$status, $userId]);
logActivity($_SESSION['user_id'], 'user_status_changed', "User ID: $userId, Status: $status");
$_SESSION['success'] = 'User status updated';
break;
case 'assign_role':
requirePermission('assign-roles');
$userId = $_POST['user_id'];
$roleId = $_POST['role_id'];
$expiresAt = !empty($_POST['expires_at']) ? $_POST['expires_at'] : null;
assignRoleToUser($userId, $roleId, $_SESSION['user_id'], $expiresAt);
logActivity($_SESSION['user_id'], 'role_assigned', "User ID: $userId, Role ID: $roleId");
$_SESSION['success'] = 'Role assigned successfully';
break;
case 'remove_role':
requirePermission('assign-roles');
$userId = $_POST['user_id'];
$roleId = $_POST['role_id'];
removeRoleFromUser($userId, $roleId);
logActivity($_SESSION['user_id'], 'role_removed', "User ID: $userId, Role ID: $roleId");
$_SESSION['success'] = 'Role removed successfully';
break;
}
header('Location: users.php');
exit;
}
}
// Get filters
$filters = [
'role' => $_GET['role'] ?? null,
'search' => $_GET['search'] ?? null,
'status' => $_GET['status'] ?? null
];
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$users = getUsersWithRoles($filters, $page, 20);
// Get all roles for dropdown
$roles = getRoles();
include '../includes/header.php';
?>
<div class="admin-container">
<div class="page-header">
<h1>User Management</h1>
</div>
<?php if (isset($_SESSION['success'])): ?>
<div class="alert success"><?php echo $_SESSION['success']; unset($_SESSION['success']); ?></div>
<?php endif; ?>
<!-- Filters -->
<div class="filters-bar">
<form method="GET" class="filters-form">
<input type="text" name="search" placeholder="Search users..." 
value="<?php echo htmlspecialchars($filters['search'] ?? ''); ?>">
<select name="role">
<option value="">All Roles</option>
<?php foreach ($roles as $role): ?>
<option value="<?php echo $role['id']; ?>" 
<?php echo $filters['role'] == $role['id'] ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($role['name']); ?>
</option>
<?php endforeach; ?>
</select>
<select name="status">
<option value="">All Status</option>
<option value="active" <?php echo $filters['status'] == 'active' ? 'selected' : ''; ?>>Active</option>
<option value="suspended" <?php echo $filters['status'] == 'suspended' ? 'selected' : ''; ?>>Suspended</option>
<option value="banned" <?php echo $filters['status'] == 'banned' ? 'selected' : ''; ?>>Banned</option>
</select>
<button type="submit" class="btn-primary">Filter</button>
<a href="users.php" class="btn-secondary">Clear</a>
</form>
</div>
<!-- Users Table -->
<div class="card">
<table class="data-table">
<thead>
<tr>
<th>User</th>
<th>Email</th>
<th>Roles</th>
<th>Status</th>
<th>Joined</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($users['users'] as $user): ?>
<tr>
<td>
<div class="user-info">
<img src="<?php echo $user['profile_image'] ?? '/assets/default-avatar.png'; ?>" 
alt="" class="avatar-small">
<div>
<strong><?php echo htmlspecialchars($user['full_name']); ?></strong>
<small>@<?php echo htmlspecialchars($user['username']); ?></small>
</div>
</div>
</td>
<td><?php echo htmlspecialchars($user['email']); ?></td>
<td>
<?php foreach ($user['roles'] as $role): ?>
<span class="role-badge" style="background: <?php echo $role['color'] ?? '#667eea'; ?>">
<?php echo htmlspecialchars($role['name']); ?>
</span>
<?php endforeach; ?>
</td>
<td>
<span class="status-badge status-<?php echo $user['account_status']; ?>">
<?php echo ucfirst($user['account_status']); ?>
</span>
</td>
<td><?php echo date('M j, Y', strtotime($user['created_at'])); ?></td>
<td>
<button class="btn-small" onclick="viewUser(<?php echo $user['id']; ?>)">View</button>
<?php if (hasPermission('assign-roles')): ?>
<button class="btn-small" onclick="manageRoles(<?php echo $user['id']; ?>)">Roles</button>
<?php endif; ?>
<?php if (hasPermission('suspend-users') && $user['id'] != $_SESSION['user_id']): ?>
<button class="btn-small <?php echo $user['account_status'] == 'active' ? 'btn-warning' : 'btn-success'; ?>"
onclick="toggleStatus(<?php echo $user['id']; ?>, '<?php echo $user['account_status']; ?>')">
<?php echo $user['account_status'] == 'active' ? 'Suspend' : 'Activate'; ?>
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- Pagination -->
<?php if ($users['totalPages'] > 1): ?>
<div class="pagination">
<?php for ($i = 1; $i <= $users['totalPages']; $i++): ?>
<a href="?page=<?php echo $i; ?><?php echo http_build_query($filters); ?>" 
class="page-link <?php echo $i == $page ? 'active' : ''; ?>">
<?php echo $i; ?>
</a>
<?php endfor; ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Role Assignment Modal -->
<div id="roleModal" class="modal" style="display: none;">
<div class="modal-content">
<span class="close">&times;</span>
<h2>Manage User Roles</h2>
<div id="userRoles"></div>
<form method="POST" id="assignRoleForm">
<input type="hidden" name="action" value="assign_role">
<input type="hidden" name="user_id" id="assignUserId">
<div class="form-group">
<label for="roleSelect">Assign New Role</label>
<select id="roleSelect" name="role_id" class="form-control">
<option value="">Select Role</option>
<?php foreach ($roles as $role): ?>
<option value="<?php echo $role['id']; ?>">
<?php echo htmlspecialchars($role['name']); ?> (Level <?php echo $role['level']; ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="expiresAt">Expires At (optional)</label>
<input type="datetime-local" id="expiresAt" name="expires_at" class="form-control">
</div>
<button type="submit" class="btn-primary">Assign Role</button>
</form>
</div>
</div>
<script>
function viewUser(userId) {
window.location.href = `view-user.php?id=${userId}`;
}
function manageRoles(userId) {
document.getElementById('assignUserId').value = userId;
// Load current roles
fetch(`get-user-roles.php?user_id=${userId}`)
.then(response => response.json())
.then(roles => {
let html = '<h3>Current Roles</h3>';
if (roles.length > 0) {
html += '<ul>';
roles.forEach(role => {
html += `
<li>
${role.name} 
${role.expires_at ? `(expires: ${new Date(role.expires_at).toLocaleDateString()})` : ''}
<button onclick="removeRole(${userId}, ${role.id})">Remove</button>
</li>
`;
});
html += '</ul>';
} else {
html += '<p>No roles assigned</p>';
}
document.getElementById('userRoles').innerHTML = html;
document.getElementById('roleModal').style.display = 'block';
});
}
function removeRole(userId, roleId) {
if (confirm('Remove this role from user?')) {
const form = document.createElement('form');
form.method = 'POST';
form.innerHTML = `
<input type="hidden" name="action" value="remove_role">
<input type="hidden" name="user_id" value="${userId}">
<input type="hidden" name="role_id" value="${roleId}">
`;
document.body.appendChild(form);
form.submit();
}
}
function toggleStatus(userId, currentStatus) {
const newStatus = currentStatus == 'active' ? 'suspended' : 'active';
const action = currentStatus == 'active' ? 'suspend' : 'activate';
if (confirm(`Are you sure you want to ${action} this user?`)) {
const form = document.createElement('form');
form.method = 'POST';
form.innerHTML = `
<input type="hidden" name="action" value="update_status">
<input type="hidden" name="user_id" value="${userId}">
<input type="hidden" name="status" value="${newStatus}">
`;
document.body.appendChild(form);
form.submit();
}
}
// Close modal
document.querySelectorAll('.modal .close').forEach(btn => {
btn.onclick = function() {
this.closest('.modal').style.display = 'none';
}
});
</script>
<style>
.user-info {
display: flex;
align-items: center;
gap: 0.5rem;
}
.avatar-small {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
}
.role-badge {
display: inline-block;
padding: 0.2rem 0.5rem;
border-radius: 3px;
color: white;
font-size: 0.8rem;
margin: 0.1rem;
}
.status-badge {
display: inline-block;
padding: 0.2rem 0.5rem;
border-radius: 3px;
font-size: 0.8rem;
}
.status-active {
background: #d4edda;
color: #155724;
}
.status-suspended {
background: #fff3cd;
color: #856404;
}
.status-banned {
background: #f8d7da;
color: #721c24;
}
.filters-bar {
background: white;
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.filters-form {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.filters-form input,
.filters-form select {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 3px;
min-width: 200px;
}
.filters-form button,
.filters-form a {
padding: 0.5rem 1rem;
border: none;
border-radius: 3px;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.btn-warning {
background: #ffc107;
color: #212529;
}
.btn-success {
background: #28a745;
color: white;
}
.pagination {
display: flex;
justify-content: center;
gap: 0.5rem;
padding: 1rem;
}
.page-link {
padding: 0.5rem 0.8rem;
border: 1px solid #ddd;
border-radius: 3px;
text-decoration: none;
color: #667eea;
}
.page-link.active {
background: #667eea;
color: white;
border-color: #667eea;
}
@media (max-width: 768px) {
.filters-form {
flex-direction: column;
}
.filters-form input,
.filters-form select {
width: 100%;
}
}
</style>
<?php include '../includes/footer.php'; ?>

6. login.php (Authentication)

<?php
require_once 'includes/role-config.php';
require_once 'includes/role-functions.php';
// Redirect if already logged in
if (isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$error = '';
$pdo = getDB();
// Handle login form
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
// Check login attempts
if (!checkLoginAttempts($_SERVER['REMOTE_ADDR'], $email)) {
$error = 'Too many login attempts. Please try again later.';
} else {
// Get user
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
// Check if account is active
if ($user['account_status'] !== 'active') {
$error = 'Your account has been suspended. Please contact support.';
} else {
// Login successful
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['user_name'] = $user['full_name'];
// Clear login attempts
recordLoginAttempt($_SERVER['REMOTE_ADDR'], $email, true);
// Log activity
logActivity($user['id'], 'login', 'User logged in');
// Update last login
$pdo->prepare("UPDATE users SET last_login = NOW() WHERE id = ?")->execute([$user['id']]);
// Check for 2FA
$stmt = $pdo->prepare("SELECT * FROM two_factor_auth WHERE user_id = ? AND is_enabled = 1");
$stmt->execute([$user['id']]);
$twoFactor = $stmt->fetch();
if ($twoFactor) {
$_SESSION['2fa_required'] = true;
header('Location: two-factor.php');
exit;
}
// Redirect to intended page
$redirect = $_SESSION['redirect_after_login'] ?? 'index.php';
unset($_SESSION['redirect_after_login']);
header("Location: $redirect");
exit;
}
} else {
// Login failed
recordLoginAttempt($_SERVER['REMOTE_ADDR'], $email, false);
$error = 'Invalid email or password';
}
}
}
$page_title = 'Login';
include 'includes/header.php';
?>
<link rel="stylesheet" href="css/auth.css">
<div class="auth-container">
<div class="auth-card">
<div class="auth-header">
<h1>Welcome Back</h1>
<p>Sign in to your account</p>
</div>
<?php if ($error): ?>
<div class="alert error"><?php echo $error; ?></div>
<?php endif; ?>
<form method="POST" class="auth-form">
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" 
id="email" 
name="email" 
class="form-control" 
value="<?php echo htmlspecialchars($_POST['email'] ?? ''); ?>"
required 
autofocus>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" 
id="password" 
name="password" 
class="form-control" 
required>
</div>
<div class="form-group checkbox">
<label>
<input type="checkbox" name="remember" value="1">
Remember me
</label>
<a href="forgot-password.php" class="forgot-link">Forgot Password?</a>
</div>
<button type="submit" class="btn-auth">Sign In</button>
</form>
<div class="auth-footer">
<p>Don't have an account? <a href="register.php">Sign up</a></p>
</div>
</div>
</div>
<style>
.auth-container {
min-height: calc(100vh - 200px);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.auth-card {
background: white;
border-radius: 10px;
box-shadow: 0 4px 30px rgba(0,0,0,0.2);
padding: 2.5rem;
width: 100%;
max-width: 400px;
}
.auth-header {
text-align: center;
margin-bottom: 2rem;
}
.auth-header h1 {
color: #333;
margin-bottom: 0.5rem;
}
.auth-header p {
color: #666;
}
.auth-form .form-group {
margin-bottom: 1.5rem;
}
.auth-form label {
display: block;
margin-bottom: 0.5rem;
color: #333;
font-weight: 500;
}
.auth-form .form-control {
width: 100%;
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1rem;
transition: border-color 0.3s;
}
.auth-form .form-control:focus {
outline: none;
border-color: #667eea;
}
.auth-form .checkbox {
display: flex;
justify-content: space-between;
align-items: center;
}
.auth-form .checkbox label {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: normal;
margin: 0;
}
.forgot-link {
color: #667eea;
text-decoration: none;
}
.forgot-link:hover {
text-decoration: underline;
}
.btn-auth {
width: 100%;
padding: 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 5px;
font-size: 1.1rem;
font-weight: 500;
cursor: pointer;
transition: opacity 0.3s;
}
.btn-auth:hover {
opacity: 0.9;
}
.auth-footer {
text-align: center;
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.auth-footer a {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.auth-footer a:hover {
text-decoration: underline;
}
.alert {
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
}
.alert.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
@media (max-width: 480px) {
.auth-card {
padding: 1.5rem;
}
}
</style>
<?php include 'includes/footer.php'; ?>

7. profile/security.php (User Security Settings)

<?php
require_once '../includes/role-config.php';
require_once '../includes/middleware.php';
requireAuth();
$pdo = getDB();
$user = getCurrentUser();
$page_title = 'Security Settings';
// Handle 2FA setup
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['enable_2fa'])) {
// Generate secret
$secret = generate2FASecret();
$qrCode = get2FAQRCode($user['email'], $secret);
$_SESSION['2fa_secret'] = $secret;
$_SESSION['2fa_qr'] = $qrCode;
} elseif (isset($_POST['verify_2fa'])) {
$code = $_POST['code'];
$secret = $_SESSION['2fa_secret'];
if (verify2FACode($secret, $code)) {
// Save to database
$backupCodes = generateBackupCodes();
$stmt = $pdo->prepare("
INSERT INTO two_factor_auth (user_id, secret, backup_codes, is_enabled) 
VALUES (?, ?, ?, 1)
ON DUPLICATE KEY UPDATE 
secret = ?, backup_codes = ?, is_enabled = 1
");
$stmt->execute([$user['id'], $secret, json_encode($backupCodes), $secret, json_encode($backupCodes)]);
unset($_SESSION['2fa_secret']);
unset($_SESSION['2fa_qr']);
$_SESSION['success'] = 'Two-factor authentication enabled';
$_SESSION['backup_codes'] = $backupCodes;
header('Location: security.php?show_codes=1');
exit;
} else {
$error = 'Invalid verification code';
}
} elseif (isset($_POST['disable_2fa'])) {
$pdo->prepare("DELETE FROM two_factor_auth WHERE user_id = ?")->execute([$user['id']]);
$_SESSION['success'] = 'Two-factor authentication disabled';
header('Location: security.php');
exit;
} elseif (isset($_POST['change_password'])) {
$current = $_POST['current_password'];
$new = $_POST['new_password'];
$confirm = $_POST['confirm_password'];
// Verify current password
if (!password_verify($current, $user['password'])) {
$error = 'Current password is incorrect';
} elseif ($new !== $confirm) {
$error = 'New passwords do not match';
} elseif (strlen($new) < PASSWORD_MIN_LENGTH) {
$error = 'Password must be at least ' . PASSWORD_MIN_LENGTH . ' characters';
} else {
// Update password
$hash = password_hash($new, PASSWORD_DEFAULT);
$pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$hash, $user['id']]);
logActivity($user['id'], 'password_changed', 'User changed password');
$_SESSION['success'] = 'Password changed successfully';
header('Location: security.php');
exit;
}
}
}
// Get 2FA status
$stmt = $pdo->prepare("SELECT * FROM two_factor_auth WHERE user_id = ?");
$stmt->execute([$user['id']]);
$twoFactor = $stmt->fetch();
// Get active sessions
$stmt = $pdo->prepare("
SELECT * FROM user_sessions 
WHERE user_id = ? AND is_active = 1 
ORDER BY last_activity DESC
");
$stmt->execute([$user['id']]);
$sessions = $stmt->fetchAll();
include '../includes/header.php';
?>
<div class="profile-container">
<h1>Security Settings</h1>
<?php if (isset($_SESSION['success'])): ?>
<div class="alert success"><?php echo $_SESSION['success']; unset($_SESSION['success']); ?></div>
<?php endif; ?>
<?php if (isset($_GET['show_codes']) && isset($_SESSION['backup_codes'])): ?>
<div class="alert warning">
<h3>Save These Backup Codes!</h3>
<p>These codes can be used to access your account if you lose your 2FA device. Store them securely.</p>
<div class="backup-codes">
<?php foreach ($_SESSION['backup_codes'] as $code): ?>
<code><?php echo $code; ?></code>
<?php endforeach; ?>
</div>
<button onclick="printCodes()" class="btn-primary">Print Codes</button>
<button onclick="downloadCodes()" class="btn-secondary">Download Codes</button>
</div>
<?php unset($_SESSION['backup_codes']); ?>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert error"><?php echo $error; ?></div>
<?php endif; ?>
<div class="security-grid">
<!-- Password Change -->
<div class="security-card">
<h2>Change Password</h2>
<form method="POST" class="security-form">
<input type="hidden" name="change_password" value="1">
<div class="form-group">
<label for="current_password">Current Password</label>
<input type="password" id="current_password" name="current_password" class="form-control" required>
</div>
<div class="form-group">
<label for="new_password">New Password</label>
<input type="password" id="new_password" name="new_password" class="form-control" required>
<small class="help-text">Minimum <?php echo PASSWORD_MIN_LENGTH; ?> characters</small>
</div>
<div class="form-group">
<label for="confirm_password">Confirm New Password</label>
<input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
</div>
<button type="submit" class="btn-primary">Update Password</button>
</form>
</div>
<!-- Two-Factor Authentication -->
<div class="security-card">
<h2>Two-Factor Authentication</h2>
<?php if ($twoFactor && $twoFactor['is_enabled']): ?>
<div class="status-enabled">
<span class="badge-success">Enabled</span>
<p>Two-factor authentication is active on your account.</p>
<form method="POST" onsubmit="return confirm('Disable 2FA? This will make your account less secure.');">
<input type="hidden" name="disable_2fa" value="1">
<button type="submit" class="btn-danger">Disable 2FA</button>
</form>
</div>
<?php elseif (isset($_SESSION['2fa_qr'])): ?>
<div class="setup-2fa">
<h3>Scan QR Code</h3>
<p>Scan this QR code with Google Authenticator or any TOTP app</p>
<div class="qr-code">
<img src="<?php echo $_SESSION['2fa_qr']; ?>" alt="2FA QR Code">
</div>
<p class="secret-text">Secret: <?php echo $_SESSION['2fa_secret']; ?></p>
<form method="POST" class="verify-form">
<input type="hidden" name="verify_2fa" value="1">
<div class="form-group">
<label for="code">Enter Verification Code</label>
<input type="text" id="code" name="code" class="form-control" 
placeholder="000000" pattern="[0-9]{6}" required>
</div>
<button type="submit" class="btn-primary">Verify & Enable</button>
<button type="button" onclick="location.reload()" class="btn-secondary">Cancel</button>
</form>
</div>
<?php else: ?>
<div class="status-disabled">
<span class="badge-warning">Disabled</span>
<p>Two-factor authentication adds an extra layer of security to your account.</p>
<form method="POST">
<input type="hidden" name="enable_2fa" value="1">
<button type="submit" class="btn-primary">Enable 2FA</button>
</form>
</div>
<?php endif; ?>
</div>
<!-- Active Sessions -->
<div class="security-card full-width">
<h2>Active Sessions</h2>
<table class="sessions-table">
<thead>
<tr>
<th>Device</th>
<th>IP Address</th>
<th>Last Activity</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($sessions as $session): ?>
<tr>
<td>
<?php 
$agent = parseUserAgent($session['user_agent']);
echo $agent['browser'] . ' on ' . $agent['os'];
?>
<?php if (session_id() == $session['id']): ?>
<span class="current-badge">(Current)</span>
<?php endif; ?>
</td>
<td><?php echo $session['ip_address']; ?></td>
<td><?php echo date('M j, Y g:i A', $session['last_activity']); ?></td>
<td>
<?php if (session_id() != $session['id']): ?>
<button class="btn-small btn-danger" onclick="terminateSession('<?php echo $session['id']; ?>')">
Terminate
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="session-actions">
<button class="btn-warning" onclick="terminateAllSessions()">Terminate All Other Sessions</button>
</div>
</div>
</div>
</div>
<script>
function terminateSession(sessionId) {
if (confirm('Terminate this session?')) {
fetch('terminate-session.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'session_id=' + sessionId
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error terminating session');
}
});
}
}
function terminateAllSessions() {
if (confirm('Terminate all other sessions? You will be logged out from other devices.')) {
fetch('terminate-all-sessions.php', {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error terminating sessions');
}
});
}
}
function printCodes() {
const codes = document.querySelectorAll('.backup-codes code');
let content = 'Backup Codes for 2FA\n\n';
codes.forEach(code => {
content += code.textContent + '\n';
});
const win = window.open('', '_blank');
win.document.write('<pre>' + content + '</pre>');
win.print();
win.close();
}
function downloadCodes() {
const codes = document.querySelectorAll('.backup-codes code');
let content = 'Backup Codes for 2FA\n\n';
codes.forEach(code => {
content += code.textContent + '\n';
});
const blob = new Blob([content], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '2fa-backup-codes.txt';
a.click();
window.URL.revokeObjectURL(url);
}
</script>
<style>
.profile-container {
max-width: 1200px;
margin: 2rem auto;
padding: 0 1rem;
}
.security-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.security-card {
background: white;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
padding: 1.5rem;
}
.security-card.full-width {
grid-column: 1 / -1;
}
.security-card h2 {
margin-bottom: 1.5rem;
color: #333;
font-size: 1.3rem;
}
.security-form .form-group {
margin-bottom: 1.5rem;
}
.security-form label {
display: block;
margin-bottom: 0.5rem;
color: #555;
font-weight: 500;
}
.security-form .form-control {
width: 100%;
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1rem;
}
.security-form .form-control:focus {
outline: none;
border-color: #667eea;
}
.help-text {
display: block;
color: #888;
font-size: 0.85rem;
margin-top: 0.3rem;
}
.badge-success {
display: inline-block;
padding: 0.3rem 0.8rem;
background: #d4edda;
color: #155724;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 500;
}
.badge-warning {
display: inline-block;
padding: 0.3rem 0.8rem;
background: #fff3cd;
color: #856404;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 500;
}
.qr-code {
margin: 1.5rem 0;
text-align: center;
}
.qr-code img {
max-width: 200px;
border: 1px solid #ddd;
padding: 0.5rem;
}
.secret-text {
text-align: center;
font-family: monospace;
background: #f8f9fa;
padding: 0.5rem;
border-radius: 5px;
margin: 1rem 0;
}
.verify-form {
max-width: 300px;
margin: 0 auto;
}
.backup-codes {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
margin: 1rem 0;
}
.backup-codes code {
background: #f8f9fa;
padding: 0.5rem;
border-radius: 3px;
font-family: monospace;
text-align: center;
border: 1px solid #dee2e6;
}
.sessions-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
}
.sessions-table th {
text-align: left;
padding: 0.8rem;
background: #f8f9fa;
color: #666;
font-weight: 500;
}
.sessions-table td {
padding: 0.8rem;
border-bottom: 1px solid #e9ecef;
}
.current-badge {
color: #28a745;
font-size: 0.85rem;
margin-left: 0.5rem;
}
.session-actions {
display: flex;
justify-content: flex-end;
}
.btn-primary, .btn-danger, .btn-warning, .btn-secondary {
padding: 0.5rem 1rem;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 0.9rem;
transition: opacity 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-warning {
background: #ffc107;
color: #212529;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-primary:hover, .btn-danger:hover, .btn-warning:hover, .btn-secondary:hover {
opacity: 0.9;
}
.btn-small {
padding: 0.2rem 0.5rem;
font-size: 0.8rem;
}
.alert {
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
}
.alert.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.alert.warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
}
@media (max-width: 768px) {
.security-grid {
grid-template-columns: 1fr;
}
.sessions-table {
display: block;
overflow-x: auto;
}
.backup-codes {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
<?php
// Helper functions for 2FA
function generate2FASecret($length = 16) {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$secret = '';
for ($i = 0; $i < $length; $i++) {
$secret .= $chars[random_int(0, strlen($chars) - 1)];
}
return $secret;
}
function get2FAQRCode($label, $secret, $issuer = 'Blog Platform') {
$url = "otpauth://totp/" . urlencode($issuer) . ":" . urlencode($label) . 
"?secret=" . $secret . "&issuer=" . urlencode($issuer);
return "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=" . urlencode($url);
}
function verify2FACode($secret, $code) {
// Simple TOTP verification (in production use a proper library)
$timeSlice = floor(time() / 30);
for ($i = -1; $i <= 1; $i++) {
$calculatedCode = generateTOTP($secret, $timeSlice + $i);
if ($calculatedCode == $code) {
return true;
}
}
return false;
}
function generateTOTP($secret, $timeSlice) {
// Simplified TOTP - in production use a proper library
$hash = hash_hmac('sha1', pack('N*', 0) . pack('N*', $timeSlice), $secret, true);
$offset = ord($hash[19]) & 0xf;
$code = (ord($hash[$offset]) & 0x7f) << 24 |
(ord($hash[$offset + 1]) & 0xff) << 16 |
(ord($hash[$offset + 2]) & 0xff) << 8 |
(ord($hash[$offset + 3]) & 0xff);
$code = $code % 1000000;
return str_pad($code, 6, '0', STR_PAD_LEFT);
}
function generateBackupCodes($count = 8) {
$codes = [];
for ($i = 0; $i < $count; $i++) {
$codes[] = bin2hex(random_bytes(4));
}
return $codes;
}
function parseUserAgent($ua) {
$result = ['browser' => 'Unknown', 'os' => 'Unknown'];
if (preg_match('/Firefox\/([0-9.]+)/', $ua, $matches)) {
$result['browser'] = 'Firefox';
} elseif (preg_match('/Chrome\/([0-9.]+)/', $ua, $matches)) {
$result['browser'] = 'Chrome';
} elseif (preg_match('/Safari\/([0-9.]+)/', $ua, $matches)) {
$result['browser'] = 'Safari';
} elseif (preg_match('/MSIE|Trident/', $ua)) {
$result['browser'] = 'Internet Explorer';
}
if (strpos($ua, 'Windows') !== false) {
$result['os'] = 'Windows';
} elseif (strpos($ua, 'Mac') !== false) {
$result['os'] = 'macOS';
} elseif (strpos($ua, 'Linux') !== false) {
$result['os'] = 'Linux';
} elseif (strpos($ua, 'Android') !== false) {
$result['os'] = 'Android';
} elseif (strpos($ua, 'iOS') !== false || strpos($ua, 'iPhone') !== false) {
$result['os'] = 'iOS';
}
return $result;
}
include '../includes/footer.php';
?>

🚀 How to Use This Project Step by Step

Step 1: Database Setup

  1. Run the database/roles.sql script to create all necessary tables
  2. Verify tables are created: roles, permissions, user_roles, etc.

Step 2: Integration with Existing System

  1. Update your existing users table to include status fields if not already present
  2. Add user_type column to differentiate between regular users, clients, freelancers
  3. Update registration form to collect necessary user information

Step 3: Configure Environment

  1. Update .env file with security settings
  2. Set appropriate values for:
  • MAX_LOGIN_ATTEMPTS
  • SESSION_LIFETIME
  • PASSWORD_MIN_LENGTH
  • ENABLE_2FA

Step 4: Update Existing Pages

Add middleware checks to existing pages:

// At the top of protected pages
require_once 'includes/middleware.php';
requirePermission('view-posts'); // or specific permission

Step 5: Test Role Assignments

  1. Create a super admin account manually in database
  2. Log in and navigate to /admin/roles.php
  3. Create custom roles as needed
  4. Assign permissions to roles
  5. Test different user accounts

Step 6: Set Up Cron Jobs (Optional)

For session cleanup and expired role removal:

# Add to crontab
*/30 * * * * php /path/to/blog-website/includes/cleanup-sessions.php
0 0 * * * php /path/to/blog-website/includes/remove-expired-roles.php

🔒 Security Best Practices

  1. Password Policies: Enforce strong passwords with minimum length and complexity
  2. 2FA: Enable two-factor authentication for admin accounts
  3. Session Management: Implement proper session timeout and regeneration
  4. Brute Force Protection: Rate limiting on login attempts
  5. IP Whitelisting: Restrict admin access to trusted IPs
  6. Audit Logging: Track all sensitive actions
  7. CSRF Protection: Include tokens in all forms
  8. XSS Prevention: Escape all output
  9. SQL Injection: Use prepared statements consistently
  10. Regular Security Audits: Review logs and permissions periodically

📊 Monitoring & Reporting

Activity Logs

  • Track user actions with timestamps
  • Monitor failed login attempts
  • Audit permission changes
  • Export logs for analysis

User Statistics

  • Active users by role
  • New registrations
  • Suspended accounts
  • 2FA adoption rate

Security Reports

  • Brute force attempts
  • Suspicious IP addresses
  • Permission anomalies
  • Session hijacking attempts

🚀 Future Enhancements

  1. LDAP/Active Directory Integration: Enterprise authentication
  2. OAuth Providers: Google, Facebook, GitHub login
  3. Single Sign-On (SSO): SAML support
  4. Advanced 2FA: Hardware tokens, biometrics
  5. Role Templates: Pre-configured role sets for common use cases
  6. Permission Inheritance: Child roles inheriting parent permissions
  7. Granular Time Restrictions: Access only during business hours
  8. Geographic Restrictions: Block access from certain countries
  9. Machine Learning: Anomaly detection in user behavior
  10. GDPR Compliance: Data export and deletion tools

📝 Conclusion

This Multi-User Role Management System provides a comprehensive foundation for access control across your entire platform. With granular permissions, role hierarchies, audit logging, and security features, it ensures that users only have access to what they need. The system is designed to be scalable and can be extended to meet the needs of any application, from small blogs to large enterprise platforms.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper