Files
vidconf/backend/schemas/auth.py
Max Ronzhin 25ffd9e678
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
feat(auth): несколько эталонных mail-доменов для верификации регистрации
Настройка «Эталон mail-домена» теперь хранит список доменов вместо
одного — email при регистрации принимается, если совпадает с любым из
них. Старое значение в БД ({"domain": str|None}) читается прозрачно
(обратная совместимость без Alembic-миграции) и переписывается в новую
форму ({"domains": [...]}) при первом же сохранении настроек. В админке
добавление/удаление доменов — списком чипов; на экране регистрации
подсказка о несовпадении домена перечисляет все эталонные варианты.
2026-07-27 20:40:23 +03:00

114 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Pydantic-схемы для аутентификации и профиля пользователя."""
import uuid
from pydantic import BaseModel, ConfigDict, EmailStr, Field
class RegisterIn(BaseModel):
"""Тело запроса регистрации нового пользователя.
`team_id` допустим только при включённой настройке инстанса
`registration_team_choice` (см. `GET /auth/registration-options`) и
существующей команде — иначе `POST /auth/register` вернёт 400.
"""
email: EmailStr
name_user: str = Field(min_length=1, max_length=255)
password: str = Field(min_length=8)
team_id: uuid.UUID | None = None
class VerifyEmailIn(BaseModel):
"""Тело запроса подтверждения email."""
token: str = Field(min_length=1)
class TokenOut(BaseModel):
"""Ответ с access-токеном; refresh-токен передаётся отдельно в httpOnly cookie."""
access_token: str
token_type: str = "bearer"
class UserOut(BaseModel):
"""Публичное представление пользователя (профиль текущего пользователя).
`email: str`, а не `EmailStr` — намеренно: это response-схема,
отражающая уже сохранённые в БД данные, а не принимающая новый ввод.
`EmailStr` дополнительно отсекает синтаксически валидные, но
зарезервированные домены (`.local`, `.test` и т.п. из RFC 6761) — валидный
email на входе (`RegisterIn`, ниже) мог быть заведён напрямую в БД (seed,
ручная миграция) с таким доменом; строгая `EmailStr` на выходе привела бы
к 500 `ResponseValidationError` для уже существующих пользователей.
"""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
email: str
name_user: str
role: str
class UserListItemOut(BaseModel):
"""Элемент списка пользователей (пикер участников конференции/мультиселект брони)."""
id: uuid.UUID
display_name: str
avatar_url: str | None = None
class ProfileUpdateIn(BaseModel):
"""Тело правки профиля текущего пользователя.
Email НЕ принимается — read-only поле профиля. `team_id` не различает
«поле не передано» и «явный `null`» через сравнение с `None` — роутер
читает `model_fields_set` (тот же приём, что `AdminUserUpdateIn.team_id`).
"""
name_user: str | None = Field(default=None, min_length=1, max_length=255)
team_id: uuid.UUID | None = None
class UserProfileOut(UserOut):
"""Профиль пользователя (свой либо открытый администратором) — `UserOut` + аватар/команда."""
avatar_url: str | None = None
team_id: uuid.UUID | None = None
team_name: str | None = None
class PasswordChangeIn(BaseModel):
"""Тело смены пароля текущим пользователем (`POST /users/me/password`).
Политика сложности `new_password` — та же, что при регистрации
(`RegisterIn.password`, min 8 символов).
"""
current_password: str
new_password: str = Field(min_length=8)
class RegistrationTeamOptionOut(BaseModel):
"""Команда в списке опций публичной карточки регистрации."""
id: uuid.UUID
name: str
class RegistrationOptionsOut(BaseModel):
"""Публичные опции формы регистрации (`GET /auth/registration-options`).
`teams` отдаётся только при `team_choice_enabled=True` — иначе пустой
список (справочник команд не раскрывается, пока выбор выключен).
`email_domains` — эталонные домены при включённой верификации регистрации
по домену email (настройка инстанса `registration_email_domain`), иначе
пустой список.
"""
team_choice_enabled: bool
teams: list[RegistrationTeamOptionOut]
email_domains: list[str] = Field(default_factory=list)