Skip to content

feat(blob-gateway): operator self-service rename via PATCH /auth/token/label - #15

Open
realdecimalist wants to merge 2 commits into
mainfrom
feat/operator-rename-label
Open

feat(blob-gateway): operator self-service rename via PATCH /auth/token/label#15
realdecimalist wants to merge 2 commits into
mainfrom
feat/operator-rename-label

Conversation

@realdecimalist

Copy link
Copy Markdown
Contributor

Summary

  • Adds PATCH /auth/token/label (Bearer auth) so operators can rename themselves with a one-line curl instead of asking us to mint a new token.
  • Adds PATCH /auth/token/:hash/label (admin auth) for ops/normalisation use.
  • Adds updateTokenLabel() helper to api-tokens with the usual safety net (idempotent, refuses revoked rows).
  • bearerAuth now propagates verify.tokenHash onto AuthedRequest so 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 build clean

  • Self-service smoke test in operator-facing form:

    curl -X PATCH https://materios.fluxpointstudios.com/preprod-blobs/auth/token/label \
      -H "Authorization: Bearer matra_xxxxx" \
      -H "Content-Type: application/json" \
      -d '{"label": "punkr-Draupnir"}'

    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

  • Revoked tokens refused with 409 (admin path) / 401 from middleware (self path).
  • Legacy `x-api-key` tier explicitly rejected on the self-service route with a 400 explaining why (no `tokenHash` to mutate).
  • `label: null` clears the label (heartbeats fall back to SS58).
  • Strings trimmed + capped at 128 chars to match `issueToken` mint-side limit.

🤖 Generated with Claude Code

…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +248 to +250
app.patch(
"/auth/token/label",
bearerAuth({ required: true }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants