Files
maddin 6fbd1bb3c2
CI / checks (push) Has been cancelled
chore: initialize public repository
2026-03-22 12:57:09 +00:00

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))