feat: TOTP two-factor auth for admin login
Matches Remnawave's security-first positioning (passkeys/OAuth there) with the more universal standard instead — RFC 6238 TOTP, works with Google Authenticator/Authy/1Password/anything. Implemented from scratch on stdlib only (hashlib/hmac/struct/base64) — no new dependency — and verified against the official RFC 4226 HOTP test vectors (all 10 pass exactly) before wiring it into any auth path. Per-admin, optional: setup shows the secret + otpauth:// URI (no QR render, just copyable text — didn't want to fake a QR library), confirmed by entering a real code before it's persisted. Login is now two-step when 2FA is on: password first (returns a short-lived pending_token instead of a session if totp_secret is set), then a second call with the pending_token + code creates the real session. pending_totp rows are single-use and expire in 5 minutes, cleaned up alongside the existing admin_sessions cleanup in periodic_sync. Disabling 2FA requires re-entering the current password. Verified end-to-end: enable/disable round trip, pending-token resolve+single-use+expiry, code verification against the stored secret, wrong-code and wrong-password rejection — on top of the raw HOTP correctness check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
9e6e314c94
commit
7098e59967
5 changed files with 282 additions and 11 deletions
1
bot.py
1
bot.py
|
|
@ -369,6 +369,7 @@ async def periodic_sync():
|
|||
log.exception("payment reconciliation failed")
|
||||
try:
|
||||
db.delete_expired_admin_sessions()
|
||||
db.delete_expired_pending_totp()
|
||||
except Exception:
|
||||
log.exception("expired admin session cleanup failed")
|
||||
await asyncio.sleep(90)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue