refractor redis service

This commit is contained in:
2026-01-06 23:42:12 +03:00
parent 2e14a7f364
commit 423ce8bc62
5 changed files with 109 additions and 27 deletions
+1
View File
@@ -21,6 +21,7 @@ class Settings(BaseSettings):
SECRET_KEY: str
ALGORITHM: str
ACCESS_TOKEN_EXPIRE_MINUTES: int = 15
EMAIL_TOKEN_EXPIRE_MINUTES: int = 60
REFRESH_TOKEN_EXPIRE_DAYS: int = 30
SMTP_SERVER: str
+1 -1
View File
@@ -16,7 +16,7 @@ class EmailService:
template_path="confirm_email.html",
username=username,
url=url,
expire_minutes=60,
expire_minutes=settings.EMAIL_TOKEN_EXPIRE_MINUTES,
company_name=settings.COMPANY_NAME
)
body = EmailClient.render(
+72
View File
@@ -0,0 +1,72 @@
import logging
import uuid
from app.utils.redis import get_redis
log = logging.getLogger(__name__)
class RefreshTokenStorage:
PREFIX: str = "refresh"
@classmethod
async def save_token(cls, token: uuid.UUID, user_id: int, ttl: int):
redis_client = await get_redis()
await redis_client.setex(f"{cls.PREFIX}:{token}", ttl, user_id)
await redis_client.sadd(f"user:{user_id}:{cls.PREFIX}", str(token))
await redis_client.expire(f"user:{user_id}:{cls.PREFIX}", ttl+3600)
log.info("Save new refresh token from redis", extra={"user_id": user_id, "token": token})
@classmethod
async def getdel_token(cls, token: uuid.UUID) -> int:
redis_client = await get_redis()
user_id = await redis_client.getdel(f"{cls.PREFIX}:{token}")
await redis_client.srem(f"user:{user_id}:{cls.PREFIX}", str(token))
log.info("Remove token", extra={"user_id": user_id, "token": token})
return user_id
@classmethod
async def get_token(cls, token: uuid.UUID) -> int:
redis_client = await get_redis()
user_id = await redis_client.get(f"{cls.PREFIX}:{token}")
log.debug("User_id fetched from refresh token", extra={"user_id": user_id, "token": token})
return user_id
@classmethod
async def abort_all_tokens(cls, user_id: int):
redis_client = await get_redis()
log.debug("Start abort all tokens", extra={"user_id": user_id})
tokens = await redis_client.smembers(f"user:{user_id}:{cls.PREFIX}")
for token in tokens:
await redis_client.delete(f"{cls.PREFIX}:{token}")
await redis_client.delete(f"user:{user_id}:{cls.PREFIX}")
log.info("Successfully abort all tokens", extra={"user_id": user_id})
class EmailTokenStorage:
PREFIX: str = "email"
@classmethod
async def save_token(cls, token: uuid.UUID, user_id: int, ttl: int):
redis_client = await get_redis()
await redis_client.setex(f"{cls.PREFIX}:{token}", ttl, user_id)
log.info("Save new refresh token from redis", extra={"user_id": user_id, "token": token})
@classmethod
async def getdel_token(cls, token: uuid.UUID) -> int:
redis_client = await get_redis()
log.debug("User_id fetched from email token", extra={"token": token})
return await redis_client.getdel(f"{cls.PREFIX}:{token}")
+15 -7
View File
@@ -1,7 +1,8 @@
from typing import Dict
import logging
import uuid
from fastapi import APIRouter, status, Response, Depends, Request
from fastapi import APIRouter, status, Response, Depends, Request, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from app.users.schemas import UserCreate, User, Token
@@ -16,18 +17,20 @@ auth_router = APIRouter(prefix="/auth", tags=["Auth"])
log = logging.getLogger(__name__)
@auth_router.post("/register", status_code=status.HTTP_201_CREATED)
async def register(user: UserCreate) -> User:
return await UserService.register_new_user(user)
@auth_router.get("/verify/{token}")
async def verify_email(token: uuid.UUID):
async def verify_email(token: uuid.UUID) -> Dict:
await UserService.verify_email(token)
return {"status": True}
return {"status": True, "message": "User successfully verified email"}
@auth_router.post("/send/verify-email")
async def resend_verify_email(user: UserModel = Depends(get_current_user)) -> Dict:
if user.is_verified:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Email already verified")
@auth_router.post("/send/verify_email")
async def resend_verify_email(user: UserModel = Depends(get_current_user)):
await UserService.send_verify_email(user)
return {"status": True, "message": "Successfully send email letter"}
@@ -72,9 +75,14 @@ async def refresh_token(request: Request, response: Response) -> Token:
return new_token
@auth_router.post("/logout")
async def logout(request: Request, response: Response, user: UserModel = Depends(get_current_user)):
async def logout(request: Request, response: Response, user: UserModel = Depends(get_current_user)) -> Dict:
response.delete_cookie("access_token")
response.delete_cookie("refresh_token")
await AuthService.logout(uuid.UUID(request.cookies.get("refresh_token")))
return {"status": True, "message": "Logged out successfully"}
@auth_router.post("/abort")
async def abort_all_sessions(user: UserModel = Depends(get_current_user)) -> Dict:
await AuthService.abort_all_sessions(user.id)
return {"status": True, "message": "All sessions was aborted"}
+19 -18
View File
@@ -8,6 +8,7 @@ from jose import jwt
from sqlalchemy import or_
from app.utils.hash_password import hash_password, verify_password
from app.services.redis_service import RefreshTokenStorage, EmailTokenStorage
from app.utils.redis import get_redis
from app.exceptions import InvalidTokenException, TokenExpiredException
from app.users.models import UserModel
@@ -23,29 +24,26 @@ log = logging.getLogger(__name__)
class AuthService:
@classmethod
async def create_token(cls, user_id: int) -> Token:
redis_client = await get_redis()
access_token = cls._create_access_token(user_id)
refresh_token_expires = timedelta(
days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
refresh_token = cls._create_refresh_token()
await redis_client.setex(f"refresh:{refresh_token}", int(refresh_token_expires.total_seconds()), user_id)
await RefreshTokenStorage.save_token(refresh_token, user_id, int(refresh_token_expires.total_seconds()))
log.info("Token created has user", extra={"user_id": user_id})
return Token(access_token=access_token, refresh_token=refresh_token, token_type="bearer")
@classmethod
async def logout(cls, token: uuid.UUID) -> None:
redis_client = await get_redis()
user_id = await redis_client.getdel(f"refresh:{token}")
user_id = await RefreshTokenStorage.getdel_token(token)
log.info("User logged out", extra={"user_id": user_id})
@classmethod
async def refresh_token(cls, token: uuid.UUID) -> Token:
redis_client = await get_redis()
async with async_session_maker() as session:
refresh_session = await redis_client.getdel(f"refresh:{token}")
refresh_session = await RefreshTokenStorage.getdel_token(token)
if refresh_session is None:
log.warning("Refresh token not found")
@@ -61,10 +59,10 @@ class AuthService:
days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
refresh_token = cls._create_refresh_token()
await redis_client.setex(
f"refresh:{refresh_token}",
int(refresh_token_expires.total_seconds()),
user.id
await RefreshTokenStorage.save_token(
refresh_token,
user.id,
int(refresh_token_expires.total_seconds())
)
await session.commit()
@@ -87,11 +85,9 @@ class AuthService:
log.warning("Authentication failed", extra={"email": email_or_username})
return None
# @classmethod
# async def abort_all_sessions(cls, user_id: uuid.UUID):
# async with async_session_maker() as session:
# await RefreshSessionDAO.delete(session, RefreshSessionModel.user_id == user_id)
# await session.commit()
@classmethod
async def abort_all_sessions(cls, user_id: int):
await RefreshTokenStorage.abort_all_tokens(user_id)
@classmethod
def _create_access_token(cls, user_id: int) -> str:
@@ -105,7 +101,7 @@ class AuthService:
return f'Bearer {encoded_jwt}'
@classmethod
def _create_refresh_token(cls) -> str:
def _create_refresh_token(cls) -> uuid.UUID:
return uuid.uuid4()
@@ -159,8 +155,13 @@ class UserService:
token = cls._create_email_verification_token()
url = f"{settings.URL}/api/v1/auth/verify/{token}"
email_token_expires = timedelta(minutes=settings.EMAIL_TOKEN_EXPIRE_MINUTES)
await redis_client.setex(f"email:{token}", timedelta(minutes=60), user.id)
await EmailTokenStorage.save_token(
token,
user.id,
int(email_token_expires.total_seconds())
)
EmailTasks.send_verify_email_task.delay(email=user.email, username=user.username, url=url)
@@ -173,7 +174,7 @@ class UserService:
async def verify_email(cls, token: uuid.UUID):
redis_client = await get_redis()
async with async_session_maker() as session:
user_id = await redis_client.getdel(f"email:{token}")
user_id = await EmailTokenStorage.getdel_token(token)
if user_id is None:
raise TokenExpiredException