# CHUNK #10 — Multi-User Admin & Sharing **Duration:** ~2 days · **Prereqs:** CHUNK #2 (auth skeleton, ServerAdapter) · **Follows:** CHUNK #9 · **Unblocks:** CHUNK #11 (parity: admin works in web + desktop identically) **Purpose:** Complete the multi-user product: full admin user management (create, disable, change role), project sharing with read/edit permissions, an audit log, and hard multi-tenant isolation — all reachable from **both** the web app and the desktop app identically. --- ## Steps ### 1. Admin API (`server/src/routes/admin.ts`) 1. `GET /api/admin/users` — list all users (name, email, role, status, lastActive). 2. `POST /api/admin/users` — create a user (email + role; server generates a one-time password or sends an invite link). 3. `PATCH /api/admin/users/:id` — change role (`admin`/`author`) or disable/enable. 4. `GET /api/admin/activity` — audit log tail (filtered by user/action/date). 5. `GET /api/admin/stats` — instance-wide: user count, project count, storage used. 6. All routes behind `requireAdmin` middleware. ### 2. Sharing API (`server/src/routes/projects.ts` — extend Chunk 2) 1. `POST /api/projects/:id/shares` — invite by email, grant `read`/`edit`. 2. `PATCH /api/projects/:id/shares/:userId` — change permission. 3. `DELETE /api/projects/:id/shares/:userId` — revoke. 4. `POST /api/projects/:id/transfer` — transfer ownership (owner only). 5. `GET /api/projects` — return owned ∪ shared (with `perm` on each). ### 3. Access control (`server/src/auth.ts` — extend Chunk 2) 1. `canAccessProject(projectId, perm)` — enforces owner OR shared-with (correct `perm`). Middleware on every content route. 2. **Multi-tenant isolation**: every query filters by `project_id` the user can access. A user can never read another's project — enforced at the SQL layer, not just the app layer. 3. Session revocation: disabling a user → their session token is invalidated within one event round-trip (Chunk 2 acceptance criterion). ### 4. Audit log (`server/src/db.ts` — extend Chunk 2) 1. `audit(id, at, user_id, action, detail JSON)` — log: user create/disable, role change, share grant/revoke, ownership transfer, project delete. 2. `GET /api/admin/activity` returns the tail; retention: 1 year (v1, no purge). ### 5. Admin UI — web (`apps/web/src/features/admin/AdminView.tsx`) 1. **Users tab**: table (name, email, role, status, lastActive); "Create user" modal; inline role toggle; disable/enable switch. 2. **Sharing tab** (per project): list shared users with permission dropdown; transfer ownership; revoke. 3. **Activity tab**: filterable audit log table. 4. **Stats tab**: instance-wide counters. ### 6. Admin UI — desktop parity 1. The desktop app renders the **same** `AdminView` (it's part of the shared React app). No desktop-specific admin code — parity is structural. 2. Desktop uses bearer token for auth (Chunk 2); web uses cookie. Both hit the same API. ### 7. Invite flow 1. `POST /api/admin/users` with `sendInvite: true` → generates an invite token, stores a pending invite, (v1) logs the invite link to the audit log; the invitee clicks → `/invite/:token` → sets password → becomes an author. ### 8. Tests 1. Unit: `canAccessProject` — owner can edit; shared-read can read not edit; unrelated user gets 403. 2. Integration: admin creates user → user logs in → sees only shared projects; admin disables user → user's next API call gets 401. 3. Integration: project isolation — user A's adapter never returns user B's project (contract suite extension). 4. E2E: admin creates a user, shares a project (edit) → user sees it; revoke → it disappears. --- ## Acceptance criteria (all must pass) - [ ] Admin creates/disables/changes-role of users from the web app. - [ ] Admin manages sharing (grant/revoke/transfer) from the web app. - [ ] Admin sees activity log + instance stats. - [ ] The **same** admin UI works identically in the desktop app. - [ ] A shared-with-read user cannot edit; a shared-with-edit user can. - [ ] Multi-tenant isolation: user A can never read user B's project (enforced at SQL layer). - [ ] Disabling a user revokes their session within one event round-trip. - [ ] Audit log records all admin actions. - [ ] Contract suite still green on both adapters. ## Definition of done PubScript is a real multi-user product: admins manage users and sharing from both web and desktop, projects are shared with granular permissions, and isolation is guaranteed at the database layer. ## Risks & fallbacks - **Permission bugs → data leaks** → enforce isolation at the SQL layer (WHERE project_id in accessible); contract suite tests cross-user access. - **Session revocation latency** → check token validity on every request (small cost) rather than caching; cap the auth lookup. - **Invite email delivery** → v1 logs the invite link (no SMTP); document SMTP as a later addition.