Commit graph

3 commits

Author SHA1 Message Date
ScreenTinker 7615eabdd5 feat(admin): Workspace column + inline move/assign on the Users page
Adds a "Workspace" column (after Plan) to the platform Users admin table so a
platform_admin can see and reassign a user's workspace inline, alongside the
Role/Plan dropdowns. Single-workspace move/assign model.

Backend:
- GET /api/auth/users (platform branch): one aggregate query adds
  workspace_count and, for exactly-one membership, the workspace id/name + org
  name (no N+1).
- PUT /api/admin/users/:id/workspace (requirePlatformAdmin - operator excluded):
  move (1 membership) or assign (0) into the chosen workspace, default role
  workspace_viewer, in a transaction; no-op if already there; REFUSES (400) a
  user with >1 membership (manage in the members view). logActivity
  admin_set_user_workspace.

Frontend (admin.js):
- Editable <select> only for a 'user' with 0/1 membership; multi-membership ->
  read-only "N workspaces", platform staff -> read-only "Platform (all)".
- Options grouped by org via <optgroup>, built ONCE from /me's
  accessible_workspaces (same source as the Add User picker) and reused per row.
- Picking "Unassigned" or the same workspace is a no-op so a stray pick can't
  strip a membership. Success -> toast + refresh. EN i18n only.

Tests: 4 added (single-membership move 200 + changed, zero-membership assign
200, multi-membership 400 refused, non-platform-admin/operator 403). npm test
16/16. Verified headless: column renders, selected value correct, "Platform
(all)" for staff, and a dropdown move persisted (throwaway user, cleaned up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:34:47 -05:00
ScreenTinker 5502a3eaa8 fix(roles): make platform_operator assignable + add deny/assign regression tests
The bug: #13 added 'platform_operator' to the frontend role dropdown
(PLATFORM_ROLE_OPTIONS) but #14's PUT /api/auth/users/:id/role whitelist
(ASSIGNABLE_PLATFORM_ROLES) only listed ['user','platform_admin'], so
selecting "Platform operator" returned 400 "Invalid role" - the role was
unassignable via the UI.

Fix: add 'platform_operator' to ASSIGNABLE_PLATFORM_ROLES. One line; the
self-demote guard is intentionally left untouched (a platform_admin still
cannot self-assign the non-owner operator role and lock themselves out).

Tests (node:test, isolated in-memory DB injection - no DB_PATH change):
- admin-users.test.js: platform_admin can PUT role=platform_operator on a
  target user -> 200 and the row persists as platform_operator (regression
  guard for the whitelist gap).
- operator-permissions.test.js (new): verify-then-test of the highest-blast
  -radius deny. Operator CAN update/delete a workspace-scoped content row
  (cross-org write works) but is denied (403) updating or deleting a shared
  (workspace_id IS NULL) row - proving the separate PLATFORM_ROLES gate in
  content.js's checkContentWrite still holds after canWrite was broadened to
  isPlatformStaff.

Verified read-only (no leak): the other shared-asset write sites keep their
PLATFORM_ROLES gate that excludes operator - kiosk.js:57, widgets.js:110,
folders.js:31, layouts.js:59/117/133.

cd server && npm test -> 12 pass / 0 fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:44:39 -05:00
ScreenTinker 7674f6dc9f test(admin): node:test coverage for Add User + role gating
Adds server/test/admin-users.test.js and a `npm test` (node --test) script.
No DB_PATH override: the suite mounts the real routers against an isolated
in-memory better-sqlite3 instance injected into the require cache, seeded by
the test itself. Node v20 built-ins only (node:test, node:assert, fetch).

Covers: Add User success (response omits password/hash, hash stored not
plaintext, membership written, hosted lifecycle sentinels stamped, audit row
without the password), duplicate-email 409 (no overwrite), non-admin 403,
platform_operator denied (403), org_admin scoped to their own org only,
input validation, and the must_change_password lifecycle (set on create,
surfaced on login, cleared on PUT /api/auth/me).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 11:23:06 -05:00