1
1
package rest
2
2
3
3
import (
4
+ "encoding/base64"
4
5
"fmt"
5
6
"net/http"
6
7
"net/http/httptest"
@@ -9,6 +10,8 @@ import (
9
10
10
11
"github.com/stretchr/testify/assert"
11
12
"github.com/stretchr/testify/require"
13
+ "golang.org/x/crypto/argon2"
14
+ "golang.org/x/crypto/bcrypt"
12
15
)
13
16
14
17
func TestBasicAuth (t * testing.T ) {
@@ -142,3 +145,218 @@ func TestBasicAuthWithPrompt(t *testing.T) {
142
145
assert .Equal (t , `Basic realm="restricted", charset="UTF-8"` , resp .Header .Get ("WWW-Authenticate" ))
143
146
}
144
147
}
148
+
149
+ func TestBasicAuthWithHash (t * testing.T ) {
150
+ hashedPassword , err := bcrypt .GenerateFromPassword ([]byte ("good" ), bcrypt .MinCost )
151
+ require .NoError (t , err )
152
+ t .Logf ("hashed password: %s" , string (hashedPassword ))
153
+
154
+ mw := BasicAuthWithBcryptHash ("dev" , string (hashedPassword ))
155
+
156
+ ts := httptest .NewServer (mw (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
157
+ t .Logf ("request %s" , r .URL )
158
+ w .WriteHeader (http .StatusOK )
159
+ _ , err := w .Write ([]byte ("blah" ))
160
+ require .NoError (t , err )
161
+ assert .True (t , IsAuthorized (r .Context ()))
162
+ })))
163
+ defer ts .Close ()
164
+
165
+ u := fmt .Sprintf ("%s%s" , ts .URL , "/something" )
166
+ client := http.Client {Timeout : 5 * time .Second }
167
+
168
+ tests := []struct {
169
+ name string
170
+ username string
171
+ password string
172
+ expectedStatus int
173
+ }{
174
+ {
175
+ name : "no auth provided" ,
176
+ username : "" ,
177
+ password : "" ,
178
+ expectedStatus : http .StatusUnauthorized ,
179
+ },
180
+ {
181
+ name : "correct credentials" ,
182
+ username : "dev" ,
183
+ password : "good" ,
184
+ expectedStatus : http .StatusOK ,
185
+ },
186
+ {
187
+ name : "wrong username" ,
188
+ username : "wrong" ,
189
+ password : "good" ,
190
+ expectedStatus : http .StatusForbidden ,
191
+ },
192
+ {
193
+ name : "wrong password" ,
194
+ username : "dev" ,
195
+ password : "bad" ,
196
+ expectedStatus : http .StatusForbidden ,
197
+ },
198
+ {
199
+ name : "empty password" ,
200
+ username : "dev" ,
201
+ password : "" ,
202
+ expectedStatus : http .StatusForbidden ,
203
+ },
204
+ }
205
+
206
+ for _ , tc := range tests {
207
+ t .Run (tc .name , func (t * testing.T ) {
208
+ req , err := http .NewRequest ("GET" , u , http .NoBody )
209
+ require .NoError (t , err )
210
+
211
+ if tc .username != "" || tc .password != "" {
212
+ req .SetBasicAuth (tc .username , tc .password )
213
+ }
214
+
215
+ resp , err := client .Do (req )
216
+ require .NoError (t , err )
217
+ assert .Equal (t , tc .expectedStatus , resp .StatusCode )
218
+ })
219
+ }
220
+ }
221
+
222
+ func TestBasicAuthWithArgon2Hash (t * testing.T ) {
223
+ password := "good"
224
+ hash , salt , err := GenerateArgon2Hash (password )
225
+ require .NoError (t , err )
226
+ t .Logf ("hash: %s, salt: %s" , hash , salt )
227
+
228
+ // verify the returned values are valid base64
229
+ _ , err = base64 .StdEncoding .DecodeString (hash )
230
+ require .NoError (t , err , "hash should be valid base64" )
231
+ _ , err = base64 .StdEncoding .DecodeString (salt )
232
+ require .NoError (t , err , "salt should be valid base64" )
233
+
234
+ mw := BasicAuthWithArgon2Hash ("dev" , hash , salt )
235
+
236
+ ts := httptest .NewServer (mw (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
237
+ t .Logf ("request %s" , r .URL )
238
+ w .WriteHeader (http .StatusOK )
239
+ _ , err := w .Write ([]byte ("blah" ))
240
+ require .NoError (t , err )
241
+ assert .True (t , IsAuthorized (r .Context ()))
242
+ })))
243
+ defer ts .Close ()
244
+
245
+ u := fmt .Sprintf ("%s%s" , ts .URL , "/something" )
246
+ client := http.Client {Timeout : 5 * time .Second }
247
+
248
+ tests := []struct {
249
+ name string
250
+ username string
251
+ password string
252
+ expectedStatus int
253
+ }{
254
+ {
255
+ name : "no auth provided" ,
256
+ username : "" ,
257
+ password : "" ,
258
+ expectedStatus : http .StatusUnauthorized ,
259
+ },
260
+ {
261
+ name : "correct credentials" ,
262
+ username : "dev" ,
263
+ password : "good" ,
264
+ expectedStatus : http .StatusOK ,
265
+ },
266
+ {
267
+ name : "wrong username" ,
268
+ username : "wrong" ,
269
+ password : "good" ,
270
+ expectedStatus : http .StatusForbidden ,
271
+ },
272
+ {
273
+ name : "wrong password" ,
274
+ username : "dev" ,
275
+ password : "bad" ,
276
+ expectedStatus : http .StatusForbidden ,
277
+ },
278
+ }
279
+
280
+ for _ , tc := range tests {
281
+ t .Run (tc .name , func (t * testing.T ) {
282
+ req , err := http .NewRequest ("GET" , u , http .NoBody )
283
+ require .NoError (t , err )
284
+
285
+ if tc .username != "" || tc .password != "" {
286
+ req .SetBasicAuth (tc .username , tc .password )
287
+ }
288
+
289
+ resp , err := client .Do (req )
290
+ require .NoError (t , err )
291
+ assert .Equal (t , tc .expectedStatus , resp .StatusCode )
292
+ })
293
+ }
294
+ }
295
+
296
+ func TestHashGenerationFunctions (t * testing.T ) {
297
+ t .Run ("bcrypt hash generation" , func (t * testing.T ) {
298
+ hash , err := GenerateBcryptHash ("testpassword" )
299
+ require .NoError (t , err )
300
+ require .NotEmpty (t , hash )
301
+
302
+ err = bcrypt .CompareHashAndPassword ([]byte (hash ), []byte ("testpassword" ))
303
+ require .NoError (t , err )
304
+ })
305
+
306
+ t .Run ("argon2 hash generation" , func (t * testing.T ) {
307
+ hash , salt , err := GenerateArgon2Hash ("testpassword" )
308
+ require .NoError (t , err )
309
+ require .NotEmpty (t , hash )
310
+ require .NotEmpty (t , salt )
311
+
312
+ // verify the values are valid base64
313
+ hashBytes , err := base64 .StdEncoding .DecodeString (hash )
314
+ require .NoError (t , err , "hash should be valid base64" )
315
+ saltBytes , err := base64 .StdEncoding .DecodeString (salt )
316
+ require .NoError (t , err , "salt should be valid base64" )
317
+
318
+ // verify the hash works
319
+ newHash := argon2 .IDKey ([]byte ("testpassword" ), saltBytes , 1 , 64 * 1024 , 4 , 32 )
320
+ require .Equal (t , hashBytes , newHash )
321
+
322
+ // test with wrong password
323
+ wrongHash := argon2 .IDKey ([]byte ("wrongpassword" ), saltBytes , 1 , 64 * 1024 , 4 , 32 )
324
+ require .NotEqual (t , hashBytes , wrongHash )
325
+ })
326
+ }
327
+
328
+ func TestArgon2InvalidInputs (t * testing.T ) {
329
+ t .Run ("invalid base64 salt" , func (t * testing.T ) {
330
+ mw := BasicAuthWithArgon2Hash ("dev" , "validbase64==" , "invalid-base64" )
331
+ ts := httptest .NewServer (mw (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
332
+ t .Error ("Handler should not be called with invalid base64" )
333
+ })))
334
+ defer ts .Close ()
335
+
336
+ req , err := http .NewRequest ("GET" , ts .URL , http .NoBody )
337
+ require .NoError (t , err )
338
+ req .SetBasicAuth ("dev" , "password" )
339
+
340
+ client := http.Client {Timeout : 5 * time .Second }
341
+ resp , err := client .Do (req )
342
+ require .NoError (t , err )
343
+ assert .Equal (t , http .StatusForbidden , resp .StatusCode )
344
+ })
345
+
346
+ t .Run ("invalid base64 hash" , func (t * testing.T ) {
347
+ mw := BasicAuthWithArgon2Hash ("dev" , "invalid-base64" , "validbase64==" )
348
+ ts := httptest .NewServer (mw (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
349
+ t .Error ("Handler should not be called with invalid base64" )
350
+ })))
351
+ defer ts .Close ()
352
+
353
+ req , err := http .NewRequest ("GET" , ts .URL , http .NoBody )
354
+ require .NoError (t , err )
355
+ req .SetBasicAuth ("dev" , "password" )
356
+
357
+ client := http.Client {Timeout : 5 * time .Second }
358
+ resp , err := client .Do (req )
359
+ require .NoError (t , err )
360
+ assert .Equal (t , http .StatusForbidden , resp .StatusCode )
361
+ })
362
+ }
0 commit comments