Handle Seerr CSRF on request creation
Magent CI/CD / verify (push) Canceled after 2m30s
Magent CI/CD / deploy-prod (push) Canceled after 0s
Magent CI/CD / deploy-beta (push) Canceled after 0s

This commit is contained in:
2026-08-31 15:11:57 +12:00
parent a55369190b
commit 547ed754e6
3 changed files with 91 additions and 2 deletions
+20 -1
View File
@@ -62,6 +62,24 @@ class ApiClient:
return f"{payload[:500]}..."
return payload
async def _send_request(
self,
client: httpx.AsyncClient,
method: str,
url: str,
*,
headers: Dict[str, str],
params: Optional[Dict[str, Any]],
payload: Optional[Dict[str, Any]],
) -> httpx.Response:
return await client.request(
method,
url,
headers=headers,
params=params,
json=payload,
)
async def _request(
self,
method: str,
@@ -89,7 +107,8 @@ class ApiClient:
)
try:
async with httpx.AsyncClient(timeout=timeout_seconds) as client:
response = await client.request(
response = await self._send_request(
client,
method,
url,
headers=self.headers(),
+35 -1
View File
@@ -1,10 +1,44 @@
from typing import Any, Dict, Optional
from urllib.parse import quote
from urllib.parse import quote, unquote, urlsplit
import httpx
from .base import ApiClient
class JellyseerrClient(ApiClient):
async def _send_request(
self,
client: httpx.AsyncClient,
method: str,
url: str,
*,
headers: Dict[str, str],
params: Optional[Dict[str, Any]],
payload: Optional[Dict[str, Any]],
) -> httpx.Response:
request_headers = dict(headers)
if method.upper() in {"POST", "PUT", "PATCH", "DELETE"} and self.base_url:
# Seerr's optional CSRF protection also applies to API-key writes.
# Seed its secret/token cookie pair, then echo the readable token in
# the header Seerr's own web client uses.
csrf_response = await client.get(
f"{self.base_url}/api/v1/auth/me",
headers=self.headers(),
)
csrf_response.raise_for_status()
csrf_token = client.cookies.get("XSRF-TOKEN")
if csrf_token:
request_headers["XSRF-TOKEN"] = unquote(csrf_token)
parsed_base = urlsplit(self.base_url)
request_headers["Origin"] = f"{parsed_base.scheme}://{parsed_base.netloc}"
return await super()._send_request(
client,
method,
url,
headers=request_headers,
params=params,
payload=payload,
)
async def get_status(self) -> Optional[Dict[str, Any]]:
return await self.get("/api/v1/status")