Files
python-api/app/services/auth_service.py
2026-03-01 07:44:19 +09:00

85 lines
3.0 KiB
Python

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)