From 947ed68edd8f63357b85ba54533257760e744677 Mon Sep 17 00:00:00 2001 From: bluzername Date: Sat, 5 Sep 2026 07:17:53 +0300 Subject: [PATCH] Trim the name before matching an API key in disable/enable/revoke Creating a key trim the name (apiKeyNameSchema use z.string().trim()), so " incident key " get stored as "incident key". But resolveApiKeyId, the one function used by disable, enable and revoke, compare the raw argument with === and never trim it. So if an operator or a script reuse the exact spelling they typed at creation (with the spaces), disable/enable/revoke say "no API key named ..." even if the key is right there in `dor apikey ls`. Same problem for revoke, which is worse because it silently do nothing instead of throwing. Fix is one line: trim the name inside resolveApiKeyId before the lookup, so it always compare against the same normalized spelling creation already stored. Added a test that create a key with extra spaces, disable it with the same untrimmed spelling, then revoke it with the trimmed spelling, and check both calls succeed. This test fail before the fix and pass after. --- .changeset/tame-apples-fly.md | 5 +++++ packages/cli/src/commands.test.ts | 18 ++++++++++++++++++ packages/cli/src/commands.ts | 8 +++++++- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 .changeset/tame-apples-fly.md diff --git a/.changeset/tame-apples-fly.md b/.changeset/tame-apples-fly.md new file mode 100644 index 0000000..aa5f85e --- /dev/null +++ b/.changeset/tame-apples-fly.md @@ -0,0 +1,5 @@ +--- +"@dormice/cli": patch +--- + +`dor apikey disable`, `enable` and `revoke` now trim the name argument before matching, the same way key creation trims a name before storing it. Before this, a name typed with the exact leading/trailing whitespace used at creation time (or any other spelling that only differs by whitespace) failed to resolve, so a script or operator could not disable or revoke a credential. diff --git a/packages/cli/src/commands.test.ts b/packages/cli/src/commands.test.ts index 319590d..9da290d 100644 --- a/packages/cli/src/commands.test.ts +++ b/packages/cli/src/commands.test.ts @@ -272,6 +272,24 @@ describe('apikey commands over real HTTP', () => { ); }); + it('disable and revoke resolve a name by the same trimmed spelling creation stored', async () => { + // Creation trims the name (apiKeyNameSchema), so `ls` and every other + // read shows "incident key". Disable/enable/revoke must resolve by + // that same trimmed spelling, whether the operator retypes it exactly + // or reuses the untrimmed spelling they originally typed at creation. + await apikeyCreate(client, ' incident key '); + expect(await apikeyLs(client)).toMatch(/incident key\s{2,}/); + + expect(await apikeyDisable(client, ' incident key ')).toBe( + 'Disabled API key " incident key " — it stops working until re-enabled.', + ); + expect(await apikeyLs(client)).toMatch(/incident key\s{2,}.*disabled/); + + expect(await apikeyRevoke(client, 'incident key')).toBe( + 'Revoked API key "incident key" — it stops working immediately.', + ); + }); + it('--expires mints a TTL key through end-of-day and refuses garbage dates', async () => { const created = await apikeyCreate(client, 'ttl', '2030-06-15'); expect(created.split('\n')[0]).toMatch( diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index beeda56..733d2a2 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -281,8 +281,14 @@ async function resolveApiKeyId( client: Dormice, name: string, ): Promise { + // Creation trims the name (apiKeyNameSchema), so the stored spelling + // never has surrounding whitespace — match on the same trimmed spelling + // here so the untrimmed input an operator originally typed still resolves. + const trimmed = name.trim(); const keys = await client.listApiKeys(); - return keys.find((k) => k.name === name && k.revokedAt === null)?.id ?? null; + return ( + keys.find((k) => k.name === trimmed && k.revokedAt === null)?.id ?? null + ); } /**