Accounts: a new page for the user accounts, projects and sessions; API lists auth/session, auth/login and auth/logout and the 401 without a session, Architecture the auth modules, Concepts the sign-in and the Logout button that changes what the canvas draws instead of reloading, Development the check sum 1118 and how the tools sign in

admin committed 2026-09-20 12:50:57 +02:00
1 parent 685f72c5d3
commit c10fd83436
7 files changed
+93 -4

No files matched your search

+9
@@ -2,6 +2,9 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT
| Method | Route | Body → Result |
|---|---|---|
| GET | `auth/session` | `{user}` — the name of whoever is signed in, `null` when nobody is. Always 200, and the only route that needs no session |
| POST | `auth/login` | `{username,password}` → `{user}` and the session cookie; **401** `{error}` for a wrong pair |
| POST | `auth/logout` | 204, and the cookie is cleared |
| GET | `projects` | `[{id,name,revision,updatedAt}]` |
| POST | `projects` | `{name}` → project document (201) |
| GET | `projects/{id}` | `{id,name,revision,updatedAt,project}` |
@@ -20,6 +23,12 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT
| GET | `plugins/{file}` | the module source as `text/javascript` (`nosniff`, `no-cache`); 403 while the plugin is switched off, 404 for anything but a bare file name there — see [Plugins](Plugins) |
| PUT | `plugins/{file}` | `{active}` → the entry as above. Switches a plugin on or off, server-wide, stored in `data/plugins/plugins.json`; 422 when `active` is not a boolean |
**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
project answers **404**, not 403 — that an id exists is already more than a stranger should learn.
Images, fonts, sounds and plugins are shared between accounts; being signed in is still the price
of admission. See [Accounts](Accounts).
Every project document carries `formatVersion` (`PROJECT_FORMAT_VERSION` in `core/model/Project.ts`,
currently 2). There are no load-time migrations: `serialize.ts` reads what it recognises and drops
the rest.
+65
@@ -0,0 +1,65 @@
Since #177 the editor is behind a sign-in screen: every project belongs to an account, and
nothing is loaded before somebody is signed in.
## Signing in
The sign-in screen is drawn in the same canvas as the editor — no second canvas, no HTML form.
It asks for a user name and a password; Enter moves from the first field to the second and sends
the form from there. The password is drawn as bullets and never appears as itself.
A refused attempt says what went wrong under the form: *Enter your password* before anything is
sent, *Wrong user name or password* when the server refuses the pair.
The **Logout** button sits at the far right of the toolbar. It ends the session and brings the
sign-in screen back. Who is signed in stands in the window title (`Game Maker — user`), not in
the toolbar, which has no room to spare.
Signing in and out never reloads the page and never builds a second canvas: the picture changes,
nothing else. What is unsaved is written before the session ends, the editor then lets go of
everything it held — its tabs, the project, the running plugins, the console lines — and the
sign-in screen is drawn in its place. Signing in builds a fresh editor in the same canvas, so a
second account starts from nothing the first one left behind.
## The session outlasts a deployment
A login is kept on the server (`data/sessions/`, one file per session, named after the hash of
the token in the cookie) and lasts **30 days** of not being used; every request pushes that out
again. So `npm run build` — which is the deployment — replaces the editor under an open tab
without asking for the password again, and so does closing the browser overnight.
PHP's own session handler would not do that: it throws sessions away after
`session.gc_maxlifetime`, 24 minutes by default.
## The accounts
There are three, and for now they are made on the server, not through the editor:
| Account | What it holds |
|---|---|
| `admin` | no projects; the account that will get the admin role |
| `e2e` | the four projects the check tools write: fixture, Jump and Run, Tetris, Actor demo |
| `user` | every other project |
A project file carries its owner (`owner` in `data/projects/{id}.json`). A file without one
belongs to nobody and is invisible to everybody — the safe way round, and the one that shows up
straight away when a new project file is put there by hand without an owner.
## Making an account
php tools/make-user.php <username> <password> # creates it, or sets the password
php tools/make-user.php --list
Passwords are stored as hashes (`password_hash`) in `data/users.json`, which is git-ignored, as
is `data/sessions/`. There is no route for signing up: registration (#178), administering users
(#179), roles (#180), shared projects (#181) and changing one's own password (#182) are
follow-ups of their own.
## What the check tools do
Every seed, smoke and e2e run signs in as `e2e` first and sends the same session cookie a browser
would (`tools/lib/auth.mjs`). The password comes from `GAME_MAKER_E2E_PASSWORD` or from
`data/e2e-password.txt`, which is git-ignored — CI writes its own on a fresh checkout and makes
the account with it (`tools/ci/serve.sh`).
The browser in an e2e run is handed that session as a cookie, so it starts on the editor. The
sign-in screen itself is driven by hand in step 45, the one step that sees it.
+7 -2
@@ -79,6 +79,7 @@ src/core/graph/ graph model, NodeRegistry/NodeContext/values, interpreter
src/core/particles/ nodes (the catalogue), compile (a graph → one function per output),
emission (which particles are alive at a second — a pure rule, no state)
src/core/store/ serialize (untrusted JSON → Project), tilemapCells (the compact form of a layer's cells in the file), http (one API call), ProjectStore/HttpProjectStore,
AuthStore/HttpAuthStore (signing in and out, #177),
FileStore (what the server holds of one kind) + HttpFileStore (one generic client over the three
file collections) — TextureStore, FontStore and SoundStore are its type parameters, nothing else
src/core/time.ts the global clock: one scaled step a frame, shared by the world, the particles, the shaders and the animation preview
@@ -102,7 +103,9 @@ src/viewport/ view (transforms), items (what to draw), symbols (how to
drawOrder (objects and tilemap layers by z, #174), activeCamera — no editor concepts,
so tests reach it freely
src/editor/ App → Editor (layout + ticker), PlaySession (the run), ProjectSession (load/create),
EditorRun (run in editor), state, actions, shortcuts, debugHook, autosave,
EditorRun (run in editor), state, actions, shortcuts, debugHook,
textProbe (finding widgets by their text — the ground floor of the test hook, shared with the sign-in screen),
auth/LoginScreen + auth/loginRules (the sign-in screen in the canvas and what it may complain about, #177), autosave,
polygonEdit (corners of an outline: what is under the pointer, whether a point is inside, put / move / take away – scene view and Texture tab),
fileImport (drop/paste) → assetImport (one upload pipeline, an ImportKind per sort) ← textureImport / fontImport / soundImport,
FilePicker (the shared "+ → Texture / Font / Sound" dialog; ImagePicker/FontPicker/SoundPicker only describe their row),
@@ -144,7 +147,9 @@ src/editor/particles/ ParticlesEditor (graph canvas left of a preview panel, sp
ParticlePreview (the panel: the asset's own particles, wearing its own rows)
src/editor/list/ ListEditor (a row per item), slot (which list is being edited, and how it is written back)
src/editor/console/ log (ring buffer, argument formatting, the console.* capture), ConsolePanel (the tail under the tabs)
api/ PHP backend: index.php (routing), lib/{Store,TextureStore,FontStore,SoundStore,PluginStore,OriginalName,Response}.php
api/ PHP backend: index.php (routing and the sign-in gate),
lib/{Store,TextureStore,FontStore,SoundStore,PluginStore,OriginalName,Response}.php,
lib/{Auth,UserStore,SessionStore}.php (#177: accounts in data/users.json, sessions in data/sessions/)
data/projects/ persisted projects, data/textures/ + data/fonts/ + data/sounds/ content-addressed uploads,
data/plugins/ editor plugins and plugins.json (which are switched on); filled from plugins/ by the build and the seeder
tests/ Vitest unit tests tools/ seed-*, e2e (steps in tools/e2e/, rig in tools/e2e/lib/), smoke-*, lib/, assets/ (the walk demo's sheet)
+1
@@ -1,5 +1,6 @@
| Term | Meaning |
|---|---|
| **Account** | Who you are signed in as. The editor asks for a user name and a password before it loads anything, and every project belongs to one account — see [Accounts](Accounts). The **Logout** button is at the far right of the toolbar; signing in and out changes what the canvas draws, it never reloads the page. |
| **Asset** | Named thing with a type (`scene`, `camera`, `sprite`, `text`, `behavior`, `collider`, `container`, `texture`, `animation`, `statemachine`, `shader`, `font`, `sound`, `particles`, `variables`) and typed properties. Assets have **no position**. |
| **Texture** | An uploaded image. Scenes, textures, animations, state machines, shaders, fonts, sounds and Variables assets are the eight types that cannot be placed: a texture has no position, it is *worn* by a sprite through the ordinary property `texture`. See [Textures](Textures). |
| **Font** | An uploaded typeface, worn by a text through the ordinary property `font`; no font means the editor's own. Uploaded and picked exactly like a texture, and stored the same way. See [Fonts](Fonts). |
+9 -2
@@ -12,7 +12,8 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
# code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1109 editor checks, screenshots in /tmp/gm-e2e-*.png
php tools/make-user.php <name> <password> # an account; every tool signs in (see Accounts)
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1118 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
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
@@ -22,6 +23,12 @@ node tools/seed-actor.mjs && node tools/smoke-actor.mjs # 21 checks: the w
node tools/seed-e2e.mjs && node tools/perf-shaders.mjs # what a shader costs per sprite; a measuring tool, not a check
```
**Every tool signs in first.** Since #177 the API answers 401 without a session, so the seeds, the
smokes and the e2e run log in as the `e2e` account (`tools/lib/auth.mjs`) and send the cookie a
browser would; the e2e browser is handed the same session over CDP. The password comes from
`GAME_MAKER_E2E_PASSWORD` or from the git-ignored `data/e2e-password.txt`. A machine that has
neither fails with a message saying exactly that — see [Accounts](Accounts).
**A run needs a working GPU.** An idle editor page keeps rendering a picture that never changes, and
in software that is ruinous: measured here on an idle page over 20 s, software WebGL costs **373% of
a core at 8 fps**, the same page on the Intel HD 630 via ANGLE/Vulkan costs **69% at 56 fps**. The
@@ -64,7 +71,7 @@ The e2e run is a list of steps in `tools/e2e/` (hierarchy, inspector, play, asse
graph, parenting, colliders, containers, text/enabled, menus, textures, frames, overflow, aspect,
container tab, run-in-editor, pause, nested behaviors, key repeat, list tab, struct variable,
variable nodes, property nodes, animations, state machines, console, shaders, transforms, fonts,
particles, shared scope, render on change, sounds, load scene, graph groups, nine-slice, plugins, tiles, tilemaps, physics, shapes, queries, documents) that build on each other's
particles, shared scope, render on change, sounds, load scene, graph groups, nine-slice, plugins, tiles, tilemaps, physics, shapes, queries, documents, sign-in) that build on each other's
scene state and share the CDP driver in `tools/lib/harness.mjs` — `press`, `undo`, `play`,
`object(id)`, `world()`, `mutate(js)` and friends, so a check reads like the interaction it
exercises.
+1
@@ -10,6 +10,7 @@ Live: <https://nockal.com/game-maker/> · Widget playground: <https://nockal.com
| Page | What it covers |
|---|---|
| [Concepts](Concepts) | Assets, scene objects, overrides, containers, play mode, the camera, the console, editing while playing, running without Play |
| [Accounts](Accounts) | Signing in and out, the three accounts, who a project belongs to, how a session outlives a deployment |
| [Scenes](Scenes) | The active scene and the start scene, the switch next to Play, `Load scene` and what a swap keeps |
| [Scene editing](Scene-editing) | Placing by drag, editor symbols, z-order, hierarchy and parenting, the editor offset |
| [Behaviors](Behaviors) | Node graphs: variables and properties, Variables assets, the node set, structs and lists, loops, behaviors as nodes, editing the graph, the live view |
+1
@@ -1,6 +1,7 @@
**Using the editor**
- [Concepts](Concepts)
- [Accounts](Accounts)
- [Scenes](Scenes)
- [Scene editing](Scene-editing)
- [Behaviors](Behaviors)