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
|
|
@ -163,6 +163,26 @@ export function buildRevocationRequest(params: RevocationRequestParams): {
|
|||
// there is no precedent to match, and the raw form is ambiguous for a
|
||||
// client id containing `:` and makes `btoa` throw outright on a
|
||||
// non-Latin-1 secret. Encoding is what the server decodes.
|
||||
//
|
||||
// ⚠️ `encodeURIComponent` is **not** the form-urlencoded algorithm §2.3.1
|
||||
// names (RFC 6749 Appendix B), and #2222 was filed on the assumption that
|
||||
// the difference breaks a secret containing `+`. It does not, and the
|
||||
// reasoning is worth keeping because the next reader will have the same
|
||||
// doubt: `encodeURIComponent` **escapes** `+` as `%2B`. The characters it
|
||||
// leaves bare are exactly `!'()*-._~` plus alphanumerics, and a
|
||||
// form-urldecoder passes every one of them through unchanged. It can
|
||||
// therefore never emit the one character the two algorithms disagree
|
||||
// about, so its output decodes identically under both — verified over
|
||||
// every code point up to U+2FFF, and pinned by the round-trip cases in
|
||||
// `revocation.test.ts`.
|
||||
//
|
||||
// Switching to `URLSearchParams` would be the literal algorithm and a
|
||||
// small *regression*: it encodes a space as `+`, which a compliant server
|
||||
// reads back as a space but a lenient one — decoding with
|
||||
// `decodeURIComponent` alone, having never implemented the `+` rule —
|
||||
// reads as a literal `+`. `%20` is understood by both. So the encoding
|
||||
// here is the one that survives either server, which matters more than
|
||||
// matching the spec's wording for a header no server sees twice.
|
||||
const credentials = `${encodeURIComponent(client.client_id)}:${encodeURIComponent(client.client_secret)}`;
|
||||
headers.Authorization = `Basic ${base64Encode(credentials)}`;
|
||||
} else if (method === "client_secret_post" && client.client_secret) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue