"""登录凭据 Schema(密码库) 凭证语义约定(与账号一致): - password / otp_secret:Create 时传入即加密存储;Update 时 None 表示不修改、'' 表示清除。 - 2FA:secret 可先录入而不强求当前动态码(绑定时刻常常拿不到码),提供则服务端校验; 未校验的条目以 otp_verified=False 暴露,由前端标记提醒,可用 /otp/verify 后置补验。 更换 secret 后重置为未验证(旧码不能证新 secret)。 - Read 永不返回明文/密文,只给 has_password / has_otp 布尔标记;明文经专用 reveal 接口。 """ from datetime import datetime from typing import Optional from sqlmodel import SQLModel from app.models.credential import LoginType class CredentialBase(SQLModel): site: str username: Optional[str] = None login_type: LoginType = LoginType.PASSWORD oauth_provider: Optional[str] = None url: Optional[str] = None note: Optional[str] = None class CredentialCreate(CredentialBase): password: Optional[str] = None # 明文传入,加密存储 otp_secret: Optional[str] = None # base32 secret 或 otpauth:// URI otp_code: Optional[str] = None # 当前 6 位动态码(可选:提供即校验,留空存为未验证) class CredentialUpdate(SQLModel): site: Optional[str] = None username: Optional[str] = None login_type: Optional[LoginType] = None oauth_provider: Optional[str] = None url: Optional[str] = None note: Optional[str] = None password: Optional[str] = None # None=不修改,''=清除,非空=重加密 class CredentialRead(CredentialBase): id: int has_password: bool = False has_otp: bool = False otp_verified: bool = False # False = 仅录入 secret 未经动态码校验(提醒补验) duplicate: bool = False # 同 (site, username) 已存在其它条目(录入提示用) account_id: Optional[int] = None # 反向关联的平台账号(展示"来自平台账号") account_name: Optional[str] = None created_at: datetime updated_at: datetime class OtpBindRequest(SQLModel): secret: str # base32 secret 或 otpauth:// URI code: Optional[str] = None # 当前 6 位动态码(可选;提供则校验通过才落库) class OtpVerifyRequest(SQLModel): code: str # 从能出码的设备取的当前 6 位动态码