configFile = __DIR__ . '/../auth_config.php'; } else { $this->configFile = $configFile; } // Use Argon2ID if available, fallback to bcrypt if (defined('PASSWORD_ARGON2ID')) { $this->passwordAlgo = PASSWORD_ARGON2ID; } else { $this->passwordAlgo = PASSWORD_BCRYPT; } $this->loadConfig(); } private function loadConfig() { if (file_exists($this->configFile)) { $this->config = require $this->configFile; } else { $this->config = array( 'users' => array(), 'failed_attempts' => array(), ); } } private function saveConfig() { $content = "config, true) . ";\n"; file_put_contents($this->configFile, $content); @chmod($this->configFile, 0600); } public function createUser($username, $password) { if (isset($this->config['users'][$username])) { return false; } $this->config['users'][$username] = array( 'password_hash' => $this->hashPassword($password), 'created_at' => time(), ); $this->saveConfig(); return true; } public function changePassword($username, $oldPassword, $newPassword) { if (!$this->verifyPassword($username, $oldPassword)) { return false; } $this->config['users'][$username]['password_hash'] = $this->hashPassword($newPassword); $this->saveConfig(); return true; } private function hashPassword($password) { if ($this->passwordAlgo === PASSWORD_ARGON2ID) { return password_hash($password, PASSWORD_ARGON2ID, array( 'memory_cost' => 65536, 'time_cost' => 4, 'threads' => 1, )); } return password_hash($password, PASSWORD_BCRYPT, array('cost' => 12)); } private function verifyPassword($username, $password) { if (!isset($this->config['users'][$username])) { $this->hashPassword($password); return false; } return password_verify($password, $this->config['users'][$username]['password_hash']); } private function isLockedOut($ip) { if (!isset($this->config['failed_attempts'][$ip])) { return false; } $attempts = $this->config['failed_attempts'][$ip]; $lockoutTime = $this->lockoutTime; $filtered = array(); foreach ($attempts as $time) { if ($time > time() - $lockoutTime) { $filtered[] = $time; } } $this->config['failed_attempts'][$ip] = $filtered; return count($filtered) >= $this->maxAttempts; } private function recordFailedAttempt($ip) { if (!isset($this->config['failed_attempts'][$ip])) { $this->config['failed_attempts'][$ip] = array(); } $this->config['failed_attempts'][$ip][] = time(); $this->saveConfig(); } private function clearFailedAttempts($ip) { unset($this->config['failed_attempts'][$ip]); $this->saveConfig(); } public function login($username, $password, $ip) { if ($this->isLockedOut($ip)) { return array( 'success' => false, 'message' => 'Too many failed attempts. Please try again later.', 'locked' => true, ); } if ($this->verifyPassword($username, $password)) { $this->clearFailedAttempts($ip); $token = $this->generateSessionToken(); return array( 'success' => true, 'message' => 'Login successful', 'token' => $token, 'username' => $username, ); } $this->recordFailedAttempt($ip); $attempts = isset($this->config['failed_attempts'][$ip]) ? $this->config['failed_attempts'][$ip] : array(); $remaining = $this->maxAttempts - count($attempts); return array( 'success' => false, 'message' => "Invalid username or password. {$remaining} attempts remaining.", 'locked' => false, ); } private function generateSessionToken() { if (function_exists('random_bytes')) { return bin2hex(random_bytes(32)); } return bin2hex(openssl_random_pseudo_bytes(32)); } public function startSession($username, $token) { if (session_status() === PHP_SESSION_NONE) { session_start(); } session_regenerate_id(true); $_SESSION['authenticated'] = true; $_SESSION['username'] = $username; $_SESSION['token'] = $token; $_SESSION['login_time'] = time(); $_SESSION['last_activity'] = time(); } public function isAuthenticated($timeout = 3600) { if (session_status() === PHP_SESSION_NONE) { session_start(); } if (empty($_SESSION['authenticated'])) { return false; } $lastActivity = isset($_SESSION['last_activity']) ? $_SESSION['last_activity'] : 0; if (time() - $lastActivity > $timeout) { $this->logout(); return false; } $_SESSION['last_activity'] = time(); return true; } public function logout() { if (session_status() === PHP_SESSION_NONE) { session_start(); } $_SESSION = array(); if (isset($_COOKIE[session_name()])) { setcookie(session_name(), '', time() - 3600, '/'); } session_destroy(); } public function getCurrentUser() { if (!$this->isAuthenticated()) { return null; } return isset($_SESSION['username']) ? $_SESSION['username'] : null; } public function hasUsers() { return !empty($this->config['users']); } public static function getClientIP() { $headers = array('HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'); foreach ($headers as $header) { if (!empty($_SERVER[$header])) { $ip = $_SERVER[$header]; if (strpos($ip, ',') !== false) { $parts = explode(',', $ip); $ip = trim($parts[0]); } if (filter_var($ip, FILTER_VALIDATE_IP)) { return $ip; } } } return '0.0.0.0'; } }