Commit e0e527

2026-03-17 03:55:44 Claude (MCP): [mcp] Update admin panel design: Phase 2 user management with explicit roster model
Design/Admin_Panel_Reenablement.md ..
@@ 13,6 13,10 @@
Otterwiki has 8 admin panels. Three are gated by `PLATFORM_MODE` (added in `feat/P2-8-admin-panel-hiding`). Per-wiki databases landed on 2026-03-17, meaning each wiki's `preferences`, `drafts`, `user`, and `cache` tables are now isolated.
+ ## Upstream bug found
+
+ `handle_permissions_and_registration` in `otterwiki/preferences.py` is missing the `has_permission("ADMIN")` guard that every other admin handler has. A non-admin authenticated user can POST and overwrite READ_ACCESS/WRITE_ACCESS settings. Fix submitted as `fix/permissions-admin-guard` on our fork — PR pending to redimp. Red/green verified: POST returns 302 (succeeds) without fix, 403 with fix.
+
## Panel Status
### Already working (no changes needed)
@@ 26,7 30,7 @@
**Flag:** `SERVER_NAME` in Application Preferences could break Flask's URL generation in multi-tenant mode. Should be hidden or read-only in `PLATFORM_MODE`.
- ### Re-enable now: Permissions panel (medium effort)
+ ### Phase 1: Permissions panel (in progress)
**Route:** `/-/admin/permissions_and_registration`
@@ 36,35 40,68 @@
- `ATTACHMENT_ACCESS` — same options
- Registration settings (DISABLE_REGISTRATION, AUTO_APPROVAL, etc.) — **not applicable to ATProto auth**
- **Current state:** `ProxyHeaderAuth.has_permission()` ignores these entirely — it trusts proxy headers from the resolver.
+ **Permission model (three layers):**
+ 1. Platform ACL grants the **ceiling** (owner/editor/viewer → READ, WRITE, UPLOAD, ADMIN)
+ 2. Per-wiki preferences (READ_ACCESS/WRITE_ACCESS/ATTACHMENT_ACCESS) restrict further
+ 3. Per-user flags in per-wiki user table (Phase 2) restrict further still
+
+ Wiki owners can restrict but never escalate beyond the platform ACL ceiling. ADMIN permission is never affected by wiki preferences.
+
+ **`READ_ACCESS` as source of truth for public access:** The wiki's `READ_ACCESS` preference replaces the separate `is_public` flag in robot.db. If `READ_ACCESS=ANONYMOUS`, anonymous users get through. Otherwise, authentication is required. One place to manage access, in Otterwiki's native admin UI.
- **Proposed integration:** The resolver reads these preferences after `_swap_database()` and intersects them with platform ACL permissions before injecting proxy headers.
- - Platform ACL grants the **ceiling** (owner/editor/viewer → READ, WRITE, UPLOAD, ADMIN)
- - Per-wiki READ_ACCESS/WRITE_ACCESS restrict further (e.g., if WRITE_ACCESS=APPROVED and user isn't approved, strip WRITE from headers)
- - Wiki owner can restrict but never escalate beyond what the platform ACL grants
+ **APPROVED level:** Treated as REGISTERED until Phase 2 (user tracking). This is safe — more restrictive than intended, never less.
- **Changes needed:**
- 1. **otterwiki fork (wikibot-io branch):** Modify the permissions_and_registration template to hide registration-related fields when `PLATFORM_MODE` is true. Remove `@platform_mode_disabled` decorator from the route.
- 2. **robot.wtf resolver:** After `_swap_database()` and `update_app_config()`, read `READ_ACCESS`/`WRITE_ACCESS`/`ATTACHMENT_ACCESS` from `app.config`. Apply them when computing the `x-otterwiki-permissions` header — intersect with ACL-granted permissions.
+ **MCP bearer tokens:** Full access regardless of wiki preferences. MCP tokens are platform-granted; wiki owners shouldn't be able to break their own integrations.
- ### Phase 2: User Management (high effort)
+ **Changes (two repos):**
+ 1. **otterwiki fork (wikibot-io):** Remove `@platform_mode_disabled`, hide registration fields in template, server-side guard for registration saves
+ 2. **robot.wtf resolver:** `_apply_wiki_access_restrictions()` intersects ACL permissions with wiki preferences before injecting proxy headers
+
+ ### Phase 2: User Management
**Routes:** `/-/admin/user_management`, `/-/user/<uid>`
- **Problem:** `ProxyHeaderAuth` creates transient user objects from headers. Nothing persists to the per-wiki DB. `get_all_user()` returns only `[current_user]`.
+ #### Design: Explicit roster, not activity tracking
+
+ The per-wiki `user` table is an **explicit roster managed by wiki admins**, not an activity log. Users only exist there because an admin added them. No passive tracking — if a wiki is publicly readable, authenticated visitors are not recorded.
+
+ **Admin workflow:**
+ 1. Admin opens User Management panel
+ 2. Enters a DID handle (e.g., `@alice.bsky.social`) to add a user
+ 3. Sets per-user flags: `is_approved`, `allow_read`, `allow_write`, `allow_upload`, `is_admin`
+ 4. User can now access the wiki at whatever level their flags grant (intersected with wiki preferences and platform ACL ceiling)
+
+ **Wiki admins can create other wiki admins.** No reason to restrict this — the platform ACL is still the outer boundary.
+
+ #### `email` field decision — deferred
+
+ Otterwiki's User model keys on `email`. Two options:
+ - **Appropriate `email` for DID handles** — quick, minimal schema divergence, but blocks future email notifications
+ - **Add a `handle` column** — cleaner, but more fork divergence and schema migration work
+
+ Decision deferred. Future notification mechanism (email vs. BlueSky DMs) affects this choice.
+
+ #### Permission flow with APPROVED level
+
+ Once Phase 2 lands, the APPROVED access level becomes fully functional:
+ 1. Wiki owner sets `READ_ACCESS=APPROVED` in Permissions panel
+ 2. Resolver checks per-wiki `user` table for the authenticated user's handle
+ 3. If found and `is_approved=True`: READ permission granted
+ 4. If not found or not approved: READ stripped (along with WRITE, UPLOAD per dependency chain)
+
+ #### Required changes
- **Required changes:**
- 1. **User tracking:** Resolver upserts a User record into per-wiki DB on each authenticated request (ATProto handle as email field, display name as name, null password_hash)
- 2. **`get_all_user()` override:** Query per-wiki user table instead of returning `[current_user]`
- 3. **Template changes:** Hide password/email fields in PLATFORM_MODE; show ATProto handles
- 4. **ACL integration:** Per-user flags (allow_read, allow_write, is_admin) feed into permission computation alongside READ_ACCESS/WRITE_ACCESS
+ 1. **User Management UI:** Admin enters DID handles to add users, sets flags. Hide password/email-editing fields in PLATFORM_MODE.
+ 2. **`get_all_user()` override:** `ProxyHeaderAuth.get_all_user()` must query the per-wiki user table instead of returning `[current_user]`.
+ 3. **Resolver permission check:** Extend `_apply_wiki_access_restrictions()` to query per-wiki user table when access level is APPROVED.
+ 4. **User Edit page:** Same PLATFORM_MODE treatment — hide password actions, show handle.
### Keep disabled: Mail Preferences
- Per-wiki SMTP config is a security/spam risk. If mail is needed, platform provides a shared service.
+ Per-wiki SMTP config is a security/spam risk. If mail is needed, platform provides a shared service. Notification mechanism (email vs. BlueSky DMs) is an open question.
## Implementation Order
- 1. **Permissions panel** — re-enable with resolver integration (this sprint)
+ 1. **Permissions panel** — in progress (Phase 1)
2. **Hide SERVER_NAME** — quick template fix (opportunistic)
- 3. **User Management** — user tracking + template customization (Phase 2)
+ 3. **User Management** — explicit roster + APPROVED level (Phase 2)
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9