JakeFake222 commited on
Commit
5b3dfd8
·
verified ·
1 Parent(s): 4f17d4f

Add app.py

Browse files
Files changed (1) hide show
  1. app.py +445 -0
app.py ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Secure Auth App with GitHub-backed Encrypted Storage
3
+ Material Design UI with Gradio
4
+ """
5
+
6
+ import gradio as gr
7
+ import bcrypt
8
+ from datetime import datetime
9
+ from github_storage import read_users, write_users
10
+
11
+ # Material Design CSS
12
+ MATERIAL_CSS = """
13
+ @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
14
+
15
+ * {
16
+ font-family: 'Roboto', sans-serif !important;
17
+ }
18
+
19
+ .gradio-container {
20
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
21
+ min-height: 100vh !important;
22
+ }
23
+
24
+ .main-container {
25
+ max-width: 420px !important;
26
+ margin: 40px auto !important;
27
+ padding: 0 !important;
28
+ }
29
+
30
+ .auth-card {
31
+ background: white !important;
32
+ border-radius: 16px !important;
33
+ box-shadow: 0 10px 40px rgba(0,0,0,0.2) !important;
34
+ padding: 40px !important;
35
+ margin: 20px !important;
36
+ }
37
+
38
+ .auth-card h1 {
39
+ color: #333 !important;
40
+ font-weight: 500 !important;
41
+ font-size: 28px !important;
42
+ text-align: center !important;
43
+ margin-bottom: 8px !important;
44
+ }
45
+
46
+ .auth-card p {
47
+ color: #666 !important;
48
+ text-align: center !important;
49
+ margin-bottom: 32px !important;
50
+ }
51
+
52
+ .auth-card input {
53
+ border: 2px solid #e0e0e0 !important;
54
+ border-radius: 8px !important;
55
+ padding: 14px 16px !important;
56
+ font-size: 16px !important;
57
+ transition: border-color 0.3s ease !important;
58
+ }
59
+
60
+ .auth-card input:focus {
61
+ border-color: #667eea !important;
62
+ outline: none !important;
63
+ }
64
+
65
+ .primary-btn {
66
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
67
+ color: white !important;
68
+ border: none !important;
69
+ border-radius: 8px !important;
70
+ padding: 14px 32px !important;
71
+ font-size: 16px !important;
72
+ font-weight: 500 !important;
73
+ cursor: pointer !important;
74
+ transition: transform 0.2s ease, box-shadow 0.2s ease !important;
75
+ text-transform: uppercase !important;
76
+ letter-spacing: 1px !important;
77
+ }
78
+
79
+ .primary-btn:hover {
80
+ transform: translateY(-2px) !important;
81
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important;
82
+ }
83
+
84
+ .secondary-btn {
85
+ background: transparent !important;
86
+ color: #667eea !important;
87
+ border: 2px solid #667eea !important;
88
+ border-radius: 8px !important;
89
+ padding: 12px 24px !important;
90
+ font-size: 14px !important;
91
+ font-weight: 500 !important;
92
+ cursor: pointer !important;
93
+ transition: all 0.2s ease !important;
94
+ }
95
+
96
+ .secondary-btn:hover {
97
+ background: #667eea !important;
98
+ color: white !important;
99
+ }
100
+
101
+ .error-msg {
102
+ background: #ffebee !important;
103
+ color: #c62828 !important;
104
+ padding: 12px 16px !important;
105
+ border-radius: 8px !important;
106
+ margin: 16px 0 !important;
107
+ font-size: 14px !important;
108
+ }
109
+
110
+ .success-msg {
111
+ background: #e8f5e9 !important;
112
+ color: #2e7d32 !important;
113
+ padding: 12px 16px !important;
114
+ border-radius: 8px !important;
115
+ margin: 16px 0 !important;
116
+ font-size: 14px !important;
117
+ }
118
+
119
+ .welcome-container {
120
+ text-align: center !important;
121
+ }
122
+
123
+ .welcome-container h1 {
124
+ font-size: 32px !important;
125
+ margin-bottom: 16px !important;
126
+ }
127
+
128
+ .welcome-email {
129
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
130
+ -webkit-background-clip: text !important;
131
+ -webkit-text-fill-color: transparent !important;
132
+ background-clip: text !important;
133
+ font-size: 24px !important;
134
+ font-weight: 500 !important;
135
+ margin: 24px 0 !important;
136
+ }
137
+
138
+ .avatar-circle {
139
+ width: 100px !important;
140
+ height: 100px !important;
141
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
142
+ border-radius: 50% !important;
143
+ margin: 0 auto 24px !important;
144
+ display: flex !important;
145
+ align-items: center !important;
146
+ justify-content: center !important;
147
+ font-size: 40px !important;
148
+ color: white !important;
149
+ font-weight: 500 !important;
150
+ }
151
+
152
+ .link-btn {
153
+ color: #667eea !important;
154
+ background: none !important;
155
+ border: none !important;
156
+ cursor: pointer !important;
157
+ font-size: 14px !important;
158
+ text-decoration: underline !important;
159
+ }
160
+ """
161
+
162
+ def hash_password(password: str) -> str:
163
+ """Hash password using bcrypt."""
164
+ return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
165
+
166
+ def verify_password(password: str, hashed: str) -> bool:
167
+ """Verify password against hash."""
168
+ return bcrypt.checkpw(password.encode(), hashed.encode())
169
+
170
+ def sign_up(email: str, password: str, confirm_password: str):
171
+ """Handle user registration."""
172
+ # Validation
173
+ if not email or not password or not confirm_password:
174
+ return (
175
+ gr.update(visible=True), # sign_in_container
176
+ gr.update(visible=False), # sign_up_container
177
+ gr.update(visible=False), # welcome_container
178
+ "", # welcome_email
179
+ gr.update(value="⚠️ All fields are required", visible=True), # message
180
+ )
181
+
182
+ if "@" not in email or "." not in email:
183
+ return (
184
+ gr.update(visible=True),
185
+ gr.update(visible=False),
186
+ gr.update(visible=False),
187
+ "",
188
+ gr.update(value="⚠️ Please enter a valid email address", visible=True),
189
+ )
190
+
191
+ if len(password) < 6:
192
+ return (
193
+ gr.update(visible=True),
194
+ gr.update(visible=False),
195
+ gr.update(visible=False),
196
+ "",
197
+ gr.update(value="⚠️ Password must be at least 6 characters", visible=True),
198
+ )
199
+
200
+ if password != confirm_password:
201
+ return (
202
+ gr.update(visible=True),
203
+ gr.update(visible=False),
204
+ gr.update(visible=False),
205
+ "",
206
+ gr.update(value="⚠️ Passwords do not match", visible=True),
207
+ )
208
+
209
+ try:
210
+ # Load existing users
211
+ data = read_users()
212
+
213
+ # Check if user exists
214
+ if email.lower() in data.get("users", {}):
215
+ return (
216
+ gr.update(visible=True),
217
+ gr.update(visible=False),
218
+ gr.update(visible=False),
219
+ "",
220
+ gr.update(value="⚠️ An account with this email already exists", visible=True),
221
+ )
222
+
223
+ # Create new user
224
+ data.setdefault("users", {})
225
+ data["users"][email.lower()] = {
226
+ "password_hash": hash_password(password),
227
+ "created_at": datetime.utcnow().isoformat()
228
+ }
229
+
230
+ # Save to GitHub
231
+ if write_users(data):
232
+ return (
233
+ gr.update(visible=False),
234
+ gr.update(visible=False),
235
+ gr.update(visible=True),
236
+ email,
237
+ gr.update(value="", visible=False),
238
+ )
239
+ else:
240
+ return (
241
+ gr.update(visible=True),
242
+ gr.update(visible=False),
243
+ gr.update(visible=False),
244
+ "",
245
+ gr.update(value="⚠️ Failed to save user data. Please try again.", visible=True),
246
+ )
247
+ except Exception as e:
248
+ return (
249
+ gr.update(visible=True),
250
+ gr.update(visible=False),
251
+ gr.update(visible=False),
252
+ "",
253
+ gr.update(value=f"⚠️ Error: {str(e)}", visible=True),
254
+ )
255
+
256
+ def sign_in(email: str, password: str):
257
+ """Handle user login."""
258
+ if not email or not password:
259
+ return (
260
+ gr.update(visible=True),
261
+ gr.update(visible=False),
262
+ gr.update(visible=False),
263
+ "",
264
+ gr.update(value="⚠️ Email and password are required", visible=True),
265
+ )
266
+
267
+ try:
268
+ data = read_users()
269
+ user = data.get("users", {}).get(email.lower())
270
+
271
+ if not user:
272
+ return (
273
+ gr.update(visible=True),
274
+ gr.update(visible=False),
275
+ gr.update(visible=False),
276
+ "",
277
+ gr.update(value="⚠️ No account found with this email", visible=True),
278
+ )
279
+
280
+ if not verify_password(password, user["password_hash"]):
281
+ return (
282
+ gr.update(visible=True),
283
+ gr.update(visible=False),
284
+ gr.update(visible=False),
285
+ "",
286
+ gr.update(value="⚠️ Incorrect password", visible=True),
287
+ )
288
+
289
+ # Success
290
+ return (
291
+ gr.update(visible=False),
292
+ gr.update(visible=False),
293
+ gr.update(visible=True),
294
+ email,
295
+ gr.update(value="", visible=False),
296
+ )
297
+ except Exception as e:
298
+ return (
299
+ gr.update(visible=True),
300
+ gr.update(visible=False),
301
+ gr.update(visible=False),
302
+ "",
303
+ gr.update(value=f"⚠️ Error: {str(e)}", visible=True),
304
+ )
305
+
306
+ def show_sign_up():
307
+ """Switch to sign up view."""
308
+ return (
309
+ gr.update(visible=False),
310
+ gr.update(visible=True),
311
+ gr.update(visible=False),
312
+ "",
313
+ gr.update(value="", visible=False),
314
+ )
315
+
316
+ def show_sign_in():
317
+ """Switch to sign in view."""
318
+ return (
319
+ gr.update(visible=True),
320
+ gr.update(visible=False),
321
+ gr.update(visible=False),
322
+ "",
323
+ gr.update(value="", visible=False),
324
+ )
325
+
326
+ def logout():
327
+ """Handle logout."""
328
+ return (
329
+ gr.update(visible=True),
330
+ gr.update(visible=False),
331
+ gr.update(visible=False),
332
+ "",
333
+ gr.update(value="", visible=False),
334
+ )
335
+
336
+ # Build the UI
337
+ with gr.Blocks(css=MATERIAL_CSS, title="Secure Auth") as demo:
338
+
339
+ # State for storing current user email
340
+ current_email = gr.State("")
341
+
342
+ with gr.Column(elem_classes="main-container"):
343
+
344
+ # Sign In Container
345
+ with gr.Column(visible=True, elem_classes="auth-card") as sign_in_container:
346
+ gr.HTML("<h1>Welcome Back</h1>")
347
+ gr.HTML("<p>Sign in to continue</p>")
348
+
349
+ sign_in_email = gr.Textbox(
350
+ label="Email",
351
+ placeholder="Enter your email",
352
+ type="email"
353
+ )
354
+ sign_in_password = gr.Textbox(
355
+ label="Password",
356
+ placeholder="Enter your password",
357
+ type="password"
358
+ )
359
+
360
+ message = gr.HTML("", visible=False, elem_classes="error-msg")
361
+
362
+ sign_in_btn = gr.Button("Sign In", elem_classes="primary-btn")
363
+
364
+ gr.HTML("<br>")
365
+ gr.HTML("<p style='text-align: center; color: #666;'>Don't have an account?</p>")
366
+ go_to_sign_up_btn = gr.Button("Create Account", elem_classes="secondary-btn")
367
+
368
+ # Sign Up Container
369
+ with gr.Column(visible=False, elem_classes="auth-card") as sign_up_container:
370
+ gr.HTML("<h1>Create Account</h1>")
371
+ gr.HTML("<p>Sign up to get started</p>")
372
+
373
+ sign_up_email = gr.Textbox(
374
+ label="Email",
375
+ placeholder="Enter your email",
376
+ type="email"
377
+ )
378
+ sign_up_password = gr.Textbox(
379
+ label="Password",
380
+ placeholder="Create a password (min 6 characters)",
381
+ type="password"
382
+ )
383
+ sign_up_confirm = gr.Textbox(
384
+ label="Confirm Password",
385
+ placeholder="Confirm your password",
386
+ type="password"
387
+ )
388
+
389
+ sign_up_btn = gr.Button("Sign Up", elem_classes="primary-btn")
390
+
391
+ gr.HTML("<br>")
392
+ gr.HTML("<p style='text-align: center; color: #666;'>Already have an account?</p>")
393
+ go_to_sign_in_btn = gr.Button("Sign In", elem_classes="secondary-btn")
394
+
395
+ # Welcome Container
396
+ with gr.Column(visible=False, elem_classes="auth-card") as welcome_container:
397
+ gr.HTML("<div class='welcome-container'>")
398
+ gr.HTML("<div class='avatar-circle'>👋</div>")
399
+ gr.HTML("<h1>Welcome!</h1>")
400
+ gr.HTML("<p>You're successfully signed in as:</p>")
401
+ welcome_email_display = gr.HTML("<div class='welcome-email'></div>")
402
+ gr.HTML("</div>")
403
+
404
+ gr.HTML("<br><br>")
405
+ logout_btn = gr.Button("Sign Out", elem_classes="secondary-btn")
406
+
407
+ # Event handlers
408
+ outputs = [sign_in_container, sign_up_container, welcome_container, current_email, message]
409
+
410
+ sign_in_btn.click(
411
+ sign_in,
412
+ inputs=[sign_in_email, sign_in_password],
413
+ outputs=outputs
414
+ )
415
+
416
+ sign_up_btn.click(
417
+ sign_up,
418
+ inputs=[sign_up_email, sign_up_password, sign_up_confirm],
419
+ outputs=outputs
420
+ )
421
+
422
+ go_to_sign_up_btn.click(
423
+ show_sign_up,
424
+ outputs=outputs
425
+ )
426
+
427
+ go_to_sign_in_btn.click(
428
+ show_sign_in,
429
+ outputs=outputs
430
+ )
431
+
432
+ logout_btn.click(
433
+ logout,
434
+ outputs=outputs
435
+ )
436
+
437
+ # Update welcome email display when current_email changes
438
+ current_email.change(
439
+ lambda email: f"<div class='welcome-email'>{email}</div>",
440
+ inputs=[current_email],
441
+ outputs=[welcome_email_display]
442
+ )
443
+
444
+ if __name__ == "__main__":
445
+ demo.launch()