Spaces:
Paused
Paused
| """ | |
| User authentication service for OpenManus | |
| Handles user registration, login, and session management with D1 database | |
| """ | |
| import json | |
| import sqlite3 | |
| from datetime import datetime | |
| from typing import Optional, Tuple | |
| from app.auth import ( | |
| User, | |
| UserAuth, | |
| UserSession, | |
| UserSignupRequest, | |
| UserLoginRequest, | |
| AuthResponse, | |
| UserProfile, | |
| ) | |
| from app.logger import logger | |
| class AuthService: | |
| """Authentication service for user management""" | |
| def __init__(self, db_connection=None): | |
| """Initialize auth service with database connection""" | |
| self.db = db_connection | |
| self.logger = logger | |
| async def register_user(self, signup_data: UserSignupRequest) -> AuthResponse: | |
| """Register a new user""" | |
| try: | |
| # Format mobile number consistently | |
| formatted_mobile = UserAuth.format_mobile_number(signup_data.mobile_number) | |
| # Check if user already exists | |
| existing_user = await self.get_user_by_mobile(formatted_mobile) | |
| if existing_user: | |
| return AuthResponse( | |
| success=False, message="User with this mobile number already exists" | |
| ) | |
| # Create new user | |
| user_id = UserAuth.generate_user_id() | |
| password_hash = UserAuth.hash_password(signup_data.password) | |
| user = User( | |
| id=user_id, | |
| mobile_number=formatted_mobile, | |
| full_name=signup_data.full_name, | |
| password_hash=password_hash, | |
| created_at=datetime.utcnow(), | |
| updated_at=datetime.utcnow(), | |
| ) | |
| # Save user to database | |
| success = await self.save_user(user) | |
| if not success: | |
| return AuthResponse( | |
| success=False, message="Failed to create user account" | |
| ) | |
| # Create session | |
| session = UserAuth.create_session(user) | |
| session_saved = await self.save_session(session) | |
| if not session_saved: | |
| return AuthResponse( | |
| success=False, message="User created but failed to create session" | |
| ) | |
| self.logger.info(f"New user registered: {formatted_mobile}") | |
| return AuthResponse( | |
| success=True, | |
| message="Account created successfully", | |
| session_id=session.session_id, | |
| user_id=user.id, | |
| full_name=user.full_name, | |
| ) | |
| except Exception as e: | |
| self.logger.error(f"User registration error: {str(e)}") | |
| return AuthResponse( | |
| success=False, message="An error occurred during registration" | |
| ) | |
| async def login_user(self, login_data: UserLoginRequest) -> AuthResponse: | |
| """Authenticate user login""" | |
| try: | |
| # Format mobile number consistently | |
| formatted_mobile = UserAuth.format_mobile_number(login_data.mobile_number) | |
| # Get user from database | |
| user = await self.get_user_by_mobile(formatted_mobile) | |
| if not user: | |
| return AuthResponse( | |
| success=False, message="Invalid mobile number or password" | |
| ) | |
| # Verify password | |
| if not UserAuth.verify_password(login_data.password, user.password_hash): | |
| return AuthResponse( | |
| success=False, message="Invalid mobile number or password" | |
| ) | |
| # Check if user is active | |
| if not user.is_active: | |
| return AuthResponse( | |
| success=False, | |
| message="Account is deactivated. Please contact support.", | |
| ) | |
| # Create new session | |
| session = UserAuth.create_session(user) | |
| session_saved = await self.save_session(session) | |
| if not session_saved: | |
| return AuthResponse( | |
| success=False, | |
| message="Login successful but failed to create session", | |
| ) | |
| self.logger.info(f"User logged in: {formatted_mobile}") | |
| return AuthResponse( | |
| success=True, | |
| message="Login successful", | |
| session_id=session.session_id, | |
| user_id=user.id, | |
| full_name=user.full_name, | |
| ) | |
| except Exception as e: | |
| self.logger.error(f"User login error: {str(e)}") | |
| return AuthResponse(success=False, message="An error occurred during login") | |
| async def validate_session(self, session_id: str) -> Optional[UserSession]: | |
| """Validate user session""" | |
| try: | |
| if not self.db: | |
| return None | |
| cursor = self.db.cursor() | |
| cursor.execute( | |
| """ | |
| SELECT s.id, s.user_id, u.mobile_number, u.full_name, | |
| s.created_at, s.expires_at | |
| FROM sessions s | |
| JOIN users u ON s.user_id = u.id | |
| WHERE s.id = ? AND u.is_active = 1 | |
| """, | |
| (session_id,), | |
| ) | |
| row = cursor.fetchone() | |
| if not row: | |
| return None | |
| session = UserSession( | |
| session_id=row[0], | |
| user_id=row[1], | |
| mobile_number=row[2], | |
| full_name=row[3], | |
| created_at=datetime.fromisoformat(row[4]), | |
| expires_at=datetime.fromisoformat(row[5]), | |
| ) | |
| # Check if session is still valid | |
| if not session.is_valid: | |
| # Clean up expired session | |
| await self.delete_session(session_id) | |
| return None | |
| return session | |
| except Exception as e: | |
| self.logger.error(f"Session validation error: {str(e)}") | |
| return None | |
| async def logout_user(self, session_id: str) -> bool: | |
| """Logout user by deleting session""" | |
| return await self.delete_session(session_id) | |
| async def get_user_profile(self, user_id: str) -> Optional[UserProfile]: | |
| """Get user profile by user ID""" | |
| try: | |
| user = await self.get_user_by_id(user_id) | |
| if not user: | |
| return None | |
| return UserProfile( | |
| user_id=user.id, | |
| full_name=user.full_name, | |
| mobile_number=UserProfile.mask_mobile_number(user.mobile_number), | |
| avatar_url=user.avatar_url, | |
| created_at=user.created_at.isoformat() if user.created_at else None, | |
| ) | |
| except Exception as e: | |
| self.logger.error(f"Get user profile error: {str(e)}") | |
| return None | |
| # Database operations | |
| async def save_user(self, user: User) -> bool: | |
| """Save user to database""" | |
| try: | |
| if not self.db: | |
| return False | |
| cursor = self.db.cursor() | |
| cursor.execute( | |
| """ | |
| INSERT INTO users (id, mobile_number, full_name, password_hash, | |
| avatar_url, preferences, is_active, created_at, updated_at) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) | |
| """, | |
| ( | |
| user.id, | |
| user.mobile_number, | |
| user.full_name, | |
| user.password_hash, | |
| user.avatar_url, | |
| user.preferences, | |
| user.is_active, | |
| user.created_at.isoformat() if user.created_at else None, | |
| user.updated_at.isoformat() if user.updated_at else None, | |
| ), | |
| ) | |
| self.db.commit() | |
| return True | |
| except Exception as e: | |
| self.logger.error(f"Save user error: {str(e)}") | |
| return False | |
| async def get_user_by_mobile(self, mobile_number: str) -> Optional[User]: | |
| """Get user by mobile number""" | |
| try: | |
| if not self.db: | |
| return None | |
| cursor = self.db.cursor() | |
| cursor.execute( | |
| """ | |
| SELECT id, mobile_number, full_name, password_hash, avatar_url, | |
| preferences, is_active, created_at, updated_at | |
| FROM users | |
| WHERE mobile_number = ? | |
| """, | |
| (mobile_number,), | |
| ) | |
| row = cursor.fetchone() | |
| if not row: | |
| return None | |
| return User( | |
| id=row[0], | |
| mobile_number=row[1], | |
| full_name=row[2], | |
| password_hash=row[3], | |
| avatar_url=row[4], | |
| preferences=row[5], | |
| is_active=bool(row[6]), | |
| created_at=datetime.fromisoformat(row[7]) if row[7] else None, | |
| updated_at=datetime.fromisoformat(row[8]) if row[8] else None, | |
| ) | |
| except Exception as e: | |
| self.logger.error(f"Get user by mobile error: {str(e)}") | |
| return None | |
| async def get_user_by_id(self, user_id: str) -> Optional[User]: | |
| """Get user by ID""" | |
| try: | |
| if not self.db: | |
| return None | |
| cursor = self.db.cursor() | |
| cursor.execute( | |
| """ | |
| SELECT id, mobile_number, full_name, password_hash, avatar_url, | |
| preferences, is_active, created_at, updated_at | |
| FROM users | |
| WHERE id = ? AND is_active = 1 | |
| """, | |
| (user_id,), | |
| ) | |
| row = cursor.fetchone() | |
| if not row: | |
| return None | |
| return User( | |
| id=row[0], | |
| mobile_number=row[1], | |
| full_name=row[2], | |
| password_hash=row[3], | |
| avatar_url=row[4], | |
| preferences=row[5], | |
| is_active=bool(row[6]), | |
| created_at=datetime.fromisoformat(row[7]) if row[7] else None, | |
| updated_at=datetime.fromisoformat(row[8]) if row[8] else None, | |
| ) | |
| except Exception as e: | |
| self.logger.error(f"Get user by ID error: {str(e)}") | |
| return None | |
| async def save_session(self, session: UserSession) -> bool: | |
| """Save session to database""" | |
| try: | |
| if not self.db: | |
| return False | |
| cursor = self.db.cursor() | |
| cursor.execute( | |
| """ | |
| INSERT INTO sessions (id, user_id, title, metadata, created_at, | |
| updated_at, expires_at) | |
| VALUES (?, ?, ?, ?, ?, ?, ?) | |
| """, | |
| ( | |
| session.session_id, | |
| session.user_id, | |
| "User Session", | |
| json.dumps({"login_type": "mobile_password"}), | |
| session.created_at.isoformat(), | |
| session.created_at.isoformat(), | |
| session.expires_at.isoformat(), | |
| ), | |
| ) | |
| self.db.commit() | |
| return True | |
| except Exception as e: | |
| self.logger.error(f"Save session error: {str(e)}") | |
| return False | |
| async def delete_session(self, session_id: str) -> bool: | |
| """Delete session from database""" | |
| try: | |
| if not self.db: | |
| return False | |
| cursor = self.db.cursor() | |
| cursor.execute("DELETE FROM sessions WHERE id = ?", (session_id,)) | |
| self.db.commit() | |
| return True | |
| except Exception as e: | |
| self.logger.error(f"Delete session error: {str(e)}") | |
| return False | |