Files
gouki d711ad5827 feat(vault): 凭据库(密码+2FA 动态码)与 MASTER_KEY 密钥托管
credentials 表为登录凭据唯一事实源(站点×登录方式,含 oauth/2FA);账号密码读写重定向凭据层并幂等迁移历史数据;TOTP 按 RFC6238 零依赖自实现,绑定需当前动态码校验;Key Escrow 防 MASTER_KEY 遗失;前端新增凭据库视图与账号 2FA 联动。64 pytest + 16 浏览器端到端验证通过。
2026-09-05 15:54:41 +00:00

56 lines
2.0 KiB
Python
Raw Permalink 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(密码库)
凭证语义约定(与账号一致):
- password / otp_secretCreate 时传入即加密存储;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 位动态码(服务端校验通过才落库)