feat(): yandex sign up interactor

This commit is contained in:
doas root
2025-11-18 23:53:58 +03:00
parent beeca57c1e
commit 0992e6c038
9 changed files with 93 additions and 5 deletions
@@ -1,10 +1,10 @@
from typing import override, Any
from json import JSONDecodeError
from typing import Any, override
import httpx
from template_project.application.common.containers import SecretString
from template_project.application.common.oauth_client import (
from template_project.application.common.oauth.yandex import (
OAuthClient,
OAuthExchangeCodeError,
OAuthLoadUserInfoError,
@@ -13,6 +13,7 @@ AuthIdentityId = NewType("AuthIdentityId", UUID)
class AuthMethod(StrEnum):
EMAIL = "email"
YANDEX = "yandex"
@to_entity
@@ -12,3 +12,13 @@ class UserAlreadyExistsError(ApplicationError):
@override
def __str__(self) -> str:
return f"User with identifier={self.identifier!r} and auth method={self.auth_method.value} already exists"
@to_error
class AuthError(ApplicationError):
pass
@to_error
class InvalidCodeError(ApplicationError):
pass
@@ -2,10 +2,15 @@ from template_project.application.access_token.cryptographer import AccessTokenC
from template_project.application.access_token.entity_factory import AccessTokenFactory
from template_project.application.auth_identity.data_gateway import AuthIdentityDataGateway
from template_project.application.auth_identity.entity import AuthIdentity, AuthMethod
from template_project.application.auth_identity.errors import UserAlreadyExistsError
from template_project.application.auth_identity.errors import AuthError, InvalidCodeError, UserAlreadyExistsError
from template_project.application.common.containers import SecretString
from template_project.application.common.data_structure import to_data_structure
from template_project.application.common.interactor import to_interactor
from template_project.application.common.oauth.yandex import (
OAuthClient as YandexOAuthClient,
OAuthExchangeCodeError,
OAuthLoadUserInfoError,
)
from template_project.application.common.unit_of_work import UnitOfWork
from template_project.application.user.entity import User, UserId
from template_project.application.user.password_utils import PasswordHasher
@@ -24,18 +29,19 @@ class AuthIdentityInteractor:
auth_identity_data_gateway: AuthIdentityDataGateway
access_token_factory: AccessTokenFactory
access_token_cryptographer: AccessTokenCryptographer
yandex_oauth_client: YandexOAuthClient
async def sign_up_email(
self,
email: str,
password: SecretString,
) -> UserSignUpResponse:
existing_user = await self.auth_identity_data_gateway.load_by_method_and_identifier(
existing_auth_identity = await self.auth_identity_data_gateway.load_by_method_and_identifier(
method=AuthMethod.EMAIL,
identifier=email,
)
if existing_user:
if existing_auth_identity:
raise UserAlreadyExistsError(identifier=email, auth_method=AuthMethod.EMAIL)
hashed_password = self.password_hasher.hash(password)
@@ -61,3 +67,47 @@ class AuthIdentityInteractor:
await self.unit_of_work.commit()
return UserSignUpResponse(user_id=user.id, access_token=crypted_access_token)
async def sign_up_yandex(self, code: str) -> UserSignUpResponse:
try:
token_response = await self.yandex_oauth_client.exchange_code(code)
access_token_yandex = token_response.access_token
except OAuthExchangeCodeError as error:
raise InvalidCodeError from error
try:
user_info = await self.yandex_oauth_client.load_user_info(access_token_yandex)
except OAuthLoadUserInfoError as error:
raise AuthError from error
existing_auth_identity = await self.auth_identity_data_gateway.load_by_method_and_identifier(
method=AuthMethod.YANDEX,
identifier=user_info.user_id,
)
if existing_auth_identity:
user_id = existing_auth_identity.user_id
else:
user = User.factory()
user_id = user.id
auth_identity = AuthIdentity.factory(
user_id=user_id,
method=AuthMethod.YANDEX,
identifier=user_info.user_id,
secret_key=None,
)
await self.unit_of_work.add(user)
await self.unit_of_work.add(auth_identity)
access_token = self.access_token_factory.execute(user_id)
crypted_access_token = self.access_token_cryptographer.crypto(access_token.id)
await self.unit_of_work.add(access_token)
await self.unit_of_work.commit()
return UserSignUpResponse(user_id=user_id, access_token=crypted_access_token)
@@ -36,11 +36,18 @@ class ServerConfiguration:
return f"http://{self.host}:{self.port}"
@to_configuration
class YandexOAuthConfiguration:
client_id: str
client_secret: SecretString
@to_configuration
class Configuration:
server: ServerConfiguration
database: DatabaseConfiguration
access_token: AccessTokenConfiguration
yandex_oauth: YandexOAuthConfiguration
retort = Retort(
+4
View File
@@ -6,6 +6,7 @@ from template_project.web_api.configuration import (
Configuration,
DatabaseConfiguration,
ServerConfiguration,
YandexOAuthConfiguration,
)
from template_project.web_api.ioc.connection import ConnectionProvider
from template_project.web_api.ioc.cryptographer import CryptographerProvider
@@ -13,6 +14,7 @@ from template_project.web_api.ioc.data_gateway import DataGatewayProvider
from template_project.web_api.ioc.factory import FactoryProvider
from template_project.web_api.ioc.idp import IdPProvider
from template_project.web_api.ioc.interactor import InteractorProvider
from template_project.web_api.ioc.oauth import OAuthClientProvider
def make_ioc(configuration: Configuration) -> AsyncContainer:
@@ -24,10 +26,12 @@ def make_ioc(configuration: Configuration) -> AsyncContainer:
InteractorProvider(),
DataGatewayProvider(),
CryptographerProvider(),
OAuthClientProvider(),
validation_settings=STRICT_VALIDATION,
context={
ServerConfiguration: configuration.server,
DatabaseConfiguration: configuration.database,
AccessTokenConfiguration: configuration.access_token,
YandexOAuthConfiguration: configuration.yandex_oauth,
},
)
+16
View File
@@ -0,0 +1,16 @@
from dishka import BaseScope, Provider, Scope, provide
from template_project.adapters.oauth.yandex import YandexOAuthClient as YandexOAuthClientImpl
from template_project.application.common.oauth.yandex import OAuthClient as YandexOAuthClient
from template_project.web_api.configuration import YandexOAuthConfiguration
class OAuthClientProvider(Provider):
scope: BaseScope | None = Scope.REQUEST
@provide(scope=Scope.REQUEST)
async def yandex_oauth_client(self, configuration: YandexOAuthConfiguration) -> YandexOAuthClient:
return YandexOAuthClientImpl(
client_id=configuration.client_id,
client_secret=configuration.client_secret,
)