"""登录凭据 Schema(密码库) 凭证语义约定(与账号一致): - password / otp_secret:Create 时传入即加密存储;Update 时 None 表示不修改、'' 表示清除。 - 2FA 绑定必须携带当前 6 位动态码(otp_code)做服务端校验,防 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 位动态码(otp_secret 提供时必填) 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 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: str # 当前 6 位动态码(服务端校验通过才落库)