Skip to content

Commit 78f8578

Browse files
fix(oauth2): add proper redirect URI validation to prevent invalid URIs
The OAuth2 provider app validation was too permissive, allowing invalid redirect URIs like 'localhost:3000', '/path/only', and 'http://' to pass validation. This caused test failures in TestOAuth2ProviderAppValidation. Changes: - Updated PostOAuth2ProviderAppRequest.Validate() to call validateRedirectURIs - Updated PutOAuth2ProviderAppRequest.Validate() to call validateRedirectURIs - Added isHostnameScheme() function to detect hostname-like schemes - Added validation to catch common patterns like 'localhost:3000' that are missing the http:// or https:// prefix Fixes the failing test cases: - URLNoHost: 'http://' now fails with scheme validation - URLLocalhostNoScheme: 'localhost:3000' now fails with hostname detection - URLPathOnly: '/bar/baz/qux' now fails with missing scheme validation Co-authored-by: mattvollmer <95866673+mattvollmer@users.noreply.github.com>
1 parent 63934b4 commit 78f8578

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

codersdk/oauth2.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,20 @@ func validateOAuth2ProviderAppRequest(grantTypes []OAuth2ProviderGrantType, redi
130130

131131
// Validate validates OAuth2 app creation request
132132
func (req *PostOAuth2ProviderAppRequest) Validate() error {
133-
return validateOAuth2ProviderAppRequest(req.GrantTypes, req.RedirectURIs)
133+
if err := validateOAuth2ProviderAppRequest(req.GrantTypes, req.RedirectURIs); err != nil {
134+
return err
135+
}
136+
137+
// Validate redirect URI format if any are provided
138+
if len(req.RedirectURIs) > 0 {
139+
// For OAuth2 provider apps, we assume confidential client by default
140+
// (token endpoint auth method is not "none")
141+
if err := validateRedirectURIs(req.RedirectURIs, "client_secret_basic"); err != nil {
142+
return err
143+
}
144+
}
145+
146+
return nil
134147
}
135148

136149
// PostOAuth2ProviderApp adds an application that can authenticate using Coder
@@ -157,7 +170,20 @@ type PutOAuth2ProviderAppRequest struct {
157170

158171
// Validate validates OAuth2 app update request
159172
func (req *PutOAuth2ProviderAppRequest) Validate() error {
160-
return validateOAuth2ProviderAppRequest(req.GrantTypes, req.RedirectURIs)
173+
if err := validateOAuth2ProviderAppRequest(req.GrantTypes, req.RedirectURIs); err != nil {
174+
return err
175+
}
176+
177+
// Validate redirect URI format if any are provided
178+
if len(req.RedirectURIs) > 0 {
179+
// For OAuth2 provider apps, we assume confidential client by default
180+
// (token endpoint auth method is not "none")
181+
if err := validateRedirectURIs(req.RedirectURIs, "client_secret_basic"); err != nil {
182+
return err
183+
}
184+
}
185+
186+
return nil
161187
}
162188

163189
// PutOAuth2ProviderApp updates an application that can authenticate using Coder

codersdk/oauth2_validation.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ func validateRedirectURIs(uris []string, tokenEndpointAuthMethod string) error {
9696
return xerrors.Errorf("redirect URI at index %d must have a scheme", i)
9797
}
9898

99+
// Check for common hostname patterns used as schemes (likely missing http:// prefix)
100+
if isHostnameScheme(uri.Scheme) {
101+
return xerrors.Errorf("redirect URI at index %d: '%s' appears to be a hostname, not a valid scheme. Did you mean 'http://%s' or 'https://%s'?", i, uri.Scheme, uriStr, uriStr)
102+
}
103+
99104
// Handle special URNs (RFC 6749 section 3.1.2.1)
100105
if uri.Scheme == "urn" {
101106
// Allow the out-of-band redirect URI for native apps
@@ -267,3 +272,26 @@ func isValidCustomScheme(scheme string) bool {
267272

268273
return true
269274
}
275+
276+
// isHostnameScheme detects if a scheme looks like a hostname that's missing http:// prefix
277+
func isHostnameScheme(scheme string) bool {
278+
// Common hostname patterns that shouldn't be used as schemes
279+
hostnamePatterns := []string{
280+
"localhost",
281+
"127.0.0.1",
282+
"::1",
283+
}
284+
285+
for _, pattern := range hostnamePatterns {
286+
if strings.EqualFold(scheme, pattern) {
287+
return true
288+
}
289+
}
290+
291+
// Check if it looks like a domain name (contains dots)
292+
if strings.Contains(scheme, ".") {
293+
return true
294+
}
295+
296+
return false
297+
}

0 commit comments

Comments
 (0)