edef2b632340b4ac2c77456b253f2b99622f82d9
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cfd6d7a8df |
Lock the server down before the editor goes public (#427)
The editor is about to be opened to strangers: a post on Reddit and a guest access (#428). nginx served the whole workspace and ran every PHP file in it, the API let anybody guess passwords as fast as they liked, and one account could fill the disk. - nginx (shared web stack, outside this repo, backup in /root/config-backup-20261015/) hands out only the two pages, dist/, public/brand/, the uploads by kind and ending, and api/index.php. Everything else under /feuerwerk/ is a 404. - PHP behind it has display_errors off and no X-Powered-By. Plain HTTP forwards to HTTPS, the API refuses it. - Headers: a Content-Security-Policy without inline code or eval, nosniff, frame-ancestors, HSTS, Referrer-, Permissions-, COOP, CORP. - vite.config.ts writes the same policy into the built pages, so CI runs the whole suite under it. Pixi gets by without eval through pixi.js/unsafe-eval. The page CSS left its inline style for a file. - The harness fails a module for anything the policy refuses, from Chrome's log and from the page's securitypolicyviolation event. - LoginThrottle: five wrong passwords from one address within 15 minutes, and it gets 429 until 15 minutes after the last of them. The address is the last X-Forwarded-For entry, the Brandmauer's own. - Quota: 1 GB per account for uploads, projects and snapshots. Only a write that grows the account is refused (413). Admins have no limit. - A write whose Sec-Fetch-Site says another site is refused (403). - The session cookie carries Secure, nginx passes HTTPS on. - showFailure puts the message in as text, not as HTML. - Every PHP script outside api/ stops unless it runs on the command line. - The video worker sees the checkout read-only and can only write data/videos. No capabilities, 2 GB and 256 processes at most. - tests/php/rights.test.php runs the API under php -S on a scratch directory and asks every route as signed out, owner, stranger and admin. A route named in api/index.php but missing in the matrix fails it. limits.test.php covers the brake and the quota. Findings: - tmp/ was readable from outside, with 79 copies of project files. Closed on 15.10. at 20:02 with chmod 750. The log back to 11.10. shows only our own requests. - Port 8080 is bound on 0.0.0.0, so the LAN could reach nginx past the Brandmauer. Left as it is: the web stack is not this project's. - npm audit finds nothing in what is shipped. Dev tools (vitest, brace-expansion, source-map-js) have advisories, not touched here. Tests: check 313 files / 4843 tests. PHP limits 21, rights 60, accounts 56, snapshots 15, releases 20, files 46 (videos 50/51, no ffmpeg in the web container). e2e on stein2, atx was off: 34+38+41 197/198, the one red check (a click in 41) passes with 41 alone, 27/27. 67 misses frames at 1080p on stein2 with and without this. Path list from outside: 10 served, 26 refused. ZAP baseline against the live site: nothing High or Medium under /feuerwerk/. Counter-checks: rights.test.php without the cross-site guard, the quota, the brake, the admin check of users, or with an undocumented route fails each time. A policy without wasm-unsafe-eval fails e2e 41 with the harness's own CSP line. 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> |
||
|
|
c830c7a3aa |
Changing the password: the Logout button at the far right of the toolbar became an Account menu that names the signed-in account in an entry of its own that cannot be picked, with Change password... above Logout, and the new dialog (src/editor/auth/PasswordDialog.ts) asks for the current password, the new one and its repetition, all three as bullets, and stays standing when the server refuses so that it can say why, unlike the other form dialogs; the rules for empty, too short, new equal to old and a differing repetition sit as the pure function passwordProblem() next to loginProblem(), the API gained POST ?r=auth/password which answers 204, insists on the current password although a session is there, enforces a minimum length of 8 in UserStore::MIN_PASSWORD_LENGTH and again in the client so the mask can answer without asking the server, and the new SessionStore::destroyAllFor($user, $keep) ends every other session of the account while the tab that changed the password keeps its own; the placeholders admin/admin and user/user stay as they are, which the length check does not touch because it only runs when a password is set through the API; e2e 45 grew by 8 checks for the menu, the dialog, both refusals and signing in with the old and the new password, and it sets the e2e account back at the end - a run dying in between leaves it on <password>-tmp, which a make-user.php call repairs (#182)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1ef497fa17 |
e2e 45 no longer hangs on the project name: the two checks that opened the editor compared the project's name against "E2E fixture", which is what the seeder writes locally, while CI creates the shell with the key from tools/ci/project-shells.mjs and so calls the same project "fixture" - that is why the run for 8e4bb6e was red with two FAILs although signing in, the editor and the project all worked; both checks now read the project id and the seeded scene name "Level 1", which say the same thing in both places, and since the firewall in front of nockal.com stopped turning every non-2xx into its own 418 page (measured today: the API's 401 comes through with its body) the wrong-password check asks for the wording again instead of just for any message, with the unit test for a page made up in between kept but no longer named after that firewall (#177)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8e4bb6e1f9 |
User management: signing in is required before anything is loaded, the sign-in screen is drawn in the same canvas (user name, password as bullets, Enter moves on and submits) and the editor is built only after it; the API gained auth/session, auth/login and auth/logout while every other route answers 401 without a session, projects carry an owner and a foreign one comes back as 404, accounts live in data/users.json with a password hash and sessions as files in data/sessions with a sliding 30 days, so a build under the open tab does not sign anybody out; Logout sits at the far right of the toolbar and, as asked in the issue, does not reload the page: Editor.dispose() takes the editor down while the canvas stays, which is why the autosave, the focus handlers, the window listeners of the tilemap panel, the shortcuts and the plugins all got a way back; the tools sign in as e2e (tools/lib/auth.mjs, password from GAME_MAKER_E2E_PASSWORD or the git-ignored data/e2e-password.txt) and the browser gets the same session by cookie, new e2e module 45 covers the screen, the wrong pair, the bullets, logout without a reload and signing in again, new unit tests for HttpAuthStore and for what a session leaves behind (#177)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |