['admin', 'operator', 'readonly'], 'operator' => ['operator', 'readonly'], 'readonly' => ['readonly'], ]; /** * Constructor * * @param string $userFile Path to users JSON file * @param Logger $logger Logger instance * @param array $config Application config */ public function __construct(string $userFile, Logger $logger, array $config) { $this->db = new FlatFileDB($userFile); $this->logger = $logger; $this->config = $config; // Initialize users if empty if (!$this->db->exists('users')) { $this->initializeDefaultAdmin(); } } /** * Initialize default admin user * * @return void */ private function initializeDefaultAdmin(): void { $defaultAdmin = $this->config['default_admin'] ?? [ 'username' => 'admin', 'password' => 'pathvector', 'role' => 'admin', ]; $users = [ $defaultAdmin['username'] => [ 'username' => $defaultAdmin['username'], 'password' => password_hash($defaultAdmin['password'], PASSWORD_DEFAULT), 'role' => $defaultAdmin['role'], 'created_at' => date('c'), 'updated_at' => date('c'), 'last_login' => null, 'is_active' => true, ], ]; $this->db->set('users', $users); } /** * Start session if not already started * * @return void */ public function startSession(): void { if (session_status() === PHP_SESSION_NONE) { session_start(); } } /** * Generate CSRF token * * @return string */ public function generateCsrfToken(): string { $this->startSession(); if (!isset($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } /** * Validate CSRF token * * @param string $token * @return bool */ public function validateCsrfToken(string $token): bool { $this->startSession(); return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); } /** * Regenerate CSRF token * * @return string */ public function regenerateCsrfToken(): string { $this->startSession(); $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); return $_SESSION['csrf_token']; } /** * Authenticate user * * @param string $username * @param string $password * @return bool */ public function login(string $username, string $password): bool { $this->startSession(); $users = $this->db->get('users') ?? []; if (!isset($users[$username])) { $this->logger->warning('auth', "Failed login attempt for unknown user: $username"); return false; } $user = $users[$username]; if (!$user['is_active']) { $this->logger->warning('auth', "Login attempt for inactive user: $username"); return false; } if (!password_verify($password, $user['password'])) { $this->logger->warning('auth', "Failed login attempt for user: $username"); return false; } // Update last login $users[$username]['last_login'] = date('c'); $this->db->set('users', $users); // Set session $_SESSION['user'] = [ 'username' => $user['username'], 'role' => $user['role'], 'logged_in_at' => date('c'), ]; // Regenerate session ID for security session_regenerate_id(true); $this->logger->success('auth', "User logged in: $username"); return true; } /** * Logout user * * @return void */ public function logout(): void { $this->startSession(); $username = $_SESSION['user']['username'] ?? 'unknown'; $this->logger->info('auth', "User logged out: $username"); $_SESSION = []; if (ini_get('session.use_cookies')) { $params = session_get_cookie_params(); setcookie( session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly'] ); } session_destroy(); } /** * Check if user is logged in * * @return bool */ public function isLoggedIn(): bool { $this->startSession(); return isset($_SESSION['user']); } /** * Get current user * * @return array|null */ public function getCurrentUser(): ?array { $this->startSession(); return $_SESSION['user'] ?? null; } /** * Check if current user has role * * @param string $requiredRole * @return bool */ public function hasRole(string $requiredRole): bool { $this->startSession(); if (!isset($_SESSION['user']['role'])) { return false; } $userRole = $_SESSION['user']['role']; return in_array($requiredRole, $this->roles[$userRole] ?? []); } /** * Require login - redirect if not logged in * * @return void */ public function requireLogin(): void { if (!$this->isLoggedIn()) { header('Location: login.php'); exit; } } /** * Require specific role * * @param string $role * @return void */ public function requireRole(string $role): void { $this->requireLogin(); if (!$this->hasRole($role)) { header('HTTP/1.1 403 Forbidden'); echo 'Access Denied: Insufficient permissions'; exit; } } /** * Create new user * * @param string $username * @param string $password * @param string $role * @return bool */ public function createUser(string $username, string $password, string $role = 'readonly'): bool { $users = $this->db->get('users') ?? []; if (isset($users[$username])) { return false; } $users[$username] = [ 'username' => $username, 'password' => password_hash($password, PASSWORD_DEFAULT), 'role' => $role, 'created_at' => date('c'), 'updated_at' => date('c'), 'last_login' => null, 'is_active' => true, ]; $result = $this->db->set('users', $users); if ($result) { $this->logger->success('auth', "User created: $username with role: $role"); } return $result; } /** * Update user * * @param string $username * @param array $data * @return bool */ public function updateUser(string $username, array $data): bool { $users = $this->db->get('users') ?? []; if (!isset($users[$username])) { return false; } if (isset($data['password'])) { $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); } $data['updated_at'] = date('c'); $users[$username] = array_merge($users[$username], $data); $result = $this->db->set('users', $users); if ($result) { $this->logger->info('auth', "User updated: $username"); } return $result; } /** * Delete user * * @param string $username * @return bool */ public function deleteUser(string $username): bool { $users = $this->db->get('users') ?? []; if (!isset($users[$username])) { return false; } unset($users[$username]); $result = $this->db->set('users', $users); if ($result) { $this->logger->info('auth', "User deleted: $username"); } return $result; } /** * Get user by username * * @param string $username * @return array|null */ public function getUser(string $username): ?array { $users = $this->db->get('users') ?? []; return $users[$username] ?? null; } /** * Get all users * * @return array */ public function getAllUsers(): array { $users = $this->db->get('users') ?? []; // Remove password hashes for security return array_map(function($user) { unset($user['password']); return $user; }, $users); } /** * Change user password * * @param string $username * @param string $currentPassword * @param string $newPassword * @return bool */ public function changePassword(string $username, string $currentPassword, string $newPassword): bool { $users = $this->db->get('users') ?? []; if (!isset($users[$username])) { return false; } if (!password_verify($currentPassword, $users[$username]['password'])) { $this->logger->warning('auth', "Password change failed for user: $username (wrong current password)"); return false; } $users[$username]['password'] = password_hash($newPassword, PASSWORD_DEFAULT); $users[$username]['updated_at'] = date('c'); $result = $this->db->set('users', $users); if ($result) { $this->logger->success('auth', "Password changed for user: $username"); } return $result; } /** * Reset user password (admin function) * * @param string $username * @param string $newPassword * @return bool */ public function resetPassword(string $username, string $newPassword): bool { $users = $this->db->get('users') ?? []; if (!isset($users[$username])) { return false; } $users[$username]['password'] = password_hash($newPassword, PASSWORD_DEFAULT); $users[$username]['updated_at'] = date('c'); $result = $this->db->set('users', $users); if ($result) { $this->logger->success('auth', "Password reset for user: $username by admin"); } return $result; } }