Files
吉鑫升andClaude Opus 4.8 9572b66ecb feat(白名单): 账号级登录口令——管理员设明文口令,用户凭 /login/<用户名>?password= 直登
白名单管理页可给单个账号设明文登录口令(users.login_password,可空):
设置后该账号用户名直登(/login/username)须带匹配 ?password=,且优先于全局共享口令门;
未设口令的账号行为不变。前端登录 URL 已支持透传 ?password=,无需改登录页。

- 后端:UserRow/User 新增 login_password + 仓储映射;/login/username 账号级口令闸门
  (_lookup_user_by_username 仅查不建);新管理员接口 PUT /users/{id}/login-password;
  UserListItem 暴露明文(仅 admin 列表);migration 20260625_04 + 幂等补列 SQL。
- 前端:白名单页新增「登录口令」列(设置/修改/清除 + 复制登录链接);
  core/auth api+hooks 增 setUserLoginPassword/useSetUserLoginPassword(含 Y-Log)。
- 文档:根 + 后端 CLAUDE.md 同步。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:03:39 +08:00

44 lines
2.0 KiB
Python

"""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