0.2.38: optional two-factor authentication (authenticator-app TOTP)

Per-user opt-in 2FA: enroll from the Two-factor option next to Change
password (QR + confirm code + 8 one-time recovery codes), login becomes
two-step for enrolled users, disable requires the account password.
Escape hatch for lost phones: reset-2fa CLI + Reset Two-Factor StartOS
action. Second-factor guesses share the login rate limiter; the pending
login window expires after 5 minutes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jonathan Kirkwood
2026-07-11 22:08:04 +02:00
co-authored by Claude Fable 5
parent 053bfeab23
commit 0822eca887
20 changed files with 846 additions and 41 deletions
@@ -0,0 +1,33 @@
"""add users TOTP two-factor columns (secret, enabled flag, recovery codes)
Revision ID: f2a3b4c5d6e7
Revises: e1f2a3b4c5d6
Create Date: 2026-07-11 09:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = 'f2a3b4c5d6e7'
down_revision: Union[str, None] = 'e1f2a3b4c5d6'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.add_column(sa.Column('totp_secret', sa.String(), nullable=True))
batch_op.add_column(
sa.Column('totp_enabled', sa.Boolean(), nullable=False, server_default=sa.false())
)
batch_op.add_column(sa.Column('totp_recovery_codes', sa.String(), nullable=True))
def downgrade() -> None:
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.drop_column('totp_recovery_codes')
batch_op.drop_column('totp_enabled')
batch_op.drop_column('totp_secret')