import pyotp import qrcode import io import base64 import secrets import hashlib from typing import List, Optional def generate_totp_secret() -> str: """Generates a random base32 TOTP secret.""" return pyotp.random_base32() def generate_totp_uri(secret: str, account_name: str, issuer_name: str) -> str: """Generates a Google Authenticator-compatible TOTP URI.""" return pyotp.totp.TOTP(secret).provisioning_uri(name=account_name, issuer_name=issuer_name) def generate_qr_code_base64(uri: str) -> str: """Generates a base64 encoded QR code image for a given URI.""" qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4, ) qr.add_data(uri) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white") buffered = io.BytesIO() img.save(buffered, format="PNG") return base64.b64encode(buffered.getvalue()).decode("utf-8") def verify_totp_code(secret: str, code: str) -> bool: """Verifies a TOTP code against a secret.""" totp = pyotp.TOTP(secret) return totp.verify(code) def generate_recovery_codes(num_codes: int = 10) -> List[str]: """Generates a list of random recovery codes.""" return [secrets.token_urlsafe(16) for _ in range(num_codes)] def hash_recovery_code(code: str) -> str: """Hashes a single recovery code using SHA256.""" return hashlib.sha256(code.encode('utf-8')).hexdigest() def verify_recovery_code(plain_code: str, hashed_code: str) -> bool: """Verifies a plain recovery code against its hashed version.""" return hash_recovery_code(plain_code) == hashed_code def hash_recovery_codes(codes: List[str]) -> List[str]: """Hashes a list of recovery codes.""" return [hash_recovery_code(code) for code in codes] def verify_recovery_codes(plain_code: str, hashed_codes: List[str]) -> bool: """Verifies if a plain recovery code matches any of the hashed recovery codes.""" for hashed_code in hashed_codes: if verify_recovery_code(plain_code, hashed_code): return True return False