# ============================================================================== # Formeleon - Multi-tenant Form Generator # Author: Björn Runden # Module: services/auth_service.py # Description: Business logic for handling user authentication and session management. # ============================================================================== import uuid import logging from typing import Dict, Any, Tuple, Optional from flask import current_app, session from models import db, User, UserRole, Tenant, AuditLog logger = logging.getLogger(__name__) class AuthService: """Service layer for authentication and user management.""" @staticmethod def is_dev_mode() -> bool: """Check if application is in development mode.""" return current_app.config.get('DEV_MODE', False) @staticmethod def ensure_dev_user_exists() -> User: """ Ensure the development user exists in the database. :return: User object from database """ config = current_app.config user_id = config.get('DEV_USER_ID', 'user_dev_admin_001') user = User.query.get(user_id) if not user: role = UserRole.ADMIN if config.get('DEV_USER_ROLE', 'admin') == 'admin' else UserRole.USER user = User( id=user_id, email=config.get('DEV_USER_EMAIL', 'dev@formeleon.local'), name=config.get('DEV_USER_NAME', 'Dev Admin'), role=role, sub=f'dev-{uuid.uuid4()}' ) db.session.add(user) db.session.commit() logger.info(f'Created dev user: {user.id}') return user @staticmethod def ensure_dev_tenant_exists() -> Tenant: """ Ensure the development tenant exists in the database. :return: Tenant object from database """ config = current_app.config tenant_id = config.get('DEV_TENANT_ID', 'tenant_dev_123') tenant = Tenant.query.get(tenant_id) if not tenant: tenant = Tenant( id=tenant_id, name='Development Tenant', public_key=f'dev-key-{uuid.uuid4().hex[:16]}', allowed_origins='*' ) db.session.add(tenant) db.session.commit() logger.info(f'Created dev tenant: {tenant.id}') return tenant @staticmethod def create_dev_user_session() -> dict: """ Create a development mode user session with fake Zitadel user. :return: Dictionary with user session data """ config = current_app.config # Use configured dev user settings or defaults dev_user = { 'id': config.get('DEV_USER_ID', 'user_dev_admin_001'), 'email': config.get('DEV_USER_EMAIL', 'dev@formeleon.local'), 'name': config.get('DEV_USER_NAME', 'Dev Admin'), 'role': config.get('DEV_USER_ROLE', 'admin'), 'sub': f'dev-{uuid.uuid4()}', 'tenant_id': config.get('DEV_TENANT_ID', 'tenant_dev_123'), 'dev_mode': True, 'authenticated': True } return dev_user @staticmethod def process_oidc_callback(user_info: dict) -> Tuple[Optional[User], Optional[str]]: """ Process user information from OIDC callback and create/update user. :param user_info: User information dictionary from OIDC provider :return: Tuple of (User object or None, error message or None) """ sub = user_info.get('sub') email = user_info.get('email') name = user_info.get('name', user_info.get('preferred_username', 'Unknown')) if not email: return None, 'No email in user info' # Create or update user user = User.query.filter_by(sub=sub).first() if not user: user_id = f'user_{email.split("@")[0]}_{str(uuid.uuid4())[:8]}' user = User( id=user_id, email=email, name=name, role=UserRole.USER, # Default role for new OIDC users sub=sub ) db.session.add(user) db.session.commit() logger.info(f'Created new user from OIDC: {user.id}') else: # Update existing user user.name = name user.email = email db.session.commit() return user, None