artifacts-dashboard/backend/app/api/auth.py
Paweł Orzech 75313b83c0
Add multi-user workflows/pipelines and error tracking
Add multi-user automation features and per-user error tracking.

- Database migrations: add workflow_configs/workflow_runs (004), app_errors (005), pipeline_configs/pipeline_runs (006), and add user_token_hash to app_errors (007).
- Backend: introduce per-request token handling (X-API-Token) via app.api.deps and update many API routes (auth, automations, bank, characters, dashboard, events, exchange, logs) to use user-scoped Artifacts client and character scoping. Auth endpoints no longer store tokens server-side (validate-only); clear is a no-op on server.
- New Errors API and services: endpoint to list, filter, resolve, and report errors scoped to the requesting user; add error models, schemas, middleware/error handler and error_service for recording/hashing tokens.
- Pipelines & Workflows: add API routers, models, schemas and engine modules (pipeline/worker/coordinator, workflow runner/conditions) and action_executor updates to support workflow/pipeline execution.
- Logs: logs endpoint now prefers fetching recent action logs from the game API (with fallback to local DB), supports paging and filtering, and scopes results to the user.
- Frontend: add pipeline/workflow builders, lists, progress components and hooks (use-errors, use-pipelines, use-workflows), sentry client config, and updates to API client/constants/types.
- Misc: add middleware error handler, various engine strategy tweaks, tests adjusted.

Overall this change enables per-user API tokens, scopes DB queries to each user, introduces pipelines/workflows runtime support, and centralizes application error tracking.
2026-03-01 23:02:34 +01:00

97 lines
3.1 KiB
Python

"""Auth endpoints for per-user API token management.
Each user provides their own Artifacts API token via the frontend.
The token is stored in the browser's localStorage and sent with every
request as the ``X-API-Token`` header. The backend validates the token
but does NOT store it globally — this allows true multi-user support.
"""
import logging
import httpx
from fastapi import APIRouter, Request
from pydantic import BaseModel
from app.config import settings
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/auth", tags=["auth"])
class AuthStatus(BaseModel):
has_token: bool
source: str # "header", "env", or "none"
class SetTokenRequest(BaseModel):
token: str
class SetTokenResponse(BaseModel):
success: bool
source: str
account: str | None = None
error: str | None = None
@router.get("/status", response_model=AuthStatus)
async def auth_status(request: Request) -> AuthStatus:
"""Check whether the *requesting* client has a valid token.
The frontend sends the token in the ``X-API-Token`` header.
This endpoint tells the frontend whether that token is present.
"""
token = request.headers.get("X-API-Token")
if token:
return AuthStatus(has_token=True, source="header")
return AuthStatus(has_token=False, source="none")
@router.post("/token", response_model=SetTokenResponse)
async def validate_token(body: SetTokenRequest) -> SetTokenResponse:
"""Validate an Artifacts API token.
Does NOT store the token on the server. The frontend is responsible
for persisting it in localStorage and sending it with every request.
"""
token = body.token.strip()
if not token:
return SetTokenResponse(success=False, source="none", error="Token cannot be empty")
# Validate the token by making a test call
try:
async with httpx.AsyncClient(
base_url=settings.artifacts_api_url,
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
},
timeout=httpx.Timeout(10.0),
) as test_client:
resp = await test_client.get("/my/characters")
if resp.status_code == 401:
return SetTokenResponse(success=False, source="none", error="Invalid token")
resp.raise_for_status()
except httpx.HTTPStatusError as exc:
return SetTokenResponse(
success=False,
source="none",
error=f"API error: {exc.response.status_code}",
)
except Exception as exc:
logger.warning("Token validation failed: %s", exc)
return SetTokenResponse(
success=False,
source="none",
error="Could not validate token. Check your network connection.",
)
logger.info("API token validated via UI")
return SetTokenResponse(success=True, source="user")
@router.delete("/token")
async def clear_token() -> AuthStatus:
"""No-op on the backend — the frontend clears its own localStorage."""
return AuthStatus(has_token=False, source="none")