- Migration 062: adds users.passwordless_enabled and system_config.allow_passwordless (both default false)
- User model: passwordless_enabled field after must_change_password
- SystemConfig model: allow_passwordless field after enforce_mfa_new_users
- auth.py login(): block passwordless-enabled accounts from password login path (403) with audit log
- auth.py auth_status(): change has_passkeys query to full COUNT, add passkey_count + passwordless_enabled to response
- auth.py get_current_user(): add /api/auth/passkeys/login/begin and /login/complete to lock_exempt set
- passkeys.py: add PasswordlessEnableRequest + PasswordlessDisableRequest schemas
- passkeys.py: PUT /passwordless/enable — verify password, check system config, require >= 2 passkeys, set flag
- passkeys.py: POST /passwordless/disable/begin — generate user-bound challenge for passkey auth ceremony
- passkeys.py: PUT /passwordless/disable — verify passkey auth response, clear flag, update sign count
- passkeys.py: PasskeyLoginCompleteRequest.unlock field — passkey re-auth into locked session without new session
- passkeys.py: delete guard — 409 if passwordless user attempts to drop below 2 passkeys
- schemas/admin.py: add passwordless_enabled to UserListItem + UserDetailResponse; add allow_passwordless to SystemConfigResponse + SystemConfigUpdate; add TogglePasswordlessRequest
- admin.py: PUT /users/{user_id}/passwordless — admin-only disable (enabled=False only), revokes all sessions, audit log
- admin.py: update_system_config handles allow_passwordless field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
31 lines
1.1 KiB
Python
31 lines
1.1 KiB
Python
from sqlalchemy import Boolean, CheckConstraint, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
from datetime import datetime
|
|
from app.database import Base
|
|
|
|
|
|
class SystemConfig(Base):
|
|
"""
|
|
Singleton system configuration table (always id=1).
|
|
Stores global toggles for registration, MFA enforcement, etc.
|
|
"""
|
|
__tablename__ = "system_config"
|
|
__table_args__ = (
|
|
CheckConstraint("id = 1", name="ck_system_config_singleton"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
allow_registration: Mapped[bool] = mapped_column(
|
|
Boolean, default=False, server_default="false"
|
|
)
|
|
enforce_mfa_new_users: Mapped[bool] = mapped_column(
|
|
Boolean, default=False, server_default="false"
|
|
)
|
|
allow_passwordless: Mapped[bool] = mapped_column(
|
|
Boolean, default=False, server_default="false"
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(default=func.now(), server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
default=func.now(), onupdate=func.now(), server_default=func.now()
|
|
)
|