From e208792738ae37115d8e53de881e4734936d0b0c Mon Sep 17 00:00:00 2001 From: dev1lfreak Date: Mon, 8 Jun 2026 11:55:51 +0300 Subject: [PATCH 1/4] feat: add change password --- .../repositories/i_user_repository.py | 8 +++++ src/application/commands/__init__.py | 2 ++ src/application/commands/set_password.py | 31 +++++++++++++++++++ src/application/domain/password_policy.py | 21 +++++++++++++ .../database/repositories/user_repository.py | 19 +++++++++++- src/presentation/dependencies/commands.py | 8 +++++ src/presentation/routing/__init__.py | 2 ++ src/presentation/routing/users.py | 17 ++++++++++ src/presentation/schemas/password.py | 13 ++++++++ 9 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/application/commands/set_password.py create mode 100644 src/application/domain/password_policy.py create mode 100644 src/presentation/routing/users.py create mode 100644 src/presentation/schemas/password.py diff --git a/src/application/abstractions/repositories/i_user_repository.py b/src/application/abstractions/repositories/i_user_repository.py index c9c8d79..eff4843 100644 --- a/src/application/abstractions/repositories/i_user_repository.py +++ b/src/application/abstractions/repositories/i_user_repository.py @@ -25,3 +25,11 @@ class IUserRepository(ABC): @abstractmethod async def exists_by_email(self, email: str) -> bool: raise NotImplementedError + + @abstractmethod + async def get_password_hash(self, user_id: str) -> str: + raise NotImplementedError + + @abstractmethod + async def set_password(self, user_id: str, password_hash: str) -> UserEntity: + raise NotImplementedError diff --git a/src/application/commands/__init__.py b/src/application/commands/__init__.py index a450151..635cf30 100644 --- a/src/application/commands/__init__.py +++ b/src/application/commands/__init__.py @@ -5,6 +5,7 @@ from src.application.commands.get_admin_me import GetAdminMeCommand from src.application.commands.create_organization import CreateOrganizationCommand from src.application.commands.create_organization_wallets import CreateOrganizationWalletsCommand from src.application.commands.upload_organization_document import UploadOrganizationDocumentCommand +from src.application.commands.set_password import SetPasswordCommand from src.application.commands.organization_commands import ( ListOrganizationsCommand, GetOrganizationCommand, @@ -38,4 +39,5 @@ __all__ = [ 'SetPurchaseRequestQuoteCommand', 'ListOrganizationDocumentsCommand', 'GetOrganizationDocumentCommand', + 'SetPasswordCommand', ] diff --git a/src/application/commands/set_password.py b/src/application/commands/set_password.py new file mode 100644 index 0000000..64c6627 --- /dev/null +++ b/src/application/commands/set_password.py @@ -0,0 +1,31 @@ +from src.application.abstractions import IUnitOfWork +from src.application.contracts import IHashService, ILogger +from src.application.domain.exceptions import ApplicationException +from src.infrastructure.database.decorators import transactional + + +class SetPasswordCommand: + def __init__( + self, + unit_of_work: IUnitOfWork, + hash_service: IHashService, + logger: ILogger + ): + self._unit_of_work = unit_of_work + self._hash_service = hash_service + self._logger = logger + + @transactional + async def __call__(self, email: str, password: str) -> bool: + try: + user = await self._unit_of_work.user_repository.get_user_by_email(email) + + password_hash = self._hash_service.hash_password(password) + await self._unit_of_work.user_repository.set_password( + user_id=user.id, + password_hash=password_hash, + ) + self._logger.info(f'Set password for user {user.id}') + return True + except ApplicationException: + raise diff --git a/src/application/domain/password_policy.py b/src/application/domain/password_policy.py new file mode 100644 index 0000000..169072f --- /dev/null +++ b/src/application/domain/password_policy.py @@ -0,0 +1,21 @@ +import re + +SPECIAL_CHARS = '!@#$%^&*()_+-=.,:;?/[]{}<>' + + +def validate_password_strength(password: str) -> str: + if re.search(r'\s', password): + raise ValueError('Password must not contain whitespace') + if len(password) < 12: + raise ValueError('Password must be at least 12 characters') + if not re.search(r'[a-z]', password): + raise ValueError('Password must contain at least one lowercase letter') + if not re.search(r'[A-Z]', password): + raise ValueError('Password must contain at least one uppercase letter') + if not re.search(r'\d', password): + raise ValueError('Password must contain at least one digit') + if not any(c in SPECIAL_CHARS for c in password): + raise ValueError( + 'Password must contain at least one special character from: !@#$%^&*()_+-=.,:;?/[]{}<>' + ) + return password diff --git a/src/infrastructure/database/repositories/user_repository.py b/src/infrastructure/database/repositories/user_repository.py index 0d694c2..a82cf37 100644 --- a/src/infrastructure/database/repositories/user_repository.py +++ b/src/infrastructure/database/repositories/user_repository.py @@ -11,7 +11,7 @@ from src.application.abstractions.repositories import IUserRepository from src.application.contracts import ILogger from src.application.domain.entities import UserEntity from src.application.domain.enums.account_type import AccountType -from src.application.domain.exceptions import ApplicationException +from src.application.domain.exceptions import InternalServerException, ApplicationException from src.infrastructure.database.models import UserModel @@ -73,6 +73,23 @@ class UserRepository(IUserRepository): except SQLAlchemyError as exc: self._logger.exception(str(exc)) raise ApplicationException(status_code=500, message='Database error') + + async def _update_field(self, user_id: str, **fields: object) -> UserEntity: + try: + user = await self._get_active_user(user_id) + for key, value in fields.items(): + setattr(user, key, value) + await self._session.flush() + await self._session.refresh(user) + return self._to_entity(user) + except ApplicationException: + raise + except SQLAlchemyError as exception: + self._logger.exception(str(exception)) + raise InternalServerException(message=f'Database error: {str(exception)}') + + async def set_password(self, user_id: str, password_hash: str) -> UserEntity: + return await self._update_field(user_id, password_hash=password_hash) async def get_user_by_email(self, email: str) -> UserEntity: try: diff --git a/src/presentation/dependencies/commands.py b/src/presentation/dependencies/commands.py index f5fec47..75546f4 100644 --- a/src/presentation/dependencies/commands.py +++ b/src/presentation/dependencies/commands.py @@ -20,6 +20,7 @@ from src.application.commands import ( UpdateOrganizationCommand, UpdatePurchaseRequestStatusCommand, UploadOrganizationDocumentCommand, + SetPasswordCommand, ) from src.application.contracts import IHashService, IJwtService, ILogger from src.infrastructure.config import settings @@ -159,3 +160,10 @@ def get_set_purchase_request_quote_command( logger: ILogger = Depends(get_logger), ) -> SetPurchaseRequestQuoteCommand: return SetPurchaseRequestQuoteCommand(uow, logger) + +def get_set_password_command( + uow: IUnitOfWork = Depends(get_unit_of_work), + hash_service: IHashService = Depends(get_hash_service), + logger: ILogger = Depends(get_logger), +) -> SetPasswordCommand: + return SetPasswordCommand(uow, hash_service, logger) diff --git a/src/presentation/routing/__init__.py b/src/presentation/routing/__init__.py index b0f0bc2..2dfb85c 100644 --- a/src/presentation/routing/__init__.py +++ b/src/presentation/routing/__init__.py @@ -5,6 +5,7 @@ from src.presentation.routing.documents import documents_router from src.presentation.routing.jwt import jwt_router from src.presentation.routing.organizations import organizations_router from src.presentation.routing.purchase_requests import purchase_requests_router +from src.presentation.routing.users import users_router v1_router = APIRouter(prefix='/v1') v1_router.include_router(auth_router) @@ -12,3 +13,4 @@ v1_router.include_router(jwt_router) v1_router.include_router(organizations_router) v1_router.include_router(documents_router) v1_router.include_router(purchase_requests_router) +v1_router.include_router(users_router) \ No newline at end of file diff --git a/src/presentation/routing/users.py b/src/presentation/routing/users.py new file mode 100644 index 0000000..a291571 --- /dev/null +++ b/src/presentation/routing/users.py @@ -0,0 +1,17 @@ +from fastapi import APIRouter, Depends, Request +from fastapi.responses import ORJSONResponse +from starlette import status +from src.presentation.dependencies.commands import get_set_password_command +from src.application.commands.set_password import SetPasswordCommand +from presentation.schemas.password import SetPasswordRequest + +users_router = APIRouter(prefix='/users', tags=['users']) + +@users_router.patch(path='/password', response_class=ORJSONResponse, status_code=status.HTTP_200_OK) +async def set_password( + request: Request, + body: SetPasswordRequest, + command: SetPasswordCommand = Depends(get_set_password_command), +): + await command(email=body.email, password=body.password) + return ORJSONResponse(content={'message': 'Password updated successfully'}) \ No newline at end of file diff --git a/src/presentation/schemas/password.py b/src/presentation/schemas/password.py new file mode 100644 index 0000000..5f001ec --- /dev/null +++ b/src/presentation/schemas/password.py @@ -0,0 +1,13 @@ +import re +from pydantic import BaseModel, field_validator +from src.application.domain.password_policy import validate_password_strength + + +class SetPasswordRequest(BaseModel): + email: str + password: str + + @field_validator('password') + @classmethod + def validate_password(cls, v: str) -> str: + return validate_password_strength(v) From 4fc74f404f80a29d3ec4cc3b30613d53142fc964 Mon Sep 17 00:00:00 2001 From: dev1lfreak Date: Wed, 10 Jun 2026 18:35:26 +0300 Subject: [PATCH 2/4] fix: merge conflict --- .../repositories/i_user_repository.py | 9 ++++++++ src/application/commands/__init__.py | 22 +++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/application/abstractions/repositories/i_user_repository.py b/src/application/abstractions/repositories/i_user_repository.py index eff4843..4702708 100644 --- a/src/application/abstractions/repositories/i_user_repository.py +++ b/src/application/abstractions/repositories/i_user_repository.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod from datetime import datetime from src.application.domain.entities import UserEntity +from src.application.domain.entities.organization import PartySearchEntity class IUserRepository(ABC): @@ -26,6 +27,14 @@ class IUserRepository(ABC): async def exists_by_email(self, email: str) -> bool: raise NotImplementedError + @abstractmethod + async def search_individuals(self, *, query: str, limit: int, offset: int) -> list[PartySearchEntity]: + raise NotImplementedError + + @abstractmethod + async def count_individuals(self, *, query: str) -> int: + raise NotImplementedError + @abstractmethod async def get_password_hash(self, user_id: str) -> str: raise NotImplementedError diff --git a/src/application/commands/__init__.py b/src/application/commands/__init__.py index 635cf30..06f0f6c 100644 --- a/src/application/commands/__init__.py +++ b/src/application/commands/__init__.py @@ -1,23 +1,27 @@ -from src.application.commands.admin_login import AdminLoginCommand -from src.application.commands.admin_logout import AdminLogoutCommand from src.application.commands.admin_jwt_refresh import AdminJwtRefreshCommand from src.application.commands.get_admin_me import GetAdminMeCommand from src.application.commands.create_organization import CreateOrganizationCommand from src.application.commands.create_organization_wallets import CreateOrganizationWalletsCommand -from src.application.commands.upload_organization_document import UploadOrganizationDocumentCommand -from src.application.commands.set_password import SetPasswordCommand +from src.application.commands.put_organization_document import PutOrganizationDocumentCommand from src.application.commands.organization_commands import ( ListOrganizationsCommand, GetOrganizationCommand, + SearchPartiesCommand, UpdateOrganizationCommand, ) from src.application.commands.organization_document_commands import ( ListOrganizationDocumentsCommand, GetOrganizationDocumentCommand, ) +from src.application.commands.organization_wallet_commands import ( + GetOrganizationMnemonicCommand, + GetOrganizationSecretKeysCommand, + ListOrganizationWalletsCommand, +) from src.application.commands.purchase_request_commands import ( ListPurchaseRequestsCommand, GetPurchaseRequestCommand, + UpdatePurchaseRequestCommand, UpdatePurchaseRequestStatusCommand, SetPurchaseRequestQuoteCommand, ) @@ -29,15 +33,19 @@ __all__ = [ 'GetAdminMeCommand', 'CreateOrganizationCommand', 'CreateOrganizationWalletsCommand', - 'UploadOrganizationDocumentCommand', + 'PutOrganizationDocumentCommand', 'ListOrganizationsCommand', 'GetOrganizationCommand', + 'SearchPartiesCommand', 'UpdateOrganizationCommand', 'ListPurchaseRequestsCommand', 'GetPurchaseRequestCommand', + 'UpdatePurchaseRequestCommand', 'UpdatePurchaseRequestStatusCommand', 'SetPurchaseRequestQuoteCommand', 'ListOrganizationDocumentsCommand', 'GetOrganizationDocumentCommand', - 'SetPasswordCommand', -] + 'ListOrganizationWalletsCommand', + 'GetOrganizationMnemonicCommand', + 'GetOrganizationSecretKeysCommand', +] \ No newline at end of file From 18c9c1e01a09a240a50ae70fbd984b091efa4dd5 Mon Sep 17 00:00:00 2001 From: dev1lfreak Date: Wed, 10 Jun 2026 18:42:06 +0300 Subject: [PATCH 3/4] fix: merge conflict 2 --- src/presentation/dependencies/commands.py | 56 ++++++++++++++++++----- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/src/presentation/dependencies/commands.py b/src/presentation/dependencies/commands.py index 75546f4..5cd04be 100644 --- a/src/presentation/dependencies/commands.py +++ b/src/presentation/dependencies/commands.py @@ -10,17 +10,21 @@ from src.application.commands import ( GetAdminMeCommand, CreateOrganizationCommand, CreateOrganizationWalletsCommand, + GetOrganizationMnemonicCommand, + GetOrganizationSecretKeysCommand, GetOrganizationCommand, + ListOrganizationWalletsCommand, GetPurchaseRequestCommand, ListOrganizationsCommand, GetOrganizationDocumentCommand, ListOrganizationDocumentsCommand, ListPurchaseRequestsCommand, + SearchPartiesCommand, SetPurchaseRequestQuoteCommand, UpdateOrganizationCommand, + UpdatePurchaseRequestCommand, UpdatePurchaseRequestStatusCommand, - UploadOrganizationDocumentCommand, - SetPasswordCommand, + PutOrganizationDocumentCommand, ) from src.application.contracts import IHashService, IJwtService, ILogger from src.infrastructure.config import settings @@ -92,11 +96,32 @@ def get_create_organization_wallets_command( return CreateOrganizationWalletsCommand(uow, logger) -def get_upload_organization_document_command( +def get_list_organization_wallets_command( uow: IUnitOfWork = Depends(get_unit_of_work), logger: ILogger = Depends(get_logger), -) -> UploadOrganizationDocumentCommand: - return UploadOrganizationDocumentCommand(uow, get_s3_documents_service(), logger) +) -> ListOrganizationWalletsCommand: + return ListOrganizationWalletsCommand(uow, logger) + + +def get_get_organization_mnemonic_command( + uow: IUnitOfWork = Depends(get_unit_of_work), + logger: ILogger = Depends(get_logger), +) -> GetOrganizationMnemonicCommand: + return GetOrganizationMnemonicCommand(uow, logger) + + +def get_get_organization_secret_keys_command( + uow: IUnitOfWork = Depends(get_unit_of_work), + logger: ILogger = Depends(get_logger), +) -> GetOrganizationSecretKeysCommand: + return GetOrganizationSecretKeysCommand(uow, logger) + + +def get_put_organization_document_command( + uow: IUnitOfWork = Depends(get_unit_of_work), + logger: ILogger = Depends(get_logger), +) -> PutOrganizationDocumentCommand: + return PutOrganizationDocumentCommand(uow, get_s3_documents_service(), logger) def get_list_organizations_command( @@ -106,6 +131,13 @@ def get_list_organizations_command( return ListOrganizationsCommand(uow, logger) +def get_search_parties_command( + uow: IUnitOfWork = Depends(get_unit_of_work), + logger: ILogger = Depends(get_logger), +) -> SearchPartiesCommand: + return SearchPartiesCommand(uow, logger) + + def get_get_organization_command( uow: IUnitOfWork = Depends(get_unit_of_work), logger: ILogger = Depends(get_logger), @@ -155,15 +187,15 @@ def get_update_purchase_request_status_command( return UpdatePurchaseRequestStatusCommand(uow, logger) +def get_update_purchase_request_command( + uow: IUnitOfWork = Depends(get_unit_of_work), + logger: ILogger = Depends(get_logger), +) -> UpdatePurchaseRequestCommand: + return UpdatePurchaseRequestCommand(uow, logger) + + def get_set_purchase_request_quote_command( uow: IUnitOfWork = Depends(get_unit_of_work), logger: ILogger = Depends(get_logger), ) -> SetPurchaseRequestQuoteCommand: return SetPurchaseRequestQuoteCommand(uow, logger) - -def get_set_password_command( - uow: IUnitOfWork = Depends(get_unit_of_work), - hash_service: IHashService = Depends(get_hash_service), - logger: ILogger = Depends(get_logger), -) -> SetPasswordCommand: - return SetPasswordCommand(uow, hash_service, logger) From 5d7cdf67bef61445fbb2ccfe60fd2f030328b376 Mon Sep 17 00:00:00 2001 From: dev1lfreak Date: Wed, 10 Jun 2026 19:24:41 +0300 Subject: [PATCH 4/4] fix: mr conflict rollback --- .../repositories/i_user_repository.py | 9 --- src/application/commands/__init__.py | 22 +++----- src/presentation/dependencies/commands.py | 56 ++++--------------- 3 files changed, 19 insertions(+), 68 deletions(-) diff --git a/src/application/abstractions/repositories/i_user_repository.py b/src/application/abstractions/repositories/i_user_repository.py index 4702708..eff4843 100644 --- a/src/application/abstractions/repositories/i_user_repository.py +++ b/src/application/abstractions/repositories/i_user_repository.py @@ -2,7 +2,6 @@ from abc import ABC, abstractmethod from datetime import datetime from src.application.domain.entities import UserEntity -from src.application.domain.entities.organization import PartySearchEntity class IUserRepository(ABC): @@ -27,14 +26,6 @@ class IUserRepository(ABC): async def exists_by_email(self, email: str) -> bool: raise NotImplementedError - @abstractmethod - async def search_individuals(self, *, query: str, limit: int, offset: int) -> list[PartySearchEntity]: - raise NotImplementedError - - @abstractmethod - async def count_individuals(self, *, query: str) -> int: - raise NotImplementedError - @abstractmethod async def get_password_hash(self, user_id: str) -> str: raise NotImplementedError diff --git a/src/application/commands/__init__.py b/src/application/commands/__init__.py index 06f0f6c..635cf30 100644 --- a/src/application/commands/__init__.py +++ b/src/application/commands/__init__.py @@ -1,27 +1,23 @@ +from src.application.commands.admin_login import AdminLoginCommand +from src.application.commands.admin_logout import AdminLogoutCommand from src.application.commands.admin_jwt_refresh import AdminJwtRefreshCommand from src.application.commands.get_admin_me import GetAdminMeCommand from src.application.commands.create_organization import CreateOrganizationCommand from src.application.commands.create_organization_wallets import CreateOrganizationWalletsCommand -from src.application.commands.put_organization_document import PutOrganizationDocumentCommand +from src.application.commands.upload_organization_document import UploadOrganizationDocumentCommand +from src.application.commands.set_password import SetPasswordCommand from src.application.commands.organization_commands import ( ListOrganizationsCommand, GetOrganizationCommand, - SearchPartiesCommand, UpdateOrganizationCommand, ) from src.application.commands.organization_document_commands import ( ListOrganizationDocumentsCommand, GetOrganizationDocumentCommand, ) -from src.application.commands.organization_wallet_commands import ( - GetOrganizationMnemonicCommand, - GetOrganizationSecretKeysCommand, - ListOrganizationWalletsCommand, -) from src.application.commands.purchase_request_commands import ( ListPurchaseRequestsCommand, GetPurchaseRequestCommand, - UpdatePurchaseRequestCommand, UpdatePurchaseRequestStatusCommand, SetPurchaseRequestQuoteCommand, ) @@ -33,19 +29,15 @@ __all__ = [ 'GetAdminMeCommand', 'CreateOrganizationCommand', 'CreateOrganizationWalletsCommand', - 'PutOrganizationDocumentCommand', + 'UploadOrganizationDocumentCommand', 'ListOrganizationsCommand', 'GetOrganizationCommand', - 'SearchPartiesCommand', 'UpdateOrganizationCommand', 'ListPurchaseRequestsCommand', 'GetPurchaseRequestCommand', - 'UpdatePurchaseRequestCommand', 'UpdatePurchaseRequestStatusCommand', 'SetPurchaseRequestQuoteCommand', 'ListOrganizationDocumentsCommand', 'GetOrganizationDocumentCommand', - 'ListOrganizationWalletsCommand', - 'GetOrganizationMnemonicCommand', - 'GetOrganizationSecretKeysCommand', -] \ No newline at end of file + 'SetPasswordCommand', +] diff --git a/src/presentation/dependencies/commands.py b/src/presentation/dependencies/commands.py index 5cd04be..75546f4 100644 --- a/src/presentation/dependencies/commands.py +++ b/src/presentation/dependencies/commands.py @@ -10,21 +10,17 @@ from src.application.commands import ( GetAdminMeCommand, CreateOrganizationCommand, CreateOrganizationWalletsCommand, - GetOrganizationMnemonicCommand, - GetOrganizationSecretKeysCommand, GetOrganizationCommand, - ListOrganizationWalletsCommand, GetPurchaseRequestCommand, ListOrganizationsCommand, GetOrganizationDocumentCommand, ListOrganizationDocumentsCommand, ListPurchaseRequestsCommand, - SearchPartiesCommand, SetPurchaseRequestQuoteCommand, UpdateOrganizationCommand, - UpdatePurchaseRequestCommand, UpdatePurchaseRequestStatusCommand, - PutOrganizationDocumentCommand, + UploadOrganizationDocumentCommand, + SetPasswordCommand, ) from src.application.contracts import IHashService, IJwtService, ILogger from src.infrastructure.config import settings @@ -96,32 +92,11 @@ def get_create_organization_wallets_command( return CreateOrganizationWalletsCommand(uow, logger) -def get_list_organization_wallets_command( +def get_upload_organization_document_command( uow: IUnitOfWork = Depends(get_unit_of_work), logger: ILogger = Depends(get_logger), -) -> ListOrganizationWalletsCommand: - return ListOrganizationWalletsCommand(uow, logger) - - -def get_get_organization_mnemonic_command( - uow: IUnitOfWork = Depends(get_unit_of_work), - logger: ILogger = Depends(get_logger), -) -> GetOrganizationMnemonicCommand: - return GetOrganizationMnemonicCommand(uow, logger) - - -def get_get_organization_secret_keys_command( - uow: IUnitOfWork = Depends(get_unit_of_work), - logger: ILogger = Depends(get_logger), -) -> GetOrganizationSecretKeysCommand: - return GetOrganizationSecretKeysCommand(uow, logger) - - -def get_put_organization_document_command( - uow: IUnitOfWork = Depends(get_unit_of_work), - logger: ILogger = Depends(get_logger), -) -> PutOrganizationDocumentCommand: - return PutOrganizationDocumentCommand(uow, get_s3_documents_service(), logger) +) -> UploadOrganizationDocumentCommand: + return UploadOrganizationDocumentCommand(uow, get_s3_documents_service(), logger) def get_list_organizations_command( @@ -131,13 +106,6 @@ def get_list_organizations_command( return ListOrganizationsCommand(uow, logger) -def get_search_parties_command( - uow: IUnitOfWork = Depends(get_unit_of_work), - logger: ILogger = Depends(get_logger), -) -> SearchPartiesCommand: - return SearchPartiesCommand(uow, logger) - - def get_get_organization_command( uow: IUnitOfWork = Depends(get_unit_of_work), logger: ILogger = Depends(get_logger), @@ -187,15 +155,15 @@ def get_update_purchase_request_status_command( return UpdatePurchaseRequestStatusCommand(uow, logger) -def get_update_purchase_request_command( - uow: IUnitOfWork = Depends(get_unit_of_work), - logger: ILogger = Depends(get_logger), -) -> UpdatePurchaseRequestCommand: - return UpdatePurchaseRequestCommand(uow, logger) - - def get_set_purchase_request_quote_command( uow: IUnitOfWork = Depends(get_unit_of_work), logger: ILogger = Depends(get_logger), ) -> SetPurchaseRequestQuoteCommand: return SetPurchaseRequestQuoteCommand(uow, logger) + +def get_set_password_command( + uow: IUnitOfWork = Depends(get_unit_of_work), + hash_service: IHashService = Depends(get_hash_service), + logger: ILogger = Depends(get_logger), +) -> SetPasswordCommand: + return SetPasswordCommand(uow, hash_service, logger)