44
55from uuid import UUID
66
7+ from opentelemetry import trace
78from sqlalchemy .ext .asyncio import AsyncSession
89
910from app .models .user import User
1011from app .repositories import user_repo
1112from app .security .password import get_dummy_hash , hash_password , verify_password
1213
14+ tracer = trace .get_tracer (__name__ )
15+
1316
1417class UserAlreadyExists (Exception ):
1518 """Email уже занят другим пользователем."""
@@ -25,23 +28,29 @@ class UserInactive(Exception):
2528
2629async def register_user (session : AsyncSession , email : str , password : str ) -> User :
2730 """Регистрирует нового пользователя. Поднимает UserAlreadyExists если email занят."""
28- if await user_repo .get_user_by_email (session , email ):
29- raise UserAlreadyExists (email )
30- pass_hash = await hash_password (password = password )
31- return await user_repo .create_user (session , email = email , hashed_password = pass_hash )
31+ with tracer .start_as_current_span ("auth.register_user" ):
32+ if await user_repo .get_user_by_email (session , email ):
33+ raise UserAlreadyExists (email )
34+ pass_hash = await hash_password (password = password )
35+ return await user_repo .create_user (
36+ session ,
37+ email = email ,
38+ hashed_password = pass_hash ,
39+ )
3240
3341
3442async def authenticate_user (
3543 session : AsyncSession , email : str , password : str
3644) -> User | None :
3745 """Проверяет email + пароль. Возвращает пользователя или None если данные неверны."""
38- user = await user_repo .get_user_by_email (session , email )
39- # Всегда прогоняем Argon2, даже если email не найден — защита от тайминг-атаки.
40- # Без этого атакующий может по времени ответа определить, существует ли email.
41- hashed = user .hashed_password if user else get_dummy_hash ()
42- if not await verify_password (password = password , hashed_password = hashed ):
43- return None
44- return user
46+ with tracer .start_as_current_span ("auth.authenticate_user" ):
47+ user = await user_repo .get_user_by_email (session , email )
48+ # Всегда прогоняем Argon2, даже если email не найден — защита от тайминг-атаки.
49+ # Без этого атакующий может по времени ответа определить, существует ли email.
50+ hashed = user .hashed_password if user else get_dummy_hash ()
51+ if not await verify_password (password = password , hashed_password = hashed ):
52+ return None
53+ return user
4554
4655
4756async def authenticate_active_user (
@@ -62,9 +71,10 @@ async def authenticate_active_user(
6271
6372async def validate_refresh_subject (session : AsyncSession , user_id : UUID ) -> User :
6473 """Проверяет, что пользователь для refresh существует и активен."""
65- user = await user_repo .get_user_by_id (session , user_id )
66- if not user :
67- raise UserNotFound
68- if not user .is_active :
69- raise UserInactive
70- return user
74+ with tracer .start_as_current_span ("auth.validate_refresh_subject" ):
75+ user = await user_repo .get_user_by_id (session , user_id )
76+ if not user :
77+ raise UserNotFound
78+ if not user .is_active :
79+ raise UserInactive
80+ return user
0 commit comments