prepare("SELECT COUNT(*) FROM rate_limits WHERE ip = :ip AND endpoint = :ep AND attempted_at > datetime('now', '-1 minutes')"); $count->execute(['ip' => $ip, 'ep' => $endpoint]); if ((int)$count->fetchColumn() >= $max) return true; $db->prepare("INSERT INTO rate_limits (ip, endpoint) VALUES (:ip, :ep)")->execute(['ip' => $ip, 'ep' => $endpoint]); return false; } function makeSession(PDO $db, int $tenantId, int $hours = 24): string { $token = bin2hex(random_bytes(32)); $db->prepare("INSERT INTO sessions (id, tenant_id, expires_at) VALUES (:t, :u, datetime('now', '+{$hours} hours'))") ->execute(['t' => $token, 'u' => $tenantId]); return $token; } function getSessionTenant(PDO $db): ?array { $token = $_COOKIE['relay_session'] ?? ''; if (!$token) return null; $stmt = $db->prepare("SELECT t.* FROM sessions s JOIN tenants t ON t.id = s.tenant_id WHERE s.id = :tok AND s.expires_at > datetime('now')"); $stmt->execute(['tok' => $token]); $tenant = $stmt->fetch() ?: null; if ($tenant) touchLastSeen($db, (int)$tenant['id']); return $tenant; } function destroySession(PDO $db): void { $token = $_COOKIE['relay_session'] ?? ''; if ($token) { $db->prepare('DELETE FROM sessions WHERE id = :t')->execute(['t' => $token]); } setcookie('relay_session', '', [ 'expires' => time() - 3600, 'path' => '/', 'httponly' => true, 'samesite' => 'Strict', 'secure' => !empty($_SERVER['HTTPS']) || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https', ]); } function setSessionCookie(string $token): void { setcookie('relay_session', $token, [ 'expires' => time() + 86400, 'path' => '/', 'httponly' => true, 'samesite' => 'Strict', 'secure' => !empty($_SERVER['HTTPS']) || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https', ]); } /** * Authenticate a tenant via the client API headers (X-Relay-User + X-Relay-Key). * Returns the tenant row on success, null on failure. */ function authClient(PDO $db): ?array { $username = $_SERVER['HTTP_X_RELAY_USER'] ?? ''; $key = $_SERVER['HTTP_X_RELAY_KEY'] ?? ''; if (!$username || !$key) return null; $tenant = getTenantByUsername($db, $username); if (!$tenant) return null; if (!password_verify($key, $tenant['secret_hash'])) return null; touchLastSeen($db, (int)$tenant['id']); return $tenant; } function touchLastSeen(PDO $db, int $tenantId): void { $db->prepare("UPDATE tenants SET last_seen_at = datetime('now') WHERE id = :id") ->execute(['id' => $tenantId]); }