Skip to content

Commit f675952

Browse files
Merge pull request #828 from MervinPraison/claude/issue-810-fix
docs: add Gateway Operator Scopes feature page (fixes #810)
2 parents ab05b28 + 4706e1a commit f675952

3 files changed

Lines changed: 312 additions & 0 deletions

File tree

docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@
361361
"docs/features/context-files",
362362
"docs/features/gateway",
363363
"docs/features/gateway-hot-reload",
364+
"docs/features/gateway-operator-scopes",
364365
"docs/features/gateway-overview",
365366
"docs/features/gateway-session-persistence",
366367
"docs/features/gateway-session-continuity",

docs/features/gateway-bind-aware-auth.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ The `PRAISONAI_ALLOW_DEFAULT_CREDS=1` escape hatch should only be used for ephem
231231
## Related
232232

233233
<CardGroup cols={2}>
234+
<Card title="Operator Scopes" icon="shield-check" href="/docs/features/gateway-operator-scopes">
235+
Least-privilege multi-operator access control
236+
</Card>
234237
<Card title="Gateway Documentation" icon="gateway" href="/docs/gateway">
235238
Core gateway functionality and configuration
236239
</Card>
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
---
2+
title: "Gateway Operator Scopes"
3+
sidebarTitle: "Operator Scopes"
4+
description: "Least-privilege role-based access control for multi-operator Gateway deployments"
5+
icon: "shield-check"
6+
---
7+
8+
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.
9+
10+
```mermaid
11+
graph LR
12+
subgraph "Multi-Operator Gateway"
13+
V[👁 Viewer<br/>read] --> GW[🌐 Gateway]
14+
O[✉️ Ops<br/>read + write] --> GW
15+
A[🛡 Admin<br/>admin] --> GW
16+
GW --> Agent[🤖 Agent]
17+
end
18+
19+
classDef viewer fill:#189AB4,stroke:#7C90A0,color:#fff
20+
classDef ops fill:#10B981,stroke:#7C90A0,color:#fff
21+
classDef admin fill:#8B0000,stroke:#7C90A0,color:#fff
22+
classDef gateway fill:#F59E0B,stroke:#7C90A0,color:#fff
23+
classDef agent fill:#6366F1,stroke:#7C90A0,color:#fff
24+
25+
class V viewer
26+
class O ops
27+
class A admin
28+
class GW gateway
29+
class Agent agent
30+
```
31+
32+
## Quick Start
33+
34+
<Steps>
35+
<Step title="Single-operator (no scopes — unchanged)">
36+
37+
Today's setup keeps working. Authenticated clients receive all scopes when no policy is configured.
38+
39+
```python
40+
from praisonaiagents import Agent
41+
42+
agent = Agent(
43+
name="assistant",
44+
instructions="You are a helpful assistant.",
45+
)
46+
47+
# $ praisonai gateway start --host 127.0.0.1
48+
agent.start("hello")
49+
```
50+
51+
</Step>
52+
53+
<Step title="Multi-operator (scoped tokens)">
54+
55+
Map each operator token to the scopes they need in `gateway.yaml`, then run your agent as usual.
56+
57+
```yaml
58+
gateway:
59+
host: "0.0.0.0"
60+
port: 8765
61+
auth:
62+
tokens:
63+
- token: "${VIEWER_TOKEN}"
64+
scopes: [read]
65+
- token: "${OPS_TOKEN}"
66+
scopes: [read, write]
67+
- token: "${ADMIN_TOKEN}"
68+
scopes: [admin]
69+
70+
agents:
71+
assistant:
72+
instructions: "You are a helpful assistant."
73+
model: gpt-4o-mini
74+
```
75+
76+
```python
77+
from praisonaiagents import Agent
78+
from praisonaiagents.gateway import OperatorScope
79+
80+
agent = Agent(name="assistant", instructions="You are a helpful assistant.")
81+
# OperatorScope.READ, .WRITE, .APPROVALS, .PAIRING, .ADMIN
82+
print([s.value for s in OperatorScope.all()])
83+
```
84+
85+
</Step>
86+
</Steps>
87+
88+
<Note>
89+
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.
90+
</Note>
91+
92+
---
93+
94+
## How It Works
95+
96+
```mermaid
97+
sequenceDiagram
98+
participant Client
99+
participant Gateway
100+
participant Policy as resolve_scopes
101+
participant Route as /api/approval/resolve
102+
participant WS as WebSocket subscribers
103+
104+
Client->>Gateway: Connect with token
105+
Gateway->>Policy: resolve_scopes(token)
106+
Policy-->>Gateway: [read, approvals]
107+
Client->>Route: POST resolve approval
108+
alt has approvals scope
109+
Route-->>Client: 200 OK
110+
Gateway->>WS: approval event (approvals clients only)
111+
else missing scope
112+
Route-->>Client: 403 insufficient scope
113+
end
114+
```
115+
116+
1. Client connects with a bearer token.
117+
2. Gateway resolves scopes via `GatewayConfig.resolve_scopes(token)`.
118+
3. Each HTTP route and WebSocket action checks the required scope.
119+
4. Outbound events are filtered — approval events only reach clients with the `approvals` scope.
120+
121+
---
122+
123+
## Scope Reference
124+
125+
| Scope | Value | Grants |
126+
|---|---|---|
127+
| Read | `read` | View dashboard, session transcripts, and status events |
128+
| Write | `write` | Send messages as the agent (WebSocket `message`) |
129+
| Approvals | `approvals` | Resolve tool-execution approvals and manage allowlist |
130+
| Pairing | `pairing` | Approve or revoke device pairing |
131+
| Admin | `admin` | Channel pause/resume/reconnect — implies all scopes |
132+
133+
```mermaid
134+
graph TB
135+
Admin[admin] --> Read[read]
136+
Admin --> Write[write]
137+
Admin --> Approvals[approvals]
138+
Admin --> Pairing[pairing]
139+
140+
classDef admin fill:#8B0000,stroke:#7C90A0,color:#fff
141+
classDef scope fill:#189AB4,stroke:#7C90A0,color:#fff
142+
143+
class Admin admin
144+
class Read,Write,Approvals,Pairing scope
145+
```
146+
147+
### Which scope should this operator have?
148+
149+
```mermaid
150+
graph TD
151+
Start{What do they need?}
152+
Start -->|View only| R[read]
153+
Start -->|Send messages| RW[read + write]
154+
Start -->|Resolve approvals| RA[read + approvals]
155+
Start -->|Manage pairing| RP[read + pairing]
156+
Start -->|Full control| AD[admin]
157+
158+
classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
159+
classDef scope fill:#10B981,stroke:#7C90A0,color:#fff
160+
161+
class Start decision
162+
class R,RW,RA,RP,AD scope
163+
```
164+
165+
| Role | Recommended scopes |
166+
|---|---|
167+
| Read-only stakeholder | `[read]` |
168+
| Junior support (send, not approve) | `[read, write]` |
169+
| On-call approver | `[read, approvals]` |
170+
| SRE / platform admin | `[admin]` |
171+
172+
---
173+
174+
## Configuration
175+
176+
### YAML — structured (recommended)
177+
178+
```yaml
179+
gateway:
180+
auth:
181+
tokens:
182+
- token: "${VIEWER_TOKEN}"
183+
scopes: [read]
184+
- token: "${OPS_TOKEN}"
185+
scopes: [read, write, approvals]
186+
- token: "${ADMIN_TOKEN}"
187+
scopes: [admin]
188+
```
189+
190+
### YAML — flat mapping
191+
192+
```yaml
193+
gateway:
194+
auth_scopes:
195+
"${VIEWER_TOKEN}": [read]
196+
"${OPS_TOKEN}": [read, write, approvals]
197+
"${ADMIN_TOKEN}": [admin]
198+
```
199+
200+
### Python
201+
202+
```python
203+
from praisonaiagents.gateway import GatewayConfig, OperatorScope
204+
205+
config = GatewayConfig(
206+
host="0.0.0.0",
207+
port=8765,
208+
auth_token="${ADMIN_TOKEN}",
209+
auth_scopes={
210+
"${VIEWER_TOKEN}": [OperatorScope.READ.value],
211+
"${OPS_TOKEN}": [OperatorScope.READ.value, OperatorScope.WRITE.value],
212+
"${ADMIN_TOKEN}": [OperatorScope.ADMIN.value],
213+
},
214+
)
215+
216+
print(config.has_scope_policy) # True when auth_scopes is non-empty
217+
print(config.resolve_scopes("${VIEWER_TOKEN}")) # ['read']
218+
```
219+
220+
---
221+
222+
## Scope-Gated Routes
223+
224+
| Route | Method | Required scope |
225+
|---|---|---|
226+
| `/api/channels/{name}/pause` | POST | `admin` |
227+
| `/api/channels/{name}/resume` | POST | `admin` |
228+
| `/api/channels/{name}/reconnect` | POST | `admin` |
229+
| `/api/approval/resolve` | POST | `approvals` |
230+
| `/api/approval/allowlist` | GET | any authenticated |
231+
| `/api/approval/allowlist` | POST/DELETE | `approvals` |
232+
| `/api/pairing/approve` | POST | `pairing` |
233+
| `/api/pairing/revoke` | POST | `pairing` |
234+
| WebSocket `message` || `write` |
235+
236+
---
237+
238+
## Common Patterns
239+
240+
**Read-only dashboard viewer**`[read]` for status and transcripts without send or approve rights.
241+
242+
**Send but not approve**`[read, write]` for operators who reply to users but cannot resolve tool approvals.
243+
244+
**Approvals-only on-call**`[read, approvals]` for security-sensitive approval resolution without channel admin rights.
245+
246+
**Full admin**`[admin]` for SREs who need pause/resume/reconnect plus all other capabilities.
247+
248+
---
249+
250+
## Error Handling
251+
252+
HTTP 403 when scope check fails:
253+
254+
```json
255+
{ "error": "insufficient scope", "required_scope": "approvals" }
256+
```
257+
258+
WebSocket `message` without `write` scope:
259+
260+
```json
261+
{
262+
"type": "error",
263+
"code": "insufficient_scope",
264+
"message": "insufficient scope",
265+
"required_scope": "write"
266+
}
267+
```
268+
269+
---
270+
271+
<Warning>
272+
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.
273+
</Warning>
274+
275+
---
276+
277+
## Best Practices
278+
279+
<AccordionGroup>
280+
<Accordion title="Default to read and add scopes as needed">
281+
Start every operator with `[read]` and expand only when their role requires it.
282+
</Accordion>
283+
284+
<Accordion title="Rotate per-token secrets independently">
285+
Issue separate tokens per operator so you can revoke one role without rotating everyone.
286+
</Accordion>
287+
288+
<Accordion title="Pair approvals with the allowlist">
289+
Combine `approvals` scope with `/api/approval/allowlist` for defence-in-depth on tool execution.
290+
</Accordion>
291+
292+
<Accordion title="Use admin sparingly">
293+
Prefer explicit scope lists over `[admin]` unless the operator truly needs channel control.
294+
</Accordion>
295+
</AccordionGroup>
296+
297+
---
298+
299+
## Related
300+
301+
<CardGroup cols={2}>
302+
<Card title="Bind-Aware Auth" icon="shield" href="/docs/features/gateway-bind-aware-auth">
303+
Token requirements when binding to external interfaces
304+
</Card>
305+
<Card title="Gateway Overview" icon="broadcast-tower" href="/docs/features/gateway-overview">
306+
Multi-channel gateway architecture and setup
307+
</Card>
308+
</CardGroup>

0 commit comments

Comments
 (0)