Say that only the owner deletes a project (#272)

The API now answers an administrator 403 when they delete somebody
else's project. The pages still said an administrator deletes every
project like their own.

- API: the DELETE row names the 403, and the paragraph on
  administrators says deleting stays with the owner.
- Accounts: the list of what an administrator may do, and the Delete
  row of the user management.
- Architecture: one line on what `seesAll` covers.
- Development: the e2e total, 1602 to 1603.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adminandClaude Opus 5.5 committed 2026-09-30 21:13:31 +02:00
1 parent 6413fa595f
commit 442ad12097
4 files changed
+15 -9

No files matched your search

+6 -3
@@ -10,7 +10,7 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT
| POST | `projects` | `{name}` → project document (201) | | POST | `projects` | `{name}` → project document (201) |
| GET | `projects/{id}` | `{id,name,revision,updatedAt,project}` | | GET | `projects/{id}` | `{id,name,revision,updatedAt,project}` |
| PUT | `projects/{id}` | `{revision, project}` → `{revision,updatedAt}` or **409** `{error,revision}` when stale | | PUT | `projects/{id}` | `{revision, project}` → `{revision,updatedAt}` or **409** `{error,revision}` when stale |
| DELETE | `projects/{id}` | 204, and the snapshots and the release of the project go with it | | DELETE | `projects/{id}` | 204, and the snapshots and the release of the project go with it. **403** `{error}` for an administrator who is not the owner |
| GET | `projects/{id}/snapshots` | `[{id,name,revision,createdAt}]`, newest first — see [Concepts](Concepts) | | GET | `projects/{id}/snapshots` | `[{id,name,revision,createdAt}]`, newest first — see [Concepts](Concepts) |
| POST | `projects/{id}/snapshots` | `{name?}` → `{id,name,revision,createdAt}` (201). Copies the project as stored; a blank name becomes the UTC date and time. **422** over 120 characters | | POST | `projects/{id}/snapshots` | `{name?}` → `{id,name,revision,createdAt}` (201). Copies the project as stored; a blank name becomes the UTC date and time. **422** over 120 characters |
| DELETE | `projects/{id}/snapshots/{sid}` | 204 | | DELETE | `projects/{id}/snapshots/{sid}` | 204 |
@@ -39,8 +39,11 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT
**Everything but `auth/*` needs a session** and answers **401** `{"error":"Sign in first"}` without **Everything but `auth/*` needs a session** and answers **401** `{"error":"Sign in first"}` without
one. Projects belong to an account: `projects` lists only the caller's own, and somebody else's one. Projects belong to an account: `projects` lists only the caller's own, and somebody else's
project answers **404**, not 403 — that an id exists is already more than a stranger should learn. project answers **404**, not 403 — that an id exists is already more than a stranger should learn.
An **administrator** (#180, #179) reads, saves and deletes every project with the same routes, and An **administrator** (#180, #179) reads and saves every project with the same routes, and a save
a save keeps the project's `owner`; their `projects` list stays their own. The `users` routes and keeps the project's `owner`; their `projects` list stays their own. Deleting a project stays with
its owner (#272): an administrator gets **403** `{"error":"Only its owner may delete a project"}`,
also for a file without an owner. Snapshots and the release of the project are theirs to manage
all the same. The `users` routes and
deleting a stored image, font or sound answer anybody but an administrator **403** deleting a stored image, font or sound answer anybody but an administrator **403**
`{"error":"Administrators only"}`. `{"error":"Administrators only"}`.
Images, fonts, sounds, cels and plugins are shared between accounts; being signed in is still the price Images, fonts, sounds, cels and plugins are shared between accounts; being signed in is still the price
+7 -5
@@ -59,10 +59,12 @@ not offer; it shows the role nowhere else, there is no badge and no word in the
An administrator An administrator
- manages the accounts in the **User management** tab (below), and - manages the accounts in the **User management** tab (below), and
- opens, edits and deletes **every** project as if it were their own (#179). A save keeps the - opens and edits **every** project as if it were their own (#179). A save keeps the project's
project's owner: an administrator who fixes somebody's project does not take it over. Their own owner: an administrator who fixes somebody's project does not take it over. Their own project
project list (*Open project…*) still shows only their own; the others are reached through the list (*Open project…*) still shows only their own; the others are reached through the user
user management. management.
- does **not** delete somebody else's project (#272). Only the owner deletes a project; the
server answers an administrator **403**, so nothing goes missing without its owner knowing.
## User management ## User management
@@ -78,7 +80,7 @@ how many projects it owns, the projects listed under it with **Open**. On the ri
| **Rename…** | A new name. The account's projects move with it — the name is their `owner` — and the account is signed out everywhere, since its sessions carry the old name | | **Rename…** | A new name. The account's projects move with it — the name is their `owner` — and the account is signed out everywhere, since its sessions carry the old name |
| **Role** | `user` or `admin`, picked from a menu | | **Role** | `user` or `admin`, picked from a menu |
| **Password…** | Sets a new password without the old one. The account's sessions end; on the administrator's own row their own session stays | | **Password…** | Sets a new password without the old one. The account's sessions end; on the administrator's own row their own session stays |
| **Delete** | Asks first, then removes the account and signs it out. **Refused while it owns projects** — they would belong to nobody and vanish from every list; delete them first | | **Delete** | Asks first, then removes the account and signs it out. **Refused while it owns projects** — they would belong to nobody and vanish from every list. The account deletes them first; an administrator cannot (#272) |
| **New user…** | In the tab's header: name, password (twice) and role | | **New user…** | In the tab's header: name, password (twice) and role |
On their **own row** an administrator may only set a password: renaming themselves would end the On their **own row** an administrator may only set a password: renaming themselves would end the
+1
@@ -199,6 +199,7 @@ src/editor/users/ the user management (#179): ManagementPane (the tab behin
server's own sentence. The server decides everything: `api/lib/AccountRules.php` server's own sentence. The server decides everything: `api/lib/AccountRules.php`
(not the own account, not the last administrator, not an account with projects), (not the own account, not the last administrator, not an account with projects),
`Auth::requireAdmin()` (403), `Store` with `seesAll` for an administrator `Auth::requireAdmin()` (403), `Store` with `seesAll` for an administrator
(reads and saves every project, deletes only their own: `NotOwnerException`, #272)
src/player/ GameScreen (the game on the page: Auto zoom, the caption before and after a run), src/player/ GameScreen (the game on the page: Auto zoom, the caption before and after a run),
Player (loads, starts on a click, ticks, stops), playerHook (`window.__gameMakerPlay` Player (loads, starts on a click, ticks, stops), playerHook (`window.__gameMakerPlay`
for the check scripts) — a released game (#266), without the editor for the check scripts) — a released game (#266), without the editor
+1 -1
@@ -18,7 +18,7 @@ php tests/php/accounts.test.php # the API's accoun
# has and the web server's container has not (SKIP there) # has and the web server's container has not (SKIP there)
php tests/php/snapshots.test.php # the snapshot store (#231), the same way php tests/php/snapshots.test.php # the snapshot store (#231), the same way
php tests/php/releases.test.php # the release store (#266), the same way php tests/php/releases.test.php # the release store (#266), the same way
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1602 editor checks, screenshots in /tmp/gm-e2e-*.png node tools/seed-e2e.mjs && node tools/e2e.mjs # 1603 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result ./e2e.sh status # live progress, or the last run's result