"""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 from datetime import datetime, timezone from fastapi import APIRouter, HTTPException, Request, Response, status from pydantic import BaseModel from app.api.deps import ( SESSION_COOKIE, SESSION_MAX_AGE, configured_token, create_session, password_matches, token_matches, ) 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")