github.com/volatiletech/authboss@v2.4.1+incompatible/remember/remember_test.go (about)

     1  package remember
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/sha512"
     7  	"encoding/base64"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"testing"
    11  
    12  	"github.com/volatiletech/authboss"
    13  	"github.com/volatiletech/authboss/mocks"
    14  )
    15  
    16  func TestInit(t *testing.T) {
    17  	t.Parallel()
    18  
    19  	ab := authboss.New()
    20  	r := &Remember{}
    21  	err := r.Init(ab)
    22  	if err != nil {
    23  		t.Fatal(err)
    24  	}
    25  }
    26  
    27  type testHarness struct {
    28  	remember *Remember
    29  	ab       *authboss.Authboss
    30  
    31  	session *mocks.ClientStateRW
    32  	cookies *mocks.ClientStateRW
    33  	storer  *mocks.ServerStorer
    34  }
    35  
    36  func testSetup() *testHarness {
    37  	harness := &testHarness{}
    38  
    39  	harness.ab = authboss.New()
    40  	harness.session = mocks.NewClientRW()
    41  	harness.cookies = mocks.NewClientRW()
    42  	harness.storer = mocks.NewServerStorer()
    43  
    44  	harness.ab.Config.Core.Logger = mocks.Logger{}
    45  	harness.ab.Config.Storage.SessionState = harness.session
    46  	harness.ab.Config.Storage.CookieState = harness.cookies
    47  	harness.ab.Config.Storage.Server = harness.storer
    48  
    49  	harness.remember = &Remember{harness.ab}
    50  
    51  	return harness
    52  }
    53  
    54  func TestRememberAfterAuth(t *testing.T) {
    55  	t.Parallel()
    56  
    57  	h := testSetup()
    58  
    59  	user := &mocks.User{Email: "test@test.com"}
    60  
    61  	r := mocks.Request("POST")
    62  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyValues, mocks.Values{Remember: true}))
    63  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
    64  	rec := httptest.NewRecorder()
    65  	w := h.ab.NewResponse(rec)
    66  
    67  	if handled, err := h.remember.RememberAfterAuth(w, r, false); err != nil {
    68  		t.Fatal(err)
    69  	} else if handled {
    70  		t.Error("should never be handled")
    71  	}
    72  
    73  	// Force flush of headers so cookies are written
    74  	w.WriteHeader(http.StatusOK)
    75  
    76  	if len(h.storer.RMTokens["test@test.com"]) != 1 {
    77  		t.Error("token was not persisted:", h.storer.RMTokens)
    78  	}
    79  
    80  	if cookie, ok := h.cookies.ClientValues[authboss.CookieRemember]; !ok || len(cookie) == 0 {
    81  		t.Error("remember me cookie was not set")
    82  	}
    83  }
    84  
    85  func TestRememberAfterAuthSkip(t *testing.T) {
    86  	t.Parallel()
    87  
    88  	h := testSetup()
    89  
    90  	r := mocks.Request("POST")
    91  	rec := httptest.NewRecorder()
    92  	w := h.ab.NewResponse(rec)
    93  
    94  	if handled, err := h.remember.RememberAfterAuth(w, r, false); err != nil {
    95  		t.Fatal(err)
    96  	} else if handled {
    97  		t.Error("should never be handled")
    98  	}
    99  
   100  	if len(h.storer.RMTokens["test@test.com"]) != 0 {
   101  		t.Error("expected no tokens to be created")
   102  	}
   103  
   104  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyValues, mocks.Values{Remember: false}))
   105  
   106  	if handled, err := h.remember.RememberAfterAuth(w, r, false); err != nil {
   107  		t.Fatal(err)
   108  	} else if handled {
   109  		t.Error("should never be handled")
   110  	}
   111  
   112  	if len(h.storer.RMTokens["test@test.com"]) != 0 {
   113  		t.Error("expected no tokens to be created")
   114  	}
   115  }
   116  
   117  func TestMiddlewareAuth(t *testing.T) {
   118  	t.Parallel()
   119  
   120  	h := testSetup()
   121  
   122  	user := &mocks.User{Email: "test@test.com"}
   123  	hash, token, _ := GenerateToken(user.Email)
   124  
   125  	h.storer.Users[user.Email] = user
   126  	h.storer.RMTokens[user.Email] = []string{hash}
   127  	h.cookies.ClientValues[authboss.CookieRemember] = token
   128  
   129  	r := mocks.Request("POST")
   130  	rec := httptest.NewRecorder()
   131  	w := h.ab.NewResponse(rec)
   132  
   133  	var err error
   134  	r, err = h.ab.LoadClientState(w, r)
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  
   139  	called := false
   140  	server := Middleware(h.ab)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   141  		called = true
   142  		w.WriteHeader(http.StatusOK)
   143  	}))
   144  
   145  	server.ServeHTTP(w, r)
   146  
   147  	if !called {
   148  		t.Error("it should have called the underlying handler")
   149  	}
   150  
   151  	if h.session.ClientValues[authboss.SessionKey] != user.Email {
   152  		t.Error("should have saved the pid in the session")
   153  	}
   154  	// Elided the rest of the checks, authenticate tests do this
   155  }
   156  
   157  func TestAuthenticateSuccess(t *testing.T) {
   158  	t.Parallel()
   159  
   160  	h := testSetup()
   161  
   162  	user := &mocks.User{Email: "test@test.com"}
   163  	hash, token, _ := GenerateToken(user.Email)
   164  
   165  	h.storer.Users[user.Email] = user
   166  	h.storer.RMTokens[user.Email] = []string{hash}
   167  	h.cookies.ClientValues[authboss.CookieRemember] = token
   168  
   169  	r := mocks.Request("POST")
   170  	rec := httptest.NewRecorder()
   171  	w := h.ab.NewResponse(rec)
   172  
   173  	var err error
   174  	r, err = h.ab.LoadClientState(w, r)
   175  	if err != nil {
   176  		t.Fatal(err)
   177  	}
   178  
   179  	if err = Authenticate(h.ab, w, &r); err != nil {
   180  		t.Fatal(err)
   181  	}
   182  
   183  	w.WriteHeader(http.StatusOK)
   184  
   185  	if cookie := h.cookies.ClientValues[authboss.CookieRemember]; cookie == token {
   186  		t.Error("the cookie should have been replaced with a new token")
   187  	}
   188  
   189  	if len(h.storer.RMTokens[user.Email]) != 1 {
   190  		t.Error("one token should have been removed, and one should have been added")
   191  	} else if h.storer.RMTokens[user.Email][0] == token {
   192  		t.Error("a new token should have been saved")
   193  	}
   194  
   195  	if h.session.ClientValues[authboss.SessionKey] != user.Email {
   196  		t.Error("should have saved the pid in the session")
   197  	}
   198  	if h.session.ClientValues[authboss.SessionHalfAuthKey] != "true" {
   199  		t.Error("it should have become a half-authed session")
   200  	}
   201  
   202  	if r.Context().Value(authboss.CTXKeyPID).(string) != "test@test.com" {
   203  		t.Error("should have set the context value to log the user in")
   204  	}
   205  }
   206  
   207  func TestAuthenticateTokenNotFound(t *testing.T) {
   208  	t.Parallel()
   209  
   210  	h := testSetup()
   211  
   212  	user := &mocks.User{Email: "test@test.com"}
   213  	_, token, _ := GenerateToken(user.Email)
   214  
   215  	h.storer.Users[user.Email] = user
   216  	h.cookies.ClientValues[authboss.CookieRemember] = token
   217  
   218  	r := mocks.Request("POST")
   219  	rec := httptest.NewRecorder()
   220  	w := h.ab.NewResponse(rec)
   221  
   222  	var err error
   223  	r, err = h.ab.LoadClientState(w, r)
   224  	if err != nil {
   225  		t.Fatal(err)
   226  	}
   227  
   228  	if err = Authenticate(h.ab, w, &r); err != nil {
   229  		t.Fatal(err)
   230  	}
   231  
   232  	w.WriteHeader(http.StatusOK)
   233  
   234  	if len(h.cookies.ClientValues[authboss.CookieRemember]) != 0 {
   235  		t.Error("there should be no remember cookie left")
   236  	}
   237  
   238  	if len(h.session.ClientValues[authboss.SessionKey]) != 0 {
   239  		t.Error("it should have not logged the user in")
   240  	}
   241  
   242  	if r.Context().Value(authboss.CTXKeyPID) != nil {
   243  		t.Error("the context's pid should be empty")
   244  	}
   245  }
   246  
   247  func TestAuthenticateBadTokens(t *testing.T) {
   248  	t.Parallel()
   249  
   250  	h := testSetup()
   251  
   252  	doTest := func(t *testing.T) {
   253  		t.Helper()
   254  
   255  		r := mocks.Request("POST")
   256  		rec := httptest.NewRecorder()
   257  		w := h.ab.NewResponse(rec)
   258  
   259  		var err error
   260  		r, err = h.ab.LoadClientState(w, r)
   261  		if err != nil {
   262  			t.Fatal(err)
   263  		}
   264  
   265  		if err = Authenticate(h.ab, w, &r); err != nil {
   266  			t.Fatal(err)
   267  		}
   268  
   269  		w.WriteHeader(http.StatusOK)
   270  
   271  		if len(h.cookies.ClientValues[authboss.CookieRemember]) != 0 {
   272  			t.Error("there should be no remember cookie left")
   273  		}
   274  
   275  		if len(h.session.ClientValues[authboss.SessionKey]) != 0 {
   276  			t.Error("it should have not logged the user in")
   277  		}
   278  
   279  		if r.Context().Value(authboss.CTXKeyPID) != nil {
   280  			t.Error("the context's pid should be empty")
   281  		}
   282  	}
   283  
   284  	t.Run("base64", func(t *testing.T) {
   285  		h.cookies.ClientValues[authboss.CookieRemember] = "a"
   286  		doTest(t)
   287  	})
   288  	t.Run("cookieformat", func(t *testing.T) {
   289  		h.cookies.ClientValues[authboss.CookieRemember] = `aGVsbG8=` // hello
   290  		doTest(t)
   291  	})
   292  }
   293  
   294  func TestAfterPasswordReset(t *testing.T) {
   295  	t.Parallel()
   296  
   297  	h := testSetup()
   298  
   299  	user := &mocks.User{Email: "test@test.com"}
   300  	hash1, _, _ := GenerateToken(user.Email)
   301  	hash2, token2, _ := GenerateToken(user.Email)
   302  
   303  	h.storer.Users[user.Email] = user
   304  	h.storer.RMTokens[user.Email] = []string{hash1, hash2}
   305  	h.cookies.ClientValues[authboss.CookieRemember] = token2
   306  
   307  	r := mocks.Request("POST")
   308  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   309  	rec := httptest.NewRecorder()
   310  	w := h.ab.NewResponse(rec)
   311  
   312  	if handled, err := h.remember.AfterPasswordReset(w, r, false); err != nil {
   313  		t.Error(err)
   314  	} else if handled {
   315  		t.Error("it should never be handled")
   316  	}
   317  
   318  	w.WriteHeader(http.StatusOK) // Force header flush
   319  
   320  	if len(h.storer.RMTokens[user.Email]) != 0 {
   321  		t.Error("all remember me tokens should have been removed")
   322  	}
   323  	if len(h.cookies.ClientValues[authboss.CookieRemember]) != 0 {
   324  		t.Error("there should be no remember cookie left")
   325  	}
   326  }
   327  
   328  func TestGenerateToken(t *testing.T) {
   329  	t.Parallel()
   330  
   331  	hash, tok, err := GenerateToken("test")
   332  	if err != nil {
   333  		t.Fatal(err)
   334  	}
   335  
   336  	rawToken, err := base64.URLEncoding.DecodeString(tok)
   337  	if err != nil {
   338  		t.Error(err)
   339  	}
   340  
   341  	index := bytes.IndexByte(rawToken, ';')
   342  	if index < 0 {
   343  		t.Fatalf("problem with the token format: %v", rawToken)
   344  	}
   345  
   346  	bytPID := rawToken[:index]
   347  	if string(bytPID) != "test" {
   348  		t.Errorf("pid wrong: %s", bytPID)
   349  	}
   350  
   351  	sum := sha512.Sum512(rawToken)
   352  	gotHash := base64.StdEncoding.EncodeToString(sum[:])
   353  	if hash != gotHash {
   354  		t.Errorf("hash wrong, want: %s, got: %s", hash, gotHash)
   355  	}
   356  }