github-auth / app.py
JakeFake222's picture
Add app.py
5b3dfd8 verified
"""
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()