auth
auth/oauth2_test.go
Update TODOs with error messages and test obtaining a token. Update the TODOs about returning errors when obtaining a token with the actual error code that should be returned. Write a unit test that covers obtaining a token from a grant code, but doesn't cover any of the error conditions or states.
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.Log(`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.Errorf("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 TestGetBasicAuth(t *testing.T) {
559 tests := map[string]struct {
560 un string
561 pass string
562 err error
563 }{
564 "Basic dGVzdHVzZXI6cGFzc3dvcmQx": {"testuser", "password1", nil},
565 "": {"", "", ErrNoAuth},
566 "dGVzdHVzZXI6cGFzc3dvcmQx": {"", "", ErrInvalidAuthFormat},
567 "Basic _*&^##$@#$@&!!@": {"", "", ErrInvalidAuthFormat},
568 "Basic abcdefgh": {"", "", ErrInvalidAuthFormat},
569 "Basic dXNlcjo=": {"user", "", nil},
570 }
572 for header, test := range tests {
573 req, err := http.NewRequest("GET", "https://auth.secondbit.org", nil)
574 if err != nil {
575 t.Error("Unexpected error creating base request:", err)
576 }
577 req.Header.Set("Authorization", header)
578 un, pass, err := getBasicAuth(req)
579 if un != test.un {
580 t.Errorf(`Expected header "%s" to return username "%s", got "%s"`, header, test.un, un)
581 }
582 if pass != test.pass {
583 t.Errorf(`Expected header "%s" to return password "%s", got "%s"`, header, test.pass, pass)
584 }
585 if err != test.err {
586 t.Errorf(`Expected header "%s" to return error "%s", got "%s"`, header, test.err, err)
587 }
588 }
589 }
591 func TestCheckCookie(t *testing.T) {
592 t.Parallel()
593 req, err := http.NewRequest("GET", "https://auth.secondbit.org", nil)
594 if err != nil {
595 t.Error("Unexpected error creating base request:", err)
596 }
597 store := NewMemstore()
598 testContext := Context{
599 sessions: store,
600 }
601 session, err := checkCookie(req, testContext)
602 if err != ErrNoSession {
603 t.Errorf("Expected ErrNoSession, got %s", err)
604 }
605 session = Session{
606 ID: "testsession",
607 Active: true,
608 }
609 err = testContext.CreateSession(session)
610 if err != nil {
611 t.Error("Unexpected error persisting session:", err)
612 }
613 invalidSession := Session{
614 ID: "testsession2",
615 Active: false,
616 }
617 err = testContext.CreateSession(invalidSession)
618 if err != nil {
619 t.Error("Unexpected error persisting session:", err)
620 }
621 result, err := checkCookie(req, testContext)
622 if err != ErrNoSession {
623 t.Errorf("Expected ErrNoSession, got %s", err)
624 }
625 req.AddCookie(&http.Cookie{
626 Name: "wrongcookie",
627 Value: "wrong value",
628 })
629 result, err = checkCookie(req, testContext)
630 if err != ErrNoSession {
631 t.Error("Expected ErrNoSession, got", err)
632 }
633 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
634 if err != nil {
635 t.Error("Unexpected error creating base request:", err)
636 }
637 req.AddCookie(&http.Cookie{
638 Name: "Stillwrongcookie",
639 Value: session.ID,
640 })
641 result, err = checkCookie(req, testContext)
642 if err != ErrNoSession {
643 t.Error("Expected ErrNoSession, got", err)
644 }
645 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
646 if err != nil {
647 t.Error("Unexpected error creating base request:", err)
648 }
649 req.AddCookie(&http.Cookie{
650 Name: authCookieName,
651 Value: "wrong value",
652 })
653 result, err = checkCookie(req, testContext)
654 if err != ErrInvalidSession {
655 t.Error("Expected ErrInvalidSession, got", err)
656 }
657 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
658 if err != nil {
659 t.Error("Unexpected error creating base request:", err)
660 }
661 req.AddCookie(&http.Cookie{
662 Name: authCookieName,
663 Value: invalidSession.ID,
664 })
665 result, err = checkCookie(req, testContext)
666 if err != ErrInvalidSession {
667 t.Error("Expected ErrInvalidSession, got", err)
668 }
669 req, err = http.NewRequest("GET", "https://auth.secondbit.org", nil)
670 if err != nil {
671 t.Error("Unexpected error creating base request:", err)
672 }
673 req.AddCookie(&http.Cookie{
674 Name: authCookieName,
675 Value: session.ID,
676 })
677 result, err = checkCookie(req, testContext)
678 if err != nil {
679 t.Error("Unexpected error:", err)
680 }
681 success, field, expectation, outcome := compareSessions(session, result)
682 if !success {
683 t.Errorf(`Expected field %s to be %v, but got %v`, field, expectation, outcome)
684 }
685 }
687 func TestBuildLoginRedirect(t *testing.T) {
688 t.Parallel()
689 req, err := http.NewRequest("GET", "https://client.secondbit.org/my/awesome/path?has=query¶ms=to&screw=this&all=up", nil)
690 if err != nil {
691 t.Error("Unexpected error creating base request:", err)
692 }
693 result := buildLoginRedirect(req, Context{})
694 if result != "" {
695 t.Error("Expected empty string as the result, got", result)
696 }
697 uri, err := url.Parse("https://auth.secondbit.org/login?query=string&other=param")
698 if err != nil {
699 t.Error("Unexpected error parsing URL:", err)
700 }
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)
706 }
707 }
709 func TestAuthenticateHelper(t *testing.T) {
710 t.Parallel()
711 store := NewMemstore()
712 context := Context{
713 profiles: store,
714 }
715 profile := Profile{
716 ID: uuid.NewID(),
717 Name: "Test User",
718 Passphrase: "55d87acb9adff90a0d8e4c9b77f239c2d6e3a1945dbd09b0270467411198db25",
719 Iterations: 4096,
720 Salt: "this is a super secure random salt",
721 PassphraseScheme: 1,
722 Compromised: false,
723 LockedUntil: time.Time{},
724 PassphraseReset: "",
725 PassphraseResetCreated: time.Time{},
726 Created: time.Now(),
727 LastSeen: time.Time{},
728 }
729 login := Login{
730 Type: "email",
731 Value: "test@example.com",
732 ProfileID: profile.ID,
733 Created: time.Now(),
734 LastUsed: time.Time{},
735 }
736 err := context.SaveProfile(profile)
737 if err != nil {
738 t.Error("Error saving profile:", err)
739 }
740 err = context.AddLogin(login)
741 if err != nil {
742 t.Error("Error adding login:", err)
743 }
744 response, err := authenticate("test@example.com", "a really secure password", context)
745 if err != nil {
746 t.Error("Unexpected error:", err)
747 }
748 success, field, expectation, result := compareProfiles(profile, response)
749 if !success {
750 t.Errorf(`Expected field %s to be "%v", got "%v"`, field, expectation, result)
751 }
752 response, err = authenticate("test2@example.com", "a really secure password", context)
753 if err != ErrIncorrectAuth {
754 t.Error("Expected ErrIncorrectAuth, got", err)
755 }
756 response, err = authenticate("test@example.com", "not the right password", context)
757 if err != ErrIncorrectAuth {
758 t.Error("Expected ErrIncorrectAuth, got", err)
759 }
760 scheme := 1000
761 phrase := "doesn't really matter, the scheme doesn't exist"
762 change := ProfileChange{
763 PassphraseScheme: &scheme,
764 Passphrase: &phrase,
765 }
766 err = context.UpdateProfile(profile.ID, change)
767 if err != nil {
768 t.Error("Unexpected error:", err)
769 }
770 response, err = authenticate("test@example.com", "not the right password", context)
771 if err != ErrInvalidPassphraseScheme {
772 t.Error("Expected ErrIncorrectAuth, got", err)
773 }
774 }
776 func TestGetTokenHandler(t *testing.T) {
777 t.Parallel()
778 store := NewMemstore()
779 context := Context{
780 clients: store,
781 grants: store,
782 tokens: store,
783 }
784 client := Client{
785 ID: uuid.NewID(),
786 Secret: "sometimes I feel like I don't know what I'm doing",
787 OwnerID: uuid.NewID(),
788 Name: "A Super Awesome Client!",
789 Logo: "https://logos.secondbit.org/client.png",
790 Website: "https://client.secondbit.org/",
791 Type: "confidential",
792 }
793 grant := Grant{
794 Code: "testcode",
795 Created: time.Now(),
796 ExpiresIn: 600,
797 ClientID: client.ID,
798 Scope: "testscope",
799 RedirectURI: "https://client.secondbit.org/",
800 State: "teststate",
801 ProfileID: uuid.NewID(),
802 }
803 err := context.SaveGrant(grant)
804 if err != nil {
805 t.Error("Error saving grant:", err)
806 }
807 err = context.SaveClient(client)
808 if err != nil {
809 t.Error("Error saving client:", err)
810 }
811 data := url.Values{}
812 data.Set("grant_type", "authorization_code")
813 data.Set("code", grant.Code)
814 data.Set("redirect_uri", grant.RedirectURI)
815 body := bytes.NewBufferString(data.Encode())
816 req, err := http.NewRequest("POST", "https://auth.secondbit.org/", ioutil.NopCloser(body))
817 if err != nil {
818 t.Error("Error constructing request:", err)
819 }
820 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
821 req.SetBasicAuth(client.ID.String(), client.Secret)
822 w := httptest.NewRecorder()
823 GetTokenHandler(w, req, context)
824 resp := tokenResponse{}
825 err = json.Unmarshal(w.Body.Bytes(), &resp)
826 if err != nil {
827 t.Error("Error unmarshalling response:", err)
828 }
829 if resp.AccessToken == "" {
830 t.Error("Got blank access token back")
831 }
832 if resp.RefreshToken == "" {
833 t.Error("Got blank refresh token back")
834 }
835 if resp.TokenType == "" {
836 t.Error("Got blank token type back")
837 }
838 if resp.ExpiresIn == 0 {
839 t.Error("Got blank expires in back")
840 }
841 tokens, err := context.GetTokensByProfileID(grant.ProfileID, 1, 0)
842 if err != nil {
843 t.Error("Error retrieving token:", err)
844 }
845 if len(tokens) != 1 {
846 t.Errorf("Expected %d tokens, got %d", 1, len(tokens))
847 }
848 if tokens[0].AccessToken != resp.AccessToken {
849 t.Errorf(`Expected access token to be "%s", got "%s"`, tokens[0].AccessToken, resp.AccessToken)
850 }
851 if tokens[0].RefreshToken != resp.RefreshToken {
852 t.Errorf(`Expected refresh token to be "%s", got "%s"`, tokens[0].RefreshToken, resp.RefreshToken)
853 }
854 if tokens[0].ExpiresIn != resp.ExpiresIn {
855 t.Errorf(`Expected expires in to be %d, got %d`, tokens[0].ExpiresIn, resp.ExpiresIn)
856 }
857 if tokens[0].TokenType != resp.TokenType {
858 t.Errorf(`Expected token type to be %s, got %s`, tokens[0].TokenType, resp.TokenType)
859 }
860 }