diff --git a/src/domain/exceptions.py b/src/domain/exceptions.py index 97997a1..5cc0030 100644 --- a/src/domain/exceptions.py +++ b/src/domain/exceptions.py @@ -6,6 +6,10 @@ class InvalidConfirmPasswordError(DomainError): """Confirm password does not match.""" pass +class InvalidFormatEmailError(DomainError): + """Invalid email format.""" + pass + class DuplicateProfileError(DomainError): """Email already used.""" pass diff --git a/src/domain/services/profile.py b/src/domain/services/profile.py index adcd1ff..8be040c 100644 --- a/src/domain/services/profile.py +++ b/src/domain/services/profile.py @@ -5,7 +5,8 @@ from src.domain.model.profile import Profile as DomainProfile from src.domain.ports.profile_repository import ProfileRepository from src.domain.ports.password_hasher import PasswordHasher -from src.domain.exceptions import DuplicateProfileError, AuthenticationError, NotFoundError, InvalidConfirmPasswordError +from src.domain.exceptions import DuplicateProfileError, AuthenticationError, NotFoundError, InvalidConfirmPasswordError, InvalidFormatEmailError +import re class ProfileService: def __init__(self, repo: ProfileRepository, hasher: PasswordHasher): @@ -27,6 +28,11 @@ def create(self, legacy: Optional[str] = None, roles: Optional[List[str]] = None ) -> DomainProfile: + + email_regex = r"^[\w\.-]+@[\w\.-]+\.\w+$" + if not re.match(email_regex, email): + raise InvalidFormatEmailError(f"Email {email} has invalid format") + if self._repo.find_by_email(email): raise DuplicateProfileError(f"Profile with email {email} already exists") @@ -62,6 +68,11 @@ def delete(self, profile_id: UUID): def login(self, email: str, password: str) -> DomainProfile: profile = self._repo.find_by_email(email) + + email_regex = r"^[\w\.-]+@[\w\.-]+\.\w+$" + if not re.match(email_regex, email): + raise InvalidFormatEmailError(f"Email {email} has invalid format") + if not profile: raise AuthenticationError(f"Invalid password or email") diff --git a/src/entrypoints/api/routers/profile.py b/src/entrypoints/api/routers/profile.py index 6ab6037..828d00d 100644 --- a/src/entrypoints/api/routers/profile.py +++ b/src/entrypoints/api/routers/profile.py @@ -8,7 +8,7 @@ from src.container import container from src.domain.lib.jwt_manager import create_access_token from src.entrypoints.api.schemas.profile import EmailUpdate, PasswordUpdate, ProfileCreate, ProfileRead, ProfileWithToken, RolesUpdate, TokenResponse, ProfileLogin, ProfilUpdate, CoachProfileRead -from src.domain.exceptions import DuplicateProfileError, InvalidConfirmPasswordError, NotFoundError, AuthenticationError +from src.domain.exceptions import DuplicateProfileError, InvalidConfirmPasswordError, InvalidFormatEmailError, NotFoundError, AuthenticationError from src.entrypoints.api.deps.auth import UserPayload, get_current_user, require_owner_or_admin from src.entrypoints.api.deps.roles import require_roles @@ -38,6 +38,8 @@ async def create_profile( raise HTTPException(status_code=400, detail=str(e)) except InvalidConfirmPasswordError as e: raise HTTPException(status_code=400, detail=str(e)) + except InvalidFormatEmailError as e: + raise HTTPException(status_code=400, detail=str(e)) token = create_access_token( subject=str(profile.id), @@ -92,7 +94,9 @@ async def login( profile = service.login(email=dto.email, password=dto.password) except AuthenticationError as e: raise HTTPException(status_code=401, detail=str(e)) - + except InvalidFormatEmailError as e: + raise HTTPException(status_code=400, detail=str(e)) + token = create_access_token( subject=str(profile.id), roles=profile.roles, diff --git a/src/entrypoints/api/schemas/profile.py b/src/entrypoints/api/schemas/profile.py index cd608fa..ecb291a 100644 --- a/src/entrypoints/api/schemas/profile.py +++ b/src/entrypoints/api/schemas/profile.py @@ -4,7 +4,7 @@ from typing import List, Optional class ProfileCreate(BaseModel): - email: EmailStr + email: str password: str confirm_password: str name: Optional[str] = None @@ -55,7 +55,7 @@ class ProfileWithToken(BaseModel): token: TokenResponse class ProfileLogin(BaseModel): - email: EmailStr + email: str password: str class ProfilUpdate(BaseModel):