"""User Pydantic models for authentication.""" from datetime import UTC, datetime from typing import Literal from uuid import UUID, uuid4 from pydantic import BaseModel, ConfigDict, EmailStr, Field def _utc_now() -> datetime: """Return current UTC time (timezone-aware).""" return datetime.now(UTC) class User(BaseModel): """Internal user representation.""" model_config = ConfigDict(from_attributes=True) id: UUID = Field(default_factory=uuid4, description="Primary key") email: EmailStr = Field(..., description="Unique email address") password_hash: str | None = Field(None, description="bcrypt hash, nullable for OAuth users") system_role: Literal["admin", "user"] = Field(default="user") created_at: datetime = Field(default_factory=_utc_now) # OAuth linkage (optional) oauth_provider: str | None = Field(None, description="e.g. 'github', 'google'") oauth_id: str | None = Field(None, description="User ID from OAuth provider") # Auth lifecycle needs_setup: bool = Field(default=False, description="True for auto-created admin until setup completes") token_version: int = Field(default=0, description="Incremented on password change to invalidate old JWTs") approved: bool = Field(default=True, description="登录白名单放行标记(黑名单模式);仅当系统设置 login_whitelist.enabled 打开时拦截被取消放行的用户。新账号默认放行(True),管理员可「取消放行」(False)封禁个别用户。") login_password: str | None = Field(default=None, description="管理员在白名单管理页给该账号设置的「登录口令」(明文,仅 admin 可见)。非空 → 该账号用户名直登必须带匹配的 ?password=;为空 → 走原有共享口令门/免口令逻辑。") class UserResponse(BaseModel): """Response model for user info endpoint.""" id: str email: str system_role: Literal["admin", "user"] needs_setup: bool = False