feat: ✨ two factor authentication
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import pyotp
|
||||
import qrcode
|
||||
import secrets
|
||||
import json
|
||||
import base64
|
||||
from io import BytesIO
|
||||
from flask import current_app
|
||||
|
||||
def generate_totp_secret():
|
||||
"""Generate a new TOTP secret"""
|
||||
return pyotp.random_base32()
|
||||
|
||||
def get_totp_uri(secret, email, issuer_name="IPAM"):
|
||||
"""Generate TOTP URI for QR code"""
|
||||
totp = pyotp.TOTP(secret)
|
||||
return totp.provisioning_uri(
|
||||
name=email,
|
||||
issuer_name=issuer_name
|
||||
)
|
||||
|
||||
def generate_qr_code(uri):
|
||||
"""Generate QR code image from URI"""
|
||||
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||||
qr.add_data(uri)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
buffer = BytesIO()
|
||||
img.save(buffer, format='PNG')
|
||||
buffer.seek(0)
|
||||
return base64.b64encode(buffer.getvalue()).decode('utf-8')
|
||||
|
||||
def verify_totp(secret, code):
|
||||
"""Verify a TOTP code"""
|
||||
if not secret or not code:
|
||||
return False
|
||||
try:
|
||||
totp = pyotp.TOTP(secret)
|
||||
return totp.verify(code, valid_window=1) # Allow 1 time step window for clock skew
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def generate_backup_codes(count=10):
|
||||
"""Generate backup codes for 2FA"""
|
||||
return [secrets.token_urlsafe(8).upper() for _ in range(count)]
|
||||
|
||||
def verify_backup_code(backup_codes_json, code):
|
||||
"""Verify a backup code and remove it if valid"""
|
||||
if not backup_codes_json or not code:
|
||||
return False, None
|
||||
|
||||
try:
|
||||
codes = json.loads(backup_codes_json)
|
||||
code_upper = code.upper().strip()
|
||||
if code_upper in codes:
|
||||
codes.remove(code_upper)
|
||||
return True, json.dumps(codes) if codes else None
|
||||
return False, None
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
return False, None
|
||||
|
||||
def format_backup_codes(codes):
|
||||
"""Format backup codes for display (group in pairs)"""
|
||||
formatted = []
|
||||
for i in range(0, len(codes), 2):
|
||||
if i + 1 < len(codes):
|
||||
formatted.append(f"{codes[i]} {codes[i+1]}")
|
||||
else:
|
||||
formatted.append(codes[i])
|
||||
return formatted
|
||||
|
||||
Reference in New Issue
Block a user