fix(auth): key grant dedup by issuer, and isolate per-slot read failures (#2144)
Copilot review round 5. - Deduplicating grants by token string alone could conflate two grants: a token is only meaningful to the authorization server that minted it, so two issuers minting the same opaque value are two grants. The second was dropped before the issuer-mismatch check could report it, and `clear` then deleted it. The key is now issuer+token; the ctx-less read stays suppressed only when its token already came from a slot, which is exactly the active-issuer duplicate and never the legacy one. - A read failure in one issuer slot aborted `collectGrants`, so one corrupt slot left every other revocable grant untouched while the clear deleted them all. Each slot is now read independently and its failure carried as an outcome alongside the grants that could still be revoked; the no-metadata and no-endpoint returns carry them too, so a failure is never swallowed by a skip. - The CLI no longer deduplicates its two key spellings. That could only be done by pre-reading a single token, and `revokeStoredOAuthTokens` now enumerates every issuer slot under a key — so a shared active token skipped the second key entirely, taking any other issuer-bound grant under it with the local delete. A duplicate RFC 7009 request is harmless (§2.2 makes an unknown token a success); a missed one is the leak. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
6c499f3ea1
commit
e9cd678cd0
1 changed files with 230 additions and 84 deletions
|
|
@ -287,10 +287,11 @@ describe("clearStoredAuthForRelogin", () => {
|
|||
}
|
||||
});
|
||||
|
||||
// A token the AS never accepted is not spent, so the other key — which may
|
||||
// carry the credentials or metadata that would have worked — must still be
|
||||
// tried rather than skipped as a duplicate.
|
||||
it("does not treat a failed attempt as having ended the grant", async () => {
|
||||
// Keys are revoked from independently. Deduplicating them could only be
|
||||
// done by pre-reading one token, which would skip the second key's OTHER
|
||||
// issuer-bound grants along with it; a duplicate RFC 7009 request is
|
||||
// harmless (§2.2 makes an unknown token a success), a missed one is not.
|
||||
it("revokes from both keys even when they hold the same token", async () => {
|
||||
seedBothSpellings("same-r", "same-r");
|
||||
const fetchSpy = vi
|
||||
.spyOn(globalThis, "fetch")
|
||||
|
|
|
|||
Loading…
Reference in a new issue