mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
70 lines
2.2 KiB
Python
70 lines
2.2 KiB
Python
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
"""Utilities for masking API keys before they are sent to the client.
|
|||
|
|
|
|||
|
|
The rules are simple:
|
|||
|
|
1. Only expose the last *visible* characters (default 4) of a key.
|
|||
|
|
2. Incoming masked keys are considered a placeholder – if they equal the mask of
|
|||
|
|
the already-stored key, we treat them as *unchanged* and keep the real value
|
|||
|
|
in storage.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from typing import Any, Dict, Optional
|
|||
|
|
|
|||
|
|
from api.schemas.user_configuration import UserConfiguration
|
|||
|
|
from api.services.configuration.registry import ServiceConfig
|
|||
|
|
|
|||
|
|
VISIBLE_CHARS = 4 # number of trailing characters to reveal
|
|||
|
|
MASK_CHAR = "*"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def mask_key(real_key: str, visible: int = VISIBLE_CHARS) -> str:
|
|||
|
|
"""Return a masked representation of *real_key*.
|
|||
|
|
|
|||
|
|
Example:
|
|||
|
|
>>> mask_key("sk-1234567890abcdef")
|
|||
|
|
'****************cdef'
|
|||
|
|
"""
|
|||
|
|
if real_key is None:
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
if visible <= 0 or visible >= len(real_key):
|
|||
|
|
# mask entire key or nothing to mask – edge-cases
|
|||
|
|
return MASK_CHAR * len(real_key)
|
|||
|
|
|
|||
|
|
masked_part = MASK_CHAR * (len(real_key) - visible)
|
|||
|
|
return f"{masked_part}{real_key[-visible:]}"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def is_mask_of(masked: str, real_key: str) -> bool:
|
|||
|
|
"""Return *True* if *masked* equals the mask of *real_key* under the current rules."""
|
|||
|
|
return mask_key(real_key) == masked
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# High-level helpers for UserConfiguration objects
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _mask_service(service_cfg: Optional[ServiceConfig]) -> Optional[Dict[str, Any]]:
|
|||
|
|
if service_cfg is None:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
# Work on a dict copy so we don't mutate original models
|
|||
|
|
data = service_cfg.model_dump()
|
|||
|
|
if "api_key" in data and data["api_key"]:
|
|||
|
|
data["api_key"] = mask_key(data["api_key"])
|
|||
|
|
return data
|
|||
|
|
|
|||
|
|
|
|||
|
|
def mask_user_config(config: UserConfiguration) -> Dict[str, Any]:
|
|||
|
|
"""Return a JSON-serialisable dict of *config* with every api_key masked."""
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"llm": _mask_service(config.llm),
|
|||
|
|
"tts": _mask_service(config.tts),
|
|||
|
|
"stt": _mask_service(config.stt),
|
|||
|
|
"test_phone_number": config.test_phone_number,
|
|||
|
|
"timezone": config.timezone,
|
|||
|
|
}
|