feat: add basic QEMU and task vertical slice

This commit is contained in:
Sergey Antropoff
2026-07-13 00:41:13 +03:00
parent d141994540
commit 4b923e63f2
15 changed files with 452 additions and 14 deletions
+32 -5
View File
@@ -4,14 +4,16 @@ from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from typing import Any, Literal
from typing import Any, Literal, cast
from urllib.parse import parse_qsl
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from app.api.errors import ContractValidationError
from app.api.errors import ApiError, ContractValidationError
from app.config import Settings
from app.contracts.model import Method, Schema, Snapshot
from app.security.auth import verify_csrf, verify_ticket
Handler = Callable[[Request, dict[str, Any]], Awaitable[Any]]
FallbackMode = Literal["error", "schema-default", "fixture"]
@@ -79,6 +81,7 @@ def _endpoint(
fallback: FallbackMode,
) -> Callable[[Request], Awaitable[JSONResponse]]:
async def dispatch(request: Request) -> JSONResponse:
_authenticate(request, semantic_path)
handler = handlers.get(semantic_path, method.verb)
inputs = await _parse_inputs(request, method)
if handler is not None:
@@ -92,13 +95,37 @@ def _endpoint(
status_code=501,
content={"data": None, "errors": "method semantics are not implemented"},
)
if renderer == "extjs":
return JSONResponse({"data": data, "success": True})
return JSONResponse({"data": data})
content = {"data": data, "success": True} if renderer == "extjs" else {"data": data}
response = JSONResponse(content)
if semantic_path == "/access/ticket" and isinstance(data, dict):
ticket = data.get("ticket")
if isinstance(ticket, str):
response.set_cookie(
"PVEAuthCookie", ticket, httponly=True, samesite="strict", path="/"
)
return response
return dispatch
def _authenticate(request: Request, semantic_path: str) -> None:
if semantic_path in {"/version", "/access/ticket"}:
return
ticket = request.cookies.get("PVEAuthCookie")
if ticket is None:
raise ApiError(401, "authentication required")
settings = cast(Settings, request.app.state.settings)
key = settings.ticket_signing_key.get_secret_value().encode()
try:
verify_ticket(ticket, key)
except ValueError as error:
raise ApiError(401, "authentication failure") from error
if request.method not in {"GET", "HEAD", "OPTIONS"}:
token = request.headers.get("CSRFPreventionToken", "")
if not verify_csrf(ticket, token, key):
raise ApiError(403, "invalid CSRF prevention token")
async def _parse_inputs(request: Request, method: Method) -> dict[str, Any]:
supplied: dict[str, Any] = dict(request.query_params)
supplied.update(request.path_params)