github.com/volatiletech/authboss@v2.4.1+incompatible/confirm/confirm_test.go (about)

     1  package confirm
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/sha512"
     7  	"encoding/base64"
     8  	"errors"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"testing"
    12  
    13  	"github.com/volatiletech/authboss"
    14  	"github.com/volatiletech/authboss/mocks"
    15  )
    16  
    17  func TestInit(t *testing.T) {
    18  	t.Parallel()
    19  
    20  	ab := authboss.New()
    21  
    22  	router := &mocks.Router{}
    23  	renderer := &mocks.Renderer{}
    24  	errHandler := &mocks.ErrorHandler{}
    25  	ab.Config.Core.Router = router
    26  	ab.Config.Core.MailRenderer = renderer
    27  	ab.Config.Core.ErrorHandler = errHandler
    28  
    29  	c := &Confirm{}
    30  	if err := c.Init(ab); err != nil {
    31  		t.Fatal(err)
    32  	}
    33  
    34  	if err := renderer.HasLoadedViews(EmailConfirmHTML, EmailConfirmTxt); err != nil {
    35  		t.Error(err)
    36  	}
    37  
    38  	if err := router.HasGets("/confirm"); err != nil {
    39  		t.Error(err)
    40  	}
    41  }
    42  
    43  type testHarness struct {
    44  	confirm *Confirm
    45  	ab      *authboss.Authboss
    46  
    47  	bodyReader *mocks.BodyReader
    48  	mailer     *mocks.Emailer
    49  	redirector *mocks.Redirector
    50  	renderer   *mocks.Renderer
    51  	responder  *mocks.Responder
    52  	session    *mocks.ClientStateRW
    53  	storer     *mocks.ServerStorer
    54  }
    55  
    56  func testSetup() *testHarness {
    57  	harness := &testHarness{}
    58  
    59  	harness.ab = authboss.New()
    60  	harness.bodyReader = &mocks.BodyReader{}
    61  	harness.mailer = &mocks.Emailer{}
    62  	harness.redirector = &mocks.Redirector{}
    63  	harness.renderer = &mocks.Renderer{}
    64  	harness.responder = &mocks.Responder{}
    65  	harness.session = mocks.NewClientRW()
    66  	harness.storer = mocks.NewServerStorer()
    67  
    68  	harness.ab.Paths.ConfirmOK = "/confirm/ok"
    69  	harness.ab.Paths.ConfirmNotOK = "/confirm/not/ok"
    70  	harness.ab.Modules.MailNoGoroutine = true
    71  
    72  	harness.ab.Config.Core.BodyReader = harness.bodyReader
    73  	harness.ab.Config.Core.Logger = mocks.Logger{}
    74  	harness.ab.Config.Core.Mailer = harness.mailer
    75  	harness.ab.Config.Core.Redirector = harness.redirector
    76  	harness.ab.Config.Core.MailRenderer = harness.renderer
    77  	harness.ab.Config.Core.Responder = harness.responder
    78  	harness.ab.Config.Storage.SessionState = harness.session
    79  	harness.ab.Config.Storage.Server = harness.storer
    80  
    81  	harness.confirm = &Confirm{harness.ab}
    82  
    83  	return harness
    84  }
    85  
    86  func TestPreventAuthAllow(t *testing.T) {
    87  	t.Parallel()
    88  
    89  	harness := testSetup()
    90  
    91  	user := &mocks.User{
    92  		Confirmed: true,
    93  	}
    94  
    95  	r := mocks.Request("GET")
    96  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
    97  	w := httptest.NewRecorder()
    98  
    99  	handled, err := harness.confirm.PreventAuth(w, r, false)
   100  	if err != nil {
   101  		t.Error(err)
   102  	}
   103  
   104  	if handled {
   105  		t.Error("it should not have been handled")
   106  	}
   107  }
   108  
   109  func TestPreventDisallow(t *testing.T) {
   110  	t.Parallel()
   111  
   112  	harness := testSetup()
   113  
   114  	user := &mocks.User{
   115  		Confirmed: false,
   116  	}
   117  
   118  	r := mocks.Request("GET")
   119  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   120  	w := httptest.NewRecorder()
   121  
   122  	handled, err := harness.confirm.PreventAuth(w, r, false)
   123  	if err != nil {
   124  		t.Error(err)
   125  	}
   126  
   127  	if !handled {
   128  		t.Error("it should have been handled")
   129  	}
   130  
   131  	if w.Code != http.StatusTemporaryRedirect {
   132  		t.Error("redirect did not occur")
   133  	}
   134  
   135  	if p := harness.redirector.Options.RedirectPath; p != "/confirm/not/ok" {
   136  		t.Error("redirect path was wrong:", p)
   137  	}
   138  }
   139  
   140  func TestStartConfirmationWeb(t *testing.T) {
   141  	t.Parallel()
   142  
   143  	harness := testSetup()
   144  
   145  	user := &mocks.User{Email: "test@test.com"}
   146  	harness.storer.Users["test@test.com"] = user
   147  
   148  	r := mocks.Request("GET")
   149  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   150  	w := httptest.NewRecorder()
   151  
   152  	handled, err := harness.confirm.StartConfirmationWeb(w, r, false)
   153  	if err != nil {
   154  		t.Error(err)
   155  	}
   156  
   157  	if !handled {
   158  		t.Error("it should always be handled")
   159  	}
   160  
   161  	if w.Code != http.StatusTemporaryRedirect {
   162  		t.Error("redirect did not occur")
   163  	}
   164  
   165  	if p := harness.redirector.Options.RedirectPath; p != "/confirm/not/ok" {
   166  		t.Error("redirect path was wrong:", p)
   167  	}
   168  
   169  	if to := harness.mailer.Email.To[0]; to != "test@test.com" {
   170  		t.Error("mailer sent e-mail to wrong person:", to)
   171  	}
   172  }
   173  
   174  func TestGetSuccess(t *testing.T) {
   175  	t.Parallel()
   176  
   177  	harness := testSetup()
   178  
   179  	selector, verifier, token, err := GenerateConfirmCreds()
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  
   184  	user := &mocks.User{Email: "test@test.com", Confirmed: false, ConfirmSelector: selector, ConfirmVerifier: verifier}
   185  	harness.storer.Users["test@test.com"] = user
   186  	harness.bodyReader.Return = mocks.Values{
   187  		Token: token,
   188  	}
   189  
   190  	r := mocks.Request("GET")
   191  	w := httptest.NewRecorder()
   192  
   193  	if err := harness.confirm.Get(w, r); err != nil {
   194  		t.Error(err)
   195  	}
   196  
   197  	if w.Code != http.StatusTemporaryRedirect {
   198  		t.Error("expected a redirect, got:", w.Code)
   199  	}
   200  	if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmOK {
   201  		t.Error("redir path was wrong:", p)
   202  	}
   203  
   204  	if len(user.ConfirmSelector) != 0 {
   205  		t.Error("the confirm selector should have been erased")
   206  	}
   207  	if len(user.ConfirmVerifier) != 0 {
   208  		t.Error("the confirm verifier should have been erased")
   209  	}
   210  	if !user.Confirmed {
   211  		t.Error("the user should have been confirmed")
   212  	}
   213  }
   214  
   215  func TestGetValidationFailure(t *testing.T) {
   216  	t.Parallel()
   217  
   218  	harness := testSetup()
   219  
   220  	harness.bodyReader.Return = mocks.Values{
   221  		Errors: []error{errors.New("fail")},
   222  	}
   223  
   224  	r := mocks.Request("GET")
   225  	w := httptest.NewRecorder()
   226  
   227  	if err := harness.confirm.Get(w, r); err != nil {
   228  		t.Error(err)
   229  	}
   230  
   231  	if w.Code != http.StatusTemporaryRedirect {
   232  		t.Error("expected a redirect, got:", w.Code)
   233  	}
   234  	if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK {
   235  		t.Error("redir path was wrong:", p)
   236  	}
   237  	if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" {
   238  		t.Error("reason for failure was wrong:", reason)
   239  	}
   240  }
   241  
   242  func TestGetBase64DecodeFailure(t *testing.T) {
   243  	t.Parallel()
   244  
   245  	harness := testSetup()
   246  
   247  	harness.bodyReader.Return = mocks.Values{
   248  		Token: "5",
   249  	}
   250  
   251  	r := mocks.Request("GET")
   252  	w := httptest.NewRecorder()
   253  
   254  	if err := harness.confirm.Get(w, r); err != nil {
   255  		t.Error(err)
   256  	}
   257  
   258  	if w.Code != http.StatusTemporaryRedirect {
   259  		t.Error("expected a redirect, got:", w.Code)
   260  	}
   261  	if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK {
   262  		t.Error("redir path was wrong:", p)
   263  	}
   264  	if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" {
   265  		t.Error("reason for failure was wrong:", reason)
   266  	}
   267  }
   268  
   269  func TestGetUserNotFoundFailure(t *testing.T) {
   270  	t.Parallel()
   271  
   272  	harness := testSetup()
   273  
   274  	_, _, token, err := GenerateConfirmCreds()
   275  	if err != nil {
   276  		t.Fatal(err)
   277  	}
   278  
   279  	harness.bodyReader.Return = mocks.Values{
   280  		Token: token,
   281  	}
   282  
   283  	r := mocks.Request("GET")
   284  	w := httptest.NewRecorder()
   285  
   286  	if err := harness.confirm.Get(w, r); err != nil {
   287  		t.Error(err)
   288  	}
   289  
   290  	if w.Code != http.StatusTemporaryRedirect {
   291  		t.Error("expected a redirect, got:", w.Code)
   292  	}
   293  	if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK {
   294  		t.Error("redir path was wrong:", p)
   295  	}
   296  	if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" {
   297  		t.Error("reason for failure was wrong:", reason)
   298  	}
   299  }
   300  
   301  func TestMiddlewareAllow(t *testing.T) {
   302  	t.Parallel()
   303  
   304  	ab := authboss.New()
   305  	called := false
   306  	server := Middleware(ab)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   307  		called = true
   308  	}))
   309  
   310  	user := &mocks.User{
   311  		Confirmed: true,
   312  	}
   313  
   314  	r := mocks.Request("GET")
   315  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   316  	w := httptest.NewRecorder()
   317  
   318  	server.ServeHTTP(w, r)
   319  
   320  	if !called {
   321  		t.Error("The user should have been allowed through")
   322  	}
   323  }
   324  
   325  func TestMiddlewareDisallow(t *testing.T) {
   326  	t.Parallel()
   327  
   328  	ab := authboss.New()
   329  	redirector := &mocks.Redirector{}
   330  	ab.Config.Paths.ConfirmNotOK = "/confirm/not/ok"
   331  	ab.Config.Core.Logger = mocks.Logger{}
   332  	ab.Config.Core.Redirector = redirector
   333  
   334  	called := false
   335  	server := Middleware(ab)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   336  		called = true
   337  	}))
   338  
   339  	user := &mocks.User{
   340  		Confirmed: false,
   341  	}
   342  
   343  	r := mocks.Request("GET")
   344  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   345  	w := httptest.NewRecorder()
   346  
   347  	server.ServeHTTP(w, r)
   348  
   349  	if called {
   350  		t.Error("The user should not have been allowed through")
   351  	}
   352  	if redirector.Options.Code != http.StatusTemporaryRedirect {
   353  		t.Error("expected a redirect, but got:", redirector.Options.Code)
   354  	}
   355  	if p := redirector.Options.RedirectPath; p != "/confirm/not/ok" {
   356  		t.Error("redirect path wrong:", p)
   357  	}
   358  }
   359  
   360  func TestMailURL(t *testing.T) {
   361  	t.Parallel()
   362  
   363  	h := testSetup()
   364  	h.ab.Config.Paths.RootURL = "https://api.test.com:6343"
   365  	h.ab.Config.Paths.Mount = "/v1/auth"
   366  
   367  	want := "https://api.test.com:6343/v1/auth/confirm?cnf=abc"
   368  	if got := h.confirm.mailURL("abc"); got != want {
   369  		t.Error("want:", want, "got:", got)
   370  	}
   371  
   372  	h.ab.Config.Mail.RootURL = "https://test.com:3333/testauth"
   373  
   374  	want = "https://test.com:3333/testauth/confirm?cnf=abc"
   375  	if got := h.confirm.mailURL("abc"); got != want {
   376  		t.Error("want:", want, "got:", got)
   377  	}
   378  }
   379  
   380  func TestGenerateRecoverCreds(t *testing.T) {
   381  	t.Parallel()
   382  
   383  	selector, verifier, token, err := GenerateConfirmCreds()
   384  	if err != nil {
   385  		t.Error(err)
   386  	}
   387  
   388  	if verifier == selector {
   389  		t.Error("the verifier and selector should be different")
   390  	}
   391  
   392  	// base64 length: n = 64; 4*(64/3) = 85.3; round to nearest 4: 88
   393  	if len(verifier) != 88 {
   394  		t.Errorf("verifier length was wrong (%d): %s", len(verifier), verifier)
   395  	}
   396  
   397  	// base64 length: n = 64; 4*(64/3) = 85.3; round to nearest 4: 88
   398  	if len(selector) != 88 {
   399  		t.Errorf("selector length was wrong (%d): %s", len(selector), selector)
   400  	}
   401  
   402  	// base64 length: n = 64; 4*(64/3) = 85.33; round to nearest 4: 88
   403  	if len(token) != 88 {
   404  		t.Errorf("token length was wrong (%d): %s", len(token), token)
   405  	}
   406  
   407  	rawToken, err := base64.URLEncoding.DecodeString(token)
   408  	if err != nil {
   409  		t.Error(err)
   410  	}
   411  
   412  	rawSelector, err := base64.StdEncoding.DecodeString(selector)
   413  	if err != nil {
   414  		t.Error(err)
   415  	}
   416  	rawVerifier, err := base64.StdEncoding.DecodeString(verifier)
   417  	if err != nil {
   418  		t.Error(err)
   419  	}
   420  
   421  	checkSelector := sha512.Sum512(rawToken[:confirmTokenSplit])
   422  	if 0 != bytes.Compare(checkSelector[:], rawSelector) {
   423  		t.Error("expected selector to match")
   424  	}
   425  	checkVerifier := sha512.Sum512(rawToken[confirmTokenSplit:])
   426  	if 0 != bytes.Compare(checkVerifier[:], rawVerifier) {
   427  		t.Error("expected verifier to match")
   428  	}
   429  }