@@ -3,6 +3,7 @@ package httpmw_test
3
3
import (
4
4
"context"
5
5
"net/http"
6
+ "net/http/httptest"
6
7
"testing"
7
8
8
9
"github.com/justinas/nosurf"
@@ -69,3 +70,77 @@ func TestCSRFExemptList(t *testing.T) {
69
70
})
70
71
}
71
72
}
73
+
74
+ // TestCSRFError verifies the error message returned to a user when CSRF
75
+ // checks fail.
76
+ //
77
+ //nolint:bodyclose // Using httptest.Recorders
78
+ func TestCSRFError (t * testing.T ) {
79
+ t .Parallel ()
80
+
81
+ // Hard coded matching CSRF values
82
+ const csrfCookieValue = "JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4="
83
+ const csrfHeaderValue = "KNKvagCBEHZK7ihe2t7fj6VeJ0UyTDco1yVUJE8N06oNqxLu5Zx1vRxZbgfC0mJJgeGkVjgs08mgPbcWPBkZ1A=="
84
+ // Use a url with "/api" as the root, other routes bypass CSRF.
85
+ const urlPath = "https://coder.com/api/v2/hello"
86
+
87
+ var handler http.Handler = http .HandlerFunc (func (writer http.ResponseWriter , request * http.Request ) {
88
+ writer .WriteHeader (http .StatusOK )
89
+ })
90
+ handler = httpmw .CSRF (false )(handler )
91
+
92
+ // Not testing the error case, just providing the example of things working
93
+ // to base the failure tests off of.
94
+ t .Run ("ValidCSRF" , func (t * testing.T ) {
95
+ t .Parallel ()
96
+
97
+ req , err := http .NewRequestWithContext (context .Background (), http .MethodPost , urlPath , nil )
98
+ require .NoError (t , err )
99
+
100
+ req .AddCookie (& http.Cookie {Name : codersdk .SessionTokenCookie , Value : "session_token_value" })
101
+ req .AddCookie (& http.Cookie {Name : nosurf .CookieName , Value : csrfCookieValue })
102
+ req .Header .Add (nosurf .HeaderName , csrfHeaderValue )
103
+
104
+ rec := httptest .NewRecorder ()
105
+ handler .ServeHTTP (rec , req )
106
+ resp := rec .Result ()
107
+ require .Equal (t , http .StatusOK , resp .StatusCode )
108
+ })
109
+
110
+ // The classic CSRF failure returns the generic error.
111
+ t .Run ("MissingCSRFHeader" , func (t * testing.T ) {
112
+ t .Parallel ()
113
+
114
+ req , err := http .NewRequestWithContext (context .Background (), http .MethodPost , urlPath , nil )
115
+ require .NoError (t , err )
116
+
117
+ req .AddCookie (& http.Cookie {Name : codersdk .SessionTokenCookie , Value : "session_token_value" })
118
+ req .AddCookie (& http.Cookie {Name : nosurf .CookieName , Value : csrfCookieValue })
119
+
120
+ rec := httptest .NewRecorder ()
121
+ handler .ServeHTTP (rec , req )
122
+ resp := rec .Result ()
123
+ require .Equal (t , http .StatusBadRequest , resp .StatusCode )
124
+ require .Contains (t , rec .Body .String (), "Something is wrong with your CSRF token." )
125
+ })
126
+
127
+ // Include the CSRF cookie, but not the CSRF header value.
128
+ // Including the 'codersdk.SessionTokenHeader' will bypass CSRF only if
129
+ // it matches the cookie. If it does not, we expect a more helpful error.
130
+ t .Run ("MismatchedHeaderAndCookie" , func (t * testing.T ) {
131
+ t .Parallel ()
132
+
133
+ req , err := http .NewRequestWithContext (context .Background (), http .MethodPost , urlPath , nil )
134
+ require .NoError (t , err )
135
+
136
+ req .AddCookie (& http.Cookie {Name : codersdk .SessionTokenCookie , Value : "session_token_value" })
137
+ req .AddCookie (& http.Cookie {Name : nosurf .CookieName , Value : csrfCookieValue })
138
+ req .Header .Add (codersdk .SessionTokenHeader , "mismatched_value" )
139
+
140
+ rec := httptest .NewRecorder ()
141
+ handler .ServeHTTP (rec , req )
142
+ resp := rec .Result ()
143
+ require .Equal (t , http .StatusBadRequest , resp .StatusCode )
144
+ require .Contains (t , rec .Body .String (), "CSRF error encountered. Authentication via" )
145
+ })
146
+ }
0 commit comments