import re import secrets import uuid from pwdlib import PasswordHash KEY_PREFIX = "urmi_live_" KEY_RANDOM_BYTES = 24 NAME_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9 _-]{1,59}$") password_hash = PasswordHash.recommended() def validate_key_name(value: str) -> str: name = value.strip() if not NAME_PATTERN.match(name): raise ValueError("Key name must be 2–60 characters using letters, numbers, spaces, _ or -") return name def generate_api_token(key_id: uuid.UUID) -> str: return f"{KEY_PREFIX}{key_id.hex}_{secrets.token_urlsafe(KEY_RANDOM_BYTES)}" def parse_api_token(token: str) -> tuple[uuid.UUID, str] | None: if not token.startswith(KEY_PREFIX): return None rest = token[len(KEY_PREFIX) :] if "_" not in rest: return None key_hex, _secret = rest.split("_", 1) try: return uuid.UUID(hex=key_hex), token except ValueError: return None def token_prefix(token: str) -> str: visible = token[:16] return f"{visible}…" def hash_api_token(token: str) -> str: return password_hash.hash(token) def verify_api_token(token: str, stored_hash: str) -> bool: return password_hash.verify(token, stored_hash)