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