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