초기 커밋
This commit is contained in:
84
app/services/auth_service.py
Normal file
84
app/services/auth_service.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.exceptions import ConflictException, UnauthorizedException
|
||||
from app.core.security import (
|
||||
create_access_token,
|
||||
create_refresh_token,
|
||||
decode_token,
|
||||
hash_password,
|
||||
verify_password,
|
||||
)
|
||||
from app.models.mariadb.auth import RefreshToken
|
||||
from app.models.mariadb.user import User
|
||||
from app.repositories.auth_repo import AuthRepository
|
||||
from app.repositories.user_repo import UserRepository
|
||||
from app.schemas.auth import TokenResponse
|
||||
|
||||
|
||||
class AuthService:
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.user_repo = UserRepository(session)
|
||||
self.auth_repo = AuthRepository(session)
|
||||
|
||||
async def register(
|
||||
self, email: str, password: str, full_name: str = ""
|
||||
) -> User:
|
||||
existing = await self.user_repo.get_by_email(email)
|
||||
if existing:
|
||||
raise ConflictException("Email already registered")
|
||||
|
||||
user = User(
|
||||
email=email,
|
||||
hashed_password=hash_password(password),
|
||||
)
|
||||
return await self.user_repo.create_with_profile(user, full_name=full_name)
|
||||
|
||||
async def login(self, email: str, password: str) -> TokenResponse:
|
||||
user = await self.user_repo.get_by_email(email)
|
||||
if not user or not verify_password(password, user.hashed_password):
|
||||
raise UnauthorizedException("Invalid email or password")
|
||||
if not user.is_active:
|
||||
raise UnauthorizedException("Account is deactivated")
|
||||
|
||||
user.last_login_at = datetime.utcnow()
|
||||
|
||||
return await self._create_tokens(user)
|
||||
|
||||
async def refresh(self, refresh_token_str: str) -> TokenResponse:
|
||||
payload = decode_token(refresh_token_str)
|
||||
if not payload or payload.get("type") != "refresh":
|
||||
raise UnauthorizedException("Invalid refresh token")
|
||||
|
||||
stored = await self.auth_repo.get_by_token(refresh_token_str)
|
||||
if not stored:
|
||||
raise UnauthorizedException("Refresh token not found or expired")
|
||||
|
||||
stored.is_revoked = True
|
||||
|
||||
user = await self.user_repo.get_by_id(stored.user_id)
|
||||
if not user or not user.is_active:
|
||||
raise UnauthorizedException("User not found or deactivated")
|
||||
|
||||
return await self._create_tokens(user)
|
||||
|
||||
async def logout(self, user_id: int) -> None:
|
||||
await self.auth_repo.revoke_all_for_user(user_id)
|
||||
|
||||
async def _create_tokens(self, user: User) -> TokenResponse:
|
||||
access = create_access_token(user.id, user.role) # type: ignore[arg-type]
|
||||
refresh = create_refresh_token(user.id) # type: ignore[arg-type]
|
||||
|
||||
token_obj = RefreshToken(
|
||||
user_id=user.id, # type: ignore[arg-type]
|
||||
token=refresh,
|
||||
expires_at=datetime.utcnow()
|
||||
+ timedelta(days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS),
|
||||
)
|
||||
await self.auth_repo.create(token_obj)
|
||||
|
||||
return TokenResponse(access_token=access, refresh_token=refresh)
|
||||
Reference in New Issue
Block a user