You've already forked RekomenciBackend
feat(): yandex and email sign in
This commit is contained in:
@@ -22,3 +22,13 @@ class AuthError(ApplicationError):
|
||||
@to_error
|
||||
class InvalidCodeError(ApplicationError):
|
||||
pass
|
||||
|
||||
|
||||
@to_error
|
||||
class InvalidCredentialsError(ApplicationError):
|
||||
pass
|
||||
|
||||
|
||||
@to_error
|
||||
class UserNotFoundError(ApplicationError):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
from template_project.application.access_token.cryptographer import AccessTokenCryptographer
|
||||
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 AuthMethod
|
||||
from template_project.application.auth_identity.errors import (
|
||||
AuthError,
|
||||
InvalidCodeError,
|
||||
InvalidCredentialsError,
|
||||
UserNotFoundError,
|
||||
)
|
||||
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 UserId
|
||||
from template_project.application.user.password_utils import PasswordVerifying
|
||||
|
||||
|
||||
@to_data_structure
|
||||
class UserSignInResponse:
|
||||
user_id: UserId
|
||||
access_token: str
|
||||
|
||||
|
||||
@to_interactor
|
||||
class SignInInteractor:
|
||||
unit_of_work: UnitOfWork
|
||||
password_verifying: PasswordVerifying
|
||||
auth_identity_data_gateway: AuthIdentityDataGateway
|
||||
access_token_factory: AccessTokenFactory
|
||||
access_token_cryptographer: AccessTokenCryptographer
|
||||
yandex_oauth_client: YandexOAuthClient
|
||||
|
||||
async def sign_in_email(
|
||||
self,
|
||||
email: str,
|
||||
password: SecretString,
|
||||
) -> UserSignInResponse:
|
||||
auth_identity = await self.auth_identity_data_gateway.load_by_method_and_identifier(
|
||||
method=AuthMethod.EMAIL,
|
||||
identifier=email,
|
||||
)
|
||||
|
||||
if not auth_identity:
|
||||
raise UserNotFoundError
|
||||
|
||||
if not auth_identity.secret_key:
|
||||
raise InvalidCredentialsError
|
||||
|
||||
is_password_valid = self.password_verifying.verify(
|
||||
verifiable_password=password,
|
||||
hashed_password=auth_identity.secret_key,
|
||||
)
|
||||
|
||||
if not is_password_valid:
|
||||
raise InvalidCredentialsError
|
||||
|
||||
access_token = self.access_token_factory.execute(auth_identity.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 UserSignInResponse(user_id=auth_identity.user_id, access_token=crypted_access_token)
|
||||
|
||||
async def sign_in_yandex(self, code: str) -> UserSignInResponse:
|
||||
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
|
||||
|
||||
auth_identity = await self.auth_identity_data_gateway.load_by_method_and_identifier(
|
||||
method=AuthMethod.YANDEX,
|
||||
identifier=user_info.user_id,
|
||||
)
|
||||
|
||||
if not auth_identity:
|
||||
raise UserNotFoundError
|
||||
|
||||
access_token = self.access_token_factory.execute(auth_identity.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 UserSignInResponse(user_id=auth_identity.user_id, access_token=crypted_access_token)
|
||||
@@ -23,7 +23,7 @@ class UserSignUpResponse:
|
||||
|
||||
|
||||
@to_interactor
|
||||
class AuthIdentityInteractor:
|
||||
class SignUpInteractor:
|
||||
unit_of_work: UnitOfWork
|
||||
password_hasher: PasswordHasher
|
||||
auth_identity_data_gateway: AuthIdentityDataGateway
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from dishka import BaseScope, Provider, Scope, provide_all
|
||||
|
||||
from template_project.application.auth_identity.interactors.sign_up import AuthIdentityInteractor
|
||||
from template_project.application.auth_identity.interactors.sign_in import SignInInteractor
|
||||
from template_project.application.auth_identity.interactors.sign_up import SignUpInteractor
|
||||
|
||||
|
||||
class InteractorProvider(Provider):
|
||||
scope: BaseScope | None = Scope.REQUEST
|
||||
|
||||
interactors = provide_all(
|
||||
AuthIdentityInteractor,
|
||||
SignInInteractor,
|
||||
SignUpInteractor,
|
||||
)
|
||||
|
||||
@@ -3,8 +3,15 @@ from dishka.integrations.fastapi import DishkaRoute
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from pydantic import BaseModel, SecretStr
|
||||
|
||||
from template_project.application.auth_identity.errors import AuthError, InvalidCodeError
|
||||
from template_project.application.auth_identity.interactors.sign_up import AuthIdentityInteractor
|
||||
from template_project.application.auth_identity.errors import (
|
||||
AuthError,
|
||||
InvalidCodeError,
|
||||
InvalidCredentialsError,
|
||||
UserAlreadyExistsError,
|
||||
UserNotFoundError,
|
||||
)
|
||||
from template_project.application.auth_identity.interactors.sign_in import SignInInteractor
|
||||
from template_project.application.auth_identity.interactors.sign_up import SignUpInteractor
|
||||
from template_project.application.common.containers import SecretString
|
||||
|
||||
router = APIRouter(route_class=DishkaRoute)
|
||||
@@ -22,14 +29,18 @@ class EmailSignUpResponse(BaseModel):
|
||||
@router.post("/auth/sign_up/email")
|
||||
async def sign_up_email(
|
||||
request: EmailSignUpRequest,
|
||||
interactor: FromDishka[AuthIdentityInteractor],
|
||||
interactor: FromDishka[SignUpInteractor],
|
||||
) -> EmailSignUpResponse:
|
||||
response_interactor = await interactor.sign_up_email(
|
||||
email=request.email, password=SecretString(request.password.get_secret_value())
|
||||
)
|
||||
return EmailSignUpResponse(
|
||||
access_token=response_interactor.access_token,
|
||||
)
|
||||
try:
|
||||
response_interactor = await interactor.sign_up_email(
|
||||
email=request.email, password=SecretString(request.password.get_secret_value())
|
||||
)
|
||||
except UserAlreadyExistsError as error:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail="User with this email already exists"
|
||||
) from error
|
||||
|
||||
return EmailSignUpResponse(access_token=response_interactor.access_token)
|
||||
|
||||
|
||||
class YandexSignUpRequest(BaseModel):
|
||||
@@ -43,7 +54,7 @@ class YandexSignUpResponse(BaseModel):
|
||||
@router.post("/auth/sign_up/yandex")
|
||||
async def sign_up_yandex(
|
||||
request: YandexSignUpRequest,
|
||||
interactor: FromDishka[AuthIdentityInteractor],
|
||||
interactor: FromDishka[SignUpInteractor],
|
||||
) -> YandexSignUpResponse:
|
||||
try:
|
||||
response_interactor = await interactor.sign_up_yandex(code=request.code)
|
||||
@@ -52,3 +63,59 @@ async def sign_up_yandex(
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication failed") from error
|
||||
|
||||
return YandexSignUpResponse(access_token=response_interactor.access_token)
|
||||
|
||||
|
||||
class EmailSignInRequest(BaseModel):
|
||||
email: str
|
||||
password: SecretStr
|
||||
|
||||
|
||||
class EmailSignInResponse(BaseModel):
|
||||
access_token: str
|
||||
|
||||
|
||||
@router.post("/auth/sign_in/email")
|
||||
async def sign_in_email(
|
||||
request: EmailSignInRequest,
|
||||
interactor: FromDishka[SignInInteractor],
|
||||
) -> EmailSignInResponse:
|
||||
try:
|
||||
response_interactor = await interactor.sign_in_email(
|
||||
email=request.email, password=SecretString(request.password.get_secret_value())
|
||||
)
|
||||
|
||||
except UserNotFoundError as error:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") from error
|
||||
|
||||
except InvalidCredentialsError as error:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials") from error
|
||||
|
||||
return EmailSignInResponse(access_token=response_interactor.access_token)
|
||||
|
||||
|
||||
class YandexSignInRequest(BaseModel):
|
||||
code: str
|
||||
|
||||
|
||||
class YandexSignInResponse(BaseModel):
|
||||
access_token: str
|
||||
|
||||
|
||||
@router.post("/auth/sign_in/yandex")
|
||||
async def sign_in_yandex(
|
||||
request: YandexSignInRequest,
|
||||
interactor: FromDishka[SignInInteractor],
|
||||
) -> YandexSignInResponse:
|
||||
try:
|
||||
response_interactor = await interactor.sign_in_yandex(code=request.code)
|
||||
|
||||
except InvalidCodeError as error:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authorization code") from error
|
||||
|
||||
except AuthError as error:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication failed") from error
|
||||
|
||||
except UserNotFoundError as error:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") from error
|
||||
|
||||
return YandexSignInResponse(access_token=response_interactor.access_token)
|
||||
|
||||
Reference in New Issue
Block a user