From 68117656d36a5a944bcd2baeea3ab5d2b1b9eba1 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 30 Jul 2025 17:17:28 +0200 Subject: [PATCH 1/3] create new exception for email format --- src/domain/exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) 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 From 53b80d7f9d1a1a6a10f77dddb78c5c8a88d52148 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 30 Jul 2025 17:18:01 +0200 Subject: [PATCH 2/3] add regex rules on service and using exception format catch in router and show it --- src/domain/services/profile.py | 13 ++++++++++++- src/entrypoints/api/routers/profile.py | 8 ++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) 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, From 2b5358ba27f9c3ca2dfc1ce768bd2e4bbff5a0d3 Mon Sep 17 00:00:00 2001 From: Baptiste-Ferrand Date: Wed, 30 Jul 2025 17:18:17 +0200 Subject: [PATCH 3/3] update schema for email using str --- src/entrypoints/api/schemas/profile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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):