feat(blob-gateway): operator self-service rename via PATCH /auth/token/label - #15
feat(blob-gateway): operator self-service rename via PATCH /auth/token/label#15realdecimalist wants to merge 2 commits into
Conversation
…n/label
Operators currently have no way to change their attestor display name —
the label is set when the Bearer is minted and there's no update endpoint,
so today's answer is "we mint you a new token." This adds:
PATCH /auth/token/label — Bearer auth, renames caller's own
PATCH /auth/token/:hash/label — admin token, renames any operator
Body shape `{ "label": string | null }`; trimmed and capped at 128 chars to
match issueToken's mint-side limit. null clears the label so the heartbeat
falls back to the SS58.
Implementation:
- new updateTokenLabel() helper in api-tokens.ts (idempotent, refuses
revoked rows, returns accountSs58 for audit logging)
- bearerAuth now exposes verify.tokenHash on AuthedRequest so handlers
can identify the row without re-hashing
- routes/tokens.ts wires both endpoints with input validation +
structured audit logs (`via=self` vs `via=admin`)
Tests:
- 6 new unit tests for updateTokenLabel covering happy path, null clear,
unknown hash, revoked refusal, idempotency, untouched-columns invariant
- 11 new integration tests for the two routes covering Bearer auth,
admin auth, missing auth, malformed hash, legacy x-api-key tier
rejection, revoked-Bearer 401 path
Self-service path is the one operators will actually use:
curl -X PATCH https://materios.fluxpointstudios.com/preprod-blobs/auth/token/label \
-H "Authorization: Bearer matra_xxx" \
-H "Content-Type: application/json" \
-d '{"label": "punkr-Draupnir"}'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8519a5dc9b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| app.patch( | ||
| "/auth/token/label", | ||
| bearerAuth({ required: true }), |
There was a problem hiding this comment.
Move self-service label route outside admin-token guard
/auth/token/label is registered only after the if (!adminToken) { ... return; } branch in registerTokenRoutes, so when DAEMON_NOTIFY_TOKEN is unset the new self-service endpoint is never mounted and callers get a 404. This makes the feature unavailable in no-admin configurations even though it authenticates with Bearer tokens and does not require admin credentials.
Useful? React with 👍 / 👎.
…daemon parity) (#16) 96% of certified receipts on preprod had non-canonical base_root_sha256 values that didn't reconcile with the gateway-served chunks (root cause of task #213). Multiple SDK paths and example apps each chose different conventions for that field; cert-daemon's strict ROOT_VERIFIED gate (PR #8) had to be reverted because the wrong-base_root submissions made it fail-stop. This patch makes every SDK receipt submission compute base_root_sha256 byte-for-byte identical to cert-daemon's daemon/merkle.py. Changes: - Add `computeBaseRoot(content, chunkSize?)` and `DEFAULT_CHUNK_SIZE` to packages/anchors-materios. Mirrors daemon/merkle.py exactly: 1 leaf => leaf as-is, N leaves => sha256(left || right) raw-byte concat, duplicate-last on odd levels, empty => 32 zero bytes. - `submitCertifiedReceipt` now derives `base_root_sha256` from the upload chunks via `merkleRoot(manifest.chunks.map(c => c.sha256))`. The old `input.rootHash || contentHashHex` fallback is gone — that fallback produced the wrong root for any blob > 1 chunk. - Receipt-builder (tools/receipt-builder lives in materios repo, not orynq-sdk; covered in companion patch) had hex-string concat for internal nodes; raw-byte fix + 3-chunk regression vector pinned. - Examples updated: * loadtest.ts: bespoke `sha256("root-" + contentHash)` removed; one `generatePayload()` builds real bytes + canonical root, blob upload keyed by contentHash (not receiptId). * e2e-flow.ts: build content first, derive contentHash + canonical base_root from those bytes; the trace bundle's own rootHash is for trace-bundle audit, not the receipt extrinsic. - MCP tool (anchor_materios_submit): dry-run report now states the hashes are derived; real submission relies on the SDK helper since bundle.rootHash is the trace-bundle root, not chunk-Merkle. - Regression tests pin the canonical pre-image: * Single-chunk "hello world" -> b94d27b9...e2efcde9 (single-leaf shortcut). * Three-chunk @ 256 KiB deterministic byte pattern -> cff7222b...49c354f6. * Cross-layer verified against cert-daemon Python merkle_root() — byte-identical. - Bumps @fluxpointstudios/orynq-sdk-anchors-materios 0.3.0 -> 0.3.1 (patch: behavioral fix, no API removal — `ReceiptInput.rootHash` still accepted for back-compat with callers who already compute it correctly). Wire-level effect: post-this-fix, callers passing a wrong rootHash to submitCertifiedReceipt get the correct value silently (input.rootHash is ignored). Callers using submitReceipt directly should switch to computeBaseRoot(content) — `ReceiptInput.rootHash` JSDoc updated to flag this. After this lands and clients propagate, the strict ROOT_VERIFIED gate can be re-deployed (task #220). Co-authored-by: Claude Code <noreply@anthropic.com>
Summary
PATCH /auth/token/label(Bearer auth) so operators can rename themselves with a one-linecurlinstead of asking us to mint a new token.PATCH /auth/token/:hash/label(admin auth) for ops/normalisation use.updateTokenLabel()helper to api-tokens with the usual safety net (idempotent, refuses revoked rows).bearerAuthnow propagatesverify.tokenHashontoAuthedRequestso handlers don't re-hash to identify the row.Triggered by an operator question in #materios Discord ("How do I change my attestor name?") that currently has no good answer except "we'll mint you a new token."
Test plan
npx vitest run services/blob-gateway/→ 87/87 pass (was 70 before; 17 new tests)pnpm --filter @orynq/blob-gateway run buildcleanSelf-service smoke test in operator-facing form:
Returns `{"status":"updated","label":"punkr-Draupnir","accountSs58":"...","tokenHash":"..."}` and the explorer Committee tab picks it up on the next heartbeat (<=30s).
Audit-log line lands as `[blob-gateway] api-token rename account=... label="..." hash=... via=self|admin` so ops can grep migrations.
Edge cases covered
🤖 Generated with Claude Code