"""Endpoints that stay reachable without a token. `bin/wait-deploy` polls /api/version from whatever machine you pushed from, and the browser needs /api/login before it has a session, so these routes stay outside the protected API router. """ import os import json from datetime import datetime, timezone from fastapi import APIRouter, HTTPException, Request, Response, status from fastapi.responses import FileResponse from pydantic import BaseModel from app.api.deps import ( SESSION_COOKIE, SESSION_MAX_AGE, configured_token, create_session, password_matches, token_matches, ) from app.api.captures import capture_path router = APIRouter(prefix="/api") # Coolify injects the deployed commit; absent when running locally. SOURCE_COMMIT = os.environ.get("SOURCE_COMMIT", "dev") STARTED_AT = datetime.now(timezone.utc).replace(microsecond=0).isoformat() class LoginRequest(BaseModel): password: str @router.get("/health") def health(): return {"status": "ok", "service": "chart"} @router.get("/version") def version(): return {"commit": SOURCE_COMMIT, "started_at": STARTED_AT} @router.post("/login", status_code=status.HTTP_204_NO_CONTENT) def login(credentials: LoginRequest, request: Request, response: Response): presented_token = request.headers.get("x-chart-token", "") token_login = bool(configured_token(request.app)) and token_matches( request.app, presented_token ) if not token_login and not password_matches(request.app, credentials.password): raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Incorrect password") forwarded_proto = request.headers.get("x-forwarded-proto", "").split(",", 1)[0] response.set_cookie( SESSION_COOKIE, create_session(request.app), max_age=SESSION_MAX_AGE, httponly=True, secure=request.url.scheme == "https" or forwarded_proto == "https", samesite="strict", path="/", ) @router.post("/logout", status_code=status.HTTP_204_NO_CONTENT) def logout(response: Response): response.delete_cookie(SESSION_COOKIE, path="/", httponly=True, samesite="strict")