Files
vps-manager/app/schemas/account.py
T
gouki c5982a5706 feat(2fa): 动态码改为可选校验,未验证条目支持就地补验
痛点:secret 多为历史留存,录入那一刻手上常常没有能出码的验证器,
原先「绑定必须 secret + 当前 6 位码校验通过」直接把用户挡在门外。
改为「能验则验,没验就标」,抄错风险由角标持续提醒 + 后置补验兜住。

- 后端:新增 otp_verified_at 记账;code 可选(填了必须通过,错码 400
  且不覆盖已绑 secret);POST /{id}/otp/verify 补验转正;更换 secret
  一律重置为未验证;_migrate_assets_db 幂等补列并回填旧数据,避免升级
  后全量误报未验证
- 前端:列表区分「2FA 已验证/未验证」徽章,展开区未验证提醒条 + 就地
  补验输入;账号查看密码弹窗增加未验证提示;移除 CredentialModal.save()
  里的必填预校验(这是留空保存连请求都不发出的根因)
- 测试:pytest 74 绿(新增契约反转 + 6 个补验用例 + 迁移回填用例);
  Playwright 端到端 13/13(留空保存 → 未验证角标 → 错码 400 保持未验证
  → 正码补验转正 → 移动端 375px 无横向溢出 → 无 JS 运行时报错)
- 文档:goal-spec §1/§2/§4/§5/§6/§7/§9 同步新决策,替换旧的必填校验约定
2026-09-18 08:40:09 +00:00

47 lines
1.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""平台账号 Schema
凭证语义约定:
- login_password / api_config:Create 时传入即加密存储;Update 时 None 表示不修改。
- login_password 的唯一事实源在 credentials 表(账号通过 credential_id 关联),
本 schema 字段仅为录入入口,服务层重定向写入凭据。
- Read 永不返回凭证明文/密文,只给布尔标记(has_login_password / has_api_config / has_otp / otp_verified)。
"""
from datetime import datetime
from typing import Optional
from sqlmodel import SQLModel
class AccountBase(SQLModel):
name: str
platform: Optional[str] = None
remark: Optional[str] = None
login_user: Optional[str] = None
class AccountCreate(AccountBase):
login_password: Optional[str] = None # 登录密码(明文传入,加密存储)
api_config: Optional[str] = None # API 配置 JSON 字符串(加密存储,供 SDK 同步)
class AccountUpdate(SQLModel):
name: Optional[str] = None
platform: Optional[str] = None
remark: Optional[str] = None
login_user: Optional[str] = None
login_password: Optional[str] = None # None = 不修改
api_config: Optional[str] = None # None = 不修改
class AccountRead(AccountBase):
id: int
created_at: datetime
asset_count: int = 0 # 引用该账号的资产数(列表接口填充)
has_login_password: bool = False
has_api_config: bool = False
credential_id: Optional[int] = None # 关联的登录凭据(密码唯一事实源)
has_otp: bool = False # 关联凭据是否绑定了 2FA
otp_verified: bool = False # 该 2FA 是否已过动态码校验(False = 仅录入 secret)
last_synced_at: Optional[datetime] = None