|
|
"""
|
|
|
Authentication Web Interface for OpenManus
|
|
|
Mobile number + password based authentication forms
|
|
|
"""
|
|
|
|
|
|
import asyncio
|
|
|
import sqlite3
|
|
|
from typing import Optional, Tuple
|
|
|
|
|
|
import gradio as gr
|
|
|
|
|
|
from app.auth import UserSignupRequest, UserLoginRequest
|
|
|
from app.auth_service import AuthService
|
|
|
from app.logger import logger
|
|
|
|
|
|
|
|
|
class AuthInterface:
|
|
|
"""Authentication interface with Gradio"""
|
|
|
|
|
|
def __init__(self, db_path: str = "openmanus.db"):
|
|
|
self.db_path = db_path
|
|
|
self.auth_service = None
|
|
|
self.current_session = None
|
|
|
self.init_database()
|
|
|
|
|
|
def init_database(self):
|
|
|
"""Initialize database with schema"""
|
|
|
try:
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
"""
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
|
id TEXT PRIMARY KEY,
|
|
|
mobile_number TEXT UNIQUE NOT NULL,
|
|
|
full_name TEXT NOT NULL,
|
|
|
password_hash TEXT NOT NULL,
|
|
|
avatar_url TEXT,
|
|
|
preferences TEXT,
|
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
)
|
|
|
"""
|
|
|
)
|
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
"""
|
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
|
id TEXT PRIMARY KEY,
|
|
|
user_id TEXT NOT NULL,
|
|
|
title TEXT,
|
|
|
metadata TEXT,
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
expires_at DATETIME,
|
|
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
|
)
|
|
|
"""
|
|
|
)
|
|
|
|
|
|
conn.commit()
|
|
|
conn.close()
|
|
|
logger.info("Database initialized successfully")
|
|
|
|
|
|
except Exception as e:
|
|
|
logger.error(f"Database initialization error: {str(e)}")
|
|
|
|
|
|
def get_db_connection(self):
|
|
|
"""Get database connection"""
|
|
|
return sqlite3.connect(self.db_path)
|
|
|
|
|
|
async def handle_signup(
|
|
|
self, full_name: str, mobile_number: str, password: str, confirm_password: str
|
|
|
) -> Tuple[str, bool, dict]:
|
|
|
"""Handle user signup"""
|
|
|
try:
|
|
|
|
|
|
if not all([full_name, mobile_number, password, confirm_password]):
|
|
|
return "All fields are required", False, gr.update(visible=True)
|
|
|
|
|
|
|
|
|
signup_data = UserSignupRequest(
|
|
|
full_name=full_name,
|
|
|
mobile_number=mobile_number,
|
|
|
password=password,
|
|
|
confirm_password=confirm_password,
|
|
|
)
|
|
|
|
|
|
|
|
|
db_conn = self.get_db_connection()
|
|
|
auth_service = AuthService(db_conn)
|
|
|
|
|
|
result = await auth_service.register_user(signup_data)
|
|
|
db_conn.close()
|
|
|
|
|
|
if result.success:
|
|
|
self.current_session = {
|
|
|
"session_id": result.session_id,
|
|
|
"user_id": result.user_id,
|
|
|
"full_name": result.full_name,
|
|
|
}
|
|
|
return (
|
|
|
f"Welcome {result.full_name}! Account created successfully.",
|
|
|
True,
|
|
|
gr.update(visible=False),
|
|
|
)
|
|
|
else:
|
|
|
return result.message, False, gr.update(visible=True)
|
|
|
|
|
|
except ValueError as e:
|
|
|
return str(e), False, gr.update(visible=True)
|
|
|
except Exception as e:
|
|
|
logger.error(f"Signup error: {str(e)}")
|
|
|
return "An error occurred during signup", False, gr.update(visible=True)
|
|
|
|
|
|
async def handle_login(
|
|
|
self, mobile_number: str, password: str
|
|
|
) -> Tuple[str, bool, dict]:
|
|
|
"""Handle user login"""
|
|
|
try:
|
|
|
|
|
|
if not all([mobile_number, password]):
|
|
|
return (
|
|
|
"Mobile number and password are required",
|
|
|
False,
|
|
|
gr.update(visible=True),
|
|
|
)
|
|
|
|
|
|
|
|
|
login_data = UserLoginRequest(
|
|
|
mobile_number=mobile_number, password=password
|
|
|
)
|
|
|
|
|
|
|
|
|
db_conn = self.get_db_connection()
|
|
|
auth_service = AuthService(db_conn)
|
|
|
|
|
|
result = await auth_service.login_user(login_data)
|
|
|
db_conn.close()
|
|
|
|
|
|
if result.success:
|
|
|
self.current_session = {
|
|
|
"session_id": result.session_id,
|
|
|
"user_id": result.user_id,
|
|
|
"full_name": result.full_name,
|
|
|
}
|
|
|
return (
|
|
|
f"Welcome back, {result.full_name}!",
|
|
|
True,
|
|
|
gr.update(visible=False),
|
|
|
)
|
|
|
else:
|
|
|
return result.message, False, gr.update(visible=True)
|
|
|
|
|
|
except ValueError as e:
|
|
|
return str(e), False, gr.update(visible=True)
|
|
|
except Exception as e:
|
|
|
logger.error(f"Login error: {str(e)}")
|
|
|
return "An error occurred during login", False, gr.update(visible=True)
|
|
|
|
|
|
def handle_logout(self) -> Tuple[str, bool, dict]:
|
|
|
"""Handle user logout"""
|
|
|
if self.current_session:
|
|
|
|
|
|
self.current_session = None
|
|
|
|
|
|
return "Logged out successfully", False, gr.update(visible=True)
|
|
|
|
|
|
def create_interface(self) -> gr.Interface:
|
|
|
"""Create the authentication interface"""
|
|
|
|
|
|
with gr.Blocks(
|
|
|
title="OpenManus Authentication", theme=gr.themes.Soft()
|
|
|
) as auth_interface:
|
|
|
gr.Markdown(
|
|
|
"""
|
|
|
# π OpenManus Authentication
|
|
|
### Secure Mobile Number + Password Login System
|
|
|
"""
|
|
|
)
|
|
|
|
|
|
|
|
|
session_status = gr.Textbox(
|
|
|
value="Not logged in", label="Status", interactive=False
|
|
|
)
|
|
|
|
|
|
|
|
|
with gr.Column(visible=True) as auth_forms:
|
|
|
|
|
|
with gr.Tabs():
|
|
|
|
|
|
|
|
|
with gr.TabItem("π Login"):
|
|
|
gr.Markdown("### Login with your mobile number and password")
|
|
|
|
|
|
login_mobile = gr.Textbox(
|
|
|
label="π± Mobile Number",
|
|
|
placeholder="Enter your mobile number (e.g., +1234567890)",
|
|
|
lines=1,
|
|
|
)
|
|
|
|
|
|
login_password = gr.Textbox(
|
|
|
label="π Password",
|
|
|
type="password",
|
|
|
placeholder="Enter your password",
|
|
|
lines=1,
|
|
|
)
|
|
|
|
|
|
login_btn = gr.Button("π Login", variant="primary", size="lg")
|
|
|
login_result = gr.Textbox(label="Result", interactive=False)
|
|
|
|
|
|
|
|
|
with gr.TabItem("π Sign Up"):
|
|
|
gr.Markdown("### Create your new account")
|
|
|
|
|
|
signup_fullname = gr.Textbox(
|
|
|
label="π€ Full Name",
|
|
|
placeholder="Enter your full name",
|
|
|
lines=1,
|
|
|
)
|
|
|
|
|
|
signup_mobile = gr.Textbox(
|
|
|
label="π± Mobile Number",
|
|
|
placeholder="Enter your mobile number (e.g., +1234567890)",
|
|
|
lines=1,
|
|
|
)
|
|
|
|
|
|
signup_password = gr.Textbox(
|
|
|
label="π Password",
|
|
|
type="password",
|
|
|
placeholder="Create a strong password (min 8 chars, include uppercase, lowercase, digit)",
|
|
|
lines=1,
|
|
|
)
|
|
|
|
|
|
signup_confirm_password = gr.Textbox(
|
|
|
label="π Confirm Password",
|
|
|
type="password",
|
|
|
placeholder="Confirm your password",
|
|
|
lines=1,
|
|
|
)
|
|
|
|
|
|
signup_btn = gr.Button(
|
|
|
"π Create Account", variant="primary", size="lg"
|
|
|
)
|
|
|
signup_result = gr.Textbox(label="Result", interactive=False)
|
|
|
|
|
|
|
|
|
with gr.Column(visible=False) as logged_in_section:
|
|
|
gr.Markdown("### β
You are logged in!")
|
|
|
|
|
|
user_info = gr.Markdown("Welcome!")
|
|
|
|
|
|
logout_btn = gr.Button("πͺ Logout", variant="secondary")
|
|
|
logout_result = gr.Textbox(label="Result", interactive=False)
|
|
|
|
|
|
|
|
|
with gr.Accordion("π Password Requirements", open=False):
|
|
|
gr.Markdown(
|
|
|
"""
|
|
|
**Password must contain:**
|
|
|
- At least 8 characters
|
|
|
- At least 1 uppercase letter (A-Z)
|
|
|
- At least 1 lowercase letter (a-z)
|
|
|
- At least 1 digit (0-9)
|
|
|
- Maximum 128 characters
|
|
|
|
|
|
**Mobile Number Format:**
|
|
|
- 10-15 digits
|
|
|
- Can include country code
|
|
|
- Examples: +1234567890, 1234567890, +91987654321
|
|
|
"""
|
|
|
)
|
|
|
|
|
|
|
|
|
def sync_signup(*args):
|
|
|
"""Synchronous wrapper for signup"""
|
|
|
return asyncio.run(self.handle_signup(*args))
|
|
|
|
|
|
def sync_login(*args):
|
|
|
"""Synchronous wrapper for login"""
|
|
|
return asyncio.run(self.handle_login(*args))
|
|
|
|
|
|
def update_ui_after_auth(result_text, success, auth_forms_update):
|
|
|
"""Update UI after authentication"""
|
|
|
if success:
|
|
|
return (
|
|
|
result_text,
|
|
|
auth_forms_update,
|
|
|
gr.update(visible=True),
|
|
|
f"### π {self.current_session['full_name'] if self.current_session else 'User'}",
|
|
|
)
|
|
|
else:
|
|
|
return (
|
|
|
"Not logged in",
|
|
|
auth_forms_update,
|
|
|
gr.update(visible=False),
|
|
|
"Welcome!",
|
|
|
)
|
|
|
|
|
|
def update_ui_after_logout(result_text, success, auth_forms_update):
|
|
|
"""Update UI after logout"""
|
|
|
return (
|
|
|
"Not logged in",
|
|
|
auth_forms_update,
|
|
|
gr.update(visible=False),
|
|
|
"Welcome!",
|
|
|
)
|
|
|
|
|
|
|
|
|
login_btn.click(
|
|
|
fn=sync_login,
|
|
|
inputs=[login_mobile, login_password],
|
|
|
outputs=[login_result, gr.State(), gr.State()],
|
|
|
).then(
|
|
|
fn=update_ui_after_auth,
|
|
|
inputs=[login_result, gr.State(), gr.State()],
|
|
|
outputs=[session_status, auth_forms, logged_in_section, user_info],
|
|
|
)
|
|
|
|
|
|
|
|
|
signup_btn.click(
|
|
|
fn=sync_signup,
|
|
|
inputs=[
|
|
|
signup_fullname,
|
|
|
signup_mobile,
|
|
|
signup_password,
|
|
|
signup_confirm_password,
|
|
|
],
|
|
|
outputs=[signup_result, gr.State(), gr.State()],
|
|
|
).then(
|
|
|
fn=update_ui_after_auth,
|
|
|
inputs=[signup_result, gr.State(), gr.State()],
|
|
|
outputs=[session_status, auth_forms, logged_in_section, user_info],
|
|
|
)
|
|
|
|
|
|
|
|
|
logout_btn.click(
|
|
|
fn=self.handle_logout, outputs=[logout_result, gr.State(), gr.State()]
|
|
|
).then(
|
|
|
fn=update_ui_after_logout,
|
|
|
inputs=[logout_result, gr.State(), gr.State()],
|
|
|
outputs=[session_status, auth_forms, logged_in_section, user_info],
|
|
|
)
|
|
|
|
|
|
return auth_interface
|
|
|
|
|
|
|
|
|
|
|
|
def create_auth_app(db_path: str = "openmanus.db") -> gr.Interface:
|
|
|
"""Create standalone authentication app"""
|
|
|
auth_interface = AuthInterface(db_path)
|
|
|
return auth_interface.create_interface()
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
auth_app = create_auth_app()
|
|
|
auth_app.launch(server_name="0.0.0.0", server_port=7860, share=False, debug=True)
|
|
|
|