# -*- coding: utf-8 -*- """ 인증 서비스 모듈 사용자 인증 및 권한 관리를 담당합니다. """ import hashlib from typing import Optional from database.crud import CRUDManager from database.models import User from core.config import ConfigManager from core.signals import GlobalSignals from core.constants import DEPARTMENT_PERMISSIONS from core.logger import get_logger from core.exceptions import ( InvalidCredentialsError, PermissionDeniedError, UserNotActiveError, ) logger = get_logger(__name__) class AuthService: """ 인증 서비스 클래스 사용자 로그인, 로그아웃, 권한 확인 등을 담당합니다. Attributes: current_user: 현재 로그인한 사용자 is_authenticated: 인증 여부 Examples: >>> auth = AuthService() >>> auth.login("admin", "password") >>> auth.has_permission("create") True """ _instance: Optional['AuthService'] = None def __new__(cls): """싱글톤 패턴""" if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._initialized = False return cls._instance def __init__(self): if self._initialized: return self.crud = CRUDManager() self.config = ConfigManager() self.signals = GlobalSignals() self.current_user: Optional[User] = None self._initialized = True logger.info("인증 서비스 초기화 완료") @property def is_authenticated(self) -> bool: """인증 여부""" return self.current_user is not None def login(self, username: str, password: str) -> User: """ 로그인 Args: username: 사용자명 password: 비밀번호 Returns: 로그인한 사용자 Raises: InvalidCredentialsError: 잘못된 자격 증명 UserNotActiveError: 비활성 사용자 """ user = self.crud.get_user_by_username(username) if not user: logger.warning(f"로그인 실패: 사용자 없음 - {username}") raise InvalidCredentialsError() # 비밀번호 확인 if not self._verify_password(password, user.password_hash): logger.warning(f"로그인 실패: 비밀번호 불일치 - {username}") raise InvalidCredentialsError() # 활성 상태 확인 if not user.is_active: logger.warning(f"로그인 실패: 비활성 사용자 - {username}") raise UserNotActiveError() # 로그인 성공 self.current_user = user # 설정 저장 if self.config.get('user', 'remember_login', True): self.config.set('user', 'last_user_id', user.id) self.config.save() # 시그널 발생 self.signals.user_logged_in.emit(user.id, user.name) logger.info(f"로그인 성공: {user.name} ({user.department})") return user def logout(self): """로그아웃""" if self.current_user: logger.info(f"로그아웃: {self.current_user.name}") self.current_user = None self.signals.user_logged_out.emit() def has_permission(self, action: str) -> bool: """ 권한 확인 Args: action: 동작 (create, read, update, delete) Returns: 권한 여부 """ if not self.current_user: return False return self.current_user.has_permission(action) def require_permission(self, action: str): """ 권한 요구 (없으면 예외 발생) Args: action: 동작 Raises: PermissionDeniedError: 권한 없음 """ if not self.has_permission(action): raise PermissionDeniedError(action) def is_admin(self) -> bool: """관리자 여부""" if not self.current_user: return False return self.current_user.is_admin() def change_password(self, old_password: str, new_password: str) -> bool: """ 비밀번호 변경 Args: old_password: 기존 비밀번호 new_password: 새 비밀번호 Returns: 변경 성공 여부 """ if not self.current_user: return False # 기존 비밀번호 확인 if not self._verify_password(old_password, self.current_user.password_hash): return False # 새 비밀번호 해시 new_hash = self._hash_password(new_password) # 업데이트 self.crud.update_user(self.current_user.id, password_hash=new_hash) self.current_user.password_hash = new_hash logger.info(f"비밀번호 변경: {self.current_user.name}") return True def create_user( self, username: str, password: str, name: str, department: str, role: str = None ) -> User: """ 사용자 생성 (관리자 전용) Args: username: 사용자명 password: 비밀번호 name: 이름 department: 부서 role: 역할 (None이면 부서 기본값) Returns: 생성된 사용자 """ self.require_permission('create') # 역할 결정 if role is None: role = DEPARTMENT_PERMISSIONS.get(department, 'viewer') # 비밀번호 해시 password_hash = self._hash_password(password) # 생성 user = self.crud.create_user( username=username, password_hash=password_hash, name=name, department=department, role=role, is_active=True, ) logger.info(f"사용자 생성: {name} ({department})") return user def auto_login(self) -> bool: """ 자동 로그인 (마지막 사용자) Returns: 로그인 성공 여부 """ if not self.config.get('user', 'remember_login', True): return False last_user_id = self.config.get('user', 'last_user_id', 0) if not last_user_id: return False user = self.crud.get_user(last_user_id) if user and user.is_active: self.current_user = user self.signals.user_logged_in.emit(user.id, user.name) logger.info(f"자동 로그인: {user.name}") return True return False @staticmethod def _hash_password(password: str) -> str: """비밀번호 해시""" # 간단한 해시 (실제로는 bcrypt 등 사용 권장) return hashlib.sha256(password.encode()).hexdigest() @staticmethod def _verify_password(password: str, password_hash: str) -> bool: """비밀번호 검증""" return AuthService._hash_password(password) == password_hash