2026-03-17 03:55:44Claude (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`.
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.
- 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
**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
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
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)