Rotate administrator keys with safe re-enable and exact retry

This commit is contained in:
Quintine committed 2026-09-30 00:01:16 +10:00
1 parent 9cf8249398
commit 55b47ce039
20 files changed
+3072 -104

No files matched your search

+65 -17
View File
@@ -6,26 +6,54 @@ Open **Administration → Accounts** as an enabled owner. Operators, viewers and
Choose a name, owner/operator/viewer role and operational reason, then review the exact account ID, name, role and reason before confirming. The browser generates a new UUID and 256-bit personal access key with Web Crypto. It saves the exact request in this tab before sending it. The server stores only its SHA-256 hash and returns account metadata, **not the key**. After confirmation, save the one-time displayed key privately and explicitly clear its tab record. Refresh restores an unconfirmed request or the confirmed key until it is cleared. If the response is uncertain, retry the same saved request; do not generate a replacement request or discard the only key copy. Do not place keys in reasons, screenshots, issue reports or shared logs.
## Change or disable access
## Change roles, disable access or rotate a key
Choose an enabled account other than your own. Review its exact name, ID,
current role, requested new role or disable, and operational reason before
sending. A role change uses the current account timestamp; a stale target
is rejected and must be reviewed again. Disabling blocks the key and
permanently invalidates current sessions; this page cannot re-enable the
account. **Do not manually re-enable its database row:** without a separate
key rotation, its old access key would become usable again. Provision a new
account instead. Another owner must perform a self-demotion or self-disable.
The database refuses to remove the final enabled owner. Existing game rooms,
seats and recovery kits are not modified.
Choose another account and review its exact name, ID, current role and
enabled state, target action, account timestamp and operational reason.
Role changes and disabling require an enabled target. A stale target is
rejected and must be reviewed again. Disabling blocks the existing access
key and permanently invalidates current sessions. Another owner must
perform a self-demotion or self-disable. The database refuses to remove the
final enabled owner. Existing game rooms, seats and recovery kits are not
modified.
Each operation has an exact saved identifier and request fingerprint. Concurrent or repeated identical requests resolve to one account change and one actor-attributed audit record; a different request cannot reuse that identifier. Live owner role, session expiry/generation and target version are rechecked at the database write, not inferred from the rendered page. An uncertain role or disable response can be retried from this tab; a conflicting or revoked request remains blocked for review. Tab records are scoped to the original owner account.
**Rotate key** works for another enabled or disabled account. The browser
generates a fresh target-bound 256-bit key, saves the exact request in this
tab, and asks for confirmation of the target and session impact. Rotation
replaces the old hash, increments the session generation, revokes existing
sessions and enables the account while retaining its name and role. A
disabled account is re-enabled **only with the new key**. Previously
retired key hashes are kept privately so later A→B→A rotations cannot
revive an old credential. Never use manual SQL to re-enable an account
without rotating its key. Rotation of your own account is unavailable.
On success, save the one-time displayed key privately before clearing its
tab record. The server never returns the key. An uncertain response must
be retried with the same saved request; discarding it may lose the only
copy. A retry confirms a historical operation, not that its key still
works: another owner may have rotated or disabled the account afterward.
The page compares the latest visible directory row with the saved
operation and warns when it changed or is not on the current page.
Even matching metadata is only a last-read observation; verify sign-in
before distributing a key.
Action history shows successful account operations with safe role changes and an operational reason for authorized readers. External CLI provisioning or revocation may still have no human actor attribution. Rejected attempts are not yet part of that history. No browser account operation grants access to a playing seat or reveals saved game data.
Each operation has an exact saved identifier and request fingerprint.
Concurrent or repeated identical requests resolve to one account change
and one actor-attributed audit record; a different request cannot reuse
that identifier. Live owner role, session expiry/generation and target
version are rechecked at the database write, not inferred from the
rendered page. An uncertain response can be retried from this tab; a
conflicting or revoked request remains blocked for review. Tab records
are scoped to the original owner account.
Action history shows successful account operations with safe role/enabled
changes and an operational reason for authorized readers. External CLI
provisioning or revocation may still have no human actor attribution.
Rejected attempts are not yet part of that history. No browser account
operation grants access to a playing seat or reveals saved game data.
## Verification and remaining work
Implementation: [account query and transactions](../db/admin-accounts.ts), [request contract](../lib/admin-accounts.ts), [route](../app/api/admin/accounts/route.ts), [page](../app/admin/accounts/page.tsx) and [additive operation receipt](../drizzle/0019_admin_accounts.sql). Keep the original owner-protection and session-revocation triggers in migration `0008`.
Implementation: [account query and transactions](../db/admin-accounts.ts), [request contract](../lib/admin-accounts.ts), [route](../app/api/admin/accounts/route.ts), [page](../app/admin/accounts/page.tsx), [original receipt](../drizzle/0019_admin_accounts.sql) and [additive rotation receipt/key history](../drizzle/0020_admin_key_rotation.sql). Keep the original owner-protection and session-revocation triggers in migration `0008`.
```sh
npm test -- admin-accounts admin-audit
@@ -42,9 +70,29 @@ confirmation. Independent security and persistence reviews resolved
unchanged-role audit no-ops, missing point-of-risk copy and a logout-all
timestamp rollback.
The rotation follow-up passes typecheck, lint and 5,787/5,787 offline
cases, the app build, Drizzle migration check and 55/55 HTTP tests against
the supported local development server. An isolated built-worker D1 store
with its own disposable owner/target completed authenticated **network**
provision, rotation, exact replay, disable, fresh-key re-enable, old
key/session rejection, A→B→A refusal, safe audit and foreign-origin
denial. No production account, room or credential was changed.
An actual phone-width owner page exposed the enabled-target review and
confirmation with no key before submission; a separate local-only
completed-record fixture showed the superseded-key warning without
sending a mutation. Independent security review found the historical
replay presentation risk, now corrected by last-read directory
comparison; persistence review reported no scoped finding.
The full 55-case suite against `wrangler dev` of the production build did
**not** pass: the local workerd process intermittently returned 503
mid-request under that suite. A single oversized request returned 413
and later requests succeeded after draining its body, but that does not
resolve the broader worker-restart observation. Development-server
success and focused built-worker mutations are not deployed acceptance.
Use an isolated owner and disposable target for authenticated end-to-end
account changes; never use a person's normal owner key in a destructive QA
flow. Client/browser fixtures alone do not prove a signed-in **network**
HTTP mutation or production deployment. Key rotation, re-enabling a
disabled account, failed-attempt audit, complete administration and live
acceptance remain unfinished.
HTTP mutation or production deployment. Failed-attempt audit, complete
administration and live acceptance remain unfinished.
+16 -12
View File
@@ -18,12 +18,14 @@ operational reason when permitted, and bounded before/after settings. Expand
**Recorded settings** for the public configuration, lifecycle flags, revisions,
AI difficulty or changed player circle. Names come from current administrator
accounts; deleted accounts retain their recorded identifiers. Web-managed
provisioning, role changes and disabling identify the acting owner and record
the supplied reason. External CLI provisioning/revocation may lack human
actor attribution, and the page says so. A **Recorded** outcome means the
operation was applied; a
room-control operation may successfully save identical settings, and later
operations may supersede it. It does not assert the room's current state.
provisioning, role changes, disabling and fresh-key rotation/re-enable
identify the acting owner and record the supplied reason. Rotation shows
only the before/after enabled state, never the old or new key, hash or
session. External CLI provisioning/revocation may lack human actor
attribution, and the page says so. A **Recorded** outcome means the
operation was applied; a room-control operation may successfully save
identical settings; later operations may supersede it. It does not assert
the room's current state.
## Access and privacy
@@ -55,8 +57,10 @@ Implementation: [query/projection](../db/admin-audit.ts),
[route](../app/api/admin/audit/route.ts), [page](../app/admin/audit/page.tsx).
The original nine-source read needed no migration. Owner account management
adds a safe `reason` field and durable retry receipts in additive
[migration 0019](../drizzle/0019_admin_accounts.sql); it does not add a
new raw game or message source.
[migration 0019](../drizzle/0019_admin_accounts.sql). Additive
[migration 0020](../drizzle/0020_admin_key_rotation.sql) keeps rotation
receipts and retired key hashes out of the history projection. Neither
adds a raw game or message source.
```sh
npm test -- admin-audit
@@ -76,7 +80,7 @@ sources under its five-term compound-query limit, with live role demotion and
filtered reads. Materialized groups keep the unified query within that limit.
Existing records cover successful operations only. Failed/rejected attempts,
key rotation and re-enable, full operational health/diagnostics,
backup/import/restore and the remaining participant support are still required.
Local/browser/source-bound evidence is separate from deployed acceptance, which
must identify the actual running revision and use authorized QA access.
full operational health/diagnostics, backup/import/restore and the
remaining participant support are still required. Local/browser/source-bound
evidence is separate from deployed acceptance, which must identify the
actual running revision and use authorized QA access.
+9 -7
View File
@@ -9,7 +9,7 @@ recovery and persistence systems where suitable.
| Area | Required behavior | Stage |
| --- | --- | --- |
| Administrator access | Personal access keys, server-enforced owner/operator/viewer roles, persistent eight-hour sessions and individual/all-session sign-out. [Owner-only account management](ADMIN_ACCOUNTS.md) adds a paginated directory, browser-created one-time keys, role changes, disabling, live authority and exact retries. A room host is not a site administrator. Key rotation/re-enable, failed-attempt history and further operational permissions remain. | Prototyped, partial |
| Administrator access | Personal access keys, server-enforced owner/operator/viewer roles, persistent eight-hour sessions and individual/all-session sign-out. [Owner-only account management](ADMIN_ACCOUNTS.md) adds a paginated directory, browser-created one-time keys, role changes, disabling and fresh-key rotation/re-enable with session revocation, live authority and exact retries. A room host is not a site administrator. Failed-attempt history and further operational permissions remain. | Prototyped, partial |
| Room directory | Search room codes/public player names, filter status/rules/availability, sort and paginate; show host, roster, modules, game change time, pause/join flags and bounded decision ownership. Detailed setup/shared-window ownership remains. | Prototyped |
| Create and configure | [Create a lobby](ADMIN_ROOM_CREATION.md) with an explicitly owned new host seat, Basic/Advanced preview and initial AI configuration; invite humans and use ordinary lobby controls/voluntary host-seat handover. [Neutral lobby configuration](ADMIN_LOBBY_CONFIGURATION.md) adds rules/modules, AI seats and host assignment to an existing human without private access. Reserved human seats remain. | Prototyped, partial |
| Lifecycle and removal | [Pause/resume and joining locks](ADMIN_ROOM_CONTROLS.md), [administrative close/reopen](ADMIN_ROOM_CLOSURE.md) and [recoverable removal/restoration](ADMIN_ROOM_REMOVAL.md) have controls, durable audit, exact retries and player/AI enforcement. [Archive/unarchive](ADMIN_ROOM_ARCHIVE.md) has separate directory filters and exact retries. Permanent deletion and bulk actions remain missing. | Prototyped, partial |
@@ -53,12 +53,14 @@ Disabling an account invalidates its sessions; later re-enabling it cannot resto
them. The database refuses to disable, demote or delete the final enabled owner.
No default key, public account creation or host-to-administrator promotion exists.
[Manage accounts](ADMIN_ACCOUNTS.md) lets a current owner provision a personal
key, change another administrator’s role or disable its access. The exact
request and generated key remain tab-scoped through uncertain responses;
the server never returns a key. The owner reviews the target and reason
before submitting, and no game or seat is modified. Key rotation and
re-enabling disabled accounts are not available in this prototype.
[Manage accounts](ADMIN_ACCOUNTS.md) lets a current owner provision a
personal key, change another administrator’s role, disable access or
rotate another account’s key. Rotation also re-enables a disabled account
with a fresh key while invalidating every earlier key and session. The
exact request and generated key remain tab-scoped through uncertain
responses; the server never returns a key. The owner reviews the target,
current state, reason and revocation impact before submitting. No game
or seat is modified.
[Participant AI](ADMIN_PARTICIPANT_AI.md) lets owners/operators support an absent
human in a paused, started game. The player retains access and takeback controls;
+14 -9
View File
@@ -1,6 +1,6 @@
# Current development status
Updated 29 September 2026. **The full twelve-faction goal is unfinished. Basic
Updated 30 September 2026. **The full twelve-faction goal is unfinished. Basic
is playable and the six classic factions have a user-authorized Advanced preview.
Expansion starts and full rules/publication acceptance remain gated.**
@@ -34,14 +34,19 @@ arrival composition, natural assassination reveal coverage, normal
expansion starts or deployed acceptance.
The owner-only [administrator accounts](ADMIN_ACCOUNTS.md) prototype now
lists accounts, provisions a browser-generated one-time personal key, changes
another account’s role and disables access. Server actions repeat live owner
authority and target-version checks at commit, preserve exact tab-scoped
retries and attribute successful changes in action history without exposing
keys or saved games. Key rotation/re-enable, rejected-attempt history,
authenticated positive **network** HTTP on a dedicated isolated QA account, deployed
acceptance and the rest of full administration remain unfinished. This code
is not in the last verified live image.
lists, provisions, changes roles, disables and rotates another account’s
personal key. Rotation can re-enable a disabled target only with a fresh
key; old credentials and sessions remain revoked, including attempted
A→B→A reuse. Live owner/target fences, durable exact retries and safe
actor-attributed history preserve rooms and seats. A disposable owner
and target completed authenticated local **network** rotation and
re-enable on an isolated built-worker D1 store, and mobile browser review
showed exact impact and a superseded-replay warning. The supported dev
HTTP suite passes 55/55; the built-worker full suite hit intermittent
local workerd 503 restarts and is not a green production-bundle suite.
Failed-attempt history, the rest of administration, deployment and
production acceptance remain unfinished. This code is not in the last
verified live image.
The bounded [Advanced Moritani Assassinate Leaders preview](MORITANI_ASSASSINATE_LEADERS.md)
now has repeatable genuine games across two through six supported seats.
+36
View File
@@ -1,5 +1,41 @@
# Dune implementation status
## 30 September 2026 — owner key rotation and safe re-enable
The [account panel](ADMIN_ACCOUNTS.md) now rotates another enabled or
disabled administrator’s personal access key with one exact tab-scoped
request. A disabled target becomes enabled only with the new key; all
old sessions are revoked and historical hashes prevent A→B→A key reuse.
The owner reviews name, ID, role, enabled state, reason and revocation
impact; one-time key disclosure remains confined to the confirmed tab.
Additive migration `0020` preserves previous account receipts and games.
Live SQL owner/version/key checks, one durable rotation receipt and
actor-attributed safe audit prevent stale or duplicate changes.
Independent persistence review found no scoped issue. Security review
identified an exact historical replay that could show a superseded key
as currently usable. The client now compares its receipt with the
last-read account metadata, warns when changed/unlisted, and advises
sign-in verification even when matching. The focused security follow-up
found no remaining issue in those changed lines. A phone-width
authenticated local page showed exact pre-send confirmation; a
completed-record fixture showed the superseded warning without sending
a mutation.
The isolated built-worker network smoke provisioned, rotated, replayed,
disabled and re-enabled a disposable target, rejected old keys and
sessions, refused key reuse and preserved safe audit and origin denial.
No live account or saved game was modified.
`npm run check` passes typecheck, lint and 5,787/5,787 offline cases;
`npm run build`, `drizzle-kit check` and all 55/55 development-server
HTTP cases pass. A second full HTTP run against the local production
bundle did not pass: workerd intermittently returned 503 mid-request
under the suite. Draining oversized admin bodies removed an early
unread-stream return; isolated built requests returned 413 and later
requests remained healthy, but the full bundle load issue remains
unresolved. None of this proves a deployed revision, complete
administration or production recovery.
## 29 September 2026 — natural Advanced Moritani reveal and replacement
A source-unchanged genuine six-seat sample at base seed `20261307`