File size: 1,890 Bytes
b84c410
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
"""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