LOGIN / app.py
Mycelabs's picture
Update app.py
bf85e06 verified
Raw
History Blame Contribute Delete
5.16 kB
import os, secrets, hmac, hashlib, httpx, re
import gradio as gr
import spaces
from fastapi import Request, Form
from fastapi.responses import HTMLResponse, RedirectResponse
ADMIN_TOKEN = os.environ.get("ADMIN_TOKEN", "")
HF_TOKEN = os.environ.get("HF_TOKEN", "")
PRIVATE_URL = os.environ.get("PRIVATE_URL", "")
AUTH_HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
@spaces.GPU(duration=30)
def _gpu():
return ""
def make_token() -> str:
raw = secrets.token_hex(16)
key = ADMIN_TOKEN.encode() or b""
return raw + "." + hmac.new(key, raw.encode(), hashlib.sha256).hexdigest()
def check_token(t: str) -> bool:
if not t or "." not in t: return False
p = t.split(".")
k = ADMIN_TOKEN.encode() or b""
return hmac.compare_digest(p[1], hmac.new(k, p[0].encode(), hashlib.sha256).hexdigest())
def rewrite(html: str) -> str:
for q in ['"', "'"]:
for a in ["href", "src", "action"]:
html = html.replace(f'{a}={q}/', f'{a}={q}/px/')
return html
LOGIN = '<!DOCTYPE html><html lang="es" class="dark"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>VESTA</title><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Manrope:wght@700;800&display=swap" rel="stylesheet"><style>:root{--p:#004355;--s:#f8fafb;--sl:#f2f4f5;--sm:#eceeef;--os:#191c1d;--osv:#40484c;--ov:#bfc8cc;--e:#ba1a1a}html.dark{--p:#8dd0ea;--s:#0e1415;--sl:#171d1e;--sm:#1b2122;--os:#dde3e5;--osv:#bfc8cc;--ov:#3f484c;--e:#ffb4ab}*{margin:0;padding:0;box-sizing:border-box}body{background:var(--s);color:var(--os);font-family:Inter,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;padding:1.5rem;font-size:15px}.c{background:var(--sl);border:1px solid var(--ov);border-radius:16px;padding:2rem 1.8rem;max-width:340px;width:100%;text-align:center}.l{font-family:Manrope,sans-serif;font-size:1.5rem;font-weight:800;color:var(--p);margin-bottom:2px}.s{font-size:.82rem;color:var(--osv);margin-bottom:1.5rem}.e{background:rgba(186,26,26,.08);border:1px solid var(--e);color:var(--e);font-size:.8rem;font-weight:600;padding:10px;border-radius:12px;margin-bottom:1rem;display:none}</style></head><body><div class="c"><div class="l">VESTA</div><div class="s">Acceso al ecosistema</div><div class="e" id="e"></div><form method="POST" action="/login"><input name="user" placeholder="Usuario" style="width:100%;background:var(--sm);border:1.5px solid var(--ov);border-radius:12px;color:var(--os);font-size:.9rem;padding:10px 12px;outline:none;margin-bottom:12px" required><input type="password" name="pass" placeholder="Contrasena" style="width:100%;background:var(--sm);border:1.5px solid var(--ov);border-radius:12px;color:var(--os);font-size:.9rem;padding:10px 12px;outline:none;margin-bottom:12px" required><button style="width:100%;background:var(--p);color:#fff;border:none;border-radius:12px;font-weight:700;padding:10px;font-size:.85rem;cursor:pointer">Ingresar</button></form></div><script>if(new URLSearchParams(location.search).get("error")){var e=document.getElementById("e");e.textContent="Usuario o contrasena incorrectos";e.style.display="block"}</script></body></html>'
with gr.Blocks(title="VESTA") as demo:
gr.Markdown("")
app = demo.app
def cookie(req: Request) -> str:
return req.cookies.get("vesta_session", "")
@app.get("/login")
async def login_get(req: Request):
t = cookie(req)
if check_token(t):
return RedirectResponse(url="/px/")
return HTMLResponse(LOGIN)
@app.post("/login")
async def do_login(user: str = Form(...), passw: str = Form(...)):
if user.lower() != "admin" or passw != ADMIN_TOKEN:
return RedirectResponse(url="/login?error=1", status_code=303)
token = make_token()
r = RedirectResponse(url="/px/", status_code=303)
r.set_cookie("vesta_session", token, max_age=86400, path="/", httponly=True)
return r
@app.get("/logout")
async def logout():
r = RedirectResponse(url="/login")
r.delete_cookie("vesta_session", path="/")
return r
@app.get("/px/{rest:path}")
async def proxy(req: Request, rest: str = ""):
t = cookie(req)
if not check_token(t):
return RedirectResponse(url="/login")
path = "/" + rest
try:
async with httpx.AsyncClient(timeout=30) as c:
r = await c.get(f"{PRIVATE_URL}{path}", headers=AUTH_HEADERS)
content = r.text
if "text/html" in r.headers.get("content-type", ""):
content = rewrite(content)
return HTMLResponse(content)
except Exception as e:
return HTMLResponse(f"Error: {e}", 502)
@app.post("/px/proxy/predict")
async def predict(req: Request):
if not check_token(cookie(req)):
return HTMLResponse("NO_AUTH", 401)
body = await req.json()
async with httpx.AsyncClient(timeout=120) as c:
r = await c.post(f"{PRIVATE_URL}/proxy/predict", json=body, headers=AUTH_HEADERS)
return HTMLResponse(r.text, r.status_code)
demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False, head="<script>location.href='/login'</script>")