59 lines
1.5 KiB
Python
59 lines
1.5 KiB
Python
"""Authentication metadata helpers for MXAccess Gateway clients."""
|
|
|
|
from collections.abc import Sequence
|
|
from dataclasses import dataclass
|
|
|
|
AUTHORIZATION_HEADER = "authorization"
|
|
REDACTED = "[redacted]"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ApiKey:
|
|
"""API key wrapper that avoids leaking the secret through repr output."""
|
|
|
|
value: str
|
|
|
|
def __post_init__(self) -> None:
|
|
if not self.value:
|
|
raise ValueError("api_key must not be empty")
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{type(self).__name__}({REDACTED!r})"
|
|
|
|
def bearer_value(self) -> str:
|
|
return f"Bearer {self.value}"
|
|
|
|
|
|
def auth_metadata(api_key: str | ApiKey | None) -> tuple[tuple[str, str], ...]:
|
|
"""Return gRPC metadata for API key auth."""
|
|
|
|
if api_key is None:
|
|
return ()
|
|
|
|
key = api_key.value if isinstance(api_key, ApiKey) else api_key
|
|
if not key:
|
|
return ()
|
|
|
|
return ((AUTHORIZATION_HEADER, f"Bearer {key}"),)
|
|
|
|
|
|
def merge_metadata(
|
|
api_key: str | ApiKey | None,
|
|
metadata: Sequence[tuple[str, str]] | None = None,
|
|
) -> tuple[tuple[str, str], ...]:
|
|
"""Merge caller metadata with API key metadata."""
|
|
|
|
merged = list(metadata or ())
|
|
merged.extend(auth_metadata(api_key))
|
|
return tuple(merged)
|
|
|
|
|
|
def redact_secret(text: str, secrets: Sequence[str | None]) -> str:
|
|
"""Replace known secret values with a stable redaction marker."""
|
|
|
|
redacted = text
|
|
for secret in secrets:
|
|
if secret:
|
|
redacted = redacted.replace(secret, REDACTED)
|
|
return redacted
|