github.com/volatiletech/authboss@v2.4.1+incompatible/lock/lock_test.go (about)

     1  package lock
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/volatiletech/authboss"
    11  	"github.com/volatiletech/authboss/mocks"
    12  )
    13  
    14  func TestInit(t *testing.T) {
    15  	t.Parallel()
    16  
    17  	ab := authboss.New()
    18  
    19  	l := &Lock{}
    20  	if err := l.Init(ab); err != nil {
    21  		t.Fatal(err)
    22  	}
    23  }
    24  
    25  type testHarness struct {
    26  	lock *Lock
    27  	ab   *authboss.Authboss
    28  
    29  	bodyReader *mocks.BodyReader
    30  	mailer     *mocks.Emailer
    31  	redirector *mocks.Redirector
    32  	renderer   *mocks.Renderer
    33  	responder  *mocks.Responder
    34  	session    *mocks.ClientStateRW
    35  	storer     *mocks.ServerStorer
    36  }
    37  
    38  func testSetup() *testHarness {
    39  	harness := &testHarness{}
    40  
    41  	harness.ab = authboss.New()
    42  	harness.bodyReader = &mocks.BodyReader{}
    43  	harness.mailer = &mocks.Emailer{}
    44  	harness.redirector = &mocks.Redirector{}
    45  	harness.renderer = &mocks.Renderer{}
    46  	harness.responder = &mocks.Responder{}
    47  	harness.session = mocks.NewClientRW()
    48  	harness.storer = mocks.NewServerStorer()
    49  
    50  	harness.ab.Paths.LockNotOK = "/lock/not/ok"
    51  	harness.ab.Modules.LockAfter = 3
    52  	harness.ab.Modules.LockDuration = time.Hour
    53  	harness.ab.Modules.LockWindow = time.Minute
    54  
    55  	harness.ab.Config.Core.BodyReader = harness.bodyReader
    56  	harness.ab.Config.Core.Logger = mocks.Logger{}
    57  	harness.ab.Config.Core.Mailer = harness.mailer
    58  	harness.ab.Config.Core.Redirector = harness.redirector
    59  	harness.ab.Config.Core.MailRenderer = harness.renderer
    60  	harness.ab.Config.Core.Responder = harness.responder
    61  	harness.ab.Config.Storage.SessionState = harness.session
    62  	harness.ab.Config.Storage.Server = harness.storer
    63  
    64  	harness.lock = &Lock{harness.ab}
    65  
    66  	return harness
    67  }
    68  
    69  func TestBeforeAuthAllow(t *testing.T) {
    70  	t.Parallel()
    71  
    72  	harness := testSetup()
    73  
    74  	user := &mocks.User{
    75  		Email:  "test@test.com",
    76  		Locked: time.Time{},
    77  	}
    78  	harness.storer.Users["test@test.com"] = user
    79  
    80  	r := mocks.Request("GET")
    81  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
    82  	w := httptest.NewRecorder()
    83  
    84  	handled, err := harness.lock.BeforeAuth(w, r, false)
    85  	if err != nil {
    86  		t.Error(err)
    87  	}
    88  	if handled {
    89  		t.Error("it shouldn't have been handled")
    90  	}
    91  }
    92  
    93  func TestBeforeAuthDisallow(t *testing.T) {
    94  	t.Parallel()
    95  
    96  	harness := testSetup()
    97  
    98  	user := &mocks.User{
    99  		Email:  "test@test.com",
   100  		Locked: time.Now().UTC().Add(time.Hour),
   101  	}
   102  	harness.storer.Users["test@test.com"] = user
   103  
   104  	r := mocks.Request("GET")
   105  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   106  	w := httptest.NewRecorder()
   107  
   108  	handled, err := harness.lock.BeforeAuth(w, r, false)
   109  	if err != nil {
   110  		t.Error(err)
   111  	}
   112  	if !handled {
   113  		t.Error("it should have been handled")
   114  	}
   115  
   116  	if w.Code != http.StatusTemporaryRedirect {
   117  		t.Error("code was wrong:", w.Code)
   118  	}
   119  
   120  	opts := harness.redirector.Options
   121  	if opts.RedirectPath != harness.ab.Paths.LockNotOK {
   122  		t.Error("redir path was wrong:", opts.RedirectPath)
   123  	}
   124  
   125  	if len(opts.Failure) == 0 {
   126  		t.Error("expected a failure message")
   127  	}
   128  }
   129  
   130  func TestAfterAuthSuccess(t *testing.T) {
   131  	t.Parallel()
   132  
   133  	harness := testSetup()
   134  
   135  	last := time.Now().UTC().Add(-time.Hour)
   136  	user := &mocks.User{
   137  		Email:        "test@test.com",
   138  		AttemptCount: 45,
   139  		LastAttempt:  last,
   140  	}
   141  
   142  	harness.storer.Users["test@test.com"] = user
   143  
   144  	r := mocks.Request("GET")
   145  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   146  	w := httptest.NewRecorder()
   147  
   148  	handled, err := harness.lock.AfterAuthSuccess(w, r, false)
   149  	if err != nil {
   150  		t.Error(err)
   151  	}
   152  	if handled {
   153  		t.Error("it should never be handled")
   154  	}
   155  
   156  	user = harness.storer.Users["test@test.com"]
   157  	if 0 != user.GetAttemptCount() {
   158  		t.Error("attempt count wrong:", user.GetAttemptCount())
   159  	}
   160  	if !last.Before(user.GetLastAttempt()) {
   161  		t.Errorf("last attempt should be more recent, old: %v new: %v", last, user.GetLastAttempt())
   162  	}
   163  }
   164  
   165  func TestAfterAuthFailure(t *testing.T) {
   166  	t.Parallel()
   167  
   168  	harness := testSetup()
   169  
   170  	user := &mocks.User{
   171  		Email: "test@test.com",
   172  	}
   173  	harness.storer.Users["test@test.com"] = user
   174  
   175  	if IsLocked(harness.storer.Users["test@test.com"]) {
   176  		t.Error("should not be locked")
   177  	}
   178  
   179  	r := mocks.Request("GET")
   180  	w := httptest.NewRecorder()
   181  
   182  	var handled bool
   183  	var err error
   184  
   185  	for i := 1; i <= 3; i++ {
   186  		if IsLocked(harness.storer.Users["test@test.com"]) {
   187  			t.Error("should not be locked")
   188  		}
   189  
   190  		r := r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   191  		handled, err = harness.lock.AfterAuthFail(w, r, false)
   192  		if err != nil {
   193  			t.Fatal(err)
   194  		}
   195  
   196  		if i < 3 {
   197  			if handled {
   198  				t.Errorf("%d) should not be handled until lock occurs", i)
   199  			}
   200  
   201  			user := harness.storer.Users["test@test.com"]
   202  			if user.GetAttemptCount() != i {
   203  				t.Errorf("attempt count wrong, want: %d, got: %d", i, user.GetAttemptCount())
   204  			}
   205  			if IsLocked(user) {
   206  				t.Error("should not be locked")
   207  			}
   208  		}
   209  	}
   210  
   211  	if !handled {
   212  		t.Error("should have been handled at the end")
   213  	}
   214  
   215  	if !IsLocked(harness.storer.Users["test@test.com"]) {
   216  		t.Error("should be locked at the end")
   217  	}
   218  
   219  	if w.Code != http.StatusTemporaryRedirect {
   220  		t.Error("code was wrong:", w.Code)
   221  	}
   222  
   223  	opts := harness.redirector.Options
   224  	if opts.RedirectPath != harness.ab.Paths.LockNotOK {
   225  		t.Error("redir path was wrong:", opts.RedirectPath)
   226  	}
   227  
   228  	if len(opts.Failure) == 0 {
   229  		t.Error("expected a failure message")
   230  	}
   231  }
   232  
   233  func TestLock(t *testing.T) {
   234  	t.Parallel()
   235  
   236  	harness := testSetup()
   237  
   238  	user := &mocks.User{
   239  		Email: "test@test.com",
   240  	}
   241  	harness.storer.Users["test@test.com"] = user
   242  
   243  	if IsLocked(harness.storer.Users["test@test.com"]) {
   244  		t.Error("should not be locked")
   245  	}
   246  
   247  	if err := harness.lock.Lock(context.Background(), "test@test.com"); err != nil {
   248  		t.Error(err)
   249  	}
   250  
   251  	if !IsLocked(harness.storer.Users["test@test.com"]) {
   252  		t.Error("should be locked")
   253  	}
   254  }
   255  
   256  func TestUnlock(t *testing.T) {
   257  	t.Parallel()
   258  
   259  	harness := testSetup()
   260  
   261  	user := &mocks.User{
   262  		Email:  "test@test.com",
   263  		Locked: time.Now().UTC().Add(time.Hour),
   264  	}
   265  	harness.storer.Users["test@test.com"] = user
   266  
   267  	if !IsLocked(harness.storer.Users["test@test.com"]) {
   268  		t.Error("should be locked")
   269  	}
   270  
   271  	if err := harness.lock.Unlock(context.Background(), "test@test.com"); err != nil {
   272  		t.Error(err)
   273  	}
   274  
   275  	if IsLocked(harness.storer.Users["test@test.com"]) {
   276  		t.Error("should no longer be locked")
   277  	}
   278  }
   279  
   280  func TestMiddlewareAllow(t *testing.T) {
   281  	t.Parallel()
   282  
   283  	ab := authboss.New()
   284  	called := false
   285  	server := Middleware(ab)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   286  		called = true
   287  	}))
   288  
   289  	user := &mocks.User{
   290  		Locked: time.Now().UTC().Add(-time.Hour),
   291  	}
   292  
   293  	r := mocks.Request("GET")
   294  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   295  	w := httptest.NewRecorder()
   296  
   297  	server.ServeHTTP(w, r)
   298  
   299  	if !called {
   300  		t.Error("The user should have been allowed through")
   301  	}
   302  }
   303  
   304  func TestMiddlewareDisallow(t *testing.T) {
   305  	t.Parallel()
   306  
   307  	ab := authboss.New()
   308  	redirector := &mocks.Redirector{}
   309  	ab.Config.Paths.LockNotOK = "/lock/not/ok"
   310  	ab.Config.Core.Logger = mocks.Logger{}
   311  	ab.Config.Core.Redirector = redirector
   312  
   313  	called := false
   314  	server := Middleware(ab)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   315  		called = true
   316  	}))
   317  
   318  	user := &mocks.User{
   319  		Locked: time.Now().UTC().Add(time.Hour),
   320  	}
   321  
   322  	r := mocks.Request("GET")
   323  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
   324  	w := httptest.NewRecorder()
   325  
   326  	server.ServeHTTP(w, r)
   327  
   328  	if called {
   329  		t.Error("The user should not have been allowed through")
   330  	}
   331  	if redirector.Options.Code != http.StatusTemporaryRedirect {
   332  		t.Error("expected a redirect, but got:", redirector.Options.Code)
   333  	}
   334  	if p := redirector.Options.RedirectPath; p != "/lock/not/ok" {
   335  		t.Error("redirect path wrong:", p)
   336  	}
   337  }