99"flag"
1010"fmt"
1111"net/http"
12+ "net/url"
1213"reflect"
1314"strings"
1415"time"
@@ -26,6 +27,61 @@ import (
2627
2728var Validate * validator.Validate
2829
30+ // isValidOAuth2RedirectURI validates OAuth2 redirect URIs according to RFC 6749.
31+ // It requires a proper scheme and host, rejecting malformed URIs that would be
32+ // problematic for OAuth2 flows.
33+ func isValidOAuth2RedirectURI (uri string )bool {
34+ if uri == "" {
35+ return false
36+ }
37+
38+ parsed ,err := url .Parse (uri )
39+ if err != nil {
40+ return false
41+ }
42+
43+ // Must have a scheme
44+ if parsed .Scheme == "" {
45+ return false
46+ }
47+
48+ // Reject patterns that look like "host:port" without proper scheme
49+ // These get parsed as scheme="host" and path="port" which is ambiguous
50+ if parsed .Host == "" && parsed .Path != "" && ! strings .HasPrefix (uri ,parsed .Scheme + "://" ) {
51+ // Check if this looks like a host:port pattern (contains digits after colon)
52+ if strings .Contains (parsed .Path ,":" ) {
53+ return false
54+ }
55+ // Also reject if the "scheme" part looks like a hostname
56+ if strings .Contains (parsed .Scheme ,"." )|| parsed .Scheme == "localhost" {
57+ return false
58+ }
59+ }
60+
61+ // For standard schemes (http/https), host is required
62+ if parsed .Scheme == "http" || parsed .Scheme == "https" {
63+ if parsed .Host == "" {
64+ return false
65+ }
66+ }
67+
68+ // Reject scheme-only URIs like "http://"
69+ if parsed .Host == "" && parsed .Path == "" {
70+ return false
71+ }
72+
73+ // For custom schemes, we allow no host (like "myapp://callback")
74+ // But if there's a host, it should be valid
75+ if parsed .Host != "" {
76+ // Basic host validation - should not be empty after parsing
77+ if strings .TrimSpace (parsed .Host )== "" {
78+ return false
79+ }
80+ }
81+
82+ return true
83+ }
84+
2985// This init is used to create a validator and register validation-specific
3086// functionality for the HTTP API.
3187//
@@ -113,6 +169,19 @@ func init() {
113169if err != nil {
114170panic (err )
115171}
172+
173+ oauth2RedirectURIValidator := func (fl validator.FieldLevel )bool {
174+ f := fl .Field ().Interface ()
175+ str ,ok := f .(string )
176+ if ! ok {
177+ return false
178+ }
179+ return isValidOAuth2RedirectURI (str )
180+ }
181+ err = Validate .RegisterValidation ("oauth2_redirect_uri" ,oauth2RedirectURIValidator )
182+ if err != nil {
183+ panic (err )
184+ }
116185}
117186
118187// Is404Error returns true if the given error should return a 404 status code.