diff --git a/API.md b/API.md index 02e02a8..62c365a 100644 --- a/API.md +++ b/API.md @@ -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. diff --git a/Accounts.md b/Accounts.md new file mode 100644 index 0000000..68cb56f --- /dev/null +++ b/Accounts.md @@ -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 # 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. diff --git a/Architecture.md b/Architecture.md index 87db9dd..619d6d6 100644 --- a/Architecture.md +++ b/Architecture.md @@ -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) diff --git a/Concepts.md b/Concepts.md index d15f361..03590da 100644 --- a/Concepts.md +++ b/Concepts.md @@ -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). | diff --git a/Development.md b/Development.md index 293e0d4..58a69ba 100644 --- a/Development.md +++ b/Development.md @@ -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 # 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. diff --git a/Home.md b/Home.md index 83c5b00..ac630e7 100644 --- a/Home.md +++ b/Home.md @@ -10,6 +10,7 @@ Live: · Widget playground: