mirror of
https://github.com/umbra2728/ctfd-mcp.git
synced 2026-02-07 22:08:12 +03:00
89 lines
2.6 KiB
Python
89 lines
2.6 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
from urllib.parse import urlparse
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
class ConfigError(Exception):
|
|
"""Raised when configuration is missing or invalid."""
|
|
|
|
|
|
@dataclass
|
|
class Config:
|
|
base_url: str
|
|
token: str | None = None
|
|
session_cookie: str | None = None
|
|
csrf_token: str | None = None
|
|
total_timeout: float | None = None
|
|
connect_timeout: float | None = None
|
|
read_timeout: float | None = None
|
|
username: str | None = None
|
|
password: str | None = None
|
|
|
|
@property
|
|
def auth_header(self) -> dict[str, str]:
|
|
return {"Authorization": f"Token {self.token}"} if self.token else {}
|
|
|
|
|
|
def _parse_timeout(env_key: str) -> float | None:
|
|
raw = os.getenv(env_key)
|
|
if not raw:
|
|
return None
|
|
try:
|
|
return float(raw)
|
|
except ValueError as exc: # noqa: B904 - more readable error
|
|
raise ConfigError(f"{env_key} must be a number (seconds).") from exc
|
|
|
|
|
|
def load_config() -> Config:
|
|
"""Load config from environment or .env and validate values."""
|
|
load_dotenv()
|
|
|
|
base_url = os.getenv("CTFD_URL")
|
|
token = os.getenv("CTFD_TOKEN")
|
|
session_cookie = os.getenv("CTFD_SESSION")
|
|
csrf_token = os.getenv("CTFD_CSRF_TOKEN")
|
|
username = os.getenv("CTFD_USERNAME")
|
|
password = os.getenv("CTFD_PASSWORD")
|
|
total_timeout = _parse_timeout("CTFD_TIMEOUT")
|
|
connect_timeout = _parse_timeout("CTFD_CONNECT_TIMEOUT")
|
|
read_timeout = _parse_timeout("CTFD_READ_TIMEOUT")
|
|
|
|
if not base_url:
|
|
raise ConfigError(
|
|
"CTFD_URL is not set. Provide full URL to your CTFd instance."
|
|
)
|
|
|
|
parsed = urlparse(base_url)
|
|
if not parsed.scheme or not parsed.netloc:
|
|
raise ConfigError("CTFD_URL must be a full URL, e.g. https://ctfd.example.com")
|
|
|
|
# Precedence: username/password > token > session cookie.
|
|
if username and password:
|
|
token = None
|
|
session_cookie = None
|
|
csrf_token = None
|
|
elif token:
|
|
session_cookie = None
|
|
csrf_token = None
|
|
|
|
if not token and not session_cookie and not (username and password):
|
|
raise ConfigError(
|
|
"Set CTFD_TOKEN, CTFD_SESSION or both CTFD_USERNAME/CTFD_PASSWORD."
|
|
)
|
|
|
|
return Config(
|
|
base_url.rstrip("/"),
|
|
token.strip() if token else None,
|
|
session_cookie.strip() if session_cookie else None,
|
|
csrf_token.strip() if csrf_token else None,
|
|
total_timeout=total_timeout,
|
|
connect_timeout=connect_timeout,
|
|
read_timeout=read_timeout,
|
|
username=username.strip() if username else None,
|
|
password=password.strip() if password else None,
|
|
)
|