From f89f4ec87c059090f3823cd13d4341c344df8eef Mon Sep 17 00:00:00 2001 From: Paulo Date: Sat, 22 Aug 2026 08:23:11 +0200 Subject: [PATCH] Routes run authenticated, and the doc says how to read the caller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every extension router mounts behind the loader's identity gate — a Bearer PAT or the signed-in session — but writing-an-extension.md never said so, and its own OAuth example passes an account_id the author has no documented way to obtain. druks.accounts now exports current_account_id, so the read comes from a concern namespace instead of an internal module. The routes section states the gate and shows the read; the OAuth section names the caller's three doors — self.account_id in a run body, current_account_id.get() in a route, the handler's argument in a subscriber — and the stable-imports table carries the new namespace. --- backend/druks/accounts/__init__.py | 3 +++ docs/writing-an-extension.md | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/backend/druks/accounts/__init__.py b/backend/druks/accounts/__init__.py index e69de29b..7fc0ebdf 100644 --- a/backend/druks/accounts/__init__.py +++ b/backend/druks/accounts/__init__.py @@ -0,0 +1,3 @@ +from .context import current_account_id + +__all__ = ["current_account_id"] diff --git a/docs/writing-an-extension.md b/docs/writing-an-extension.md index fd8bb941..4b8e8e8a 100644 --- a/docs/writing-an-extension.md +++ b/docs/writing-an-extension.md @@ -655,6 +655,19 @@ the prefix its own resource is called: router = APIRouter(prefix="/reviews") ``` +Your routes run authenticated: the loader mounts every router behind the +platform's identity gate — a Bearer PAT or the signed-in session — so nothing +anonymous reaches your code, and you never write auth yourself. Read the +caller when a route scopes by who is asking: + +```python +from druks.accounts import current_account_id + +@router.get("/reviews") +def list_reviews() -> list[ReviewResponse]: + return Review.list_for_account(current_account_id.get()) +``` + Tagging a route `agent` also derives it into an MCP tool: you give it an explicit `operation_id`, and Druks derives the tool name by prefixing it with your extension name — write `operation_id="add_peer"` in `peer_tracker` and the tool is @@ -825,8 +838,10 @@ for connection in NightWatch.acme.list_for_account(account_id): token = await connection.get_access_token() ``` -`NightWatch.acme.get(connection_id)` returns one connection when your own -row stored its id. Each connection carries `id`, `scopes`, `identity` — the +`account_id` is the caller: `self.account_id` in a run body, +`current_account_id.get()` in a route, the handler's argument in a +subscriber. `NightWatch.acme.get(connection_id)` returns one connection +when your own row stored its id. Each connection carries `id`, `scopes`, `identity` — the provider's facts for the sign-in — and `connected_at`. Your UI starts a sign-in by opening `/api/oauth/acme/connect` — the @@ -1053,6 +1068,7 @@ Import from concern namespaces, not from `druks.durable` or internal modules: | Namespace | Public names | | --- | --- | +| `druks.accounts` | `current_account_id` | | `druks.extensions` | `Extension`, `ExtensionSettings`, `Secret` | | `druks.services` | `Service`, `ServiceConnectError`, `ServiceNotConnectedError`, `OauthClient`, `OauthExchangeError`, `OauthRefreshError` | | `druks.secrets.fields` | `EncryptedJsonField`, `SecretsMapping` |