71 lines
1.8 KiB
Python
71 lines
1.8 KiB
Python
from __future__ import annotations
|
|
|
|
from base64 import urlsafe_b64encode
|
|
from datetime import datetime, timezone
|
|
import hashlib
|
|
import secrets
|
|
|
|
from cryptography.fernet import Fernet, InvalidToken
|
|
import pyotp
|
|
|
|
|
|
def utc_now() -> datetime:
|
|
return datetime.now(timezone.utc)
|
|
|
|
|
|
def _derive_fernet_key(source: str) -> bytes:
|
|
digest = hashlib.sha256(source.encode("utf-8")).digest()
|
|
return urlsafe_b64encode(digest)
|
|
|
|
|
|
def build_fernet(secret_source: str) -> Fernet:
|
|
return Fernet(_derive_fernet_key(secret_source))
|
|
|
|
|
|
def encrypt_secret(fernet: Fernet, value: str) -> str:
|
|
return fernet.encrypt(value.encode("utf-8")).decode("utf-8")
|
|
|
|
|
|
def decrypt_secret(fernet: Fernet, value: str | None) -> str | None:
|
|
if not value:
|
|
return None
|
|
try:
|
|
return fernet.decrypt(value.encode("utf-8")).decode("utf-8")
|
|
except InvalidToken:
|
|
return None
|
|
|
|
|
|
def generate_numeric_code(length: int = 6) -> str:
|
|
if length <= 0:
|
|
raise ValueError("length must be positive")
|
|
lower = 10 ** (length - 1)
|
|
upper = (10**length) - 1
|
|
return str(secrets.randbelow(upper - lower + 1) + lower)
|
|
|
|
|
|
def hash_token(token: str) -> str:
|
|
return hashlib.sha256(token.encode("utf-8")).hexdigest()
|
|
|
|
|
|
def generate_reset_token() -> str:
|
|
return secrets.token_urlsafe(48)
|
|
|
|
|
|
def normalize_otp_code(code: str) -> str:
|
|
return "".join(ch for ch in code.strip() if ch.isdigit())
|
|
|
|
|
|
def generate_totp_secret() -> str:
|
|
return pyotp.random_base32()
|
|
|
|
|
|
def build_totp_uri(*, secret: str, account_name: str, issuer: str = "Stundenfuchs") -> str:
|
|
return pyotp.TOTP(secret).provisioning_uri(name=account_name, issuer_name=issuer)
|
|
|
|
|
|
def verify_totp_code(*, secret: str, code: str) -> bool:
|
|
normalized = normalize_otp_code(code)
|
|
if len(normalized) != 6:
|
|
return False
|
|
return bool(pyotp.TOTP(secret).verify(normalized, valid_window=1))
|