""" 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("

Welcome Back

") gr.HTML("

Sign in to continue

") 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("
") gr.HTML("

Don't have an account?

") 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("

Create Account

") gr.HTML("

Sign up to get started

") 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("
") gr.HTML("

Already have an account?

") 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("
") gr.HTML("
👋
") gr.HTML("

Welcome!

") gr.HTML("

You're successfully signed in as:

") welcome_email_display = gr.HTML("
") gr.HTML("
") gr.HTML("

") 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"
{email}
", inputs=[current_email], outputs=[welcome_email_display] ) if __name__ == "__main__": demo.launch()