0, 'path' => '/admin', 'secure' => (php_sapi_name() !== 'cli-server'), 'httponly' => true, 'samesite' => 'Strict', ]); session_start(); } /** * Gate every admin page. * * Authentication order: * 1. Session already authenticated → pass through. * 2. nginx Basic Auth password present in $_SERVER['PHP_AUTH_PW'] * → validate it with password_verify; on success create session * (seamless: user only sees the browser Basic Auth dialog). * 3. Neither → redirect to the PHP login form (fallback for when * the reverse proxy is absent / misconfigured). * * No-op if ADMIN_PASSWORD_HASH is not defined (development / cli-server). */ public static function requireLogin(): void { self::startSession(); if (!defined('ADMIN_PASSWORD_HASH')) { // No password configured → development / cli-server mode, skip PHP auth. return; } if (!empty($_SESSION[self::SESSION_KEY])) { return; // already authenticated via session } // Try to auto-authenticate from the nginx Basic Auth credentials. // If nginx Basic Auth is bypassed, PHP_AUTH_PW won't be set and this // branch is skipped — the fallback login form is shown instead. if (isset($_SERVER['PHP_AUTH_PW']) && self::login($_SERVER['PHP_AUTH_PW'])) { return; } header('Location: ' . self::LOGIN_URL); exit; } /** * Validate a plaintext password against the stored bcrypt hash. * On success: regenerates the session ID and marks the session authenticated. * * @return bool true on success, false on wrong password / no hash configured. */ public static function login(string $password): bool { $hash = defined('ADMIN_PASSWORD_HASH') ? ADMIN_PASSWORD_HASH : null; if ($hash === null || !password_verify($password, $hash)) { return false; } self::startSession(); session_regenerate_id(true); $_SESSION[self::SESSION_KEY] = true; $_SESSION['admin_login_at'] = time(); return true; } /** * Check whether the current request is authenticated (without redirecting). */ public static function isAuthenticated(): bool { self::startSession(); return !empty($_SESSION[self::SESSION_KEY]); } /** * Destroy the session (logout). */ public static function logout(): void { self::startSession(); $_SESSION = []; if (ini_get('session.use_cookies')) { $p = session_get_cookie_params(); setcookie( session_name(), '', time() - 86400, $p['path'], $p['domain'], $p['secure'], $p['httponly'] ); } session_destroy(); } }