Initial commit
This commit is contained in:
121
src/application/commands/user_registration_complete.py
Normal file
121
src/application/commands/user_registration_complete.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from datetime import timedelta, datetime, timezone
|
||||
from ulid import ULID
|
||||
from src.application.abstractions import IUnitOfWork
|
||||
from src.application.contracts import IHashService, IJwtService, ILogger, ICache
|
||||
from src.application.domain.dto import UserCreatedDto
|
||||
from src.application.domain.entities import UserEntity
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.infrastructure.config import settings
|
||||
from src.infrastructure.database.decorators import transactional
|
||||
|
||||
|
||||
class UserRegistrationCompleteCommand:
|
||||
def __init__(
|
||||
self,
|
||||
unit_of_work: IUnitOfWork,
|
||||
hash_service: IHashService,
|
||||
jwt_service: IJwtService,
|
||||
cache: ICache,
|
||||
logger: ILogger,
|
||||
):
|
||||
self._unit_of_work = unit_of_work
|
||||
self._cache = cache
|
||||
self._hash_service = hash_service
|
||||
self._jwt_service = jwt_service
|
||||
self._logger = logger
|
||||
|
||||
|
||||
@transactional
|
||||
async def __call__(
|
||||
self,
|
||||
*,
|
||||
email: str,
|
||||
password: str,
|
||||
device_id: str,
|
||||
code: str,
|
||||
user_agent: str | None,
|
||||
ip: str | None,
|
||||
) -> UserCreatedDto:
|
||||
|
||||
email = (email or '').strip().lower()
|
||||
code = (code or '').strip()
|
||||
|
||||
code_key = f'reg:code:{code}'
|
||||
email_key = f'reg:email:{email}'
|
||||
|
||||
cached_email = await self._cache.get(code_key)
|
||||
if not cached_email:
|
||||
self._logger.info(f'Registration failed: code not found (email={email}, code={code})')
|
||||
raise ApplicationException(400, 'Invalid or expired code')
|
||||
|
||||
if cached_email != email:
|
||||
self._logger.info(f'Registration failed: code-email mismatch (email={email}, cached_email={cached_email})')
|
||||
raise ApplicationException(400, 'Invalid or expired code')
|
||||
|
||||
code_hash = await self._cache.get(email_key)
|
||||
if not code_hash:
|
||||
self._logger.info(f'Registration failed: email key missing (email={email})')
|
||||
raise ApplicationException(400, 'Invalid or expired code')
|
||||
|
||||
ok = await self._hash_service.verify(
|
||||
hashed_value=code_hash,
|
||||
plain_value=code,
|
||||
)
|
||||
if not ok:
|
||||
self._logger.info(f'Registration failed: code hash mismatch (email={email})')
|
||||
raise ApplicationException(400, 'Invalid or expired code')
|
||||
|
||||
deleted_code = await self._cache.delete(code_key)
|
||||
deleted_email = await self._cache.delete(email_key)
|
||||
|
||||
if not deleted_code or not deleted_email:
|
||||
self._logger.info(
|
||||
f'Registration cleanup: keys already missing '
|
||||
f'(email={email}, deleted_code={deleted_code}, deleted_email={deleted_email})'
|
||||
)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
password_hash = await self._hash_service.hash(value=password)
|
||||
|
||||
user: UserEntity = await self._unit_of_work.user_repository.create_user(
|
||||
email=email,
|
||||
password_hash=password_hash,
|
||||
)
|
||||
|
||||
sid = str(ULID())
|
||||
jti = str(ULID())
|
||||
|
||||
refresh_jti_hash = await self._hash_service.hash(value=jti)
|
||||
refresh_expires_at = now + timedelta(seconds=int(settings.JWT_REFRESH_TTL_SECONDS))
|
||||
|
||||
await self._unit_of_work.session_repository.upsert_by_device(
|
||||
user_id=user.id,
|
||||
device_id=device_id,
|
||||
sid=sid,
|
||||
refresh_jti_hash=refresh_jti_hash,
|
||||
refresh_expires_at=refresh_expires_at,
|
||||
user_agent=user_agent,
|
||||
ip=ip,
|
||||
now=now,
|
||||
)
|
||||
|
||||
access_token = await self._jwt_service.create_access_token(
|
||||
user_id=user.id,
|
||||
sid=sid,
|
||||
)
|
||||
refresh_token = await (
|
||||
self._jwt_service.create_refresh_token(
|
||||
user_id=user.id,
|
||||
sid=sid,
|
||||
refresh_jti=jti,
|
||||
))
|
||||
|
||||
self._logger.info(f'User registered successfully user_id={user.id} device_id={device_id} sid={sid}')
|
||||
|
||||
return UserCreatedDto(
|
||||
id=user.id,
|
||||
email=user.email,
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
)
|
||||
Reference in New Issue
Block a user