fix(apikey): revoke must verify a row was actually updated
claudemesh apikey revoke <id> reported success even when the input
didn't match any row in mesh.api_key. The CLI's `apikey list` shows
truncated 8-char prefixes; users naturally paste those; broker did
exact-id match against meshApiKey.id; UPDATE affected 0 rows; old
revokeApiKey returned void so the CLI couldn't tell. Discovered via
end-to-end CLI smoke test against prod (roadmap validation pass).
Three-part fix:
- broker.revokeApiKey now returns
{ status: "revoked"|"not_found"|"not_unique"; id?, matches? } and
accepts either the full id or a unique prefix (>=6 chars). Prefix
matching is bounded to the caller's mesh and only succeeds if
exactly one row matches; ambiguous prefixes return not_unique so
we never silently revoke the wrong key.
- New WSApiKeyRevokeResponseMessage carries the structured status
back to the CLI. Old apikey_revoke_ok type removed before being
released — never shipped to users. The error path is no longer
used for not_found/not_unique cases; the unified response carries
both outcomes.
- CLI's apiKeyRevoke now resolves with { ok, id } | { ok: false,
code, message }. runApiKeyRevoke surfaces the code/message and
exits non-zero on failure (NOT_FOUND for missing, INVALID_ARGS
for ambiguous prefix).
Net effect: pasting `claudemesh apikey revoke vq0fwjdX` now actually
revokes the key whose id starts with vq0fwjdX (or fails loud if 0
or >1 keys match). Verified against prod via the new branch's CLI
binary before commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2523,9 +2523,17 @@ function handleConnection(ws: WebSocket): void {
|
||||
|
||||
case "apikey_revoke": {
|
||||
const ar = msg as Extract<WSClientMessage, { type: "apikey_revoke" }>;
|
||||
if (!ar.id) { sendError(ws, "invalid_args", "id required", _reqId); break; }
|
||||
await revokeApiKey({ meshId: conn.meshId, id: ar.id });
|
||||
log.info("ws apikey_revoke", { presence_id: presenceId, key_id: ar.id });
|
||||
if (!ar.id) { sendError(ws, "invalid_args", "id required", undefined, _reqId); break; }
|
||||
const result = await revokeApiKey({ meshId: conn.meshId, id: ar.id });
|
||||
log.info("ws apikey_revoke", { presence_id: presenceId, key_id: ar.id, status: result.status });
|
||||
const resp: WSServerMessage = {
|
||||
type: "apikey_revoke_response",
|
||||
status: result.status,
|
||||
...(result.status === "revoked" ? { id: result.id } : {}),
|
||||
...(result.status === "not_unique" ? { matches: result.matches } : {}),
|
||||
...(_reqId ? { _reqId } : {}),
|
||||
};
|
||||
conn.ws.send(JSON.stringify(resp));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user