"""Pydantic schemas for authentication endpoints.""" from __future__ import annotations from pydantic import BaseModel, Field, field_validator, model_validator from app.core.roles import CANONICAL_ROLES def _validate_canonical_role(value: str | None) -> str | None: """Reject any role that is not part of the unified canonical taxonomy. The role vocabulary is closed: admin user-management must only ever mint canonical roles (see app/core/roles.py). Legacy/unknown strings (``admin``, ``superadmin``, ``technician``, …) are rejected here so junk roles can never be (re)created through the API. """ if value is None: return None role = value.strip() if role not in CANONICAL_ROLES: raise ValueError( f"Unknown role '{value}'. Allowed roles: {', '.join(CANONICAL_ROLES)}" ) return role class LoginRequest(BaseModel): email: str password: str class TokenResponse(BaseModel): access_token: str refresh_token: str token_type: str = "bearer" class RefreshRequest(BaseModel): refresh_token: str class UserOut(BaseModel): id: int email: str full_name: str phone: str | None role: str active: bool model_config = {"from_attributes": True} # ── Admin user management (self-registration is removed) ────────────── class AdminCreateUserRequest(BaseModel): """Admin-created user. The role is mandatory and must be canonical. ``role`` is deliberately NOT optional and has no default — an admin must state the intended role explicitly; the server never infers one. """ email: str = Field(min_length=1) password: str = Field(min_length=8, description="Minimum 8 characters") full_name: str = Field(min_length=1) phone: str | None = None role: str @field_validator("role") @classmethod def _role_canonical(cls, value: str) -> str: return _validate_canonical_role(value) # type: ignore[return-value] @field_validator("email") @classmethod def _lower_email(cls, value: str) -> str: return value.strip().lower() class AdminUpdateUserRequest(BaseModel): """Admin edits to an existing user: role change and/or deactivation. At least one field must be present. ``active=False`` deactivates the account (login and token refresh then fail closed). """ role: str | None = None active: bool | None = None @field_validator("role") @classmethod def _role_canonical(cls, value: str | None) -> str | None: return _validate_canonical_role(value) @model_validator(mode="after") def _at_least_one_field(self) -> "AdminUpdateUserRequest": if self.role is None and self.active is None: raise ValueError("Provide at least one of 'role' or 'active'") return self