Spaces:
Runtime error
Runtime error
| # app.py (Gate Space) | |
| from flask import Flask, request, redirect, make_response | |
| import html, os, time, requests, json | |
| from itsdangerous import URLSafeSerializer | |
| # ===== CONFIG (set as Space Secrets or edit here) ===== | |
| MODEL_API_URL = os.getenv("MODEL_API_URL", "") # e.g. https://<you>-JARVIS-ModelAPI.hf.space/verify | |
| AP_WEBHOOK_URL = os.getenv("AP_WEBHOOK_URL", "") # optional Activepieces Catch Hook | |
| CHATBOT_URL = os.getenv("CHATBOT_URL", "https://<you>-JARVIS-Chat.hf.space") | |
| SECRET_KEY = os.getenv("SECRET_KEY", "change-me") | |
| TOKEN_TTL = int(os.getenv("TOKEN_TTL", "3600")) # seconds | |
| # ===== UI TEXT ===== | |
| TITLE = "Face Verify Gate" | |
| CAPTION = "Verify your identity to continue" | |
| BACKGROUND_GIF = "https://i.pinimg.com/originals/d9/09/57/d90957d7462b87ba8171fce62d2bf816.gif" | |
| app = Flask(__name__) | |
| signer = URLSafeSerializer(SECRET_KEY, salt="jarvis-facegate") | |
| def make_token(name: str) -> str: | |
| payload = {"name": name, "ts": int(time.time())} | |
| return signer.dumps(payload) | |
| def page(status_msg=""): | |
| status_msg = html.escape(status_msg or "") | |
| html_page = f"""<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>{TITLE}</title> | |
| <style> | |
| :root {{ | |
| --glass: rgba(10,16,24,0.55); | |
| --border: rgba(62,231,255,0.35); | |
| --accent: #3ee7ff; | |
| --accent2: #7bf5c8; | |
| --text: #e8f3ff; | |
| --muted: #a9c2d0; | |
| }} | |
| * {{ box-sizing: border-box; }} | |
| html, body {{ | |
| height: 100%; margin: 0; | |
| font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; | |
| color: var(--text); | |
| }} | |
| body {{ | |
| background: #000 center/cover no-repeat fixed; | |
| background-image: url('{BACKGROUND_GIF}'); | |
| }} | |
| .shade {{ | |
| position: fixed; inset: 0; | |
| background: radial-gradient(60% 60% at 50% 40%, rgba(0,0,32,0.10) 0%, rgba(0,0,0,0.65) 100%); | |
| }} | |
| .card {{ | |
| position: relative; | |
| max-width: 520px; | |
| margin: min(10vh, 8rem) auto 0; | |
| padding: 1.75rem 1.75rem 1.25rem; | |
| background: linear-gradient(180deg, rgba(10,16,24,0.72), var(--glass)); | |
| backdrop-filter: blur(10px) saturate(130%); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.6), inset 0 0 40px rgba(62,231,255,0.12); | |
| }} | |
| h1 {{ margin: 0 0 .25rem 0; font-weight: 800; letter-spacing: 0.5px; }} | |
| .caption {{ margin: 0 0 1.0rem 0; color: var(--muted); }} | |
| label {{ display:block; margin:.6rem 0 .35rem; color: var(--muted); font-weight:600; }} | |
| input[type="text"], input[type="file"] {{ | |
| width: 100%; padding: .7rem .85rem; | |
| border-radius: 10px; border: 1px solid rgba(255,255,255,0.18); | |
| background: rgba(255,255,255,0.08); color: var(--text); outline: none; | |
| }} | |
| input[type="text"]:focus {{ | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px rgba(62,231,255,0.25); | |
| }} | |
| button {{ | |
| margin-top: 1rem; padding: .8rem 1rem; width: 100%; | |
| border-radius: 10px; border: none; | |
| background: linear-gradient(90deg, var(--accent), var(--accent2)); | |
| color: #0a0f18; font-weight: 800; cursor: pointer; | |
| }} | |
| #status {{ min-height: 1.25rem; margin-top: .6rem; color: #ffd166; }} | |
| #status.err {{ color: #ff7b7b; }} | |
| #status.ok {{ color: #7bf5c8; }} | |
| .stub {{ margin-top: .6rem; color: var(--muted); font-size:.9rem; }} | |
| @media (max-width: 520px) {{ .card {{ margin: 8vh 1rem 0; }} }} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="shade"></div> | |
| <main class="card"> | |
| <h1>{TITLE}</h1> | |
| <p class="caption">{CAPTION}</p> | |
| <form method="POST" action="/verify" enctype="multipart/form-data" onsubmit="onSubmit()"> | |
| <label for="name">Name</label> | |
| <input id="name" name="name" type="text" placeholder="Enter your name" required /> | |
| <label for="photo">Photo</label> | |
| <input id="photo" name="photo" type="file" accept="image/*" required /> | |
| <button type="submit">Verify & Enter</button> | |
| <p id="status" aria-live="polite">{status_msg}</p> | |
| </form> | |
| <p class="stub">Jarvis mode — verification runs server-side.</p> | |
| </main> | |
| <script> | |
| function onSubmit() {{ | |
| const s = document.getElementById('status'); | |
| s.textContent = 'Verifying…'; | |
| s.className = ''; | |
| }} | |
| </script> | |
| </body> | |
| </html>""" | |
| resp = make_response(html_page) | |
| resp.headers["Content-Type"] = "text/html; charset=utf-8" | |
| return resp | |
| def index(): | |
| return page("") | |
| def call_model_api(name, file_storage): | |
| # Call Activepieces (preferred) or Model API directly | |
| url = AP_WEBHOOK_URL.strip() or MODEL_API_URL.strip() | |
| if not url: | |
| raise RuntimeError("Neither AP_WEBHOOK_URL nor MODEL_API_URL is set.") | |
| files = {"image" if "verify" in url else "photo": (file_storage.filename, file_storage.stream, file_storage.mimetype or "application/octet-stream")} | |
| data = {"name": name} | |
| r = requests.post(url, data=data, files=files, timeout=45) | |
| # Activepieces typically returns {status, redirect_url?}; Model API returns {status, score,...} | |
| try: | |
| return r.json() | |
| except Exception: | |
| return {"status": "error", "message": f"Non-JSON from backend ({r.status_code})"} | |
| def verify(): | |
| name = (request.form.get("name") or "").strip() | |
| file = request.files.get("photo") | |
| if not name or not file or not file.filename.strip(): | |
| return page("Please enter your name and select a photo."), 400 | |
| try: | |
| result = call_model_api(name, file) | |
| except Exception as e: | |
| return page(f"System error contacting verifier: {e}"), 502 | |
| status = str(result.get("status","")).lower() | |
| if status == "accepted": | |
| token = make_token(name) | |
| # Prefer redirect_url from Activepieces; else use CHATBOT_URL | |
| cb = result.get("redirect_url") or CHATBOT_URL | |
| return redirect(f"{cb}?token={token}", code=302) | |
| msg = result.get("message") or "Access denied by FaceGate." | |
| return page(msg), 401 | |
| if __name__ == "__main__": | |
| app.run(host="127.0.0.1", port=5000, debug=True) | |