hudaakram's picture
Create app.py
eaeb03e verified
# 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 &amp; 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
@app.get("/")
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})"}
@app.post("/verify")
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)