48 lines
1.2 KiB
Python
48 lines
1.2 KiB
Python
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)
|