diff --git a/DEPLOY-runbook-2026-08-04.md b/DEPLOY-runbook-2026-08-04.md index 53234ec..8b5373a 100644 --- a/DEPLOY-runbook-2026-08-04.md +++ b/DEPLOY-runbook-2026-08-04.md @@ -195,12 +195,18 @@ good deploy look broken. Sanity checks — all four should take under a minute: 1. Log in. The home page offers to select or create a project. -2. Open **Admin Console** (the link is on the home page; you need an admin account). - The user table should read as **one line per user** — if rows are three lines tall - and the table spills outside its white card, you are still on the old cached +2. Open **User Directory** (the `Users` link in the top-right menu, or the tile on the + home page). The table should read as **one line per user** — if rows are three lines + tall and the table spills outside its white card, you are still on the old cached files: hard-reload again. -3. Two new cards are present and load: **Projects**, and **Default members on new - projects**. Both should list rows, not an error. + + > Changed since this runbook was written: user accounts moved out of the Admin + > Console into `users.html` when the **Project Super User** role was added, so that + > a project admin can create accounts on their own job. If you are deploying a build + > from before that change, read this step as "Admin Console → the user table". +3. Open **Admin Console** (admin account required). Two new cards are present and + load: **Projects**, and **Default members on new projects**. Both should list rows, + not an error. 4. In the **Projects** card, click **Archive** on a project you don't mind hiding (a `DEMO-` one if there is one), confirm the prompt, then tick **Show archived** — it should reappear marked `archived`. Click **Unarchive** to put it back. That diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 489480b..0190e16 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -332,18 +332,45 @@ console's **Reset password** button). `User.role` is the **permissions** role; `User.project_role` is the person's **job function** on the project (Project Manager, Superintendent, …) and grants nothing. -Both are set in the Admin console's user table. +Both are set on the **User Directory** page (`users.html`) — not the Admin Console, +which no longer manages accounts. | Role | May do | |---|---| -| `admin` | User administration, app settings, and every project | +| `admin` | User administration everywhere, app settings, and every project | +| `project_super_user` | Everything `project_admin` may do, **plus user administration on the projects they hold the role on**: create accounts, reset passwords, set permissions, grant project access | | `project_admin` | On assigned projects: delete work packages, change a **completed** SOP, delete the project | | `project_user` | Create/edit work packages, author a SOP up to completion; may archive a WP but not delete one | Enforced server-side by `require_project_admin` in `server/app.py`; the front end -only hides controls to avoid dead-end clicks. Accounts created before this change +only hides controls to avoid dead-end clicks. Accounts created before roles existed carried the role `user`, which the migration rewrites to `project_user`. +### Project Super User — what bounds it + +The role exists so a project admin can staff their own job without an app admin. +Its limits are what make it safe to hand out, and all of them are server-side +(`managed_project_ids`, `manage_user_problem`, `grantable_roles` in `server/app.py`): + +* **Scope comes from projects, not the job title.** A super user administers the users + of the projects they hold the role on — via their account role, or via + `ProjectMember.role` for a super user on one job only. No projects, no authority. +* **Account changes need EXCLUSIVE scope.** Resetting a password, disabling, renaming, + changing permissions or deleting are global acts, so they are refused when the + target is also on a project the caller does not administer. The directory shows + those rows read-only with the reason. An app admin has to make the change. +* **No admin or super-user targets, and none granted.** A super user may hand out + `project_admin` / `project_user` only, and may not touch an admin's or another + super user's account — so the role cannot become a route to app-wide control. +* **Saving project access never reaches outside scope.** `PUT + /api/auth/users/{id}/projects` rebuilds only the caller's own slice; memberships on + projects they don't administer are left untouched. +* **App settings, feature flags and the default-member rule stay admin-only.** + +No migration is needed for the new role — `users.role` is already `String(20)` and +`project_super_user` fits. Grant it from the User Directory (Permissions column), or +per project from **Project access → Project Super User here**. + ## Feature flags **Admin console → Features.** `bim_enabled` is **OFF by default**: the SOP creator diff --git a/KNOWN-ISSUES.md b/KNOWN-ISSUES.md index aa88274..5f4952d 100644 --- a/KNOWN-ISSUES.md +++ b/KNOWN-ISSUES.md @@ -62,6 +62,14 @@ the token cannot be read, but the injected code does not need it: it runs in the victim's page and can call any API the victim can, including `POST /api/auth/users/{id}/role`. +**The `project_super_user` role (added 2026-08-05) widens the set of victims whose +session is worth stealing, without raising the ceiling.** Previously only an app +admin's session could create accounts or change permissions; now a super user's can +too, within the projects they administer. The ceiling is unchanged — it was already +`admin` — but the odds of landing on a session that can mint an account go up, and a +super user is likelier than an admin to be reading a WP creator on a live job. It is +one more reason the accidental-breakage case is not the only one that matters. + Two controls that look like they would contain this do not: - **CSP does not mitigate it.** `nginx-wp-suite.conf:58` serves @@ -83,6 +91,12 @@ malicious one. suite is exposed outside the corporate network, accounts are issued to subcontractors or clients, or self-registration is added. +Note that the second of those got easier to reach without anyone deciding to: a +Project Super User can now issue accounts on their own job without an app admin +involved, so "accounts are issued to subcontractors" can become true by ordinary +delegated use rather than by a policy change. Worth checking the directory +occasionally against who is actually on staff. + ### What closing it takes Small — roughly half an hour. The helper already exists; it was added to the SOP @@ -102,8 +116,9 @@ function escHandlerArg(v){ return escAttr(String(v==null?'':v).replace(/\\/g,'\\ 4. Confirm with a discipline named `Owner's Equipment`: the pill must respond to clicks and the name must display intact. -The equivalent fix on the admin side is `jsq()` in `html/admin.js` — same ordering, -same reasoning, worth reading before starting. +The equivalent fix on the admin side is `jsq()` in `html/console-util.js` (it moved +out of `html/admin.js` on 2026-08-05 when the User Directory started needing it) — +same ordering, same reasoning, worth reading before starting. --- diff --git a/html/admin.html b/html/admin.html index 43a6214..a0132ea 100644 --- a/html/admin.html +++ b/html/admin.html @@ -13,198 +13,45 @@ + + @@ -241,29 +88,14 @@
- +