auth
auth/oauth2_test.go
Fix go vet, fix imports, render JSON errors, deprecate getBasicAuth. Fix logging functions in our test file that were causing go vet to report errors (and which would have obscured the test output). Move some imports around to make the imports more consistent and our pre-commit hook happy. Create a helper to render JSON errors, and actually render those errors when obtaining a token using a grant. Deprecate our custom getBasicAuth in favour of the new BasicAuth() method on net/http.*Request objects, which was introduced in Go 1.4 (meaning Go 1.4 is now a requirement for compiling this.)
1 package auth
3 import (
4 "bytes"
5 "encoding/json"
6 "html/template"
7 "io/ioutil"
8 "net/http"
9 "net/http/httptest"
10 "net/url"
11 "testing"
12 "time"
14 "code.secondbit.org/uuid"
15 )
17 const (
18 scopeSet = 1 << iota
19 stateSet
20 uriSet
21 )
23 func stripParam(param string, u *url.URL) {
24 q := u.Query()
25 q.Del(param)
26 u.RawQuery = q.Encode()
27 }
29 func TestGetGrantCodeSuccess(t *testing.T) {
30 t.Parallel()
31 store := NewMemstore()
32 testContext := Context{
33 template: template.Must(template.New(getGrantTemplateName).Parse("Get auth grant")),
34 clients: store,
35 grants: store,
36 profiles: store,
37 tokens: store,
38 sessions: store,
39 }
40 client := Client{
41 ID: uuid.NewID(),
42 Secret: "super secret!",
43 OwnerID: uuid.NewID(),
44 Name: "My test client",
45 Logo: "https://secondbit.org/logo.png",
46 Website: "https://secondbit.org",
47 Type: "public",
48 }
49 uri, err := url.Parse("https://test.secondbit.org/redirect")
50 if err != nil {
51 t.Fatal("Can't parse URL:", err)
52 }
53 endpoint := Endpoint{
54 ID: uuid.NewID(),
55 ClientID: client.ID,
56 URI: *uri,
57 Added: time.Now(),
58 }
59 err = testContext.SaveClient(client)
60 if err != nil {
61 t.Fatal("Can't store client:", err)
62 }
63 err = testContext.AddEndpoint(client.ID, endpoint)
64 if err != nil {
65 t.Fatal("Can't store endpoint:", err)
66 }
67 session := Session{
68 ID: "testsession",
69 Active: true,
70 }
71 err = testContext.CreateSession(session)
72 if err != nil {
73 t.Fatal("Can't store session:", err)
74 }
75 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
76 if err != nil {
77 t.Fatal("Can't build request:", err)
78 }
79 cookie := &http.Cookie{
80 Name: authCookieName,
81 Value: session.ID,
82 }
83 req.AddCookie(cookie)
84 for i := 0; i < 1<<3; i++ {
85 w := httptest.NewRecorder()
86 params := url.Values{}
87 // see OAuth 2.0 spec, section 4.1.1
88 params.Set("response_type", "code")
89 params.Set("client_id", client.ID.String())
90 if i&uriSet != 0 {
91 params.Set("redirect_uri", endpoint.URI.String())
92 }
93 if i&scopeSet != 0 {
94 params.Set("scope", "testscope")
95 }
96 if i&stateSet != 0 {
97 params.Set("state", "my super secure state string")
98 }
99 req.URL.RawQuery = params.Encode()
100 req.Method = "GET"
101 req.Body = nil
102 req.Header.Del("Content-Type")
103 GetGrantHandler(w, req, testContext)
104 if w.Code != http.StatusOK {
105 t.Errorf("Expected status code to be %d, got %d for %s", http.StatusOK, w.Code, req.URL.String())
106 }
107 if w.Body.String() != "Get auth grant" {
108 t.Errorf("Expected body to be `%s`, got `%s` for %s", "Get auth grant", w.Body.String(), req.URL.String())
109 }
110 req.Method = "POST"
111 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
112 w = httptest.NewRecorder()
113 data := url.Values{}
114 data.Set("grant", "approved")
115 body := bytes.NewBufferString(data.Encode())
116 req.Body = ioutil.NopCloser(body)
117 GetGrantHandler(w, req, testContext)
118 if w.Code != http.StatusFound {
119 t.Errorf("Expected status code to be %d, got %d for %s", http.StatusFound, w.Code, req.URL.String())
120 }
121 redirectedTo := w.Header().Get("Location")
122 red, err := url.Parse(redirectedTo)
123 if err != nil {
124 t.Fatalf(`Being redirected to a non-URL "%s" threw error "%s" for "%s"\n`, redirectedTo, err, req.URL.String())
125 }
126 t.Log("Redirected to", redirectedTo)
127 if red.Query().Get("code") == "" {
128 t.Fatalf(`Expected code param in redirect URL to be set, but it wasn't for %s`, req.URL.String())
129 }
130 if _, err := testContext.GetGrant(red.Query().Get("code")); err != nil {
131 t.Fatalf(`Unexpected error "%s: retrieving the grant "%s" supplied in the redirect URL for %s`, err, red.Query().Get("code"), req.URL.String())
132 }
133 err = testContext.DeleteGrant(red.Query().Get("code"))
134 if err != nil {
135 t.Logf(`Unexpected error "%s" deleting grant "%s" for %s`, err, red.Query().Get("code"), req.URL.String())
136 }
137 stripParam("code", red)
138 if red.Query().Get("state") != params.Get("state") {
139 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s" for %s`, params.Get("state"), red.Query().Get("state"), req.URL.String())
140 }
141 stripParam("state", red)
142 if red.String() != endpoint.URI.String() {
143 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
144 }
145 }
146 }
148 func TestGetGrantCodeInvalidClient(t *testing.T) {
149 t.Parallel()
150 store := NewMemstore()
151 testContext := Context{
152 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")),
153 clients: store,
154 grants: store,
155 profiles: store,
156 tokens: store,
157 sessions: store,
158 }
159 client := Client{
160 ID: uuid.NewID(),
161 Secret: "super secret!",
162 OwnerID: uuid.NewID(),
163 Name: "My test client",
164 Type: "public",
165 }
166 err := testContext.SaveClient(client)
167 if err != nil {
168 t.Fatal("Can't store client:", err)
169 }
170 session := Session{
171 ID: "testsession",
172 Active: true,
173 }
174 err = testContext.CreateSession(session)
175 if err != nil {
176 t.Fatal("Can't store session:", err)
177 }
178 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
179 if err != nil {
180 t.Fatal("Can't build request:", err)
181 }
182 w := httptest.NewRecorder()
183 params := url.Values{}
184 params.Set("response_type", "code")
185 params.Set("redirect_uri", "https://test.secondbit.org/")
186 req.URL.RawQuery = params.Encode()
187 cookie := &http.Cookie{
188 Name: authCookieName,
189 Value: session.ID,
190 }
191 req.AddCookie(cookie)
192 GetGrantHandler(w, req, testContext)
193 if w.Code != http.StatusBadRequest {
194 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
195 }
196 if w.Body.String() != "Client ID must be specified in the request." {
197 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "Client ID must be specified in the request.", w.Body.String())
198 }
199 w = httptest.NewRecorder()
200 params.Set("client_id", "Not an ID")
201 req.URL.RawQuery = params.Encode()
202 GetGrantHandler(w, req, testContext)
203 if w.Code != http.StatusBadRequest {
204 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
205 }
206 if w.Body.String() != "client_id is not a valid Client ID." {
207 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "client_id is not a valid Client ID.", w.Body.String())
208 }
209 w = httptest.NewRecorder()
210 params.Set("client_id", uuid.NewID().String())
211 req.URL.RawQuery = params.Encode()
212 GetGrantHandler(w, req, testContext)
213 if w.Code != http.StatusBadRequest {
214 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
215 }
216 if w.Body.String() != "The specified Client couldn’t be found." {
217 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The specified Client couldn’t be found.", w.Body.String())
218 }
219 }
221 func TestGetGrantCodeInvalidURI(t *testing.T) {
222 t.Parallel()
223 store := NewMemstore()
224 testContext := Context{
225 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")),
226 clients: store,
227 grants: store,
228 profiles: store,
229 tokens: store,
230 sessions: store,
231 }
232 client := Client{
233 ID: uuid.NewID(),
234 Secret: "super secret!",
235 OwnerID: uuid.NewID(),
236 Name: "My test client",
237 Type: "public",
238 }
239 uri, err := url.Parse("https://test.secondbit.org/redirect")
240 if err != nil {
241 t.Fatal("Can't parse URL:", err)
242 }
243 err = testContext.SaveClient(client)
244 if err != nil {
245 t.Fatal("Can't store client:", err)
246 }
247 session := Session{
248 ID: "testsession",
249 Active: true,
250 }
251 err = testContext.CreateSession(session)
252 if err != nil {
253 t.Fatal("Can't store session:", err)
254 }
255 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
256 if err != nil {
257 t.Fatal("Can't build request:", err)
258 }
259 cookie := &http.Cookie{
260 Name: authCookieName,
261 Value: session.ID,
262 }
263 req.AddCookie(cookie)
264 w := httptest.NewRecorder()
265 params := url.Values{}
266 params.Set("response_type", "code")
267 params.Set("client_id", client.ID.String())
268 req.URL.RawQuery = params.Encode()
269 GetGrantHandler(w, req, testContext)
270 if w.Code != http.StatusBadRequest {
271 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
272 }
273 if w.Body.String() != "The redirect_uri specified is not valid." {
274 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
275 }
276 endpoint := Endpoint{
277 ID: uuid.NewID(),
278 ClientID: client.ID,
279 URI: *uri,
280 Added: time.Now(),
281 }
282 err = testContext.AddEndpoint(client.ID, endpoint)
283 if err != nil {
284 t.Fatal("Can't store endpoint:", err)
285 }
286 w = httptest.NewRecorder()
287 params.Set("redirect_uri", "https://test.secondbit.org/wrong")
288 req.URL.RawQuery = params.Encode()
289 GetGrantHandler(w, req, testContext)
290 if w.Code != http.StatusBadRequest {
291 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
292 }
293 if w.Body.String() != "The redirect_uri specified is not valid." {
294 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
295 }
296 endpoint2 := Endpoint{
297 ID: uuid.NewID(),
298 ClientID: client.ID,
299 URI: *uri,
300 Added: time.Now(),
301 }
302 err = testContext.AddEndpoint(client.ID, endpoint2)
303 if err != nil {
304 t.Fatal("Can't store endpoint:", err)
305 }
306 w = httptest.NewRecorder()
307 params.Set("redirect_uri", "")
308 req.URL.RawQuery = params.Encode()
309 GetGrantHandler(w, req, testContext)
310 if w.Code != http.StatusBadRequest {
311 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
312 }
313 if w.Body.String() != "The redirect_uri specified is not valid." {
314 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
315 }
316 w = httptest.NewRecorder()
317 params.Set("redirect_uri", "://not a URL")
318 req.URL.RawQuery = params.Encode()
319 GetGrantHandler(w, req, testContext)
320 if w.Code != http.StatusBadRequest {
321 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
322 }
323 if w.Body.String() != "The redirect_uri specified is not valid." {
324 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
325 }
326 }
328 func TestGetGrantCodeInvalidResponseType(t *testing.T) {
329 t.Parallel()
330 store := NewMemstore()
331 testContext := Context{
332 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")),
333 clients: store,
334 grants: store,
335 profiles: store,
336 tokens: store,
337 sessions: store,
338 }
339 client := Client{
340 ID: uuid.NewID(),
341 Secret: "super secret!",
342 OwnerID: uuid.NewID(),
343 Name: "My test client",
344 Logo: "https://secondbit.org/logo.png",
345 Website: "https://secondbit.org",
346 Type: "public",
347 }
348 uri, err := url.Parse("https://test.secondbit.org/redirect")
349 if err != nil {
350 t.Fatal("Can't parse URL:", err)
351 }
352 endpoint := Endpoint{
353 ID: uuid.NewID(),
354 ClientID: client.ID,
355 URI: *uri,
356 Added: time.Now(),
357 }
358 err = testContext.SaveClient(client)
359 if err != nil {
360 t.Fatal("Can't store client:", err)
361 }
362 err = testContext.AddEndpoint(client.ID, endpoint)
363 if err != nil {
364 t.Fatal("Can't store endpoint:", err)
365 }
366 session := Session{
367 ID: "testsession",
368 Active: true,
369 }
370 err = testContext.CreateSession(session)
371 if err != nil {
372 t.Fatal("Can't store session:", err)
373 }
374 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
375 if err != nil {
376 t.Fatal("Can't build request:", err)
377 }
378 cookie := &http.Cookie{
379 Name: authCookieName,
380 Value: session.ID,
381 }
382 req.AddCookie(cookie)
383 params := url.Values{}
384 params.Set("response_type", "totally not code")
385 params.Set("client_id", client.ID.String())
386 params.Set("redirect_uri", endpoint.URI.String())
387 params.Set("scope", "testscope")
388 params.Set("state", "my super secure state string")
389 req.URL.RawQuery = params.Encode()
390 w := httptest.NewRecorder()
391 GetGrantHandler(w, req, testContext)
392 if w.Code != http.StatusFound {
393 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
394 }
395 redirectedTo := w.Header().Get("Location")
396 red, err := url.Parse(redirectedTo)
397 if err != nil {
398 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
399 }
400 if red.Query().Get("error") != "invalid_request" {
401 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error"))
402 }
403 stripParam("error", red)
404 if red.Query().Get("state") != params.Get("state") {
405 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
406 }
407 stripParam("state", red)
408 if red.String() != endpoint.URI.String() {
409 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
410 }
411 stripParam("response_type", req.URL)
412 w = httptest.NewRecorder()
413 GetGrantHandler(w, req, testContext)
414 if w.Code != http.StatusFound {
415 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
416 }
417 redirectedTo = w.Header().Get("Location")
418 red, err = url.Parse(redirectedTo)
419 if err != nil {
420 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
421 }
422 if red.Query().Get("error") != "invalid_request" {
423 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error"))
424 }
425 stripParam("error", red)
426 if red.Query().Get("state") != params.Get("state") {
427 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
428 }
429 stripParam("state", red)
430 if red.String() != endpoint.URI.String() {
431 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
432 }
433 }
435 func TestGetGrantCodeDenied(t *testing.T) {
436 t.Parallel()
437 store := NewMemstore()
438 testContext := Context{
439 template: template.Must(template.New(getGrantTemplateName).Parse("{{ .error }}")),
440 clients: store,
441 grants: store,
442 profiles: store,
443 tokens: store,
444 sessions: store,
445 }
446 client := Client{
447 ID: uuid.NewID(),
448 Secret: "super secret!",
449 OwnerID: uuid.NewID(),
450 Name: "My test client",
451 Logo: "https://secondbit.org/logo.png",
452 Website: "https://secondbit.org",
453 Type: "public",
454 }
455 uri, err := url.Parse("https://test.secondbit.org/redirect")
456 if err != nil {
457 t.Fatal("Can't parse URL:", err)
458 }
459 endpoint := Endpoint{
460 ID: uuid.NewID(),
461 ClientID: client.ID,
462 URI: *uri,
463 Added: time.Now(),
464 }
465 err = testContext.SaveClient(client)
466 if err != nil {
467 t.Fatal("Can't store client:", err)
468 }
469 err = testContext.AddEndpoint(client.ID, endpoint)
470 if err != nil {
471 t.Fatal("Can't store endpoint:", err)
472 }
473 session := Session{
474 ID: "testsession",
475 Active: true,
476 }
477 err = testContext.CreateSession(session)
478 if err != nil {
479 t.Fatal("Can't store session:", err)
480 }
481 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
482 if err != nil {
483 t.Fatal("Can't build request:", err)
484 }
485 cookie := &http.Cookie{
486 Name: authCookieName,
487 Value: session.ID,
488 }
489 req.AddCookie(cookie)
490 params := url.Values{}
491 params.Set("response_type", "code")
492 params.Set("client_id", client.ID.String())
493 params.Set("redirect_uri", endpoint.URI.String())
494 params.Set("scope", "testscope")
495 params.Set("state", "my super secure state string")
496 data := url.Values{}
497 data.Set("grant", "denied")
498 req.URL.RawQuery = params.Encode()
499 req.Body = ioutil.NopCloser(bytes.NewBufferString(data.Encode()))
500 req.Method = "POST"
501 w := httptest.NewRecorder()
502 GetGrantHandler(w, req, testContext)
503 if w.Code != http.StatusFound {
504 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
505 }
506 redirectedTo := w.Header().Get("Location")
507 red, err := url.Parse(redirectedTo)
508 if err != nil {
509 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
510 }
511 if red.Query().Get("error") != "access_denied" {
512 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "access_denied", red.Query().Get("error"))
513 }
514 stripParam("error", red)
515 if red.Query().Get("state") != params.Get("state") {
516 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
517 }
518 stripParam("state", red)
519 if red.String() != endpoint.URI.String() {
520 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
521 }
522 }
524 func TestGetGrantCodeLoginRedirect(t *testing.T) {
525 t.Parallel()
526 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
527 if err != nil {
528 t.Fatal("Can't build request:", err)
529 }
530 w := httptest.NewRecorder()
531 GetGrantHandler(w, req, Context{template: template.Must(template.New(getGrantTemplateName).Parse("{{ .internal_error }}"))})
532 if w.Code != http.StatusInternalServerError {
533 t.Errorf("Expected status code to be %d, got %d", http.StatusInternalServerError, w.Code)
534 }
535 expectation := "Missing login URL."
536 if w.Body.String() != expectation {
537 t.Errorf(`Expected body to be "%s", got "%s"`, expectation, w.Body.String())
538 }
539 uri, err := url.Parse("https://client.secondbit.org/")
540 if err != nil {
541 t.Error("Unexpected error", err)
542 }
543 testContext := Context{
544 loginURI: uri,
545 }
546 w = httptest.NewRecorder()
547 GetGrantHandler(w, req, testContext)
548 if w.Code != http.StatusFound {
549 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
550 }
551 redirectedTo := w.Header().Get("Location")
552 expectation = "https://client.secondbit.org/?from=https%3A%2F%2Ftest.auth.secondbit.org%2Foauth2%2Fgrant"
553 if redirectedTo != expectation {
554 t.Errorf("Expected to be redirected to '%s', was redirected to '%s'", expectation, redirectedTo)
555 }
556 }
558 func TestCheckCookie(t *testing.T) {
559 t.Parallel()
560 req, err := http.NewRequest("GET", "https://auth.secondbit.org", nil)
561 if err != nil {
562 t.Error("Unexpected error creating base request:", err)
563 }
564 store := NewMemstore()
565 testContext := Context{
566 sessions: store,
567 }
568 session, err := checkCookie(req, testContext)
569 if err != ErrNoSession {
570 t.Errorf("Expected ErrNoSession, got %s", err)
571 }
572 session = Session{
573 ID: "testsession",
574 Active: true,
575 }
576 err = testContext.CreateSession(session)
577 if err != nil {
578 t.Error("Unexpected error persisting session:", err)
579 }
580 invalidSession := Session{
581 ID: "testsession2",
582 Active: false,
583 }
584 err = testContext.CreateSession(invalidSession)
585 if err != nil {
586 t.Error("Unexpected error persisting session:", err)
587 }
588 result, err := checkCookie(req, testContext)
589 if err != ErrNoSession {
590 t.Errorf("Expected ErrNoSession, got %s", err)
591 }
592 req.AddCookie(&http.Cookie{
593 Name: "wrongcookie",
594 Value: "wrong value",
595 })
596 result, err = checkCookie(req, testContext)
597 if err != ErrNoSession {
598 t.Error("Expected ErrNoSession, got", err)
599 }
600 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
601 if err != nil {
602 t.Error("Unexpected error creating base request:", err)
603 }
604 req.AddCookie(&http.Cookie{
605 Name: "Stillwrongcookie",
606 Value: session.ID,
607 })
608 result, err = checkCookie(req, testContext)
609 if err != ErrNoSession {
610 t.Error("Expected ErrNoSession, got", err)
611 }
612 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
613 if err != nil {
614 t.Error("Unexpected error creating base request:", err)
615 }
616 req.AddCookie(&http.Cookie{
617 Name: authCookieName,
618 Value: "wrong value",
619 })
620 result, err = checkCookie(req, testContext)
621 if err != ErrInvalidSession {
622 t.Error("Expected ErrInvalidSession, got", err)
623 }
624 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
625 if err != nil {
626 t.Error("Unexpected error creating base request:", err)
627 }
628 req.AddCookie(&http.Cookie{
629 Name: authCookieName,
630 Value: invalidSession.ID,
631 })
632 result, err = checkCookie(req, testContext)
633 if err != ErrInvalidSession {
634 t.Error("Expected ErrInvalidSession, got", err)
635 }
636 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
637 if err != nil {
638 t.Error("Unexpected error creating base request:", err)
639 }
640 req.AddCookie(&http.Cookie{
641 Name: authCookieName,
642 Value: session.ID,
643 })
644 result, err = checkCookie(req, testContext)
645 if err != nil {
646 t.Error("Unexpected error:", err)
647 }
648 success, field, expectation, outcome := compareSessions(session, result)
649 if !success {
650 t.Errorf(`Expected field %s to be %v, but got %v`, field, expectation, outcome)
651 }
652 }
654 func TestBuildLoginRedirect(t *testing.T) {
655 t.Parallel()
656 req, err := http.NewRequest("GET", "https://client.secondbit.org/my/awesome/path?has=query¶ms=to&screw=this&all=up", nil)
657 if err != nil {
658 t.Error("Unexpected error creating base request:", err)
659 }
660 result := buildLoginRedirect(req, Context{})
661 if result != "" {
662 t.Error("Expected empty string as the result, got", result)
663 }
664 uri, err := url.Parse("https://auth.secondbit.org/login?query=string&other=param")
665 if err != nil {
666 t.Error("Unexpected error parsing URL:", err)
667 }
668 c := Context{loginURI: uri}
669 result = buildLoginRedirect(req, c)
670 expectation := "https://auth.secondbit.org/login?from=https%3A%2F%2Fclient.secondbit.org%2Fmy%2Fawesome%2Fpath%3Fhas%3Dquery%26params%3Dto%26screw%3Dthis%26all%3Dup&other=param&query=string"
671 if result != expectation {
672 t.Errorf(`Expected result string to be "%s", was "%s"`, expectation, result)
673 }
674 }
676 func TestAuthenticateHelper(t *testing.T) {
677 t.Parallel()
678 store := NewMemstore()
679 context := Context{
680 profiles: store,
681 }
682 profile := Profile{
683 ID: uuid.NewID(),
684 Name: "Test User",
685 Passphrase: "55d87acb9adff90a0d8e4c9b77f239c2d6e3a1945dbd09b0270467411198db25",
686 Iterations: 4096,
687 Salt: "this is a super secure random salt",
688 PassphraseScheme: 1,
689 Compromised: false,
690 LockedUntil: time.Time{},
691 PassphraseReset: "",
692 PassphraseResetCreated: time.Time{},
693 Created: time.Now(),
694 LastSeen: time.Time{},
695 }
696 login := Login{
697 Type: "email",
698 Value: "test@example.com",
699 ProfileID: profile.ID,
700 Created: time.Now(),
701 LastUsed: time.Time{},
702 }
703 err := context.SaveProfile(profile)
704 if err != nil {
705 t.Error("Error saving profile:", err)
706 }
707 err = context.AddLogin(login)
708 if err != nil {
709 t.Error("Error adding login:", err)
710 }
711 response, err := authenticate("test@example.com", "a really secure password", context)
712 if err != nil {
713 t.Error("Unexpected error:", err)
714 }
715 success, field, expectation, result := compareProfiles(profile, response)
716 if !success {
717 t.Errorf(`Expected field %s to be "%v", got "%v"`, field, expectation, result)
718 }
719 response, err = authenticate("test2@example.com", "a really secure password", context)
720 if err != ErrIncorrectAuth {
721 t.Error("Expected ErrIncorrectAuth, got", err)
722 }
723 response, err = authenticate("test@example.com", "not the right password", context)
724 if err != ErrIncorrectAuth {
725 t.Error("Expected ErrIncorrectAuth, got", err)
726 }
727 scheme := 1000
728 phrase := "doesn't really matter, the scheme doesn't exist"
729 change := ProfileChange{
730 PassphraseScheme: &scheme,
731 Passphrase: &phrase,
732 }
733 err = context.UpdateProfile(profile.ID, change)
734 if err != nil {
735 t.Error("Unexpected error:", err)
736 }
737 response, err = authenticate("test@example.com", "not the right password", context)
738 if err != ErrInvalidPassphraseScheme {
739 t.Error("Expected ErrIncorrectAuth, got", err)
740 }
741 }
743 func TestGetTokenHandler(t *testing.T) {
744 t.Parallel()
745 store := NewMemstore()
746 context := Context{
747 clients: store,
748 grants: store,
749 tokens: store,
750 }
751 client := Client{
752 ID: uuid.NewID(),
753 Secret: "sometimes I feel like I don't know what I'm doing",
754 OwnerID: uuid.NewID(),
755 Name: "A Super Awesome Client!",
756 Logo: "https://logos.secondbit.org/client.png",
757 Website: "https://client.secondbit.org/",
758 Type: "confidential",
759 }
760 grant := Grant{
761 Code: "testcode",
762 Created: time.Now(),
763 ExpiresIn: 600,
764 ClientID: client.ID,
765 Scope: "testscope",
766 RedirectURI: "https://client.secondbit.org/",
767 State: "teststate",
768 ProfileID: uuid.NewID(),
769 }
770 err := context.SaveGrant(grant)
771 if err != nil {
772 t.Error("Error saving grant:", err)
773 }
774 err = context.SaveClient(client)
775 if err != nil {
776 t.Error("Error saving client:", err)
777 }
778 data := url.Values{}
779 data.Set("grant_type", "authorization_code")
780 data.Set("code", grant.Code)
781 data.Set("redirect_uri", grant.RedirectURI)
782 body := bytes.NewBufferString(data.Encode())
783 req, err := http.NewRequest("POST", "https://auth.secondbit.org/", ioutil.NopCloser(body))
784 if err != nil {
785 t.Error("Error constructing request:", err)
786 }
787 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
788 req.SetBasicAuth(client.ID.String(), client.Secret)
789 w := httptest.NewRecorder()
790 GetTokenHandler(w, req, context)
791 resp := tokenResponse{}
792 err = json.Unmarshal(w.Body.Bytes(), &resp)
793 if err != nil {
794 t.Error("Error unmarshalling response:", err)
795 }
796 if resp.AccessToken == "" {
797 t.Error("Got blank access token back")
798 }
799 if resp.RefreshToken == "" {
800 t.Error("Got blank refresh token back")
801 }
802 if resp.TokenType == "" {
803 t.Error("Got blank token type back")
804 }
805 if resp.ExpiresIn == 0 {
806 t.Error("Got blank expires in back")
807 }
808 tokens, err := context.GetTokensByProfileID(grant.ProfileID, 1, 0)
809 if err != nil {
810 t.Error("Error retrieving token:", err)
811 }
812 if len(tokens) != 1 {
813 t.Errorf("Expected %d tokens, got %d", 1, len(tokens))
814 }
815 if tokens[0].AccessToken != resp.AccessToken {
816 t.Errorf(`Expected access token to be "%s", got "%s"`, tokens[0].AccessToken, resp.AccessToken)
817 }
818 if tokens[0].RefreshToken != resp.RefreshToken {
819 t.Errorf(`Expected refresh token to be "%s", got "%s"`, tokens[0].RefreshToken, resp.RefreshToken)
820 }
821 if tokens[0].ExpiresIn != resp.ExpiresIn {
822 t.Errorf(`Expected expires in to be %d, got %d`, tokens[0].ExpiresIn, resp.ExpiresIn)
823 }
824 if tokens[0].TokenType != resp.TokenType {
825 t.Errorf(`Expected token type to be %s, got %s`, tokens[0].TokenType, resp.TokenType)
826 }
827 }