Blame
|
1 | --- |
||||||
| 2 | status: current |
|||||||
| 3 | platform: robot.wtf (VPS) |
|||||||
|
4 | --- |
||||||
| 5 | ||||||||
|
6 | Extracted from the original wikibot.io design. AWS-specific content archived at [[Archive/AWS_Design/Auth]]. |
||||||
|
7 | |||||||
|
8 | See also: [[Design/VPS_Architecture]], [[Design/Data_Model]]. |
||||||
|
9 | |||||||
|
10 | --- |
||||||
|
11 | |||||||
|
12 | ## Auth Overview |
||||||
|
13 | |||||||
|
14 | **ATProto OAuth login** — users authenticate via their ATProto identity (Bluesky handle/DID). The platform runs a self-hosted OAuth 2.1 Authorization Server for MCP connections. |
||||||
|
15 | |||||||
|
16 | Three auth paths, all converging on the same identity resolution and ACL check: |
||||||
|
17 | |||||||
|
18 | 1. **Browser session**: ATProto OAuth → platform issues a session JWT (signed with our RS256 key) → middleware validates JWT on each request → resolves user → checks ACL → sets Otterwiki headers. |
||||||
| 19 | 2. **MCP OAuth (Claude.ai)**: Self-hosted OAuth 2.1 AS handles DCR, PKCE, token issuance, JWKS. Claude.ai discovers `/.well-known/oauth-protected-resource`, authenticates, presents access token. Per-wiki authorization in our middleware. |
|||||||
| 20 | 3. **Bearer token (Claude Code, API clients)**: Token in `Authorization` header → middleware hashes token, looks up in DB → resolves to user + wiki → checks ACL. |
|||||||
|
21 | |||||||
|
22 | The platform middleware is the single authentication boundary. Everything downstream trusts it. |
||||||
|
23 | |||||||
|
24 | ## MCP Auth |
||||||
|
25 | |||||||
|
26 | **OAuth 2.1 (Claude.ai)**: Self-hosted AS (authlib-based) provides DCR, PKCE, AS metadata, token endpoint, and JWKS. The MCP endpoint serves `/.well-known/oauth-protected-resource` pointing to the local AS. Per-wiki authorization happens in middleware — the AS identifies the user, middleware checks wiki access. See [[Design/VPS_Architecture]] for implementation details. |
||||||
|
27 | |||||||
|
28 | **Bearer token (Claude Code / API)**: Each wiki gets a unique MCP bearer token, stored as a bcrypt hash in the platform DB. The user sees the token once (at creation) and can regenerate it from the dashboard. Usage: `claude mcp add --transport http`. |
||||||
|
29 | |||||||
|
30 | ## ACL Model |
||||||
|
31 | |||||||
| 32 | Simple role-based model: |
|||||||
| 33 | ||||||||
| 34 | | Role | Read | Write | Delete | Manage ACL | Delete wiki | |
|||||||
| 35 | |------|------|-------|--------|------------|-------------| |
|||||||
| 36 | | viewer | yes | no | no | no | no | |
|||||||
| 37 | | editor | yes | yes | yes | no | no | |
|||||||
| 38 | | owner | yes | yes | yes | yes | yes | |
|||||||
| 39 | ||||||||
|
40 | Wiki creator is always owner. Owners can grant viewer/editor access to other registered users. |
||||||
|
41 | |||||||
|
42 | ## Authorization Flow |
||||||
|
43 | |||||||
| 44 | **Layer 1 — Platform middleware** (before Otterwiki sees the request): |
|||||||
| 45 | ||||||||
| 46 | 1. Resolve user identity from JWT or bearer token |
|||||||
|
47 | 2. Resolve wiki from request routing |
||||||
|
48 | 3. Look up ACL: does this user have a grant on this wiki? |
||||||
| 49 | 4. If no grant and wiki is not public → 403 |
|||||||
|
50 | 5. Map ACL role to Otterwiki permission headers: |
||||||
|
51 | |||||||
| 52 | | ACL role | `x-otterwiki-permissions` header | |
|||||||
| 53 | |----------|----------------------------------| |
|||||||
| 54 | | viewer | `READ` | |
|||||||
| 55 | | editor | `READ,WRITE,UPLOAD` | |
|||||||
| 56 | | owner | `READ,WRITE,UPLOAD,ADMIN` | |
|||||||
| 57 | | anonymous (public wiki) | Synthetic user with `READ` only | |
|||||||
| 58 | ||||||||
|
59 | 6. Set headers: `x-otterwiki-email`, `x-otterwiki-name`, `x-otterwiki-permissions` |
||||||
| 60 | 7. Forward to Otterwiki |
|||||||
|
61 | |||||||
| 62 | **Layer 2 — Otterwiki** (`AUTH_METHOD=PROXY_HEADER`): |
|||||||
| 63 | ||||||||
| 64 | - Reads headers, creates ephemeral user object per request |
|||||||
| 65 | - No local user database — all identity comes from headers |
|||||||
| 66 | - Enforces READ/WRITE/UPLOAD/ADMIN based on the permissions header |
|||||||
| 67 | ||||||||
| 68 | **For MCP and API paths**, Otterwiki is not involved in auth — the handlers read the git repo directly. Authorization happens entirely in Layer 1. |
|||||||
| 69 | ||||||||
|
70 | ### Public wiki access |
||||||
|
71 | |||||||
|
72 | The platform middleware injects a synthetic anonymous user with READ permission for public wikis. Otterwiki config stays identical for all wikis: |
||||||
|
73 | |||||||
| 74 | ```python |
|||||||
| 75 | AUTH_METHOD = "PROXY_HEADER" |
|||||||
| 76 | READ_ACCESS = "APPROVED" # always — public access handled by middleware |
|||||||
| 77 | WRITE_ACCESS = "APPROVED" |
|||||||
| 78 | ATTACHMENT_ACCESS = "APPROVED" |
|||||||
| 79 | DISABLE_REGISTRATION = True # no Otterwiki-level registration |
|||||||
| 80 | ``` |
|||||||
| 81 | ||||||||
|
82 | ## Otterwiki Admin Panel — Section Disposition |
||||||
|
83 | |||||||
|
84 | The wiki owner (ACL role `owner`) gets `ADMIN` permission → access to `/-/admin/*`. |
||||||
|
85 | |||||||
|
86 | | Admin section | Disposition | Reason | |
||||||
| 87 | |--------------|-------------|--------| |
|||||||
| 88 | | Application Preferences | **Keep** | Wiki branding: site name, description, logo, favicon, language | |
|||||||
| 89 | | Sidebar Preferences | **Keep** | UI layout: sidebar shortcuts, custom menu items | |
|||||||
| 90 | | Content and Editing | **Keep** | Git workflow: commit message mode/template, page name casing, WikiLink style | |
|||||||
| 91 | | Repository Management | **Disable** | Conflicts with platform Git management | |
|||||||
| 92 | | Permissions and Registration | **Disable** | Conflicts with platform auth (middleware-managed) | |
|||||||
| 93 | | User Management | **Disable** | No local user database in ProxyHeaderAuth mode | |
|||||||
| 94 | | Mail Preferences | **Disable for MVP** | SMTP notifications — revisit later | |
|||||||
|
95 | |||||||
|
96 | **Implementation**: Override admin navigation template to hide disabled sections. Return 404 from disabled routes in middleware (defense in depth). |
||||||
