This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
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))
|
||||
Reference in New Issue
Block a user