Ctrl K
ComunidadGuías…

api/fields/api_key_fields.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Response contracts shared by Console API key endpoints."""

from collections.abc import Iterable, Mapping
from datetime import datetime

from pydantic import field_validator

from fields.base import ResponseModel
from libs.helper import to_timestamp


class ApiKeyItem(ResponseModel):
    id: str
    type: str
    token: str
    # Dataset keys only: the knowledge bases this key is bound to. Empty = the key can
    # access every knowledge base in the tenant when both scopes are empty. App keys are always empty.
    dataset_ids: list[str] = []
    knowledge_space_ids: list[str] = []
    last_used_at: int | None = None
    created_at: int | None = None

    @field_validator("last_used_at", "created_at", mode="before")
    @classmethod
    def _normalize_timestamp(cls, value: datetime | int | None) -> int | None:
        return to_timestamp(value)


class ApiKeyList(ResponseModel):
    data: list[ApiKeyItem]


def mask_api_token(token: str) -> str:
    """Mask a secret token for list responses.

    Reveal-once: the full secret is only returned by the create endpoint. List
    endpoints expose just enough (prefix + last 4) to identify a key, never the
    full value, so an existing key's secret cannot be retrieved after creation.
    """
    if len(token) <= 8:
        return "***"
    return f"{token[:5]}...{token[-4:]}"


def build_masked_api_key_list(
    api_tokens: Iterable[object],
    bindings_by_token: Mapping[str, list[str]] | None = None,
) -> ApiKeyList:
    """Build an ApiKeyList from token records with their secrets masked.

    ``bindings_by_token`` maps an api_token id to the dataset ids it is bound to
    (from DatasetApiTokenBinding); tokens absent from the map are unbound (empty =
    access all). When omitted, preserve the scope carried by the records.
    """
    items: list[ApiKeyItem] = []
    for api_token in api_tokens:
        item = ApiKeyItem.model_validate(api_token, from_attributes=True)
        item.token = mask_api_token(item.token)
        if bindings_by_token is not None:
            item.dataset_ids = bindings_by_token.get(item.id, [])
        items.append(item)
    return ApiKeyList(data=items)