Initial commit

This commit is contained in:
2026-04-12 09:16:16 +03:00
commit 5fe8efc5d4
98 changed files with 5351 additions and 0 deletions

View File

@@ -0,0 +1 @@
from src.presentation.schemas.user import RegistrationStart, RegistrationComplete, UserLogin, LoginStart

View File

@@ -0,0 +1,89 @@
from __future__ import annotations
import re
from typing import ClassVar
from pydantic import BaseModel, EmailStr, Field, ValidationError, field_validator, model_validator
class EmailNoSubaddressing(BaseModel):
email: EmailStr = Field(title='Email', description='Email without subaddressing')
@field_validator('email')
@classmethod
def validate_and_normalize_email(cls, v: EmailStr) -> str:
email = str(v).strip().lower()
local, _, domain = email.partition('@')
if not local or not domain:
raise ValueError('Invalid email')
if '+' in local:
raise ValueError('Email subaddressing is not allowed')
if any(ord(ch) > 127 for ch in local):
raise ValueError('Email must be ASCII')
if local.startswith('.') or local.endswith('.') or '..' in local:
raise ValueError('Invalid email local part')
if not re.fullmatch(r'[A-Za-z0-9._-]+', local):
raise ValueError('Email contains запрещенные символы')
return email
class RegistrationStart(EmailNoSubaddressing):
pass
class LoginStart(EmailNoSubaddressing):
pass
class RegistrationComplete(EmailNoSubaddressing):
password: str = Field(min_length=12)
confirm_password: str = Field(min_length=12)
code: str = Field(
min_length=6,
max_length=6,
pattern=r"^\d{6}$",
)
_allowed_specials: ClassVar[str] = '!@#$%^&*()_+-=.,:;?/[]{}<>'
@field_validator('password')
@classmethod
def validate_password_policy(cls, v: str) -> str:
if len(v) < 12:
raise ValueError('Password must be at least 12 characters long')
if not any(c.islower() for c in v):
raise ValueError('Password must contain at least one lowercase letter')
if not any(c.isupper() for c in v):
raise ValueError('Password must contain at least one uppercase letter')
if not any(c.isdigit() for c in v):
raise ValueError('Password must contain at least one digit')
if not any(c in cls._allowed_specials for c in v):
raise ValueError(
'Password must contain at least one special character '
f'from: {cls._allowed_specials}'
)
if any(c.isspace() for c in v):
raise ValueError('Password must not contain whitespace')
return v
@model_validator(mode='after')
def validate_password_confirmation(self) -> 'RegistrationComplete':
if self.password != self.confirm_password:
raise ValidationError.from_exception_data(
title='Passwords do not match',
line_errors=[{
'type': 'value_error',
'loc': ('confirm_password',),
'msg': 'Passwords do not match',
'input': self.confirm_password,
}],
)
return self
class UserLogin(EmailNoSubaddressing):
password: str = Field(min_length=12)
code: str = Field(
min_length=6,
max_length=6,
pattern=r"^\d{6}$",
)