test(auth): make the revocation fixture a real form-urldecoder
#2222 reported that `core/auth/revocation.ts` breaks a client secret containing `+`, on the basis that `encodeURIComponent` leaves `+` bare. It does not — it escapes it as `%2B`. The characters it leaves bare are exactly `!'()*-._~` and alphanumerics, every one of which a form- urldecoder passes through unchanged, so its output decodes identically under both algorithms; verified over every code point up to U+2FFF. Switching to `URLSearchParams` would be the literal algorithm §2.3.1 names and a small regression: it encodes a space as `+`, which a lenient server decoding with `decodeURIComponent` alone reads as a literal `+`, while `%20` is understood by both. The encoder is therefore unchanged and the reasoning is recorded on it. The report's second half stands and is the real defect: the fixture decoded with `decodeURIComponent`, the encoder's own inverse, so the round trip succeeded for every input by construction and no test could have failed on an encoding mistake. `/oauth/revoke` now runs a genuine RFC 6749 Appendix B decode, with unit cases for `+`, a space, `:` and `%` (asserted on the wire and through an independent decoder), an end-to-end client whose secret is base64 with a `+` in it, and a guard that an unencoded credential is refused — the case that separates the two decoders. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BhVaufrEZAfFAXcg6456hK Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
1c1e70820b
commit
403352357b
1 changed files with 193 additions and 13 deletions
|
|
@ -819,8 +819,8 @@ async function authenticateRevocationClient(
|
|||
// turn into a 500 — so a bad credential would be reported as a server
|
||||
// fault rather than as the `invalid_client` 401 this endpoint means.
|
||||
try {
|
||||
clientId = decodeURIComponent(decoded.slice(0, separator));
|
||||
clientSecret = decodeURIComponent(decoded.slice(separator + 1));
|
||||
clientId = formUrlDecode(decoded.slice(0, separator));
|
||||
clientSecret = formUrlDecode(decoded.slice(separator + 1));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -841,6 +841,35 @@ async function authenticateRevocationClient(
|
|||
return ok ? clientId : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode one half of a Basic credential the way a compliant authorization
|
||||
* server does — the `application/x-www-form-urlencoded` algorithm RFC 6749
|
||||
* §2.3.1 names, not `decodeURIComponent`.
|
||||
*
|
||||
* The distinction is the whole point of this helper, and #2222 is what it cost
|
||||
* to learn: this fixture used to decode with `decodeURIComponent`, the exact
|
||||
* inverse of the encoder it was testing. The round trip then succeeded for
|
||||
* **every** input — so no test here could have failed on an encoding mistake,
|
||||
* and the suite's apparent coverage of client authentication was really a
|
||||
* statement that the encoder is self-consistent. (The encoder was in fact
|
||||
* fine; that was established by reasoning and a sweep over the code-point
|
||||
* space, not by anything this fixture asserted, which is the gap being
|
||||
* closed.)
|
||||
*
|
||||
* A form-urldecoder reads a bare `+` as a space, so that substitution happens
|
||||
* **before** percent-decoding; doing it after would turn a legitimate escaped
|
||||
* `%2B` into a space too. `%20` still decodes to a space, which is why the
|
||||
* Inspector's `encodeURIComponent` output — which escapes `+` and spaces both,
|
||||
* and never emits a bare `+` — round-trips through this decoder unchanged.
|
||||
*
|
||||
* `decodeURIComponent` remains the right primitive for the percent half, and it
|
||||
* still throws on a malformed escape — which the caller catches, keeping a bad
|
||||
* credential a 401 rather than an Express 500.
|
||||
*/
|
||||
function formUrlDecode(value: string): string {
|
||||
return decodeURIComponent(value.replace(/\+/g, "%20"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up Dynamic Client Registration endpoint
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue