auth
auth/oauth2_test.go
Start supporting our pluggable grant_type. Define GrantType as a way to bundle information that can be used to validate requests based on their grant_type parameter. Move our validation of the authorization_code grant_type out of GetTokenHandler and into its own function. Define RegisterGrantType as a way to register new grant_type bundles and associate them with the string passed to grant_type. This enables other packages to define RegisterGrantType in their init() functions and plug in new grant types without forking this code. Implement RegisterGrantType for our authorization_code grant type.
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 }