You've already forked RekomenciBackend
feat(): yandex sign up interactor
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
from typing import override, Any
|
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
|
from typing import Any, override
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from template_project.application.common.containers import SecretString
|
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,
|
OAuthClient,
|
||||||
OAuthExchangeCodeError,
|
OAuthExchangeCodeError,
|
||||||
OAuthLoadUserInfoError,
|
OAuthLoadUserInfoError,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ AuthIdentityId = NewType("AuthIdentityId", UUID)
|
|||||||
|
|
||||||
class AuthMethod(StrEnum):
|
class AuthMethod(StrEnum):
|
||||||
EMAIL = "email"
|
EMAIL = "email"
|
||||||
|
YANDEX = "yandex"
|
||||||
|
|
||||||
|
|
||||||
@to_entity
|
@to_entity
|
||||||
|
|||||||
@@ -12,3 +12,13 @@ class UserAlreadyExistsError(ApplicationError):
|
|||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"User with identifier={self.identifier!r} and auth method={self.auth_method.value} already exists"
|
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.access_token.entity_factory import AccessTokenFactory
|
||||||
from template_project.application.auth_identity.data_gateway import AuthIdentityDataGateway
|
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.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.containers import SecretString
|
||||||
from template_project.application.common.data_structure import to_data_structure
|
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.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.common.unit_of_work import UnitOfWork
|
||||||
from template_project.application.user.entity import User, UserId
|
from template_project.application.user.entity import User, UserId
|
||||||
from template_project.application.user.password_utils import PasswordHasher
|
from template_project.application.user.password_utils import PasswordHasher
|
||||||
@@ -24,18 +29,19 @@ class AuthIdentityInteractor:
|
|||||||
auth_identity_data_gateway: AuthIdentityDataGateway
|
auth_identity_data_gateway: AuthIdentityDataGateway
|
||||||
access_token_factory: AccessTokenFactory
|
access_token_factory: AccessTokenFactory
|
||||||
access_token_cryptographer: AccessTokenCryptographer
|
access_token_cryptographer: AccessTokenCryptographer
|
||||||
|
yandex_oauth_client: YandexOAuthClient
|
||||||
|
|
||||||
async def sign_up_email(
|
async def sign_up_email(
|
||||||
self,
|
self,
|
||||||
email: str,
|
email: str,
|
||||||
password: SecretString,
|
password: SecretString,
|
||||||
) -> UserSignUpResponse:
|
) -> 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,
|
method=AuthMethod.EMAIL,
|
||||||
identifier=email,
|
identifier=email,
|
||||||
)
|
)
|
||||||
|
|
||||||
if existing_user:
|
if existing_auth_identity:
|
||||||
raise UserAlreadyExistsError(identifier=email, auth_method=AuthMethod.EMAIL)
|
raise UserAlreadyExistsError(identifier=email, auth_method=AuthMethod.EMAIL)
|
||||||
|
|
||||||
hashed_password = self.password_hasher.hash(password)
|
hashed_password = self.password_hasher.hash(password)
|
||||||
@@ -61,3 +67,47 @@ class AuthIdentityInteractor:
|
|||||||
await self.unit_of_work.commit()
|
await self.unit_of_work.commit()
|
||||||
|
|
||||||
return UserSignUpResponse(user_id=user.id, access_token=crypted_access_token)
|
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}"
|
return f"http://{self.host}:{self.port}"
|
||||||
|
|
||||||
|
|
||||||
|
@to_configuration
|
||||||
|
class YandexOAuthConfiguration:
|
||||||
|
client_id: str
|
||||||
|
client_secret: SecretString
|
||||||
|
|
||||||
|
|
||||||
@to_configuration
|
@to_configuration
|
||||||
class Configuration:
|
class Configuration:
|
||||||
server: ServerConfiguration
|
server: ServerConfiguration
|
||||||
database: DatabaseConfiguration
|
database: DatabaseConfiguration
|
||||||
access_token: AccessTokenConfiguration
|
access_token: AccessTokenConfiguration
|
||||||
|
yandex_oauth: YandexOAuthConfiguration
|
||||||
|
|
||||||
|
|
||||||
retort = Retort(
|
retort = Retort(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from template_project.web_api.configuration import (
|
|||||||
Configuration,
|
Configuration,
|
||||||
DatabaseConfiguration,
|
DatabaseConfiguration,
|
||||||
ServerConfiguration,
|
ServerConfiguration,
|
||||||
|
YandexOAuthConfiguration,
|
||||||
)
|
)
|
||||||
from template_project.web_api.ioc.connection import ConnectionProvider
|
from template_project.web_api.ioc.connection import ConnectionProvider
|
||||||
from template_project.web_api.ioc.cryptographer import CryptographerProvider
|
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.factory import FactoryProvider
|
||||||
from template_project.web_api.ioc.idp import IdPProvider
|
from template_project.web_api.ioc.idp import IdPProvider
|
||||||
from template_project.web_api.ioc.interactor import InteractorProvider
|
from template_project.web_api.ioc.interactor import InteractorProvider
|
||||||
|
from template_project.web_api.ioc.oauth import OAuthClientProvider
|
||||||
|
|
||||||
|
|
||||||
def make_ioc(configuration: Configuration) -> AsyncContainer:
|
def make_ioc(configuration: Configuration) -> AsyncContainer:
|
||||||
@@ -24,10 +26,12 @@ def make_ioc(configuration: Configuration) -> AsyncContainer:
|
|||||||
InteractorProvider(),
|
InteractorProvider(),
|
||||||
DataGatewayProvider(),
|
DataGatewayProvider(),
|
||||||
CryptographerProvider(),
|
CryptographerProvider(),
|
||||||
|
OAuthClientProvider(),
|
||||||
validation_settings=STRICT_VALIDATION,
|
validation_settings=STRICT_VALIDATION,
|
||||||
context={
|
context={
|
||||||
ServerConfiguration: configuration.server,
|
ServerConfiguration: configuration.server,
|
||||||
DatabaseConfiguration: configuration.database,
|
DatabaseConfiguration: configuration.database,
|
||||||
AccessTokenConfiguration: configuration.access_token,
|
AccessTokenConfiguration: configuration.access_token,
|
||||||
|
YandexOAuthConfiguration: configuration.yandex_oauth,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user