$lifetime, 'path' => '/', 'secure' => og_session_is_secure_request(), 'httponly' => true, 'samesite' => 'Lax', ]; } function og_session_setcookie_options(int $expiresAt): array { $options = og_session_cookie_options(0); unset($options['lifetime']); $options['expires'] = $expiresAt; return $options; } function og_session_configure(int $timeout): void { if (session_status() === PHP_SESSION_ACTIVE) { return; } ini_set('session.gc_maxlifetime', (string) $timeout); ini_set('session.cookie_httponly', '1'); ini_set('session.use_strict_mode', '1'); if (PHP_VERSION_ID >= 70300) { session_set_cookie_params(og_session_cookie_options($timeout)); return; } $path = '/; samesite=Lax'; session_set_cookie_params($timeout, $path, '', og_session_is_secure_request(), true); } function og_session_get_pdo(): ?PDO { static $pdo = null; if ($pdo instanceof PDO) { return $pdo; } try { $pdo = new PDO( 'mysql:host=localhost;dbname=togethere_cloud;charset=utf8mb4', 'root', 'HasloDoSQL', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] ); $pdo->exec('SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci'); return $pdo; } catch (Throwable $e) { return null; } } function og_session_ensure_remember_tokens_table(?PDO $pdo = null): bool { $pdo = $pdo ?: og_session_get_pdo(); if (!$pdo instanceof PDO) { return false; } try { $pdo->exec( 'CREATE TABLE IF NOT EXISTS remember_tokens ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, token VARCHAR(255) NOT NULL, expires_at DATETIME NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uniq_remember_token (token), KEY idx_remember_user (user_id), KEY idx_remember_expires (expires_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' ); } catch (Throwable $e) { return false; } return true; } function og_session_normalize_username(?string $username): string { return trim((string) $username); } function og_session_is_valid_username(?string $username): bool { $normalized = og_session_normalize_username($username); if ($normalized === '') { return false; } return preg_match('/^[A-Za-z0-9_&!]{1,20}$/', $normalized) === 1; } function og_session_has_valid_username(): bool { return og_session_is_valid_username($_SESSION['username'] ?? null); } function og_session_remember_cookie_value(): string { return isset($_COOKIE['remember_token']) ? trim((string) $_COOKIE['remember_token']) : ''; } function og_session_uses_remember_me(): bool { if (!empty($_SESSION['remember_me'])) { return true; } return og_session_remember_cookie_value() !== ''; } function og_session_timeout_seconds(): int { return og_session_uses_remember_me() ? OG_SESSION_TIMEOUT_REMEMBER : OG_SESSION_TIMEOUT_DEFAULT; } function og_session_refresh_cookie(string $name, string $value, int $lifetime): void { if (headers_sent()) { return; } $expiresAt = time() + $lifetime; if (PHP_VERSION_ID >= 70300) { setcookie($name, $value, og_session_setcookie_options($expiresAt)); return; } setcookie($name, $value, $expiresAt, '/; samesite=Lax', '', og_session_is_secure_request(), true); } function og_session_clear_cookie(string $name): void { if (headers_sent()) { return; } if (PHP_VERSION_ID >= 70300) { setcookie($name, '', og_session_setcookie_options(time() - 3600)); return; } setcookie($name, '', time() - 3600, '/; samesite=Lax', '', og_session_is_secure_request(), true); } function og_session_refresh_remember_token(): void { if (empty($_SESSION['remember_me'])) { return; } $token = og_session_remember_cookie_value(); if ($token === '') { return; } $pdo = og_session_get_pdo(); if (!$pdo instanceof PDO || !og_session_ensure_remember_tokens_table($pdo)) { return; } try { $expiresAt = date('Y-m-d H:i:s', time() + OG_SESSION_TIMEOUT_REMEMBER); $stmt = $pdo->prepare('UPDATE remember_tokens SET expires_at = :expires WHERE token = :token'); $stmt->execute([ ':expires' => $expiresAt, ':token' => hash('sha256', $token), ]); } catch (Throwable $e) { return; } og_session_refresh_cookie('remember_token', $token, OG_SESSION_TIMEOUT_REMEMBER); } function og_session_clear_remember_token(): void { $token = og_session_remember_cookie_value(); if ($token !== '') { $pdo = og_session_get_pdo(); if ($pdo instanceof PDO && og_session_ensure_remember_tokens_table($pdo)) { try { $stmt = $pdo->prepare('DELETE FROM remember_tokens WHERE token = :token'); $stmt->execute([':token' => hash('sha256', $token)]); } catch (Throwable $e) { } } } og_session_clear_cookie('remember_token'); } function og_session_destroy_auth(bool $clearRememberToken = false): void { $_SESSION = []; if ($clearRememberToken) { og_session_clear_remember_token(); } if (session_status() === PHP_SESSION_ACTIVE) { if (!headers_sent()) { og_session_clear_cookie(session_name()); } session_destroy(); } } function og_session_find_remember_user(PDO $pdo, string $token): ?array { if ($token === '') { return null; } if (!og_session_ensure_remember_tokens_table($pdo)) { return null; } try { $stmt = $pdo->prepare( 'SELECT u.id, u.username, u.email, COALESCE(u.role, "user") AS role FROM remember_tokens rt INNER JOIN users u ON u.id = rt.user_id WHERE rt.token = :token AND rt.expires_at > NOW() LIMIT 1' ); $stmt->execute([':token' => hash('sha256', $token)]); $row = $stmt->fetch(); } catch (Throwable $e) { return null; } return is_array($row) ? $row : null; } function og_session_restore_from_remember_cookie(): void { if (!empty($_SESSION['logged_in']) && !empty($_SESSION['user_id'])) { return; } $token = og_session_remember_cookie_value(); if ($token === '') { return; } $pdo = og_session_get_pdo(); if (!$pdo instanceof PDO) { return; } $user = og_session_find_remember_user($pdo, $token); if (!$user) { og_session_clear_remember_token(); return; } session_regenerate_id(true); $_SESSION['logged_in'] = true; $_SESSION['user_id'] = (int) $user['id']; $_SESSION['username'] = (string) $user['username']; $_SESSION['email'] = (string) ($user['email'] ?? ''); $_SESSION['role'] = (string) ($user['role'] ?? 'user'); $_SESSION['remember_me'] = true; $_SESSION['last_activity'] = time(); og_session_refresh_remember_token(); } function og_session_enforce_inactivity_timeout(): void { if (empty($_SESSION['logged_in']) || empty($_SESSION['user_id'])) { return; } $lastActivity = isset($_SESSION['last_activity']) ? (int) $_SESSION['last_activity'] : 0; if ($lastActivity <= 0) { return; } if ((time() - $lastActivity) > og_session_timeout_seconds()) { og_session_destroy_auth(og_session_uses_remember_me()); } } function og_session_touch(): void { if (empty($_SESSION['logged_in']) || empty($_SESSION['user_id'])) { return; } $_SESSION['last_activity'] = time(); og_session_refresh_cookie(session_name(), session_id(), og_session_timeout_seconds()); og_session_refresh_remember_token(); } $preSessionTimeout = og_session_remember_cookie_value() !== '' ? OG_SESSION_TIMEOUT_REMEMBER : OG_SESSION_TIMEOUT_DEFAULT; og_session_configure($preSessionTimeout); if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } og_session_restore_from_remember_cookie(); og_session_enforce_inactivity_timeout(); og_session_touch();