['data' => ..., 'expires' => timestamp]] private static int $cacheTTL = 300; // 5 minutes public function __construct(mysqli $conn) { $this->conn = $conn; } /** * Get cached user data if not expired */ private static function getCached(string $key): ?array { if (isset(self::$userCache[$key])) { $cached = self::$userCache[$key]; if ($cached['expires'] > time()) { return $cached['data']; } // Expired - remove from cache unset(self::$userCache[$key]); } return null; } /** * Store user data in cache with expiration */ private static function setCached(string $key, array $data): void { self::$userCache[$key] = [ 'data' => $data, 'expires' => time() + self::$cacheTTL ]; } /** * Invalidate specific user cache entry */ public static function invalidateCache(?int $userId = null, ?string $username = null): void { if ($userId !== null) { unset(self::$userCache["user_id_$userId"]); } if ($username !== null) { unset(self::$userCache["user_$username"]); } } /** * Sync user from Authelia headers (create or update) * * @param string $username Username from Remote-User header * @param string $displayName Display name from Remote-Name header * @param string $email Email from Remote-Email header * @param string $groups Comma-separated groups from Remote-Groups header * @return array User data array */ public function syncUserFromAuthelia(string $username, string $displayName = '', string $email = '', string $groups = ''): array { // Check cache first $cacheKey = "user_$username"; $cached = self::getCached($cacheKey); if ($cached !== null) { return $cached; } // Determine if user is admin based on groups $isAdmin = $this->checkAdminStatus($groups); // Try to find existing user $stmt = $this->conn->prepare("SELECT * FROM users WHERE username = ?"); $stmt->bind_param("s", $username); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { // Update existing user $user = $result->fetch_assoc(); $updateStmt = $this->conn->prepare( "UPDATE users SET display_name = ?, email = ?, groups = ?, is_admin = ?, last_login = NOW() WHERE username = ?" ); $updateStmt->bind_param("sssis", $displayName, $email, $groups, $isAdmin, $username); $updateStmt->execute(); $updateStmt->close(); // Refresh user data $user['display_name'] = $displayName; $user['email'] = $email; $user['groups'] = $groups; $user['is_admin'] = $isAdmin; } else { // Create new user $insertStmt = $this->conn->prepare( "INSERT INTO users (username, display_name, email, groups, is_admin, last_login) VALUES (?, ?, ?, ?, ?, NOW())" ); $insertStmt->bind_param("ssssi", $username, $displayName, $email, $groups, $isAdmin); $insertStmt->execute(); $userId = $this->conn->insert_id; $insertStmt->close(); // Get the newly created user $stmt = $this->conn->prepare("SELECT * FROM users WHERE user_id = ?"); $stmt->bind_param("i", $userId); $stmt->execute(); $result = $stmt->get_result(); $user = $result->fetch_assoc(); } $stmt->close(); // Cache user with TTL self::setCached($cacheKey, $user); return $user; } /** * Get system user (for hwmonDaemon) * * @return array|null System user data or null if not found */ public function getSystemUser(): ?array { // Check cache first $cached = self::getCached('system'); if ($cached !== null) { return $cached; } $stmt = $this->conn->prepare("SELECT * FROM users WHERE username = 'system'"); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $user = $result->fetch_assoc(); self::setCached('system', $user); $stmt->close(); return $user; } $stmt->close(); return null; } /** * Get user by ID * * @param int $userId User ID * @return array|null User data or null if not found */ public function getUserById(int $userId): ?array { // Check cache first $cacheKey = "user_id_$userId"; $cached = self::getCached($cacheKey); if ($cached !== null) { return $cached; } $stmt = $this->conn->prepare("SELECT * FROM users WHERE user_id = ?"); $stmt->bind_param("i", $userId); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $user = $result->fetch_assoc(); self::setCached($cacheKey, $user); $stmt->close(); return $user; } $stmt->close(); return null; } /** * Get user by username * * @param string $username Username * @return array|null User data or null if not found */ public function getUserByUsername(string $username): ?array { // Check cache first $cacheKey = "user_$username"; $cached = self::getCached($cacheKey); if ($cached !== null) { return $cached; } $stmt = $this->conn->prepare("SELECT * FROM users WHERE username = ?"); $stmt->bind_param("s", $username); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows > 0) { $user = $result->fetch_assoc(); self::setCached($cacheKey, $user); $stmt->close(); return $user; } $stmt->close(); return null; } /** * Check if user has admin privileges based on groups * * @param string $groups Comma-separated group names * @return bool True if user is in admin group */ private function checkAdminStatus(string $groups): bool { if (empty($groups)) { return false; } // Split groups by comma and check for 'admin' group $groupArray = array_map('trim', explode(',', strtolower($groups))); return in_array('admin', $groupArray); } /** * Check if user is admin * * @param array $user User data array * @return bool True if user is admin */ public function isAdmin(array $user): bool { return isset($user['is_admin']) && $user['is_admin'] == 1; } /** * Check if user has required group membership * * @param array $user User data array * @param array $requiredGroups Array of required group names * @return bool True if user is in at least one required group */ public function hasGroupAccess(array $user, array $requiredGroups = ['admin', 'employee']): bool { if (empty($user['groups'])) { return false; } $userGroups = array_map('trim', explode(',', strtolower($user['groups']))); $requiredGroups = array_map('strtolower', $requiredGroups); return !empty(array_intersect($userGroups, $requiredGroups)); } /** * Get all users (for admin panel) * * @return array Array of user records */ public function getAllUsers(): array { $stmt = $this->conn->prepare("SELECT * FROM users ORDER BY created_at DESC"); $stmt->execute(); $result = $stmt->get_result(); $users = []; while ($row = $result->fetch_assoc()) { $users[] = $row; } $stmt->close(); return $users; } /** * Get all distinct groups from all users * Used for visibility group selection UI * * @return array Array of unique group names */ public function getAllGroups(): array { $stmt = $this->conn->prepare("SELECT DISTINCT groups FROM users WHERE groups IS NOT NULL AND groups != ''"); $stmt->execute(); $result = $stmt->get_result(); $allGroups = []; while ($row = $result->fetch_assoc()) { $userGroups = array_filter(array_map('trim', explode(',', $row['groups']))); $allGroups = array_merge($allGroups, $userGroups); } $stmt->close(); // Return unique groups sorted alphabetically $uniqueGroups = array_unique($allGroups); sort($uniqueGroups); return $uniqueGroups; } }