init
This commit is contained in:
81
src/application/commands/change_password_complete.py
Normal file
81
src/application/commands/change_password_complete.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from src.application.abstractions import IUnitOfWork
|
||||
from src.application.contracts import IHashService, ILogger, ICache
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.infrastructure.database.decorators import transactional
|
||||
|
||||
|
||||
class ChangePasswordCompleteCommand:
|
||||
def __init__(
|
||||
self,
|
||||
unit_of_work: IUnitOfWork,
|
||||
hash_service: IHashService,
|
||||
cache: ICache,
|
||||
logger: ILogger,
|
||||
):
|
||||
self._unit_of_work = unit_of_work
|
||||
self._hash_service = hash_service
|
||||
self._cache = cache
|
||||
self._logger = logger
|
||||
|
||||
@transactional
|
||||
async def __call__(
|
||||
self,
|
||||
*,
|
||||
user_id: str,
|
||||
code: str,
|
||||
new_password: str,
|
||||
confirm_password: str,
|
||||
) -> bool:
|
||||
code = (code or '').strip()
|
||||
|
||||
USER_PREFIX = 'change_password:user:'
|
||||
CODE_PREFIX = 'change_password:code:'
|
||||
|
||||
user_key = f'{USER_PREFIX}{user_id}'
|
||||
code_key = f'{CODE_PREFIX}{code}'
|
||||
|
||||
if new_password != confirm_password:
|
||||
self._logger.info(f'Change password failed: passwords do not match (user_id={user_id})')
|
||||
raise ApplicationException(400, 'Passwords do not match')
|
||||
|
||||
cached_user_id = await self._cache.get(code_key)
|
||||
if not cached_user_id:
|
||||
self._logger.info(f'Change password failed: code not found (user_id={user_id})')
|
||||
raise ApplicationException(400, 'Invalid or expired code')
|
||||
|
||||
if cached_user_id != user_id:
|
||||
self._logger.info(f'Change password failed: code-user mismatch (user_id={user_id})')
|
||||
raise ApplicationException(400, 'Invalid or expired code')
|
||||
|
||||
code_hash = await self._cache.get(user_key)
|
||||
if not code_hash:
|
||||
self._logger.info(f'Change password failed: user key missing (user_id={user_id})')
|
||||
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'Change password failed: code hash mismatch (user_id={user_id})')
|
||||
raise ApplicationException(400, 'Invalid or expired code')
|
||||
|
||||
current_password_hash = await self._unit_of_work.user_repository.get_password_hash(user_id=user_id)
|
||||
|
||||
is_same = await self._hash_service.verify(hashed_value=current_password_hash, plain_value=new_password)
|
||||
if is_same:
|
||||
self._logger.info(f'Change password failed: new password same as current (user_id={user_id})')
|
||||
raise ApplicationException(400, 'New password must differ from the current one')
|
||||
|
||||
new_password_hash = await self._hash_service.hash(new_password)
|
||||
user = await self._unit_of_work.user_repository.set_password(
|
||||
user_id=user_id,
|
||||
password_hash=new_password_hash,
|
||||
)
|
||||
await self._cache.set_user(user_id, user)
|
||||
|
||||
try:
|
||||
await self._cache.delete(code_key)
|
||||
await self._cache.delete(user_key)
|
||||
except Exception as e:
|
||||
self._logger.warning(f'Change password cleanup failed (user_id={user_id}): {e}')
|
||||
|
||||
self._logger.info(f'Password changed for user_id={user_id}')
|
||||
return True
|
||||
Reference in New Issue
Block a user