hudaakram commited on
Commit
eaeb03e
·
verified ·
1 Parent(s): 08d9630

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -0
app.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py (Gate Space)
2
+ from flask import Flask, request, redirect, make_response
3
+ import html, os, time, requests, json
4
+ from itsdangerous import URLSafeSerializer
5
+
6
+ # ===== CONFIG (set as Space Secrets or edit here) =====
7
+ MODEL_API_URL = os.getenv("MODEL_API_URL", "") # e.g. https://<you>-JARVIS-ModelAPI.hf.space/verify
8
+ AP_WEBHOOK_URL = os.getenv("AP_WEBHOOK_URL", "") # optional Activepieces Catch Hook
9
+ CHATBOT_URL = os.getenv("CHATBOT_URL", "https://<you>-JARVIS-Chat.hf.space")
10
+ SECRET_KEY = os.getenv("SECRET_KEY", "change-me")
11
+ TOKEN_TTL = int(os.getenv("TOKEN_TTL", "3600")) # seconds
12
+ # ===== UI TEXT =====
13
+ TITLE = "Face Verify Gate"
14
+ CAPTION = "Verify your identity to continue"
15
+ BACKGROUND_GIF = "https://i.pinimg.com/originals/d9/09/57/d90957d7462b87ba8171fce62d2bf816.gif"
16
+
17
+ app = Flask(__name__)
18
+ signer = URLSafeSerializer(SECRET_KEY, salt="jarvis-facegate")
19
+
20
+ def make_token(name: str) -> str:
21
+ payload = {"name": name, "ts": int(time.time())}
22
+ return signer.dumps(payload)
23
+
24
+ def page(status_msg=""):
25
+ status_msg = html.escape(status_msg or "")
26
+ html_page = f"""<!DOCTYPE html>
27
+ <html lang="en">
28
+ <head>
29
+ <meta charset="UTF-8" />
30
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
31
+ <title>{TITLE}</title>
32
+ <style>
33
+ :root {{
34
+ --glass: rgba(10,16,24,0.55);
35
+ --border: rgba(62,231,255,0.35);
36
+ --accent: #3ee7ff;
37
+ --accent2: #7bf5c8;
38
+ --text: #e8f3ff;
39
+ --muted: #a9c2d0;
40
+ }}
41
+ * {{ box-sizing: border-box; }}
42
+ html, body {{
43
+ height: 100%; margin: 0;
44
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
45
+ color: var(--text);
46
+ }}
47
+ body {{
48
+ background: #000 center/cover no-repeat fixed;
49
+ background-image: url('{BACKGROUND_GIF}');
50
+ }}
51
+ .shade {{
52
+ position: fixed; inset: 0;
53
+ background: radial-gradient(60% 60% at 50% 40%, rgba(0,0,32,0.10) 0%, rgba(0,0,0,0.65) 100%);
54
+ }}
55
+ .card {{
56
+ position: relative;
57
+ max-width: 520px;
58
+ margin: min(10vh, 8rem) auto 0;
59
+ padding: 1.75rem 1.75rem 1.25rem;
60
+ background: linear-gradient(180deg, rgba(10,16,24,0.72), var(--glass));
61
+ backdrop-filter: blur(10px) saturate(130%);
62
+ border: 1px solid var(--border);
63
+ border-radius: 16px;
64
+ box-shadow: 0 20px 60px rgba(0,0,0,0.6), inset 0 0 40px rgba(62,231,255,0.12);
65
+ }}
66
+ h1 {{ margin: 0 0 .25rem 0; font-weight: 800; letter-spacing: 0.5px; }}
67
+ .caption {{ margin: 0 0 1.0rem 0; color: var(--muted); }}
68
+ label {{ display:block; margin:.6rem 0 .35rem; color: var(--muted); font-weight:600; }}
69
+ input[type="text"], input[type="file"] {{
70
+ width: 100%; padding: .7rem .85rem;
71
+ border-radius: 10px; border: 1px solid rgba(255,255,255,0.18);
72
+ background: rgba(255,255,255,0.08); color: var(--text); outline: none;
73
+ }}
74
+ input[type="text"]:focus {{
75
+ border-color: var(--accent);
76
+ box-shadow: 0 0 0 3px rgba(62,231,255,0.25);
77
+ }}
78
+ button {{
79
+ margin-top: 1rem; padding: .8rem 1rem; width: 100%;
80
+ border-radius: 10px; border: none;
81
+ background: linear-gradient(90deg, var(--accent), var(--accent2));
82
+ color: #0a0f18; font-weight: 800; cursor: pointer;
83
+ }}
84
+ #status {{ min-height: 1.25rem; margin-top: .6rem; color: #ffd166; }}
85
+ #status.err {{ color: #ff7b7b; }}
86
+ #status.ok {{ color: #7bf5c8; }}
87
+ .stub {{ margin-top: .6rem; color: var(--muted); font-size:.9rem; }}
88
+ @media (max-width: 520px) {{ .card {{ margin: 8vh 1rem 0; }} }}
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <div class="shade"></div>
93
+ <main class="card">
94
+ <h1>{TITLE}</h1>
95
+ <p class="caption">{CAPTION}</p>
96
+ <form method="POST" action="/verify" enctype="multipart/form-data" onsubmit="onSubmit()">
97
+ <label for="name">Name</label>
98
+ <input id="name" name="name" type="text" placeholder="Enter your name" required />
99
+ <label for="photo">Photo</label>
100
+ <input id="photo" name="photo" type="file" accept="image/*" required />
101
+ <button type="submit">Verify &amp; Enter</button>
102
+ <p id="status" aria-live="polite">{status_msg}</p>
103
+ </form>
104
+ <p class="stub">Jarvis mode — verification runs server-side.</p>
105
+ </main>
106
+ <script>
107
+ function onSubmit() {{
108
+ const s = document.getElementById('status');
109
+ s.textContent = 'Verifying…';
110
+ s.className = '';
111
+ }}
112
+ </script>
113
+ </body>
114
+ </html>"""
115
+ resp = make_response(html_page)
116
+ resp.headers["Content-Type"] = "text/html; charset=utf-8"
117
+ return resp
118
+
119
+ @app.get("/")
120
+ def index():
121
+ return page("")
122
+
123
+ def call_model_api(name, file_storage):
124
+ # Call Activepieces (preferred) or Model API directly
125
+ url = AP_WEBHOOK_URL.strip() or MODEL_API_URL.strip()
126
+ if not url:
127
+ raise RuntimeError("Neither AP_WEBHOOK_URL nor MODEL_API_URL is set.")
128
+ files = {"image" if "verify" in url else "photo": (file_storage.filename, file_storage.stream, file_storage.mimetype or "application/octet-stream")}
129
+ data = {"name": name}
130
+ r = requests.post(url, data=data, files=files, timeout=45)
131
+ # Activepieces typically returns {status, redirect_url?}; Model API returns {status, score,...}
132
+ try:
133
+ return r.json()
134
+ except Exception:
135
+ return {"status": "error", "message": f"Non-JSON from backend ({r.status_code})"}
136
+
137
+ @app.post("/verify")
138
+ def verify():
139
+ name = (request.form.get("name") or "").strip()
140
+ file = request.files.get("photo")
141
+ if not name or not file or not file.filename.strip():
142
+ return page("Please enter your name and select a photo."), 400
143
+
144
+ try:
145
+ result = call_model_api(name, file)
146
+ except Exception as e:
147
+ return page(f"System error contacting verifier: {e}"), 502
148
+
149
+ status = str(result.get("status","")).lower()
150
+ if status == "accepted":
151
+ token = make_token(name)
152
+ # Prefer redirect_url from Activepieces; else use CHATBOT_URL
153
+ cb = result.get("redirect_url") or CHATBOT_URL
154
+ return redirect(f"{cb}?token={token}", code=302)
155
+
156
+ msg = result.get("message") or "Access denied by FaceGate."
157
+ return page(msg), 401
158
+
159
+ if __name__ == "__main__":
160
+ app.run(host="127.0.0.1", port=5000, debug=True)