Spaces:
Sleeping
Sleeping
| """ | |
| Secure Auth App with GitHub-backed Encrypted Storage | |
| Material Design UI with Gradio | |
| """ | |
| import gradio as gr | |
| import bcrypt | |
| from datetime import datetime | |
| from github_storage import read_users, write_users | |
| # Material Design CSS | |
| MATERIAL_CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap'); | |
| * { | |
| font-family: 'Roboto', sans-serif !important; | |
| } | |
| .gradio-container { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| min-height: 100vh !important; | |
| } | |
| .main-container { | |
| max-width: 420px !important; | |
| margin: 40px auto !important; | |
| padding: 0 !important; | |
| } | |
| .auth-card { | |
| background: white !important; | |
| border-radius: 16px !important; | |
| box-shadow: 0 10px 40px rgba(0,0,0,0.2) !important; | |
| padding: 40px !important; | |
| margin: 20px !important; | |
| } | |
| .auth-card h1 { | |
| color: #333 !important; | |
| font-weight: 500 !important; | |
| font-size: 28px !important; | |
| text-align: center !important; | |
| margin-bottom: 8px !important; | |
| } | |
| .auth-card p { | |
| color: #666 !important; | |
| text-align: center !important; | |
| margin-bottom: 32px !important; | |
| } | |
| .auth-card input { | |
| border: 2px solid #e0e0e0 !important; | |
| border-radius: 8px !important; | |
| padding: 14px 16px !important; | |
| font-size: 16px !important; | |
| transition: border-color 0.3s ease !important; | |
| } | |
| .auth-card input:focus { | |
| border-color: #667eea !important; | |
| outline: none !important; | |
| } | |
| .primary-btn { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| color: white !important; | |
| border: none !important; | |
| border-radius: 8px !important; | |
| padding: 14px 32px !important; | |
| font-size: 16px !important; | |
| font-weight: 500 !important; | |
| cursor: pointer !important; | |
| transition: transform 0.2s ease, box-shadow 0.2s ease !important; | |
| text-transform: uppercase !important; | |
| letter-spacing: 1px !important; | |
| } | |
| .primary-btn:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important; | |
| } | |
| .secondary-btn { | |
| background: transparent !important; | |
| color: #667eea !important; | |
| border: 2px solid #667eea !important; | |
| border-radius: 8px !important; | |
| padding: 12px 24px !important; | |
| font-size: 14px !important; | |
| font-weight: 500 !important; | |
| cursor: pointer !important; | |
| transition: all 0.2s ease !important; | |
| } | |
| .secondary-btn:hover { | |
| background: #667eea !important; | |
| color: white !important; | |
| } | |
| .error-msg { | |
| background: #ffebee !important; | |
| color: #c62828 !important; | |
| padding: 12px 16px !important; | |
| border-radius: 8px !important; | |
| margin: 16px 0 !important; | |
| font-size: 14px !important; | |
| } | |
| .success-msg { | |
| background: #e8f5e9 !important; | |
| color: #2e7d32 !important; | |
| padding: 12px 16px !important; | |
| border-radius: 8px !important; | |
| margin: 16px 0 !important; | |
| font-size: 14px !important; | |
| } | |
| .welcome-container { | |
| text-align: center !important; | |
| } | |
| .welcome-container h1 { | |
| font-size: 32px !important; | |
| margin-bottom: 16px !important; | |
| } | |
| .welcome-email { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| -webkit-background-clip: text !important; | |
| -webkit-text-fill-color: transparent !important; | |
| background-clip: text !important; | |
| font-size: 24px !important; | |
| font-weight: 500 !important; | |
| margin: 24px 0 !important; | |
| } | |
| .avatar-circle { | |
| width: 100px !important; | |
| height: 100px !important; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| border-radius: 50% !important; | |
| margin: 0 auto 24px !important; | |
| display: flex !important; | |
| align-items: center !important; | |
| justify-content: center !important; | |
| font-size: 40px !important; | |
| color: white !important; | |
| font-weight: 500 !important; | |
| } | |
| .link-btn { | |
| color: #667eea !important; | |
| background: none !important; | |
| border: none !important; | |
| cursor: pointer !important; | |
| font-size: 14px !important; | |
| text-decoration: underline !important; | |
| } | |
| """ | |
| def hash_password(password: str) -> str: | |
| """Hash password using bcrypt.""" | |
| return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() | |
| def verify_password(password: str, hashed: str) -> bool: | |
| """Verify password against hash.""" | |
| return bcrypt.checkpw(password.encode(), hashed.encode()) | |
| def sign_up(email: str, password: str, confirm_password: str): | |
| """Handle user registration.""" | |
| # Validation | |
| if not email or not password or not confirm_password: | |
| return ( | |
| gr.update(visible=True), # sign_in_container | |
| gr.update(visible=False), # sign_up_container | |
| gr.update(visible=False), # welcome_container | |
| "", # welcome_email | |
| gr.update(value="β οΈ All fields are required", visible=True), # message | |
| ) | |
| if "@" not in email or "." not in email: | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="β οΈ Please enter a valid email address", visible=True), | |
| ) | |
| if len(password) < 6: | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="β οΈ Password must be at least 6 characters", visible=True), | |
| ) | |
| if password != confirm_password: | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="β οΈ Passwords do not match", visible=True), | |
| ) | |
| try: | |
| # Load existing users | |
| data = read_users() | |
| # Check if user exists | |
| if email.lower() in data.get("users", {}): | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="β οΈ An account with this email already exists", visible=True), | |
| ) | |
| # Create new user | |
| data.setdefault("users", {}) | |
| data["users"][email.lower()] = { | |
| "password_hash": hash_password(password), | |
| "created_at": datetime.utcnow().isoformat() | |
| } | |
| # Save to GitHub | |
| if write_users(data): | |
| return ( | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| email, | |
| gr.update(value="", visible=False), | |
| ) | |
| else: | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="β οΈ Failed to save user data. Please try again.", visible=True), | |
| ) | |
| except Exception as e: | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value=f"β οΈ Error: {str(e)}", visible=True), | |
| ) | |
| def sign_in(email: str, password: str): | |
| """Handle user login.""" | |
| if not email or not password: | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="β οΈ Email and password are required", visible=True), | |
| ) | |
| try: | |
| data = read_users() | |
| user = data.get("users", {}).get(email.lower()) | |
| if not user: | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="β οΈ No account found with this email", visible=True), | |
| ) | |
| if not verify_password(password, user["password_hash"]): | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="β οΈ Incorrect password", visible=True), | |
| ) | |
| # Success | |
| return ( | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| email, | |
| gr.update(value="", visible=False), | |
| ) | |
| except Exception as e: | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value=f"β οΈ Error: {str(e)}", visible=True), | |
| ) | |
| def show_sign_up(): | |
| """Switch to sign up view.""" | |
| return ( | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="", visible=False), | |
| ) | |
| def show_sign_in(): | |
| """Switch to sign in view.""" | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="", visible=False), | |
| ) | |
| def logout(): | |
| """Handle logout.""" | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "", | |
| gr.update(value="", visible=False), | |
| ) | |
| # Build the UI | |
| with gr.Blocks(css=MATERIAL_CSS, title="Secure Auth") as demo: | |
| # State for storing current user email | |
| current_email = gr.State("") | |
| with gr.Column(elem_classes="main-container"): | |
| # Sign In Container | |
| with gr.Column(visible=True, elem_classes="auth-card") as sign_in_container: | |
| gr.HTML("<h1>Welcome Back</h1>") | |
| gr.HTML("<p>Sign in to continue</p>") | |
| sign_in_email = gr.Textbox( | |
| label="Email", | |
| placeholder="Enter your email", | |
| type="email" | |
| ) | |
| sign_in_password = gr.Textbox( | |
| label="Password", | |
| placeholder="Enter your password", | |
| type="password" | |
| ) | |
| message = gr.HTML("", visible=False, elem_classes="error-msg") | |
| sign_in_btn = gr.Button("Sign In", elem_classes="primary-btn") | |
| gr.HTML("<br>") | |
| gr.HTML("<p style='text-align: center; color: #666;'>Don't have an account?</p>") | |
| go_to_sign_up_btn = gr.Button("Create Account", elem_classes="secondary-btn") | |
| # Sign Up Container | |
| with gr.Column(visible=False, elem_classes="auth-card") as sign_up_container: | |
| gr.HTML("<h1>Create Account</h1>") | |
| gr.HTML("<p>Sign up to get started</p>") | |
| sign_up_email = gr.Textbox( | |
| label="Email", | |
| placeholder="Enter your email", | |
| type="email" | |
| ) | |
| sign_up_password = gr.Textbox( | |
| label="Password", | |
| placeholder="Create a password (min 6 characters)", | |
| type="password" | |
| ) | |
| sign_up_confirm = gr.Textbox( | |
| label="Confirm Password", | |
| placeholder="Confirm your password", | |
| type="password" | |
| ) | |
| sign_up_btn = gr.Button("Sign Up", elem_classes="primary-btn") | |
| gr.HTML("<br>") | |
| gr.HTML("<p style='text-align: center; color: #666;'>Already have an account?</p>") | |
| go_to_sign_in_btn = gr.Button("Sign In", elem_classes="secondary-btn") | |
| # Welcome Container | |
| with gr.Column(visible=False, elem_classes="auth-card") as welcome_container: | |
| gr.HTML("<div class='welcome-container'>") | |
| gr.HTML("<div class='avatar-circle'>π</div>") | |
| gr.HTML("<h1>Welcome!</h1>") | |
| gr.HTML("<p>You're successfully signed in as:</p>") | |
| welcome_email_display = gr.HTML("<div class='welcome-email'></div>") | |
| gr.HTML("</div>") | |
| gr.HTML("<br><br>") | |
| logout_btn = gr.Button("Sign Out", elem_classes="secondary-btn") | |
| # Event handlers | |
| outputs = [sign_in_container, sign_up_container, welcome_container, current_email, message] | |
| sign_in_btn.click( | |
| sign_in, | |
| inputs=[sign_in_email, sign_in_password], | |
| outputs=outputs | |
| ) | |
| sign_up_btn.click( | |
| sign_up, | |
| inputs=[sign_up_email, sign_up_password, sign_up_confirm], | |
| outputs=outputs | |
| ) | |
| go_to_sign_up_btn.click( | |
| show_sign_up, | |
| outputs=outputs | |
| ) | |
| go_to_sign_in_btn.click( | |
| show_sign_in, | |
| outputs=outputs | |
| ) | |
| logout_btn.click( | |
| logout, | |
| outputs=outputs | |
| ) | |
| # Update welcome email display when current_email changes | |
| current_email.change( | |
| lambda email: f"<div class='welcome-email'>{email}</div>", | |
| inputs=[current_email], | |
| outputs=[welcome_email_display] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |