116 lines
4.4 KiB
Python
116 lines
4.4 KiB
Python
from fastapi import Depends
|
|
|
|
from common.database.db_session_manager import DB_SESSION_MNG
|
|
from common.database.model.models import tbl_account
|
|
from common.enums import DBWRType, ErrorType
|
|
from common.logger import LOG
|
|
from common.models.gmodel import UserInfo
|
|
from crud.user_crud import IUserCRUD, UserCRUD
|
|
from router.v1.auth.protocol import Res_CreateAccount, Res_Login, Res_RefreshToken
|
|
from router.v1.validator.dependencies import (
|
|
CreateAccessToken,
|
|
CreateRefreshToken,
|
|
DecodeRefreshToken,
|
|
GetHashedPW,
|
|
VerifyPW,
|
|
)
|
|
|
|
|
|
class AuthService:
|
|
"""비즈니스 로직 계층 (MVC 의 컨트롤러-서비스 분리에서 서비스).
|
|
|
|
- CRUD 는 Depends 로 인터페이스 타입으로 주입받는다.
|
|
- DB 접근은 DB_SESSION_MNG 의 람다 실행으로만 한다.
|
|
조회 = execute_lambda(..., DB_READ, lambda s: crud.xxx(s, ...))
|
|
변경 = execute_lambda_run([DBType], [lambda s: crud.xxx(s, ...)])
|
|
- 모든 메서드는 Res_* 를 만들어 result 에 ErrorType 을 세팅해 반환한다.
|
|
"""
|
|
|
|
def __init__(self, user_crud: IUserCRUD = Depends(UserCRUD)):
|
|
self.user_crud = user_crud
|
|
|
|
async def attempt_login(self, id: str, pw: str, connect_ip: str) -> Res_Login:
|
|
LOG.i(f"LOGIN : {id=}")
|
|
res = Res_Login()
|
|
|
|
# 1) 계정 조회 (Read DB)
|
|
err_type, account = await DB_SESSION_MNG.execute_lambda(
|
|
tbl_account.DBType(),
|
|
DBWRType.DB_READ.value,
|
|
lambda s: self.user_crud.get_account_by_id(s, id),
|
|
)
|
|
if err_type != ErrorType.SUCCESS:
|
|
# 계정 없음/조회 실패 모두 로그인 실패로 일반화
|
|
res.result.SetResult(ErrorType.ACCOUNT_INVALID_INFO)
|
|
return res
|
|
account: tbl_account
|
|
|
|
# 2) 비밀번호 검증
|
|
if not await VerifyPW(pw, account.pw):
|
|
res.result.SetResult(ErrorType.ACCOUNT_INVALID_INFO)
|
|
return res
|
|
|
|
# 3) 차단 여부
|
|
if account.is_blocked:
|
|
res.result.SetResult(ErrorType.ACCOUNT_BLOCKED_USER)
|
|
return res
|
|
|
|
# 4) 토큰 발급
|
|
user_info = UserInfo(uid=account.uid, id=account.id, nickname=account.nickname)
|
|
res.access_token = CreateAccessToken(user_info)
|
|
res.refresh_token = CreateRefreshToken(user_info)
|
|
|
|
# 5) 마지막 로그인 시간 갱신 (Write DB, 트랜잭션)
|
|
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
|
[tbl_account.DBType()],
|
|
[lambda s: self.user_crud.update_last_login(s, account.uid)],
|
|
)
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
|
|
res.uid = account.uid
|
|
res.nickname = account.nickname
|
|
return res
|
|
|
|
async def create_account(self, id: str, pw: str, nickname: str, connect_ip: str) -> Res_CreateAccount:
|
|
LOG.i(f"CREATE : {id=}, {nickname=}")
|
|
res = Res_CreateAccount()
|
|
|
|
# 1) 중복 ID 확인 (Read DB)
|
|
err_type = await DB_SESSION_MNG.execute_lambda(
|
|
tbl_account.DBType(),
|
|
DBWRType.DB_READ.value,
|
|
lambda s: self.user_crud.is_account(s, id),
|
|
)
|
|
if err_type == ErrorType.DB_ALREADY_SAME_KEY:
|
|
res.result.SetResult(ErrorType.ACCOUNT_ALREADY_EXIST)
|
|
return res
|
|
if err_type != ErrorType.SUCCESS:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
|
|
# 2) 계정 생성 (비밀번호는 bcrypt 해시로 저장)
|
|
account = tbl_account(id=id, pw=await GetHashedPW(pw), nickname=nickname or id)
|
|
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
|
[tbl_account.DBType()],
|
|
[lambda s: self.user_crud.add_account(s, account)],
|
|
)
|
|
if err_type != ErrorType.SUCCESS:
|
|
# 사전 검사와 INSERT 사이의 경쟁 조건에서 unique 위반이 나면 동일 코드로 매핑.
|
|
if err_type == ErrorType.DB_ALREADY_SAME_KEY:
|
|
res.result.SetResult(ErrorType.ACCOUNT_ALREADY_EXIST)
|
|
else:
|
|
res.result.SetResult(err_type)
|
|
return res
|
|
|
|
res.uid = account.uid
|
|
return res
|
|
|
|
async def refresh_token(self, refresh_token: str) -> Res_RefreshToken:
|
|
res = Res_RefreshToken()
|
|
# refresh 토큰 검증은 라우터 Depends(IsValidRefreshToken) 에서 1차 수행됨.
|
|
user_info = DecodeRefreshToken(refresh_token)
|
|
res.access_token = CreateAccessToken(user_info)
|
|
return res
|