Break out scopes and events.
This repo has gotten unwieldy, and there are portions of it that need to be
imported by a large number of other packages.
For example, scopes will be used in almost every API we write. Rather than
importing the entirety of this codebase into every API we write, I've opted to
move the scope logic out into a scopes package, with a subpackage for the
defined types, which is all most projects actually want to import.
We also define some event type constants, and importing those shouldn't require
a project to import all our dependencies, either. So I made an events subpackage
that just holds those constants.
This package has become a little bit of a red-headed stepchild and is do for a
refactor, but I'm trying to put that off as long as I can.
The refactoring of our scopes stuff has left a bug wherein a token can be
granted for scopes that don't exist. I'm going to need to revisit that, and also
how to limit scopes to only be granted to the users that should be able to
request them. But that's a battle for another day.
14 "code.secondbit.org/scopes.hg/types"
15 "code.secondbit.org/uuid.hg"
23 func stripParam(param string, u *url.URL) {
26 u.RawQuery = q.Encode()
29 func TestGetAuthorizationCodeCodeSuccess(t *testing.T) {
31 store := NewMemstore()
32 testContext := Context{
33 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ if not .error }}Get auth grant{{ else }}{{ .error }}{{ end }}")),
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",
52 URI: "https://test.secondbit.org/redirect",
53 Added: time.Now().Round(time.Millisecond),
55 err := testContext.SaveClient(client)
57 t.Fatal("Can't store client:", err)
59 err = testContext.AddEndpoints([]Endpoint{endpoint})
61 t.Fatal("Can't store endpoint:", err)
66 err = testContext.SaveProfile(profile)
68 t.Fatal("Can't store profile:", err)
73 ProfileID: profile.ID,
75 Expires: time.Now().Round(time.Millisecond).Add(time.Hour),
77 err = testContext.CreateSession(session)
79 t.Fatal("Can't store session:", err)
82 scope := scopeTypes.Scope{
85 Description: "Hug dispensation.",
87 err = testContext.CreateScopes([]Scope{scope})
89 t.Fatal("Can't store scope:", err)
91 // BUG(paddy): create the scopes in the scopeStore
92 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
94 t.Fatal("Can't build request:", err)
96 cookie := &http.Cookie{
100 req.AddCookie(cookie)
101 for i := 0; i < 1<<2; i++ {
102 w := httptest.NewRecorder()
103 params := url.Values{}
104 // see OAuth 2.0 spec, section 4.1.1
105 params.Set("response_type", "code")
106 params.Set("client_id", client.ID.String())
107 params.Set("scope", "testscope")
109 params.Set("redirect_uri", endpoint.URI)
112 params.Set("state", "my super secure state string")
114 req.URL.RawQuery = params.Encode()
117 req.Header.Del("Content-Type")
118 GetAuthorizationCodeHandler(w, req, testContext)
119 if w.Code != http.StatusOK {
120 t.Log("Response body:", w.Body.String())
121 t.Errorf("Expected status code to be %d, got %d for %s", http.StatusOK, w.Code, req.URL.String())
123 if w.Body.String() != "Get auth grant" {
124 t.Errorf("Expected body to be `%s`, got `%s` for %s", "Get auth grant", w.Body.String(), req.URL.String())
127 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
128 w = httptest.NewRecorder()
130 data.Set("grant", "approved")
131 data.Set("csrftoken", session.CSRFToken)
132 body := bytes.NewBufferString(data.Encode())
133 req.Body = ioutil.NopCloser(body)
134 GetAuthorizationCodeHandler(w, req, testContext)
135 if w.Code != http.StatusFound {
136 t.Errorf("Expected status code to be %d, got %d for %s", http.StatusFound, w.Code, req.URL.String())
138 redirectedTo := w.Header().Get("Location")
139 red, err := url.Parse(redirectedTo)
141 t.Fatalf(`Being redirected to a non-URL "%s" threw error "%s" for "%s"\n`, redirectedTo, err, req.URL.String())
143 t.Log("Redirected to", redirectedTo)
144 t.Log("Body", w.Body.String())
145 if red.Query().Get("code") == "" {
146 t.Fatalf(`Expected code param in redirect URL to be set, but it wasn't for %s`, req.URL.String())
148 if _, err := testContext.GetAuthorizationCode(red.Query().Get("code")); err != nil {
149 t.Fatalf(`Unexpected error "%s: retrieving the grant "%s" supplied in the redirect URL for %s`, err, red.Query().Get("code"), req.URL.String())
151 err = testContext.DeleteAuthorizationCode(red.Query().Get("code"))
153 t.Logf(`Unexpected error "%s" deleting grant "%s" for %s`, err, red.Query().Get("code"), req.URL.String())
155 stripParam("code", red)
156 if red.Query().Get("state") != params.Get("state") {
157 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())
159 stripParam("state", red)
160 if red.String() != endpoint.URI {
161 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
166 func TestGetAuthorizationCodeCodeInvalidClient(t *testing.T) {
168 store := NewMemstore()
169 testContext := Context{
170 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .error }}")),
179 Secret: "super secret!",
180 OwnerID: uuid.NewID(),
181 Name: "My test client",
184 err := testContext.SaveClient(client)
186 t.Fatal("Can't store client:", err)
192 Expires: time.Now().Round(time.Millisecond).Add(time.Hour),
194 err = testContext.CreateSession(session)
196 t.Fatal("Can't store session:", err)
198 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
200 t.Fatal("Can't build request:", err)
202 w := httptest.NewRecorder()
203 params := url.Values{}
204 params.Set("response_type", "code")
205 params.Set("redirect_uri", "https://test.secondbit.org/")
206 req.URL.RawQuery = params.Encode()
207 cookie := &http.Cookie{
208 Name: authCookieName,
211 req.AddCookie(cookie)
212 GetAuthorizationCodeHandler(w, req, testContext)
213 if w.Code != http.StatusBadRequest {
214 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
216 if w.Body.String() != "Client ID must be specified in the request." {
217 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "Client ID must be specified in the request.", w.Body.String())
219 w = httptest.NewRecorder()
220 params.Set("client_id", "Not an ID")
221 req.URL.RawQuery = params.Encode()
222 GetAuthorizationCodeHandler(w, req, testContext)
223 if w.Code != http.StatusBadRequest {
224 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
226 if w.Body.String() != "client_id is not a valid Client ID." {
227 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "client_id is not a valid Client ID.", w.Body.String())
229 w = httptest.NewRecorder()
230 params.Set("client_id", uuid.NewID().String())
231 req.URL.RawQuery = params.Encode()
232 GetAuthorizationCodeHandler(w, req, testContext)
233 if w.Code != http.StatusBadRequest {
234 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
236 if w.Body.String() != "The specified Client couldn’t be found." {
237 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The specified Client couldn’t be found.", w.Body.String())
241 func TestGetAuthorizationCodeCodeInvalidURI(t *testing.T) {
243 store := NewMemstore()
244 testContext := Context{
245 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .error }}")),
254 Secret: "super secret!",
255 OwnerID: uuid.NewID(),
256 Name: "My test client",
259 err := testContext.SaveClient(client)
261 t.Fatal("Can't store client:", err)
267 Expires: time.Now().Round(time.Millisecond).Add(time.Hour),
269 err = testContext.CreateSession(session)
271 t.Fatal("Can't store session:", err)
273 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
275 t.Fatal("Can't build request:", err)
277 cookie := &http.Cookie{
278 Name: authCookieName,
281 req.AddCookie(cookie)
282 w := httptest.NewRecorder()
283 params := url.Values{}
284 params.Set("response_type", "code")
285 params.Set("client_id", client.ID.String())
286 req.URL.RawQuery = params.Encode()
287 GetAuthorizationCodeHandler(w, req, testContext)
288 if w.Code != http.StatusBadRequest {
289 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
291 if w.Body.String() != "The redirect_uri specified is not valid." {
292 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
294 endpoint := Endpoint{
297 URI: "https://test.secondbit.org/redirect",
298 Added: time.Now().Round(time.Millisecond),
300 err = testContext.AddEndpoints([]Endpoint{endpoint})
302 t.Fatal("Can't store endpoint:", err)
304 w = httptest.NewRecorder()
305 params.Set("redirect_uri", "https://test.secondbit.org/wrong")
306 req.URL.RawQuery = params.Encode()
307 GetAuthorizationCodeHandler(w, req, testContext)
308 if w.Code != http.StatusBadRequest {
309 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
311 if w.Body.String() != "The redirect_uri specified is not valid." {
312 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
314 endpoint2 := Endpoint{
317 URI: "https://test.secondbit.org/redirect",
318 Added: time.Now().Round(time.Millisecond),
320 err = testContext.AddEndpoints([]Endpoint{endpoint2})
322 t.Fatal("Can't store endpoint:", err)
324 w = httptest.NewRecorder()
325 params.Set("redirect_uri", "")
326 req.URL.RawQuery = params.Encode()
327 GetAuthorizationCodeHandler(w, req, testContext)
328 if w.Code != http.StatusBadRequest {
329 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
331 if w.Body.String() != "The redirect_uri specified is not valid." {
332 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
334 w = httptest.NewRecorder()
335 params.Set("redirect_uri", "://not a URL")
336 req.URL.RawQuery = params.Encode()
337 GetAuthorizationCodeHandler(w, req, testContext)
338 if w.Code != http.StatusBadRequest {
339 t.Errorf("Expected status code to be %d, got %d", http.StatusBadRequest, w.Code)
341 if w.Body.String() != "The redirect_uri specified is not valid." {
342 t.Errorf(`Expected output to be "%s", got "%s" instead.`, "The redirect_uri specified is not valid.", w.Body.String())
344 // BUG(paddy): Need to test that setting redirect_uri to a non-URL redirect_uri returns the correct error.
347 func TestGetAuthorizationCodeCodeInvalidResponseType(t *testing.T) {
349 store := NewMemstore()
350 testContext := Context{
351 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .error }}")),
360 Secret: "super secret!",
361 OwnerID: uuid.NewID(),
362 Name: "My test client",
363 Logo: "https://secondbit.org/logo.png",
364 Website: "https://secondbit.org",
367 endpoint := Endpoint{
370 URI: "https://test.secondbit.org/redirect",
371 Added: time.Now().Round(time.Millisecond),
373 err := testContext.SaveClient(client)
375 t.Fatal("Can't store client:", err)
377 err = testContext.AddEndpoints([]Endpoint{endpoint})
379 t.Fatal("Can't store endpoint:", err)
385 Expires: time.Now().Round(time.Millisecond).Add(time.Hour),
387 err = testContext.CreateSession(session)
389 t.Fatal("Can't store session:", err)
391 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
393 t.Fatal("Can't build request:", err)
395 cookie := &http.Cookie{
396 Name: authCookieName,
399 req.AddCookie(cookie)
400 params := url.Values{}
401 params.Set("response_type", "totally not code")
402 params.Set("client_id", client.ID.String())
403 params.Set("redirect_uri", endpoint.URI)
404 params.Set("scope", "testscope")
405 params.Set("state", "my super secure state string")
406 req.URL.RawQuery = params.Encode()
407 w := httptest.NewRecorder()
408 GetAuthorizationCodeHandler(w, req, testContext)
409 if w.Code != http.StatusFound {
410 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
412 redirectedTo := w.Header().Get("Location")
413 red, err := url.Parse(redirectedTo)
415 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
417 if red.Query().Get("error") != "invalid_request" {
418 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error"))
420 stripParam("error", red)
421 if red.Query().Get("state") != params.Get("state") {
422 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
424 stripParam("state", red)
425 if red.String() != endpoint.URI {
426 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
428 stripParam("response_type", req.URL)
429 w = httptest.NewRecorder()
430 GetAuthorizationCodeHandler(w, req, testContext)
431 if w.Code != http.StatusFound {
432 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
434 redirectedTo = w.Header().Get("Location")
435 red, err = url.Parse(redirectedTo)
437 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
439 if red.Query().Get("error") != "invalid_request" {
440 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "invalid_request", red.Query().Get("error"))
442 stripParam("error", red)
443 if red.Query().Get("state") != params.Get("state") {
444 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
446 stripParam("state", red)
447 if red.String() != endpoint.URI {
448 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
452 func TestGetAuthorizationCodeCodeDenied(t *testing.T) {
454 store := NewMemstore()
455 testContext := Context{
456 template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .error }}")),
465 Secret: "super secret!",
466 OwnerID: uuid.NewID(),
467 Name: "My test client",
468 Logo: "https://secondbit.org/logo.png",
469 Website: "https://secondbit.org",
472 endpoint := Endpoint{
475 URI: "https://test.secondbit.org/redirect",
476 Added: time.Now().Round(time.Millisecond),
478 err := testContext.SaveClient(client)
480 t.Fatal("Can't store client:", err)
482 err = testContext.AddEndpoints([]Endpoint{endpoint})
484 t.Fatal("Can't store endpoint:", err)
490 Expires: time.Now().Round(time.Millisecond).Add(time.Hour),
492 err = testContext.CreateSession(session)
494 t.Fatal("Can't store session:", err)
497 scope := scopeTypes.Scope{
500 Description: "High five fabrication.",
502 // BUG(paddy): Create the Scopes in the scopeStore
503 err = testContext.CreateScopes([]Scope{scope})
505 t.Fatal("Can't create scope:", err)
508 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
510 t.Fatal("Can't build request:", err)
512 cookie := &http.Cookie{
513 Name: authCookieName,
516 req.AddCookie(cookie)
517 params := url.Values{}
518 params.Set("response_type", "code")
519 params.Set("client_id", client.ID.String())
520 params.Set("redirect_uri", endpoint.URI)
521 params.Set("scope", "testscope")
522 params.Set("state", "my super secure state string")
524 data.Set("grant", "denied")
525 data.Set("csrftoken", session.CSRFToken)
526 req.URL.RawQuery = params.Encode()
527 req.Body = ioutil.NopCloser(bytes.NewBufferString(data.Encode()))
528 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
530 w := httptest.NewRecorder()
531 GetAuthorizationCodeHandler(w, req, testContext)
532 if w.Code != http.StatusFound {
533 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
535 redirectedTo := w.Header().Get("Location")
536 red, err := url.Parse(redirectedTo)
538 t.Fatalf("Being redirected to a non-URL (%s) threw error: %s\n", redirectedTo, err)
540 if red.Query().Get("error") != "access_denied" {
541 t.Errorf(`Expected error param in redirect URL to be "%s", got "%s"`, "access_denied", red.Query().Get("error"))
543 stripParam("error", red)
544 if red.Query().Get("state") != params.Get("state") {
545 t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
547 stripParam("state", red)
548 if red.String() != endpoint.URI {
549 t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
553 func TestGetAuthorizationCodeCodeLoginRedirect(t *testing.T) {
555 req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
557 t.Fatal("Can't build request:", err)
559 w := httptest.NewRecorder()
560 GetAuthorizationCodeHandler(w, req, Context{template: template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ .internal_error }}"))})
561 if w.Code != http.StatusInternalServerError {
562 t.Errorf("Expected status code to be %d, got %d", http.StatusInternalServerError, w.Code)
564 expectation := "Missing login URL."
565 if w.Body.String() != expectation {
566 t.Errorf(`Expected body to be "%s", got "%s"`, expectation, w.Body.String())
568 uri, err := url.Parse("https://client.secondbit.org/")
570 t.Error("Unexpected error", err)
572 testContext := Context{
575 w = httptest.NewRecorder()
576 GetAuthorizationCodeHandler(w, req, testContext)
577 if w.Code != http.StatusFound {
578 t.Errorf("Expected status code to be %d, got %d", http.StatusFound, w.Code)
580 redirectedTo := w.Header().Get("Location")
581 expectation = "https://client.secondbit.org/?from=https%3A%2F%2Ftest.auth.secondbit.org%2Foauth2%2Fgrant"
582 if redirectedTo != expectation {
583 t.Errorf("Expected to be redirected to '%s', was redirected to '%s'", expectation, redirectedTo)
587 // BUG(paddy): Need to test for implicit grant flow
589 func TestCheckCookie(t *testing.T) {
591 req, err := http.NewRequest("GET", "https://auth.secondbit.org", nil)
593 t.Error("Unexpected error creating base request:", err)
595 store := NewMemstore()
596 testContext := Context{
599 session, err := checkCookie(req, testContext)
600 if err != ErrNoSession {
601 t.Errorf("Expected ErrNoSession, got %s", err)
607 Expires: time.Now().Round(time.Millisecond).Add(time.Hour),
609 err = testContext.CreateSession(session)
611 t.Error("Unexpected error persisting session:", err)
613 invalidSession := Session{
617 err = testContext.CreateSession(invalidSession)
619 t.Error("Unexpected error persisting session:", err)
621 result, err := checkCookie(req, testContext)
622 if err != ErrNoSession {
623 t.Errorf("Expected ErrNoSession, got %s", err)
625 req.AddCookie(&http.Cookie{
627 Value: "wrong value",
629 result, err = checkCookie(req, testContext)
630 if err != ErrNoSession {
631 t.Error("Expected ErrNoSession, got", err)
633 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
635 t.Error("Unexpected error creating base request:", err)
637 req.AddCookie(&http.Cookie{
638 Name: "Stillwrongcookie",
641 result, err = checkCookie(req, testContext)
642 if err != ErrNoSession {
643 t.Error("Expected ErrNoSession, got", err)
645 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
647 t.Error("Unexpected error creating base request:", err)
649 req.AddCookie(&http.Cookie{
650 Name: authCookieName,
651 Value: "wrong value",
653 result, err = checkCookie(req, testContext)
654 if err != ErrInvalidSession {
655 t.Error("Expected ErrInvalidSession, got", err)
657 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
659 t.Error("Unexpected error creating base request:", err)
661 req.AddCookie(&http.Cookie{
662 Name: authCookieName,
663 Value: invalidSession.ID,
665 result, err = checkCookie(req, testContext)
666 if err != ErrInvalidSession {
667 t.Error("Expected ErrInvalidSession, got", err)
669 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
671 t.Error("Unexpected error creating base request:", err)
673 req.AddCookie(&http.Cookie{
674 Name: authCookieName,
677 result, err = checkCookie(req, testContext)
679 t.Error("Unexpected error:", err)
681 success, field, expectation, outcome := compareSessions(session, result)
683 t.Errorf(`Expected field %s to be %v, but got %v`, field, expectation, outcome)
687 func TestBuildLoginRedirect(t *testing.T) {
689 req, err := http.NewRequest("GET", "https://client.secondbit.org/my/awesome/path?has=query¶ms=to&screw=this&all=up", nil)
691 t.Error("Unexpected error creating base request:", err)
693 result := buildLoginRedirect(req, Context{})
695 t.Error("Expected empty string as the result, got", result)
697 uri, err := url.Parse("https://auth.secondbit.org/login?query=string&other=param")
699 t.Error("Unexpected error parsing URL:", err)
701 c := Context{loginURI: uri}
702 result = buildLoginRedirect(req, c)
703 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"
704 if result != expectation {
705 t.Errorf(`Expected result string to be "%s", was "%s"`, expectation, result)
709 func TestAuthenticateHelper(t *testing.T) {
711 store := NewMemstore()
718 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0",
720 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b",
723 LockedUntil: time.Time{},
725 PassphraseResetCreated: time.Time{},
726 Created: time.Now().Round(time.Millisecond),
727 LastSeen: time.Time{},
731 Value: "test@example.com",
732 ProfileID: profile.ID,
733 Created: time.Now().Round(time.Millisecond),
734 LastUsed: time.Time{},
736 err := context.SaveProfile(profile)
738 t.Error("Error saving profile:", err)
740 err = context.AddLogin(login)
742 t.Error("Error adding login:", err)
744 response, err := authenticate("test@example.com", "mysecurepassphrase", context)
746 t.Error("Unexpected error:", err)
748 success, field, expectation, result := compareProfiles(profile, response)
750 t.Errorf(`Expected field %s to be "%v", got "%v"`, field, expectation, result)
752 response, err = authenticate("test2@example.com", "mysecurepassphrase", context)
753 if err != ErrIncorrectAuth {
754 t.Error("Expected ErrIncorrectAuth, got", err)
756 response, err = authenticate("test@example.com", "not the right password", context)
757 if err != ErrIncorrectAuth {
758 t.Error("Expected ErrIncorrectAuth, got", err)
761 phrase := "doesn't really matter, the scheme doesn't exist"
762 change := ProfileChange{
763 PassphraseScheme: &scheme,
766 err = context.UpdateProfile(profile.ID, change)
768 t.Error("Unexpected error:", err)
770 response, err = authenticate("test@example.com", "not the right password", context)
771 if err != ErrInvalidPassphraseScheme {
772 t.Error("Expected ErrIncorrectAuth, got", err)
776 func TestGetTokenHandler(t *testing.T) {
778 store := NewMemstore()
784 JWTPrivateKey: []byte("this is totally a secret, secure private key"),
789 Secret: "sometimes I feel like I don't know what I'm doing",
790 OwnerID: uuid.NewID(),
791 Name: "A Super Awesome Client!",
792 Logo: "https://logos.secondbit.org/client.png",
793 Website: "https://client.secondbit.org/",
794 Type: "confidential",
796 authCode := AuthorizationCode{
798 Created: time.Now().Round(time.Millisecond),
801 Scopes: scopeTypes.StringsToScopes([]string{"testscope"}),
802 RedirectURI: "https://client.secondbit.org/",
804 ProfileID: uuid.NewID(),
806 err := context.SaveAuthorizationCode(authCode)
808 t.Error("Error saving auth code:", err)
810 err = context.SaveClient(client)
812 t.Error("Error saving client:", err)
814 // BUG(paddy): We're only testing that GetTokenHandler returns the right values when we have the right input. But what about when we have the wrong input? We should test for invalid client errors and invalid grant errors to make sure they're triggered.
816 data.Set("grant_type", "authorization_code")
817 data.Set("code", authCode.Code)
818 data.Set("redirect_uri", authCode.RedirectURI)
819 body := bytes.NewBufferString(data.Encode())
820 req, err := http.NewRequest("POST", "https://auth.secondbit.org/", ioutil.NopCloser(body))
822 t.Error("Error constructing request:", err)
824 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
825 req.SetBasicAuth(client.ID.String(), client.Secret)
826 w := httptest.NewRecorder()
827 GetTokenHandler(w, req, context)
828 resp := tokenResponse{}
829 err = json.Unmarshal(w.Body.Bytes(), &resp)
831 t.Error("Error unmarshalling response:", err)
832 t.Error("Response:", w.Body.String())
834 if resp.AccessToken == "" {
835 t.Error("Got blank access token back")
837 if resp.RefreshToken == "" {
838 t.Error("Got blank refresh token back")
840 if resp.TokenType == "" {
841 t.Error("Got blank token type back")
843 if resp.ExpiresIn == 0 {
844 t.Error("Got blank expires in back")
846 tokens, err := context.GetTokensByProfileID(authCode.ProfileID, 1, 0)
848 t.Error("Error retrieving token:", err)
850 if len(tokens) != 1 {
851 t.Fatalf("Expected %d tokens, got %d", 1, len(tokens))
853 if tokens[0].AccessToken != resp.AccessToken {
854 t.Errorf(`Expected access token to be "%s", got "%s"`, tokens[0].AccessToken, resp.AccessToken)
856 if tokens[0].RefreshToken != resp.RefreshToken {
857 t.Errorf(`Expected refresh token to be "%s", got "%s"`, tokens[0].RefreshToken, resp.RefreshToken)
859 if tokens[0].ExpiresIn != resp.ExpiresIn {
860 t.Errorf(`Expected expires in to be %d, got %d`, tokens[0].ExpiresIn, resp.ExpiresIn)
862 if tokens[0].TokenType != resp.TokenType {
863 t.Errorf(`Expected token type to be %s, got %s`, tokens[0].TokenType, resp.TokenType)
865 // BUG(paddy): We need to test for the refresh_token grant type, too.
866 // BUG(paddy): We need to test for the password grant type, too.
867 // BUG(paddy): We need to test for the client_credentials grant type, too.