credentials 表为登录凭据唯一事实源(站点×登录方式,含 oauth/2FA);账号密码读写重定向凭据层并幂等迁移历史数据;TOTP 按 RFC6238 零依赖自实现,绑定需当前动态码校验;Key Escrow 防 MASTER_KEY 遗失;前端新增凭据库视图与账号 2FA 联动。64 pytest + 16 浏览器端到端验证通过。
56 lines
2.0 KiB
Python
56 lines
2.0 KiB
Python
"""登录凭据 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 位动态码(服务端校验通过才落库)
|