"""Superadmin authentication endpoints.""" import logging from flask import request, g, current_app from marshmallow import ValidationError from gatehouse_app.api.v1.superadmin import superadmin_bp from gatehouse_app.extensions import limiter from gatehouse_app.utils.response import api_response from gatehouse_app.services.superadmin_auth_service import SuperadminAuthService from gatehouse_app.decorators.superadmin import superadmin_required, superadmin_audit_log from gatehouse_app.exceptions.auth_exceptions import InvalidCredentialsError logger = logging.getLogger(__name__) class LoginSchema: """Schema for superadmin login.""" @staticmethod def load(data): """Validate login data.""" errors = {} if not data.get('email'): errors['email'] = ['Email is required'] elif '@' not in data['email']: errors['email'] = ['Invalid email format'] if not data.get('password'): errors['password'] = ['Password is required'] if errors: raise ValidationError(errors) return { 'email': data['email'].lower().strip(), 'password': data['password'], } @superadmin_bp.route("/auth/login", methods=["POST"]) @limiter.limit(lambda: current_app.config.get("RATELIMIT_AUTH_LOGIN", "100 per minute")) def login(): """Superadmin login endpoint. Authenticates with email/password and returns a session token. """ try: schema = LoginSchema() data = schema.load(request.json) # Authenticate superadmin = SuperadminAuthService.authenticate( email=data['email'], credentials=data['password'] ) # Create session (default 8 hours) session = SuperadminAuthService.create_session( superadmin_id=superadmin.id, duration_seconds=28800 # 8 hours ) expires_str = session.expires_at.isoformat() if not expires_str.endswith('Z'): expires_str += 'Z' logger.info(f"[SuperadminAuth] Login successful for: {superadmin.email}") return api_response( data={ "superadmin": superadmin.to_dict(), "token": session.token, "expires_at": expires_str, }, message="Login successful", status=200 ) except ValidationError as e: return api_response( success=False, message="Validation failed", status=400, error_type="VALIDATION_ERROR", error_details=e.messages ) except InvalidCredentialsError: return api_response( success=False, message="Invalid email or password", status=401, error_type="INVALID_CREDENTIALS" ) except Exception as e: logger.error(f"[SuperadminAuth] Login error: {e}") return api_response( success=False, message="An error occurred during login", status=500, error_type="INTERNAL_ERROR" ) @superadmin_bp.route("/auth/logout", methods=["POST"]) @superadmin_required def logout(): """Superadmin logout endpoint. Invalidates the current session. """ try: session = g.superadmin_session if session: SuperadminAuthService.revoke_session(session.id, reason="Superadmin logout") return api_response( message="Logout successful" ) except Exception as e: logger.error(f"[SuperadminAuth] Logout error: {e}") return api_response( success=False, message="An error occurred during logout", status=500, error_type="INTERNAL_ERROR" ) @superadmin_bp.route("/auth/me", methods=["GET"]) @superadmin_required def get_current_superadmin(): """Get current superadmin profile. Returns the profile of the currently authenticated superadmin. """ try: superadmin = g.current_superadmin return api_response( data={ "superadmin": superadmin.to_dict(), }, message="Superadmin retrieved successfully" ) except Exception as e: logger.error(f"[SuperadminAuth] Get me error: {e}") return api_response( success=False, message="An error occurred", status=500, error_type="INTERNAL_ERROR" ) @superadmin_bp.route("/auth/impersonate/", methods=["POST"]) @superadmin_required @superadmin_audit_log(action="impersonate", resource_type="user") def impersonate_user(user_id): """Create emergency access session by impersonating a user. Creates a temporary session for the target user that allows the superadmin to access the platform as that user. This action is fully audited. """ try: superadmin = g.current_superadmin data = request.json or {} reason = data.get('reason', 'Not specified') duration_minutes = data.get('duration_minutes', 15) # Limit duration to max 60 minutes duration_minutes = min(duration_minutes, 60) # Create emergency access result = SuperadminAuthService.create_emergency_access( superadmin_id=superadmin.id, target_user_id=user_id, reason=reason, duration_minutes=duration_minutes ) expires_str = result['expires_at'].isoformat() if not expires_str.endswith('Z'): expires_str += 'Z' logger.warning( f"[SuperadminAuth] IMPERSONATION: superadmin={superadmin.email} " f"impersonated user_id={user_id} reason={reason}" ) return api_response( data={ "session_token": result['session'].token, "expires_at": expires_str, "target_user_id": user_id, "reason": reason, "duration_minutes": duration_minutes, }, message="Emergency access session created", status=201 ) except ValueError as e: return api_response( success=False, message=str(e), status=404, error_type="NOT_FOUND" ) except Exception as e: logger.error(f"[SuperadminAuth] Impersonate error: {e}") return api_response( success=False, message="An error occurred", status=500, error_type="INTERNAL_ERROR" ) @superadmin_bp.route("/auth/emergency/", methods=["POST"]) @superadmin_required @superadmin_audit_log(action="emergency_access", resource_type="user") def grant_emergency_access(user_id): """Grant temporary elevated access to a user. Similar to impersonate but grants elevated permissions rather than creating a session as the user. This action is fully audited. """ try: superadmin = g.current_superadmin data = request.json or {} reason = data.get('reason', 'Not specified') duration_minutes = data.get('duration_minutes', 15) # Limit duration to max 60 minutes duration_minutes = min(duration_minutes, 60) # Create emergency access result = SuperadminAuthService.create_emergency_access( superadmin_id=superadmin.id, target_user_id=user_id, reason=reason, duration_minutes=duration_minutes ) expires_str = result['expires_at'].isoformat() if not expires_str.endswith('Z'): expires_str += 'Z' logger.warning( f"[SuperadminAuth] EMERGENCY ACCESS: superadmin={superadmin.email} " f"granted access to user_id={user_id} reason={reason}" ) return api_response( data={ "session_token": result['session'].token, "expires_at": expires_str, "target_user_id": user_id, "reason": reason, "duration_minutes": duration_minutes, }, message="Emergency access granted", status=201 ) except ValueError as e: return api_response( success=False, message=str(e), status=404, error_type="NOT_FOUND" ) except Exception as e: logger.error(f"[SuperadminAuth] Emergency access error: {e}") return api_response( success=False, message="An error occurred", status=500, error_type="INTERNAL_ERROR" )