Commit Graph

5 Commits

Author SHA1 Message Date
type-two
7e568d8323 ✦ revelation: CANON — factory dimensions (Stage 2C)
Four shipped patches on a read-only shelf: ✦ the heartbeat (boom-bap,
mpc 8-4, kick duck), ✦ the wheel within the wheel (WHEELS polymeter —
kick 16 / hat 12 / clap 10), ✦ the squeeze (GODSQUASH — the market &
quakes pump the master), ✦ quiet arrival (gentle earth-echo ambient,
no drums). git-tracked in factory/, authored in the client's
serializePatch shape.

Course-correction from the brief's stale anchor: the template select
reads /api/presets (account-based, auth.py), NOT the hub's legacy
patchList — so the factory shelf is injected there. list_factory() +
load_factory() prepend the ✦ patches for every signed-in player;
PUT/DELETE on a ✦ name → 403 (read-only); a missing factory/ degrades
to []. test_factory.py (deploy-excluded) validates shape.

Verified: HTTP dispatch serves the 4 ✦ names leading the list, loads
one, rejects writes (403), user saves still work, traversal/missing →
None, zero index.html changes. Taste disclaimer: these are structural
drafts — correct shape is the deliverable, beauty is the human's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:13:38 +10:00
type-two
47b51b98ca 🔒 session-token credential binding + admin-guard/CSRF hardening (adversarial review)
Fable flagged that stateless session tokens bound only to uid+expiry — so an
admin action couldn't actually invalidate a live 30-day cookie. Reproduced 3
facets, all one root cause, all fixed by binding the token to the account's
credential state:
- make_token/read_token/_bind: token = uid:exp:bind:sig where
  bind = hmac(secret, 'uid:created:salt')[:16]; read_token re-derives it from the
  user's CURRENT row and rejects on mismatch/missing. So a token dies on account
  DELETE (no ghost session/writes), password RESET (salt rotates → admin can truly
  lock someone out), and SQLite id-REUSE (different created → no identity takeover).
  Old 3-part tokens fail to parse → a one-time re-login. No schema change.

A follow-on adversarial review (4 security lenses × find→refute×2, GO gate) then
surfaced 3 low-severity issues, all fixed here too:
- self-de-admin guard used  (identity) — {admin:0}
  / '' / 'false' / [] slipped past and could strand a sole db-admin. Now only a
  real JSON bool toggles admin (closes the bypass AND the truthy-string mis-grant).
- signup invite claim was check-then-write (double-spend under concurrency) — now
  an atomic  +
  rowcount check; signup/admin-edit wrap the UNIQUE writes in try/except
  IntegrityError → friendly message instead of a 500.
- login-CSRF (Lax cookie only): hub.py _api now blocks cross-site mutating requests
  by Origin (allowlist godstrument.pro/localhost + Host-match fallback so legit
  traffic always passes; safe GETs never blocked).

Verified: auth.py --selftest (delete/reset/id-reuse kill the token; guard bypass
closed for every falsy value); live curl (CSRF 401/401/401/403, GET never blocked);
real-browser flow (new-format cookie authenticates through a page load, admin table
renders, same-origin admin POST passes CSRF). Console clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 09:51:10 +10:00
type-two
764e28b1c3 👤 admin user management — list / edit / delete accounts + reset passwords
The admin panel could mint invite codes and read stats but not manage the people.
Adds, all under the existing admin-gated /api/admin/ block (default-closed:
no session → 401, non-admin → 403):

- GET  /api/admin/users        — every account: id, email, admin flag, patch count, joined
- POST /api/admin/user/<id>    — rename / change email / grant-or-revoke admin
                                 (validated like signup: format + uniqueness)
- POST /api/admin/user/<id>/reset — set a fresh random temp password, returned ONCE
                                 (never stored plaintext or logged) to relay to the user
- DELETE /api/admin/user/<id>  — delete the account + its saved patches (feedback +
                                 used invite kept as history)

Self-lockout guards: you can't delete your own account or revoke your own admin.
Admin granted via the GODSTRUMENT_ADMIN env is marked env_admin and its toggle is
hidden (can't be removed in the db). auth.py migration-free — uses existing tables;
the is_admin column was already migrated.

Client: the ⚙-account admin panel now lists every user with inline edit (username +
email), 🔑 reset-pw (shows a copyable temp), ★ make/revoke admin, and 🗑 delete
(confirm); the panel widens for the table. Names/emails escaped on render.

Verified: auth.py --selftest (list/edit/reset/delete + endpoint guards); live curl
against the local dev db (rename, bad-email/dup rejected, admin toggle, self-guards
400, non-admin 403, delete cascades presets); and the UI end-to-end (rows render
with correct per-row actions, edit expands, reset shows a temp password). Console
clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 07:01:58 +10:00
type-two
a476b13317 admin: click-to-copy invite codes + list open (unclaimed) codes
- auth.py admin_stats now returns open_codes (all unclaimed invite codes,
  newest first); unused_codes derived from it.
- account panel: renders every open code as a click-to-copy chip ("copied ✓"
  flash); "issue new code" prepends the new code and auto-copies it. Chips are
  user-selectable (the body-wide user-select:none no longer traps them).
  copyText uses navigator.clipboard with an execCommand fallback for
  non-secure contexts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 12:21:48 +10:00
type-two
e69b7e2ad6 Accounts, socio-economic feeds, and hub fixes (track deployed work)
Captures backend work already running on godstrument.pro but never committed:
- auth.py: invite-only accounts + per-user presets (SQLite, scrypt, signed
  stateless sessions), mounted by hub.py at /api/*
- hub.py: /api static+API handler, readonly public mode, full route spec in hello
- socio-economic / market / fire / debt / crypto workers + normalize/transform
- .gitignore: never commit godstrument_users.db* or auth_secret
- test_fixes.py: framework-free self-check for the review fixes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 16:18:35 +10:00