-
Notifications
You must be signed in to change notification settings - Fork 7
docs: add Gateway Operator Scopes feature page (fixes #810) #828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,308 @@ | ||
| --- | ||
| title: "Gateway Operator Scopes" | ||
| sidebarTitle: "Operator Scopes" | ||
| description: "Least-privilege role-based access control for multi-operator Gateway deployments" | ||
| icon: "shield-check" | ||
| --- | ||
|
|
||
| Operator scopes grant teammates least-privilege access to a shared Gateway — read-only dashboards, send-but-not-approve operators, or full admins — without handing over the whole keys. | ||
|
|
||
| ```mermaid | ||
| graph LR | ||
| subgraph "Multi-Operator Gateway" | ||
| V[👁 Viewer<br/>read] --> GW[🌐 Gateway] | ||
| O[✉️ Ops<br/>read + write] --> GW | ||
| A[🛡 Admin<br/>admin] --> GW | ||
| GW --> Agent[🤖 Agent] | ||
| end | ||
|
|
||
| classDef viewer fill:#189AB4,stroke:#7C90A0,color:#fff | ||
| classDef ops fill:#10B981,stroke:#7C90A0,color:#fff | ||
| classDef admin fill:#8B0000,stroke:#7C90A0,color:#fff | ||
| classDef gateway fill:#F59E0B,stroke:#7C90A0,color:#fff | ||
| classDef agent fill:#6366F1,stroke:#7C90A0,color:#fff | ||
|
|
||
| class V viewer | ||
| class O ops | ||
| class A admin | ||
| class GW gateway | ||
| class Agent agent | ||
| ``` | ||
|
|
||
| ## Quick Start | ||
|
|
||
| <Steps> | ||
| <Step title="Single-operator (no scopes — unchanged)"> | ||
|
|
||
| Today's setup keeps working. Authenticated clients receive all scopes when no policy is configured. | ||
|
|
||
| ```python | ||
| from praisonaiagents import Agent | ||
|
|
||
| agent = Agent( | ||
| name="assistant", | ||
| instructions="You are a helpful assistant.", | ||
| ) | ||
|
|
||
| # $ praisonai gateway start --host 127.0.0.1 | ||
| agent.start("hello") | ||
| ``` | ||
|
|
||
| </Step> | ||
|
|
||
| <Step title="Multi-operator (scoped tokens)"> | ||
|
|
||
| Map each operator token to the scopes they need in `gateway.yaml`, then run your agent as usual. | ||
|
|
||
| ```yaml | ||
| gateway: | ||
| host: "0.0.0.0" | ||
| port: 8765 | ||
| auth: | ||
| tokens: | ||
| - token: "${VIEWER_TOKEN}" | ||
| scopes: [read] | ||
| - token: "${OPS_TOKEN}" | ||
| scopes: [read, write] | ||
| - token: "${ADMIN_TOKEN}" | ||
| scopes: [admin] | ||
|
|
||
| agents: | ||
| assistant: | ||
| instructions: "You are a helpful assistant." | ||
| model: gpt-4o-mini | ||
| ``` | ||
|
|
||
| ```python | ||
| from praisonaiagents import Agent | ||
| from praisonaiagents.gateway import OperatorScope | ||
|
|
||
| agent = Agent(name="assistant", instructions="You are a helpful assistant.") | ||
| # OperatorScope.READ, .WRITE, .APPROVALS, .PAIRING, .ADMIN | ||
| print([s.value for s in OperatorScope.all()]) | ||
| ``` | ||
|
|
||
| </Step> | ||
| </Steps> | ||
|
|
||
| <Note> | ||
| When **no** `auth_scopes` policy is configured, every successfully authenticated client is granted **all** scopes — identical to today's binary auth behaviour. Single-operator setups need no changes. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| </Note> | ||
|
|
||
| --- | ||
|
|
||
| ## How It Works | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant Client | ||
| participant Gateway | ||
| participant Policy as resolve_scopes | ||
| participant Route as /api/approval/resolve | ||
| participant WS as WebSocket subscribers | ||
|
|
||
| Client->>Gateway: Connect with token | ||
| Gateway->>Policy: resolve_scopes(token) | ||
| Policy-->>Gateway: [read, approvals] | ||
| Client->>Route: POST resolve approval | ||
| alt has approvals scope | ||
| Route-->>Client: 200 OK | ||
| Gateway->>WS: approval event (approvals clients only) | ||
| else missing scope | ||
| Route-->>Client: 403 insufficient scope | ||
| end | ||
| ``` | ||
|
|
||
| 1. Client connects with a bearer token. | ||
| 2. Gateway resolves scopes via `GatewayConfig.resolve_scopes(token)`. | ||
| 3. Each HTTP route and WebSocket action checks the required scope. | ||
| 4. Outbound events are filtered — approval events only reach clients with the `approvals` scope. | ||
|
|
||
| --- | ||
|
|
||
| ## Scope Reference | ||
|
|
||
| | Scope | Value | Grants | | ||
| |---|---|---| | ||
| | Read | `read` | View dashboard, session transcripts, and status events | | ||
| | Write | `write` | Send messages as the agent (WebSocket `message`) | | ||
| | Approvals | `approvals` | Resolve tool-execution approvals and manage allowlist | | ||
| | Pairing | `pairing` | Approve or revoke device pairing | | ||
| | Admin | `admin` | Channel pause/resume/reconnect — implies all scopes | | ||
|
|
||
| ```mermaid | ||
| graph TB | ||
| Admin[admin] --> Read[read] | ||
| Admin --> Write[write] | ||
| Admin --> Approvals[approvals] | ||
| Admin --> Pairing[pairing] | ||
|
|
||
| classDef admin fill:#8B0000,stroke:#7C90A0,color:#fff | ||
| classDef scope fill:#189AB4,stroke:#7C90A0,color:#fff | ||
|
|
||
| class Admin admin | ||
| class Read,Write,Approvals,Pairing scope | ||
| ``` | ||
|
|
||
| ### Which scope should this operator have? | ||
|
|
||
| ```mermaid | ||
| graph TD | ||
| Start{What do they need?} | ||
| Start -->|View only| R[read] | ||
| Start -->|Send messages| RW[read + write] | ||
| Start -->|Resolve approvals| RA[read + approvals] | ||
| Start -->|Manage pairing| RP[read + pairing] | ||
| Start -->|Full control| AD[admin] | ||
|
|
||
| classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff | ||
| classDef scope fill:#10B981,stroke:#7C90A0,color:#fff | ||
|
|
||
| class Start decision | ||
| class R,RW,RA,RP,AD scope | ||
| ``` | ||
|
|
||
| | Role | Recommended scopes | | ||
| |---|---| | ||
| | Read-only stakeholder | `[read]` | | ||
| | Junior support (send, not approve) | `[read, write]` | | ||
| | On-call approver | `[read, approvals]` | | ||
| | SRE / platform admin | `[admin]` | | ||
|
|
||
| --- | ||
|
|
||
| ## Configuration | ||
|
|
||
| ### YAML — structured (recommended) | ||
|
|
||
| ```yaml | ||
| gateway: | ||
| auth: | ||
| tokens: | ||
| - token: "${VIEWER_TOKEN}" | ||
| scopes: [read] | ||
| - token: "${OPS_TOKEN}" | ||
| scopes: [read, write, approvals] | ||
| - token: "${ADMIN_TOKEN}" | ||
| scopes: [admin] | ||
| ``` | ||
|
|
||
| ### YAML — flat mapping | ||
|
|
||
| ```yaml | ||
| gateway: | ||
| auth_scopes: | ||
| "${VIEWER_TOKEN}": [read] | ||
| "${OPS_TOKEN}": [read, write, approvals] | ||
| "${ADMIN_TOKEN}": [admin] | ||
| ``` | ||
|
|
||
| ### Python | ||
|
|
||
| ```python | ||
| from praisonaiagents.gateway import GatewayConfig, OperatorScope | ||
|
|
||
| config = GatewayConfig( | ||
| host="0.0.0.0", | ||
| port=8765, | ||
| auth_token="${ADMIN_TOKEN}", | ||
| auth_scopes={ | ||
| "${VIEWER_TOKEN}": [OperatorScope.READ.value], | ||
| "${OPS_TOKEN}": [OperatorScope.READ.value, OperatorScope.WRITE.value], | ||
| "${ADMIN_TOKEN}": [OperatorScope.ADMIN.value], | ||
| }, | ||
| ) | ||
|
|
||
| print(config.has_scope_policy) # True when auth_scopes is non-empty | ||
| print(config.resolve_scopes("${VIEWER_TOKEN}")) # ['read'] | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Scope-Gated Routes | ||
|
|
||
| | Route | Method | Required scope | | ||
| |---|---|---| | ||
| | `/api/channels/{name}/pause` | POST | `admin` | | ||
| | `/api/channels/{name}/resume` | POST | `admin` | | ||
| | `/api/channels/{name}/reconnect` | POST | `admin` | | ||
| | `/api/approval/resolve` | POST | `approvals` | | ||
| | `/api/approval/allowlist` | GET | any authenticated | | ||
| | `/api/approval/allowlist` | POST/DELETE | `approvals` | | ||
|
Comment on lines
+230
to
+231
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The actual endpoint implemented in the gateway server is |
||
| | `/api/pairing/approve` | POST | `pairing` | | ||
| | `/api/pairing/revoke` | POST | `pairing` | | ||
| | WebSocket `message` | — | `write` | | ||
|
|
||
| --- | ||
|
|
||
| ## Common Patterns | ||
|
|
||
| **Read-only dashboard viewer** — `[read]` for status and transcripts without send or approve rights. | ||
|
|
||
| **Send but not approve** — `[read, write]` for operators who reply to users but cannot resolve tool approvals. | ||
|
|
||
| **Approvals-only on-call** — `[read, approvals]` for security-sensitive approval resolution without channel admin rights. | ||
|
|
||
| **Full admin** — `[admin]` for SREs who need pause/resume/reconnect plus all other capabilities. | ||
|
|
||
| --- | ||
|
|
||
| ## Error Handling | ||
|
|
||
| HTTP 403 when scope check fails: | ||
|
|
||
| ```json | ||
| { "error": "insufficient scope", "required_scope": "approvals" } | ||
| ``` | ||
|
|
||
| WebSocket `message` without `write` scope: | ||
|
|
||
| ```json | ||
| { | ||
| "type": "error", | ||
| "code": "insufficient_scope", | ||
| "message": "insufficient scope", | ||
| "required_scope": "write" | ||
| } | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| <Warning> | ||
| Granting `approvals` is effectively remote command execution — never assign it casually. `ALLOW_LOOPBACK_BYPASS=true` grants all scopes on loopback; use for local development only, never in production. | ||
| </Warning> | ||
|
|
||
| --- | ||
|
|
||
| ## Best Practices | ||
|
|
||
| <AccordionGroup> | ||
| <Accordion title="Default to read and add scopes as needed"> | ||
| Start every operator with `[read]` and expand only when their role requires it. | ||
| </Accordion> | ||
|
|
||
| <Accordion title="Rotate per-token secrets independently"> | ||
| Issue separate tokens per operator so you can revoke one role without rotating everyone. | ||
| </Accordion> | ||
|
|
||
| <Accordion title="Pair approvals with the allowlist"> | ||
| Combine `approvals` scope with `/api/approval/allowlist` for defence-in-depth on tool execution. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| </Accordion> | ||
|
|
||
| <Accordion title="Use admin sparingly"> | ||
| Prefer explicit scope lists over `[admin]` unless the operator truly needs channel control. | ||
| </Accordion> | ||
| </AccordionGroup> | ||
|
|
||
| --- | ||
|
|
||
| ## Related | ||
|
|
||
| <CardGroup cols={2}> | ||
| <Card title="Bind-Aware Auth" icon="shield" href="/docs/features/gateway-bind-aware-auth"> | ||
| Token requirements when binding to external interfaces | ||
| </Card> | ||
| <Card title="Gateway Overview" icon="broadcast-tower" href="/docs/features/gateway-overview"> | ||
| Multi-channel gateway architecture and setup | ||
| </Card> | ||
| </CardGroup> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The phrase "without handing over the whole keys" is grammatically awkward. Consider changing it to "without handing over the master keys" or "without handing over all the keys" for better clarity and professional tone.