99 lines
2.8 KiB
Python
99 lines
2.8 KiB
Python
"""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
|