vidaio-subnet / cms /auth.py
silvanus930
CMS: overview, score column, filtering/sorting, charts, CQ health, calibration UI, library browser, alerts, auth
b84c410
Raw History Blame Contribute Delete
1.89 kB
"""HTTP Basic auth for the CMS -- this runs behind a public cloudflared
tunnel with no other access control, so it shouldn't be wide open.
Credentials: CMS_USERNAME/CMS_PASSWORD env vars if set, otherwise a random
password is generated once and persisted to a local file (not the repo, not
logged anywhere public) so it's stable across restarts without requiring
manual setup.
"""
from __future__ import annotations
import os
import secrets
from pathlib import Path
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBasic, HTTPBasicCredentials
CREDENTIALS_FILE = Path("/root/vidaio-cms/.credentials")
security = HTTPBasic()
def _generate_and_store() -> tuple[str, str]:
username = "admin"
password = secrets.token_urlsafe(18)
CREDENTIALS_FILE.parent.mkdir(parents=True, exist_ok=True)
CREDENTIALS_FILE.write_text(f"{username}:{password}\n")
CREDENTIALS_FILE.chmod(0o600)
return username, password
def get_credentials() -> tuple[str, str]:
env_user = os.getenv("CMS_USERNAME")
env_pass = os.getenv("CMS_PASSWORD")
if env_user and env_pass:
return env_user, env_pass
if CREDENTIALS_FILE.exists():
line = CREDENTIALS_FILE.read_text().strip()
if ":" in line:
user, _, pw = line.partition(":")
if user and pw:
return user, pw
return _generate_and_store()
def require_auth(credentials: HTTPBasicCredentials = Depends(security)) -> str:
expected_user, expected_pass = get_credentials()
user_ok = secrets.compare_digest(credentials.username, expected_user)
pass_ok = secrets.compare_digest(credentials.password, expected_pass)
if not (user_ok and pass_ok):
raise HTTPException(
status_code=401, detail="Invalid credentials",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username