@@ -12,17 +12,16 @@ import (
12
12
13
13
// authorizeMW serves to remove some code from the primary authorize handler.
14
14
// It decides when to show the html allow page, and when to just continue.
15
+ // TODO: For now only browser-based auth flow is officially supported but in a
16
+ // future PR we should support a cURL-based flow.
15
17
func authorizeMW (accessURL * url.URL ) func (next http.Handler ) http.Handler {
16
18
return func (next http.Handler ) http.Handler {
17
19
return http .HandlerFunc (func (rw http.ResponseWriter , r * http.Request ) {
18
20
origin := r .Header .Get (httpmw .OriginHeader )
19
- // TODO: The origin can be blank from some clients, like cURL. For now
20
- // only browser-based auth flow is officially supported but in a future PR
21
- // we should support a cURL-based and blank origin flows.
22
21
originU , err := url .Parse (origin )
23
- if err != nil || origin == "" {
22
+ if err != nil {
24
23
httpapi .Write (r .Context (), rw , http .StatusBadRequest , codersdk.Response {
25
- Message : "Invalid or missing origin header." ,
24
+ Message : "Invalid origin header." ,
26
25
Detail : err .Error (),
27
26
})
28
27
return
@@ -32,7 +31,7 @@ func authorizeMW(accessURL *url.URL) func(next http.Handler) http.Handler {
32
31
refererU , err := url .Parse (referer )
33
32
if err != nil {
34
33
httpapi .Write (r .Context (), rw , http .StatusBadRequest , codersdk.Response {
35
- Message : "Invalid or missing referer header." ,
34
+ Message : "Invalid referer header." ,
36
35
Detail : err .Error (),
37
36
})
38
37
return
@@ -41,80 +40,90 @@ func authorizeMW(accessURL *url.URL) func(next http.Handler) http.Handler {
41
40
app := httpmw .OAuth2ProviderApp (r )
42
41
ua := httpmw .UserAuthorization (r )
43
42
44
- // If the request comes from outside, then we show the html allow page.
45
- // TODO: Skip this step if the user has already clicked allow before, and
46
- // we can just reuse the token.
47
- if originU .Hostname () != accessURL .Hostname () && refererU .Path != "/login/oauth2/authorize" {
48
- if r .URL .Query ().Get ("redirected" ) != "" {
49
- site .RenderStaticErrorPage (rw , r , site.ErrorPageData {
50
- Status : http .StatusInternalServerError ,
51
- HideStatus : false ,
52
- Title : "Oauth Redirect Loop" ,
53
- Description : "Oauth redirect loop detected." ,
54
- RetryEnabled : false ,
55
- DashboardURL : accessURL .String (),
56
- Warnings : nil ,
57
- })
58
- return
59
- }
43
+ // url.Parse() allows empty URLs, which is fine because the origin is not
44
+ // always set by browsers (or other tools like cURL). If the origin does
45
+ // exist, we will make sure it matches. We require `referer` to be set at
46
+ // a minimum, however.
47
+ cameFromSelf := (origin == "" || originU .Hostname () == accessURL .Hostname ()) &&
48
+ refererU .Hostname () == accessURL .Hostname () &&
49
+ refererU .Path == "/login/oauth2/authorize"
60
50
61
- callbackURL , err := url .Parse (app .CallbackURL )
62
- if err != nil {
63
- site .RenderStaticErrorPage (rw , r , site.ErrorPageData {
64
- Status : http .StatusInternalServerError ,
65
- HideStatus : false ,
66
- Title : "Internal Server Error" ,
67
- Description : err .Error (),
68
- RetryEnabled : false ,
69
- DashboardURL : accessURL .String (),
70
- Warnings : nil ,
71
- })
72
- return
73
- }
51
+ // If we were redirected here from this same page it means the user
52
+ // pressed the allow button so defer to the authorize handler which
53
+ // generates the code, otherwise show the HTML allow page.
54
+ // TODO: Skip this step if the user has already clicked allow before, and
55
+ // we can just reuse the token.
56
+ if cameFromSelf {
57
+ next .ServeHTTP (rw , r )
58
+ return
59
+ }
74
60
75
- // Extract the form parameters for two reasons:
76
- // 1. We need the redirect URI to build the cancel URI.
77
- // 2. Since validation will run once the user clicks "allow", it is
78
- // better to validate now to avoid wasting the user's time clicking a
79
- // button that will just error anyway.
80
- params , validationErrs , err := extractAuthorizeParams (r , callbackURL )
81
- if err != nil {
82
- errStr := make ([]string , len (validationErrs ))
83
- for i , err := range validationErrs {
84
- errStr [i ] = err .Detail
85
- }
86
- site .RenderStaticErrorPage (rw , r , site.ErrorPageData {
87
- Status : http .StatusBadRequest ,
88
- HideStatus : false ,
89
- Title : "Invalid Query Parameters" ,
90
- Description : "One or more query parameters are missing or invalid." ,
91
- RetryEnabled : false ,
92
- DashboardURL : accessURL .String (),
93
- Warnings : errStr ,
94
- })
95
- return
96
- }
61
+ if r .URL .Query ().Get ("redirected" ) != "" {
62
+ site .RenderStaticErrorPage (rw , r , site.ErrorPageData {
63
+ Status : http .StatusInternalServerError ,
64
+ HideStatus : false ,
65
+ Title : "Oauth Redirect Loop" ,
66
+ Description : "Oauth redirect loop detected." ,
67
+ RetryEnabled : false ,
68
+ DashboardURL : accessURL .String (),
69
+ Warnings : nil ,
70
+ })
71
+ return
72
+ }
97
73
98
- cancel := params .redirectURL
99
- cancelQuery := params .redirectURL .Query ()
100
- cancelQuery .Add ("error" , "access_denied" )
101
- cancel .RawQuery = cancelQuery .Encode ()
74
+ callbackURL , err := url .Parse (app .CallbackURL )
75
+ if err != nil {
76
+ site .RenderStaticErrorPage (rw , r , site.ErrorPageData {
77
+ Status : http .StatusInternalServerError ,
78
+ HideStatus : false ,
79
+ Title : "Internal Server Error" ,
80
+ Description : err .Error (),
81
+ RetryEnabled : false ,
82
+ DashboardURL : accessURL .String (),
83
+ Warnings : nil ,
84
+ })
85
+ return
86
+ }
102
87
103
- redirect := r .URL
104
- vals := redirect .Query ()
105
- vals .Add ("redirected" , "true" )
106
- r .URL .RawQuery = vals .Encode ()
107
- site .RenderOAuthAllowPage (rw , r , site.RenderOAuthAllowData {
108
- AppIcon : app .Icon ,
109
- AppName : app .Name ,
110
- CancelURI : cancel .String (),
111
- RedirectURI : r .URL .String (),
112
- Username : ua .ActorName ,
88
+ // Extract the form parameters for two reasons:
89
+ // 1. We need the redirect URI to build the cancel URI.
90
+ // 2. Since validation will run once the user clicks "allow", it is
91
+ // better to validate now to avoid wasting the user's time clicking a
92
+ // button that will just error anyway.
93
+ params , validationErrs , err := extractAuthorizeParams (r , callbackURL )
94
+ if err != nil {
95
+ errStr := make ([]string , len (validationErrs ))
96
+ for i , err := range validationErrs {
97
+ errStr [i ] = err .Detail
98
+ }
99
+ site .RenderStaticErrorPage (rw , r , site.ErrorPageData {
100
+ Status : http .StatusBadRequest ,
101
+ HideStatus : false ,
102
+ Title : "Invalid Query Parameters" ,
103
+ Description : "One or more query parameters are missing or invalid." ,
104
+ RetryEnabled : false ,
105
+ DashboardURL : accessURL .String (),
106
+ Warnings : errStr ,
113
107
})
114
108
return
115
109
}
116
110
117
- next .ServeHTTP (rw , r )
111
+ cancel := params .redirectURL
112
+ cancelQuery := params .redirectURL .Query ()
113
+ cancelQuery .Add ("error" , "access_denied" )
114
+ cancel .RawQuery = cancelQuery .Encode ()
115
+
116
+ redirect := r .URL
117
+ vals := redirect .Query ()
118
+ vals .Add ("redirected" , "true" ) // For loop detection.
119
+ r .URL .RawQuery = vals .Encode ()
120
+ site .RenderOAuthAllowPage (rw , r , site.RenderOAuthAllowData {
121
+ AppIcon : app .Icon ,
122
+ AppName : app .Name ,
123
+ CancelURI : cancel .String (),
124
+ RedirectURI : r .URL .String (),
125
+ Username : ua .ActorName ,
126
+ })
118
127
})
119
128
}
120
129
}
0 commit comments