From 2781699da1efad18088dd4b7ccd88a10d4f6dd7c Mon Sep 17 00:00:00 2001 From: qin-ctx Date: Thu, 19 Mar 2026 15:40:58 +0800 Subject: [PATCH] feat(client): add account/user params for root key multi-tenant auth SDK HTTP client now supports account and user parameters so root key holders can access tenant-scoped data APIs without being rejected by the server explicit-tenant requirement. Co-Authored-By: Claude Opus 4.6 --- docs/en/guides/04-authentication.md | 37 +++++++++++++++++++++++++++++ docs/zh/guides/04-authentication.md | 37 +++++++++++++++++++++++++++++ openviking_cli/client/http.py | 14 +++++++++++ openviking_cli/client/sync_http.py | 9 ++++++- 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/docs/en/guides/04-authentication.md b/docs/en/guides/04-authentication.md index aa13f983..950c3599 100644 --- a/docs/en/guides/04-authentication.md +++ b/docs/en/guides/04-authentication.md @@ -91,6 +91,43 @@ client = ov.SyncHTTPClient( } ``` +### Accessing Tenant Data with Root Key + +When using the root key to access tenant-scoped data APIs (e.g. `ls`, `find`, `sessions`), you must specify the target account and user. The server will reject the request otherwise. Admin API and system status endpoints are not affected. + +**curl** + +```bash +curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \ + -H "X-API-Key: your-secret-root-key" \ + -H "X-OpenViking-Account: acme" \ + -H "X-OpenViking-User: alice" +``` + +**Python SDK** + +```python +import openviking as ov + +client = ov.SyncHTTPClient( + url="http://localhost:1933", + api_key="your-secret-root-key", + account="acme", + user="alice", +) +``` + +**ovcli.conf** + +```json +{ + "url": "http://localhost:1933", + "api_key": "your-secret-root-key", + "account": "acme", + "user": "alice" +} +``` + ## Roles and Permissions | Role | Scope | Capabilities | diff --git a/docs/zh/guides/04-authentication.md b/docs/zh/guides/04-authentication.md index 53fbeb1b..cf937104 100644 --- a/docs/zh/guides/04-authentication.md +++ b/docs/zh/guides/04-authentication.md @@ -91,6 +91,43 @@ client = ov.SyncHTTPClient( } ``` +### 使用 Root Key 访问租户数据 + +使用 root key 访问租户级数据 API(如 `ls`、`find`、`sessions` 等)时,必须指定目标 account 和 user,否则服务端将拒绝请求。Admin API 和系统状态端点不受此限制。 + +**curl** + +```bash +curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \ + -H "X-API-Key: your-secret-root-key" \ + -H "X-OpenViking-Account: acme" \ + -H "X-OpenViking-User: alice" +``` + +**Python SDK** + +```python +import openviking as ov + +client = ov.SyncHTTPClient( + url="http://localhost:1933", + api_key="your-secret-root-key", + account="acme", + user="alice", +) +``` + +**ovcli.conf** + +```json +{ + "url": "http://localhost:1933", + "api_key": "your-secret-root-key", + "account": "acme", + "user": "alice" +} +``` + ## 角色与权限 | 角色 | 作用域 | 能力 | diff --git a/openviking_cli/client/http.py b/openviking_cli/client/http.py index 5b0689bd..50cf16c4 100644 --- a/openviking_cli/client/http.py +++ b/openviking_cli/client/http.py @@ -135,6 +135,8 @@ def __init__( url: Optional[str] = None, api_key: Optional[str] = None, agent_id: Optional[str] = None, + account: Optional[str] = None, + user: Optional[str] = None, timeout: float = 60.0, ): """Initialize AsyncHTTPClient. @@ -143,6 +145,10 @@ def __init__( url: OpenViking Server URL. If not provided, reads from ovcli.conf. api_key: API key for authentication. If not provided, reads from ovcli.conf. agent_id: Agent identifier. If not provided, reads from ovcli.conf. + account: Account identifier for multi-tenant auth. Required when using root key + to access tenant-scoped APIs. If not provided, reads from ovcli.conf. + user: User identifier for multi-tenant auth. Required when using root key + to access tenant-scoped APIs. If not provided, reads from ovcli.conf. timeout: HTTP request timeout in seconds. Default 60.0. """ if url is None: @@ -155,6 +161,8 @@ def __init__( url = cfg.get("url") api_key = api_key or cfg.get("api_key") agent_id = agent_id or cfg.get("agent_id") + account = account or cfg.get("account") + user = user or cfg.get("user") if timeout == 60.0: # only override default with config value timeout = cfg.get("timeout", 60.0) if not url: @@ -165,6 +173,8 @@ def __init__( self._url = url.rstrip("/") self._api_key = api_key self._agent_id = agent_id + self._account = account + self._user_id = user self._user = UserIdentifier.the_default_user() self._timeout = timeout self._http: Optional[httpx.AsyncClient] = None @@ -179,6 +189,10 @@ async def initialize(self) -> None: headers["X-API-Key"] = self._api_key if self._agent_id: headers["X-OpenViking-Agent"] = self._agent_id + if self._account: + headers["X-OpenViking-Account"] = self._account + if self._user_id: + headers["X-OpenViking-User"] = self._user_id self._http = httpx.AsyncClient( base_url=self._url, headers=headers, diff --git a/openviking_cli/client/sync_http.py b/openviking_cli/client/sync_http.py index 503c26ab..d8735186 100644 --- a/openviking_cli/client/sync_http.py +++ b/openviking_cli/client/sync_http.py @@ -33,10 +33,17 @@ def __init__( url: Optional[str] = None, api_key: Optional[str] = None, agent_id: Optional[str] = None, + account: Optional[str] = None, + user: Optional[str] = None, timeout: float = 60.0, ): self._async_client = AsyncHTTPClient( - url=url, api_key=api_key, agent_id=agent_id, timeout=timeout + url=url, + api_key=api_key, + agent_id=agent_id, + account=account, + user=user, + timeout=timeout, ) self._initialized = False