feat: add redirect URL override for external auth - #28082
Conversation
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. |
|
/coder-agents-review |
|
Chat: Review posted | View chat Review history
deep-review v0.9.0 | Round 1 | Last posted: Round 1, 14 findings (2 P1, 6 P2, 3 Nit, 3 Note), REQUEST_CHANGES. Review Finding inventoryPR #28082 finding inventoryFindings
Contested and acknowledged(none yet) Round logRound 1Panel round after Netero-only pre-pass. Reviewed against 1458d27..a55c13f. Netero: no findings. Panel: Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Komugi, Kurapika, Knov, Meruem, Chopper, Luffy, Ging-go, Gon, Leorio + wildcards Zoro, Razor. Ging-go and Komugi returned no findings. 14 findings raised: 2 P1, 6 P2, 3 Nit, 3 Note. About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
There was a problem hiding this comment.
The change adds a per-provider RedirectURL override for external auth. The addition follows the sibling AuthURL/TokenURL/ValidateURL shape in ConvertConfig and copyDefaultSettings, the CLI + YAML + generated surfaces are in sync, and TestConvertYAML/CustomScopesAndEndpoint was upgraded from an empty accessURL fixture to a fully-qualified one, which Mafu-san flagged as the anti-pattern of a "confidence assertion" done right.
Two P1s block merge. Pariston: the docs example (REDIRECT_URL=https://my.tld against access URL https://example.com) sets the OAuth oauth_state/oauth_pkce_verifier/oauth_redirect cookies on the access URL host (httpmw/oauth2.go:187-200, Path: "/", no Domain) and then hands the IdP a callback URL on a different host. The browser will not send those cookies to the callback host, so the flow fails at httpmw/oauth2.go:239-244 with Cookie "oauth_state" must be provided. HTTPCookieConfig has no Domain field, so admins have no supported way to widen scope. OIDC already solved the same class with RedirectAllowedHosts + dynamic per-request redirect_uri (buildDynamicRedirectURI, httpmw/oauth2.go:162,297); this PR lands a static, weaker mechanism that skips that guardrail. Second P1: schemeless input silently parses to a path-only URL and produces a scheme-less relative redirect_uri. The PR's own CLI test at cli/server_test.go:84 sets CODER_EXTERNAL_AUTH_1_REDIRECT_URL=coder.com, which is exactly this broken shape, and the assertion never reaches ConvertConfig.
P2 cluster on the same code path. Any path prefix on the override is silently discarded (https://my.tld/coder becomes https://my.tld/external-auth/<id>/callback) because the appended callback is an absolute-path reference; this is the reverse-proxy case a subpath override exists to solve, and the docs added in this PR don't warn about it. The field name RedirectURL collides with oauth2.Config.RedirectURL (the actual redirect URI, resolved four lines away in the same function) and with the sibling APIBaseURL shape (codersdk/deployment.go:1204); a reader will reasonably set REDIRECT_URL=https://my.tld/oauth/callback and get a silently mangled result. The SDK doc comment repeats "redirects" three times, never names the /external-auth/<id>/callback suffix, and ships verbatim into swagger, generated docs, and the TypeScript type. The docs page shows a placeholder host and never diagnoses when the override is needed. The new error message drops the provider ID and offending value and names a config surface (base callback url) operators can't grep. The only positive test picks the input shape that hides the join bug.
Nits and Notes: CustomScopesAndEndpoint was repurposed for the override rather than split into two subtests, dropping the fallback assertion; no operator warning log when the override is active (sibling OIDC at cli/server.go:205 does log); double space after "redirects." ships into the rendered API table; sibling OIDC.RedirectURL is serpent.URL while this new field is plain string; the new copyDefaultSettings clause has no test.
Process note: the PR body is a private Linear link only. External reviewers cannot read it. .claude/docs/PR_STYLE_GUIDE.md asks for one to two paragraphs of context.
Netero mechanical pass came back clean.
Verbatim from Luffy: "Yes, AuthURL/TokenURL/ValidateURL don't validate either. That's a separate rot, not a license to keep adding to the pile."
Severity count: 2 P1, 6 P2, 3 Nit, 3 Note.
coderd/externalauth/externalauth_test.go:1753
Nit [CRF-10] CustomScopesAndEndpoint was repurposed for the override; the fallback branch that used to be pinned here is no longer covered. (Mafu-san Nit, Bisky Nit, Zoro Note, Luffy Note)
Before this PR the subtest asserted
redirect_uri=/external-auth/gitlab/callbackagainst an emptyaccessURL. After, the same subtest asserts the override. Fine as far as it goes, but nothing in the suite now pins the default branch,entry.RedirectURL == ""with a realaccessURL, against an expectedredirect_uristring. Split the sub-test intoRedirectURLDefault(emptyRedirectURL, realaccessURL, assertredirect_uri=https://default.com/external-auth/gitlab/callback) andRedirectURLOverride(the current assertion). One setting, two stones, one test each.
A reader hunting the test that covers the new field will not find it by name. A reader hunting the original scenario will find an assertion that has been repurposed. Split, or at minimum rename to describe what the assertion now proves.
🤖
🤖 This review was automatically generated with Coder Agents.
a55c13f to
397860c
Compare
Documentation CheckUpdates Needed
Automated review via Coder Agents |
092f438 to
1d73768
Compare
This comment has been minimized.
This comment has been minimized.
53369ef to
62339af
Compare
62339af to
2a50431
Compare
Closes https://linear.app/codercom/issue/DEVEX-759/add-external-auth-redirect-url-override
I smoke tested locally with my deployment on localhost:3000 and the redirect URL at localhost:9999 (the use case requires that localhost:9999 be redirected to the real domain, in my test I basically faked the redirect by editing the URL once GitHub sent me to 9999 rather than stand up a whole proxy).