artifacts-dashboard/backend/app/api/auth.py
Paweł Orzech 10781c7987
Some checks failed
Release / release (push) Has been cancelled
v0.2.0: Rich interactive map, automation gallery, auth & UX improvements
Map overhaul:
- Replace colored boxes with actual game tile images (skin textures from CDN)
- Overlay content icons (monsters, resources, NPCs) on tiles
- Add layer switching (Overworld/Underground/Interior)
- Fix API schema to parse interactions.content and layer fields
- Add hover tooltips, tile search with coordinate parsing, keyboard shortcuts
- Add minimap with viewport rectangle, zoom-toward-cursor, loading progress
- Show tile/content images in side panel, coordinate labels at high zoom

Automation gallery:
- 27+ pre-built automation templates (combat, gathering, crafting, trading, utility)
- Multi-character selection for batch automation creation
- Gallery component with activate dialog

Auth & settings:
- API key gate with auth provider for token management
- Enhanced settings page with token configuration

UI improvements:
- Game icon component for item/monster/resource images
- Character automations panel on character detail page
- Equipment grid and inventory grid enhancements
- Automations page layout refresh
- Bank, exchange page minor fixes
- README update with live demo link
2026-03-01 20:18:29 +01:00

114 lines
3.6 KiB
Python

"""Auth endpoints for runtime API token management.
When no ARTIFACTS_TOKEN is set in the environment, users can provide
their own token through the UI. The token is stored in memory only
and must be re-sent if the backend restarts.
"""
import logging
import httpx
from fastapi import APIRouter, Request
from pydantic import BaseModel
from app.config import settings
from app.services.artifacts_client import ArtifactsClient
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/auth", tags=["auth"])
class AuthStatus(BaseModel):
has_token: bool
source: str # "env", "user", 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:
client: ArtifactsClient = request.app.state.artifacts_client
return AuthStatus(
has_token=client.has_token,
source=client.token_source,
)
@router.post("/token", response_model=SetTokenResponse)
async def set_token(body: SetTokenRequest, request: Request) -> SetTokenResponse:
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.",
)
# Token is valid — apply it
client: ArtifactsClient = request.app.state.artifacts_client
client.set_token(token)
# Reconnect WebSocket with new token
game_ws_client = getattr(request.app.state, "game_ws_client", None)
if game_ws_client is not None:
try:
await game_ws_client.reconnect_with_token(token)
except Exception:
logger.exception("Failed to reconnect WebSocket with new token")
logger.info("API token updated via UI (source: user)")
return SetTokenResponse(success=True, source="user")
@router.delete("/token")
async def clear_token(request: Request) -> AuthStatus:
client: ArtifactsClient = request.app.state.artifacts_client
client.clear_token()
# Reconnect WebSocket with env token (or empty)
game_ws_client = getattr(request.app.state, "game_ws_client", None)
if game_ws_client is not None and settings.artifacts_token:
try:
await game_ws_client.reconnect_with_token(settings.artifacts_token)
except Exception:
logger.exception("Failed to reconnect WebSocket after token clear")
logger.info("API token cleared, reverted to env")
return AuthStatus(
has_token=client.has_token,
source=client.token_source,
)