Files
disknext/sqlmodels/user_authn.py
于小丘 800c85bf8d feat: implement WebAuthn credential registration, login verification, and management
Complete the WebAuthn/Passkey flow that was previously stubbed out:
- Add ChallengeStore (Redis + TTLCache fallback) for challenge lifecycle
- Add RP config helper to extract rp_id/origin from site settings
- Fix registration start (exclude_credentials, user_id, challenge storage)
- Implement registration finish (verify + create UserAuthn & AuthIdentity)
- Add authentication options endpoint for Discoverable Credentials login
- Fix passkey login to use challenge_token and base64url encoding
- Add credential management endpoints (list/rename/delete)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 12:56:46 +08:00

107 lines
2.8 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.
from datetime import datetime
from typing import TYPE_CHECKING
from uuid import UUID
from sqlalchemy import Column, Text
from sqlmodel import Field, Relationship
from .base import SQLModelBase
from .mixin import TableBaseMixin
if TYPE_CHECKING:
from .user import User
# ==================== DTO 模型 ====================
class AuthnFinishRequest(SQLModelBase):
"""WebAuthn 注册完成请求 DTO"""
credential: str
"""前端 navigator.credentials.create() 返回的 JSON 字符串"""
name: str | None = None
"""用户自定义的凭证名称"""
class AuthnDetailResponse(SQLModelBase):
"""WebAuthn 凭证详情响应 DTO"""
id: int
"""凭证数据库 ID"""
credential_id: str
"""凭证 IDBase64URL 编码)"""
name: str | None = None
"""用户自定义的凭证名称"""
credential_device_type: str
"""凭证设备类型"""
credential_backed_up: bool
"""凭证是否已备份"""
transports: str | None = None
"""支持的传输方式"""
created_at: datetime
"""创建时间"""
class AuthnRenameRequest(SQLModelBase):
"""WebAuthn 凭证重命名请求 DTO"""
name: str = Field(max_length=100)
"""新的凭证名称"""
# ==================== 数据库模型 ====================
class UserAuthn(SQLModelBase, TableBaseMixin):
"""用户 WebAuthn 凭证模型,与 User 为多对一关系"""
credential_id: str = Field(max_length=255, unique=True, index=True)
"""凭证 IDBase64URL 编码"""
credential_public_key: str = Field(sa_column=Column(Text))
"""凭证公钥Base64URL 编码"""
sign_count: int = Field(default=0, ge=0)
"""签名计数器,用于防重放攻击"""
credential_device_type: str = Field(max_length=32)
"""凭证设备类型:'single_device''multi_device'"""
credential_backed_up: bool = Field(default=False)
"""凭证是否已备份"""
transports: str | None = Field(default=None, max_length=255)
"""支持的传输方式,逗号分隔,如 'usb,nfc,ble,internal'"""
name: str | None = Field(default=None, max_length=100)
"""用户自定义的凭证名称,便于识别"""
# 外键
user_id: UUID = Field(
foreign_key="user.id",
index=True,
ondelete="CASCADE"
)
"""所属用户UUID"""
# 关系
user: "User" = Relationship(back_populates="authns")
def to_detail_response(self) -> AuthnDetailResponse:
"""转换为详情响应 DTO"""
return AuthnDetailResponse(
id=self.id,
credential_id=self.credential_id,
name=self.name,
credential_device_type=self.credential_device_type,
credential_backed_up=self.credential_backed_up,
transports=self.transports,
created_at=self.created_at,
)