3f7eb0314368a26382f5b1e0989f5d1d45288437
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6b49a0bea7 |
Give each account its own folder of uploaded files to delete (#344)
Uploaded images, fonts and sounds belonged to nobody. Every account saw every file in the pickers, and only an administrator could delete one, through the API alone. Now every account uploads into a folder of its own, sees its own files and nobody's, and can delete a file of its own that no project uses. - Files are stored as data/<kind>/<folder>/<sha256>.<ext>, and the folder is part of asset.file. A name without a folder belongs to nobody. - The folder is a random 12-character name kept in users.json and given with the first upload. Renaming an account moves no file, and a released game's URLs carry no account name. - The list shows the caller's own files and nobody's. An administrator sees all, each with owner and deletable. - DELETE takes the name with its folder. It answers 403 unless the file is the caller's or the caller is an administrator, and 409 while a project, snapshot or release of any account names it (FileUsage). The 409 names the caller's own projects only. - The pickers have a Delete button. The first press asks, the second deletes, and a refusal shows the server's reason. - A deleted account's folder stays and only administrators see it. - tools/migrate-file-owners.php moved every used file into the folder of the account using it (a copy each for two accounts) and rewrote projects, snapshots and releases. A project's revision goes up, so an open editor gets a conflict instead of saving old names back. - Font families are named after the hash alone, so a folder never ends up in a CSS family name. Run on stein2 on 2026-10-07, backup in /root/config-backup-20261007/data-before-344: 25 files used, 27 copies, 8 projects, 53 snapshots and 1 release rewritten, 30 unused files stay nobody's. Afterwards all 600 references resolve to a file in their owner's folder. Found while building: the Delete button cut its new label to "Del…" because a button does not measure itself again on a new text. The picker now lays its button row out again. Tests: - PHP files.test.php (new): 46 checks, counter-check 3 red - Unit core + editor: 220 files, 3403 tests - e2e 34 (melektaus-atx): ALL PASSED - 67 checks (2 new) - Gate (melektaus-atx): CHECK EXIT 0, 256 files, 3977 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cbda6d64a7 |
Let only the owner delete a project, not an administrator (#272)
#179 decided that nobody deletes a project on someone else's behalf. The API still let an administrator do it, because the admin store treated every project as its own. Option A from the issue is now in place: opening, editing, snapshots and release stay open to the administrator, deleting stays with the owner. - Store::delete checks the owner under the save lock and throws NotOwnerException for anyone else. It follows the pattern of StaleRevisionException. - The route answers that with 403 "Only its owner may delete a project". A file without an owner is covered the same way. Non-admins still get 404 for a project that is not theirs. - The editor has no way to delete a foreign project, the project picker lists only one's own. Nothing changes there. - An account that owns projects can no longer be deleted by an administrator at all. Its owner has to delete them first. - e2e 49 deleted the throwaway projects as administrator, both in its sweep and in 49i. It now signs in as the owner for that. 49i checks both answers. Tests: - tests/php/accounts.test.php: 56 checks passed (was 52, one check turned around, five new) - e2e 49: 22 checks passed (was 21), deleteByAdmin 403, deleteByOwner 204 - Gate: 197 files, 2855 tests passed - Counter-checks: without the owner check 4 PHP checks and 49i fail - The e2e total of 1603 is computed (1602 + 1), not measured Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
14972d057f |
Refactoring round 8: hide data files, safe saves, Esc as Cancel (#85)
Recurring cleanup round over the code since round 7 (
|
||
|
|
4194f3e150 |
Share a link that plays the game without the editor (#266)
A project could only be played inside the editor, behind the login.
To show a game to someone else there was no way short of an account.
A release freezes the saved project under a random token and serves
it on a page that holds nothing but the game view.
- Menu -> Release... opens a dialog with the state of the release,
the link with Copy link and Open, Publish or Update release, and
Take down behind a confirmation.
- Publish saves first and refuses while anything is unsaved, like
snapshots. Updating keeps the link. Take down kills it, and a new
Publish hands out a new token.
- The link is play.html?game=<32 hex>. The page needs no session,
paints Theme.colors.background, zooms with Auto and waits for
"Click to play" so that sound and keyboard focus are allowed.
- The game always starts in the start scene. Persistent variables
are stored per link in the browser. Plugins are not loaded, and the
dialog warns about them and about a project without a scene.
- GET ?r=releases/{token} is the only route besides auth/* that works
without a login. The public answer carries the token as project.id,
so the project id stays hidden. Release files are written with 0640
and nginx does not serve them directly (measured 403).
- Starting, the frame and Load scene moved into runtime/GameRun.ts.
Play in the editor and the play page run through the same code.
- WorldViewport moved to src/viewport/ and no longer needs the
EditorState. Pointer forwarding (gamePointer.ts) and setting up
textures, fonts and sounds (projectFiles.ts) exist only once.
- Vite builds two pages. The play page loads 4 kB of its own code
plus the shared chunk (Pixi, runtime, drawing, 653 kB) and Rapier,
without the editor.
Found on the way, not fixed here: data/projects/<id>.json answers 200
without a login because the files are 0644.
Tests:
- ./check.sh: 194 files, 2808 tests, exit 0.
- tests/php/releases.test.php: 20 checks, also added to CI.
- New e2e module 60 with 10 checks, played in a browser without a
session. Module 46 counts the menu entries and was updated.
- Full e2e run: 1572 checks passed.
- Counter-checks: 3 unit, 2 PHP and 3 browser mutations all fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
94749b4f3e |
Let a project keep named snapshots and restore them (#231)
Autosave only keeps the latest revision. Before a risky change there
was no way to put a known good state aside and go back to it later.
The main menu now has "Snapshots..." to create, restore and delete
named copies of the open project.
- Server: SnapshotStore.php keeps each snapshot as a copy of the
project JSON under data/snapshots/<project>/ (git-ignored). Four
routes under projects/{id}/snapshots use the same owner check as the
project, so an admin also sees foreign snapshots.
- Create writes the autosave first. With unsaved edits (play with open
edits, failed save, conflict) no snapshot is made and a message says
why. The rule is the pure function in src/editor/snapshots.ts.
- An empty name falls back to the local date and time.
- Restore asks first, then keeps the current state as "Before
restoring <name>". It writes the snapshot back as a new revision and
reloads the project, so the undo list is empty. A restore can itself
be undone by restoring that snapshot.
- Deleting a project deletes its snapshots too. Images, sounds, fonts
and pixel art are content-addressed and never deleted, so a snapshot
can always reach them.
- Client: SnapshotStore and HttpSnapshotStore in src/core/store, the
SnapshotsDialog, and the flows in ProjectSession. A stale revision
(409) becomes StaleRevisionError.
- CI runs the new PHP test in the gate job next to accounts.
Not covered by e2e: the lock during play and the conflict case. Both
are unit tests only.
Tests:
- check.sh: 164 files, 2401 tests, CHECK EXIT 0.
- tests/php/snapshots.test.php: 14 checks, all passed.
- tests/editor/snapshots.test.ts: 5 tests for the time format, the
blocker rule, the routes and 409.
- New e2e module 50 on a throwaway project: create, rename, restore,
undo the restore, delete, then delete the project and get 404 on
its snapshots. 46b expects the new menu entry. e2e 46,50: 20 checks
passed. The check sum goes from 1385 to 1393 (computed).
- Counter-checks, originals restored and compared with cmp: blocker
always allowed fails 1 unit test. No sorting fails 2 PHP checks.
Restore without keeping the old state fails 50c and aborts 50d.
- The first sort test with 2 snapshots passed by chance half the time
because of random ids. It now uses 8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
ceaa44e5b8 |
Add roles and a user management tab for admins (#180, #179)
Until now every account could do the same, and managing accounts meant running make-user.php on the server. Accounts now carry a role, and admins get a tab in the editor to create, rename, re-role, reset and delete accounts. Both issues share the same server files, so they land in one commit. #180 Roles: - Every account is admin or user. A missing or unknown role counts as user, on the server and in the client. - auth/session and auth/login return {user, role}. - Auth::requireAdmin() answers everyone else with 403. - make-user.php takes the role as a third argument. Without it an existing account keeps its role, so a password reset from the CLI never demotes an admin. - Admins see and save other users' projects like their own. Saving keeps the original owner. #179 User management: - The account menu gets "User management" (admins only) and "Plugins" (everyone). Both open the same closable tab, accounts on top and plugins below. The fixed Plugins tab is gone. - Per account: Rename, Role, Password (no old password needed) and Delete with a confirmation. "New user..." creates one. - Each account lists its projects with an Open button. - Delete is refused while the account still owns projects. - Renaming moves the projects along and ends every session of the account. transfer takes the same lock as save. - On their own row an admin can only set the password. The last admin cannot be demoted or deleted. The server enforces both. - The account rules live in AccountRules.php and accountRules.ts as pure functions, so they are testable without a server. Findings: - At 1280 px the Delete button reached past the column edge and was not clickable. The name column is flexible now, and module 49 checks the layout. - The server lets an admin delete other users' projects, but the editor has no button for it yet. Left open in the issue. Tests: - ./check.sh: 148 files, 2214 tests, exit 0 (was 147 / 2204). - New tests/php/accounts.test.php, 51 checks, plain PHP. CI runs it as its own step in the gate job, the host has no PHP. - New e2e module 49 with 18 checks. CI creates the e2e-admin account in serve.sh. Modules 14, 38, 46 and the scaffold follow the new tab. - e2e 1-16,37,38,45,46,49: all passed, 581 checks. - Counter-checks: owner on save, admin view, default role and self delete in PHP fail 7, 5, 5 and 1 checks. Unchecked requireAdmin, a wider column and the caller as owner each fail exactly their e2e check. - Check sum 1314 -> 1333 (+1 in 38, +18 in 49), computed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |