fix: deliver the rest of the invalid_request class to the client - #28736
Draft
BobbyHo wants to merge 4 commits into
Draft
fix: deliver the rest of the invalid_request class to the client#28736BobbyHo wants to merge 4 commits into
BobbyHo wants to merge 4 commits into
Conversation
…onstruction The redirect URI a response goes to had to pass two checks: an exact match against the app's registration, and a scheme check. The match ran inside extractAuthorizeParams, the scheme check separately in each handler afterwards, so the ordering that keeps an unchecked scheme out of a Location header was a convention two call sites had to keep rather than a property of the value. newAuthorizeResponse runs both, and is the only way to get a callback. The scheme is checked on the registered URL rather than on the match's result, because the parser returns the client's URI when the match fails and a 500 there would blame the app for a request it did not make. state moves onto the response, so no call site can build a §4.1.2.1 error the client cannot correlate by passing "". extractAuthorizeParams now returns one authorizeFailure instead of a slice and an error, giving both handlers an explicit three-way decision. Precedence change: an app whose registered callback has a rejected scheme now answers 500 even when the request also has parser errors, where that combination previously answered 400.
…lass extractAuthorizeParams discarded a callback it had already exact-matched and returned nothing on any parser error, so a malformed code_challenge, a malformed resource, an unparseable response_type, or an excess parameter stopped on Coder. GET rendered "Invalid Query Parameters", POST wrote a JSON 400, and neither carried state. The app never learned its request failed and waited on an authorization that would never arrive. RFC 6749 §4.1.2.1 delivers these to the client's own callback. Only two failures stay here: one naming the redirect URI, where the response has no destination because the URI never matched, and one naming the client identifier, where the registration the callback was matched against may not belong to whoever is asking. The redirect is safe in the same way #28450's is: the destination has passed the exact match and the scheme check, both now run at construction. Descriptions quote client input and go through the existing sanitizing chokepoint. Tests cover both verbs with redirect_uri omitted and sent explicitly, and pin the combination of a rejected registered scheme with a parser error at 500.
The two entries #28450 added cover one redirected code each. Parameter validation failures are the larger class and now arrive the same way, so integrators need to know which ones reach their callback and which two stay on Coder.
Contributor
Docs previewCheck off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here. |
CRF-15 asked for this alongside the 302 responses #28450 documented, and it was the one part left undone. Both authorize endpoints reject token, but the reference listed it as an accepted value, so a client reading the table sends a request the endpoint refuses. Enums on the typed parameter appends to the list swaggo derives from the type rather than replacing it, yielding code, token, code. Declaring the parameter as a string is what narrows it. The generated JSON already rendered it as a string with an inline enum, so only the enum array changes. codersdk.OAuth2ProviderResponseType keeps both constants: it also feeds ResponseTypesSupported, which already advertises code alone.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #28450. Review that first; this PR's diff is only the top four commits.
TL;DR
#28450 moved three authorization failures to the client's callback per RFC 6749 §4.1.2.1 and left the largest group behind. A malformed
code_challenge, a malformedresource, an unparseableresponse_type, or an excess parameter still stopped on Coder with nostate, so the app never learned its request failed and waited on an authorization that would never arrive. Same defect #28450 exists to fix, on the path #28450 did not touch.Contract change. These now redirect to the registered callback with
error=invalid_request, a description naming the failing fields, and the request'sstate, on both verbs:WriteOAuth2Error, 400Two failures still answer on Coder, because in both cases the callback is not trustworthy yet: a
redirect_urithat does not parse or does not match the registration, and a missing or unparsableclient_id.Precedence change. An app whose registered callback has a rejected scheme now answers 500 even when the request also carries parser errors; that combination used to answer 400. Only reachable for an app row that bypassed registration. No test pinned the old precedence;
DangerousCallbackSchemeOutranksParseFailurepins the new one.Both preconditions move into a constructor (2ec3bdb)
Writing a
Locationfrom inside the parser's error branch would run before the scheme check, which is the hazardDangerousCallbackSchemeNotRedirectedguards.validatedCallbackURLbecomesauthorizeResponse, built only bynewAuthorizeResponse, which runs the scheme check and the exact match against the registration in that order. Holding one is what licenses a redirect, so the ordering is structural rather than a convention each call site has to remember.p.RedirectURLreturns the client's URI on a mismatch, so checking that would answer 500 and log the app as corrupt for a request the app never made.DangerousClientSchemeIsNotTheServersFaultpins it.canRedirect()is false for anyredirect_urifailure. The type refuses to name a destination it could not validate.statemoves onto the type, out of the parameter lists ofwithQuery,errorURL,codeURL, andredirectAuthorizeError, so no call site can emit a response the client cannot correlate.Classification replaces implicit ordering (90be3d1)
extractAuthorizeParamsreturns oneauthorizeFailureinstead of two trailing values, giving both handlers the same three-way decision: bad server state answers 500, a deliverable failure redirects, everything else stays on Coder.blamesClienttests onlyclient_id, since the redirect-URI carve-out is already structural. That carve-out is reachable in practice, not just defensively:httpmwaccepts the RFC 6749 §2.3.1 Basic credential asclient_id, so the query parameter can legitimately be empty.API reference (a68a32d)
Both authorize endpoints reject
response_type=token, but the reference listed it as an accepted value. CRF-15 on #28450 asked for this alongside the 302 rows and it was the one part left undone.Enumson the typed parameter appends to the list swaggo derives from the type rather than replacing it, so narrowing it means declaring the parameter as astring.codersdk.OAuth2ProviderResponseTypekeeps both constants, since it also feedsResponseTypesSupported.Left alone on purpose. CRF-26 (the
server_errorsites) and CRF-13 (fragment delivery), both as in #28450.Refs PLAT-479.