Files
EMERGENCY/skel/auth/data/app.py
T
2026-06-30 18:30:24 +00:00

80 lines
2.8 KiB
Python

from fastapi import FastAPI, Request, Form, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
import os, hmac, hashlib, time, secrets
PASSWORD = os.environ.get("HUB_PASSWORD", "changeme")
SECRET = os.environ.get("HUB_SECRET", secrets.token_hex(32))
COOKIE_NAME = "darkelite_session"
COOKIE_DAYS = 30
COOKIE_TTL = COOKIE_DAYS * 86400
app = FastAPI(redirect_slashes=False)
def sign(payload: str) -> str:
return hmac.new(SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()
def make_token() -> str:
exp = int(time.time()) + COOKIE_TTL
payload = f"{exp}"
sig = sign(payload)
return f"{payload}.{sig}"
def verify_token(token: str) -> bool:
if not token or "." not in token:
return False
try:
payload, sig = token.rsplit(".", 1)
if not hmac.compare_digest(sig, sign(payload)):
return False
exp = int(payload)
return exp > int(time.time())
except Exception:
return False
@app.get("/api/check")
def check(request: Request):
"""Caddy forward_auth hits this. 200=allowed, 401/redirect=needs login."""
# LAN bypass
# Use the FIRST X-Forwarded-For value (real client). Caddy adds this header.
# Caddy passes real client IP in X-Real-IP via forward_auth header_up
ip = request.headers.get("x-real-ip", "").strip()
if not ip:
xff = request.headers.get("x-forwarded-for", "")
ip = xff.split(",")[0].strip()
# Only treat actual LAN subnets as bypass - NOT docker internal 172.x
if ip.startswith("10.0.0.") or ip.startswith("192.168.") or ip == "127.0.0.1":
return JSONResponse({"ok": True, "via": "lan"})
token = request.cookies.get(COOKIE_NAME)
if verify_token(token):
return JSONResponse({"ok": True, "via": "cookie"})
# If this looks like a browser (accepts HTML), redirect to login
accept = request.headers.get("accept", "")
orig_uri = request.headers.get("x-forwarded-uri", "/")
if "text/html" in accept:
from fastapi.responses import RedirectResponse
return RedirectResponse(url=f"/login/?next={orig_uri}", status_code=302)
raise HTTPException(401, "auth required")
@app.post("/api/login")
def login(password: str = Form(...)):
if not hmac.compare_digest(password, PASSWORD):
raise HTTPException(401, "bad password")
token = make_token()
resp = JSONResponse({"ok": True})
resp.set_cookie(
COOKIE_NAME, token,
max_age=COOKIE_TTL, httponly=True, samesite="lax", path="/"
)
return resp
@app.post("/api/logout")
def logout():
resp = JSONResponse({"ok": True})
resp.delete_cookie(COOKIE_NAME, path="/")
return resp
@app.get("/", response_class=HTMLResponse)
def root():
with open("/static/login.html") as f:
return f.read()