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:
co-authored by
Claude Fable 5
parent
053bfeab23
commit
0822eca887
@@ -87,6 +87,28 @@ def reset_password(args: argparse.Namespace) -> None:
|
||||
print(f"Password reset for {user.name} ({user.username}).")
|
||||
|
||||
|
||||
def reset_2fa(args: argparse.Namespace) -> None:
|
||||
"""Clear a user's two-factor enrollment so they can sign in with password alone."""
|
||||
run_migrations()
|
||||
|
||||
with Session(engine) as session:
|
||||
user = _find_user(session, args.username, args.email)
|
||||
if user is None:
|
||||
who = args.username or args.email
|
||||
print(f"Error: no user found for '{who}'.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not user.totp_enabled and not user.totp_secret:
|
||||
print(f"{user.name} ({user.username}) does not have two-factor enabled.")
|
||||
return
|
||||
user.totp_secret = None
|
||||
user.totp_enabled = False
|
||||
user.totp_recovery_codes = None
|
||||
session.add(user)
|
||||
session.commit()
|
||||
print(f"Two-factor disabled for {user.name} ({user.username}). "
|
||||
"They can sign in with just their password and re-enroll from the app.")
|
||||
|
||||
|
||||
def enable_investor_logins(args: argparse.Namespace) -> None:
|
||||
"""Give every no-login investor account the shared default password and enable sign-in.
|
||||
|
||||
@@ -325,6 +347,10 @@ def main() -> None:
|
||||
reset.add_argument("--email", required=False, default=None)
|
||||
reset.add_argument("--password", required=True)
|
||||
|
||||
r2fa = sub.add_parser("reset-2fa", help="Clear a user's two-factor enrollment (lost phone)")
|
||||
r2fa.add_argument("--username", required=False, default=None)
|
||||
r2fa.add_argument("--email", required=False, default=None)
|
||||
|
||||
sub.add_parser("list-users", help="List all user accounts")
|
||||
|
||||
sub.add_parser(
|
||||
@@ -357,6 +383,8 @@ def main() -> None:
|
||||
create_user(args)
|
||||
elif args.command == "reset-password":
|
||||
reset_password(args)
|
||||
elif args.command == "reset-2fa":
|
||||
reset_2fa(args)
|
||||
elif args.command == "list-users":
|
||||
list_users(args)
|
||||
elif args.command == "enable-investor-logins":
|
||||
|
||||
Reference in New Issue
Block a user