Files
TK-BOM-Project/backend/app/auth/auth_controller.py
Hyungi Ahn 4f8e395f87
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
feat: SWG 가스켓 전체 구성 정보 표시 개선
- H/F/I/O SS304/GRAPHITE/CS/CS 패턴에서 4개 구성요소 모두 표시
- 기존 SS304 + GRAPHITE → SS304/GRAPHITE/CS/CS로 완전한 구성 표시
- 외부링/필러/내부링/추가구성 모든 정보 포함
- 구매수량 계산 모달에서 정확한 재질 정보 확인 가능
2025-08-30 14:23:01 +09:00

394 lines
10 KiB
Python

"""
인증 컨트롤러
TK-FB-Project의 authController.js를 참고하여 FastAPI용으로 구현
"""
from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from pydantic import BaseModel, EmailStr
from typing import Optional, List, Dict, Any
from ..database import get_db
from .auth_service import get_auth_service
from .jwt_service import jwt_service
from .models import UserRepository
from ..utils.logger import get_logger
logger = get_logger(__name__)
router = APIRouter()
security = HTTPBearer()
# Pydantic 모델들
class LoginRequest(BaseModel):
username: str
password: str
class RegisterRequest(BaseModel):
username: str
password: str
name: str
email: Optional[EmailStr] = None
access_level: str = 'worker'
department: Optional[str] = None
position: Optional[str] = None
phone: Optional[str] = None
class RefreshTokenRequest(BaseModel):
refresh_token: str
class LoginResponse(BaseModel):
success: bool
access_token: str
refresh_token: str
token_type: str
expires_in: int
user: Dict[str, Any]
redirect_url: str
permissions: List[str]
class RefreshTokenResponse(BaseModel):
success: bool
access_token: str
token_type: str
expires_in: int
user: Dict[str, Any]
class RegisterResponse(BaseModel):
success: bool
message: str
user_id: int
username: str
class LogoutResponse(BaseModel):
success: bool
message: str
class UserInfoResponse(BaseModel):
success: bool
user: Dict[str, Any]
permissions: List[str]
@router.post("/login", response_model=LoginResponse)
async def login(
login_data: LoginRequest,
request: Request,
db: Session = Depends(get_db)
):
"""
사용자 로그인
Args:
login_data: 로그인 정보 (사용자명, 비밀번호)
request: FastAPI Request 객체
db: 데이터베이스 세션
Returns:
LoginResponse: 로그인 결과 (토큰, 사용자 정보 등)
"""
try:
auth_service = get_auth_service(db)
result = await auth_service.login(
username=login_data.username,
password=login_data.password,
request=request
)
return LoginResponse(**result)
except Exception as e:
logger.error(f"Login endpoint error: {str(e)}")
raise
@router.post("/register", response_model=RegisterResponse)
async def register(
register_data: RegisterRequest,
db: Session = Depends(get_db)
):
"""
사용자 등록
Args:
register_data: 등록 정보
db: 데이터베이스 세션
Returns:
RegisterResponse: 등록 결과
"""
try:
auth_service = get_auth_service(db)
result = await auth_service.register(register_data.dict())
return RegisterResponse(**result)
except Exception as e:
logger.error(f"Register endpoint error: {str(e)}")
raise
@router.post("/refresh", response_model=RefreshTokenResponse)
async def refresh_token(
refresh_data: RefreshTokenRequest,
request: Request,
db: Session = Depends(get_db)
):
"""
토큰 갱신
Args:
refresh_data: 리프레시 토큰 정보
request: FastAPI Request 객체
db: 데이터베이스 세션
Returns:
RefreshTokenResponse: 새로운 토큰 정보
"""
try:
auth_service = get_auth_service(db)
result = await auth_service.refresh_token(
refresh_token=refresh_data.refresh_token,
request=request
)
return RefreshTokenResponse(**result)
except Exception as e:
logger.error(f"Refresh token endpoint error: {str(e)}")
raise
@router.post("/logout", response_model=LogoutResponse)
async def logout(
refresh_data: RefreshTokenRequest,
db: Session = Depends(get_db)
):
"""
로그아웃
Args:
refresh_data: 리프레시 토큰 정보
db: 데이터베이스 세션
Returns:
LogoutResponse: 로그아웃 결과
"""
try:
auth_service = get_auth_service(db)
result = await auth_service.logout(refresh_data.refresh_token)
return LogoutResponse(**result)
except Exception as e:
logger.error(f"Logout endpoint error: {str(e)}")
raise
@router.get("/me", response_model=UserInfoResponse)
async def get_current_user_info(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
"""
현재 사용자 정보 조회
Args:
credentials: JWT 토큰
db: 데이터베이스 세션
Returns:
UserInfoResponse: 사용자 정보 및 권한
"""
try:
# 토큰 검증
payload = jwt_service.verify_access_token(credentials.credentials)
user_id = payload['user_id']
# 사용자 정보 조회
user_repo = UserRepository(db)
user = user_repo.find_by_id(user_id)
if not user or not user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="사용자를 찾을 수 없거나 비활성화된 계정입니다"
)
# 권한 정보 조회
permissions = user_repo.get_user_permissions(user.role)
return UserInfoResponse(
success=True,
user=user.to_dict(),
permissions=permissions
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Get current user info error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="사용자 정보 조회 중 오류가 발생했습니다"
)
@router.get("/verify")
async def verify_token(
credentials: HTTPAuthorizationCredentials = Depends(security)
):
"""
토큰 검증
Args:
credentials: JWT 토큰
Returns:
Dict: 토큰 검증 결과
"""
try:
payload = jwt_service.verify_access_token(credentials.credentials)
return {
'success': True,
'valid': True,
'user_id': payload['user_id'],
'username': payload['username'],
'role': payload['role'],
'expires_at': payload.get('exp')
}
except HTTPException as e:
return {
'success': False,
'valid': False,
'error': e.detail
}
except Exception as e:
logger.error(f"Token verification error: {str(e)}")
return {
'success': False,
'valid': False,
'error': '토큰 검증 중 오류가 발생했습니다'
}
# 관리자 전용 엔드포인트들
@router.get("/users")
async def get_all_users(
skip: int = 0,
limit: int = 100,
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
"""
모든 사용자 목록 조회 (관리자 전용)
Args:
skip: 건너뛸 레코드 수
limit: 조회할 레코드 수
credentials: JWT 토큰
db: 데이터베이스 세션
Returns:
Dict: 사용자 목록
"""
try:
# 토큰 검증 및 권한 확인
payload = jwt_service.verify_access_token(credentials.credentials)
if payload['role'] not in ['admin', 'system']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="관리자 권한이 필요합니다"
)
# 사용자 목록 조회
user_repo = UserRepository(db)
users = user_repo.get_all_users(skip=skip, limit=limit)
return {
'success': True,
'users': [user.to_dict() for user in users],
'total_count': len(users)
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Get all users error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="사용자 목록 조회 중 오류가 발생했습니다"
)
@router.delete("/users/{user_id}")
async def delete_user(
user_id: int,
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
"""
사용자 삭제 (관리자 전용)
Args:
user_id: 삭제할 사용자 ID
credentials: JWT 토큰
db: 데이터베이스 세션
Returns:
Dict: 삭제 결과
"""
try:
# 토큰 검증 및 권한 확인
payload = jwt_service.verify_access_token(credentials.credentials)
if payload['role'] not in ['admin', 'system']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="관리자 권한이 필요합니다"
)
# 자기 자신 삭제 방지
if payload['user_id'] == user_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="자기 자신은 삭제할 수 없습니다"
)
# 사용자 조회 및 삭제
user_repo = UserRepository(db)
user = user_repo.find_by_id(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="해당 사용자를 찾을 수 없습니다"
)
user_repo.delete_user(user)
logger.info(f"User deleted by admin: {user.username} (deleted by: {payload['username']})")
return {
'success': True,
'message': '사용자가 삭제되었습니다',
'deleted_user_id': user_id
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Delete user error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="사용자 삭제 중 오류가 발생했습니다"
)