Move user administration to its own page; add Project Super User
User accounts lived in the Admin Console, which is admins-only. Project admins
need to create the accounts on their own jobs without an app admin on the phone,
so accounts move to a new User Directory page and a new role carries the right.
server/auth.py, server/app.py
New permissions role `project_super_user`, between admin and project_admin:
everything a project admin may do, plus user administration SCOPED to the
projects they hold the role on. Four limits make it safe to hand out, all
enforced server-side:
* Scope comes from projects, not the job title. It resolves per membership
(managed_project_ids), so an ordinary account can hold it on one job via
ProjectMember.role, and a super user demoted on one job administers
nobody there. No projects, no authority.
* Account-level changes (password, disable, rename, permissions, delete)
require EXCLUSIVE scope: refused when the target is also on a project the
caller does not administer, because those changes are global. The
directory renders such rows read-only with the reason.
* No admin or super-user targets, and neither role can be granted by a
super user -- that is the line that stops it becoming app-wide control.
* PUT .../projects rebuilds only the caller's own slice; memberships on
projects they do not administer are left untouched. A payload that simply
omits them must not cut someone off a job the caller cannot see.
Creating requires naming at least one of your own projects: an account with
none would be one the creator instantly cannot manage.
/api/auth/users is now scoped rather than admin-only, and carries a per-row
`manageable` verdict plus the reason. Non-managers get a contact card only --
a project user has no business reading colleagues' login history. New
/api/auth/user-scope tells the page what it may offer. Administrative
password resets are now audited; they were the one account change that left
no trace. Settings, feature flags and the auto-add rule stay admin-only.
While here: one definition of "is a user manager", derived from the managed
set. An account-role-only version disagreed with the scoped one and locked
per-project super users out of routes they were entitled to.
html/users.html, html/users.js
The directory: three renderings from one page -- admin (everything), super
user (controls per row, read-only where scope is shared), everyone else (a
read-only directory of the people on their own projects).
html/console.css, html/console-util.js
Extracted from admin.html/admin.js so both console pages share them. A
divergent jsq() is an XSS and a divergent role list offers permissions the
server refuses, so neither may exist twice.
html/wp-sidenav.{js,css}
Global nav drawer, role-gated, carrying ?project= across links. Mounted on
the field view (which had no way to anywhere) plus both console pages.
No migration: users.role is already String(20) and the new value fits.
Verified: 93 scope/gate tests, 29 live HTTP tests through the real dependency
stack, 33 static JS checks. Not verified in a browser -- no JS engine on this
machine -- so users.html and field.html want one manual load.
server/smoketest.py still fails with 401s. Pre-existing: it has no login code,
so auth_gate refuses it. Confirmed unchanged by stashing this work.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user