github.com/volatiletech/authboss@v2.4.1+incompatible/auth/auth_test.go (about)

     1  package auth
     2  
     3  import (
     4  	"net/http"
     5  	"net/http/httptest"
     6  	"testing"
     7  
     8  	"github.com/volatiletech/authboss"
     9  	"github.com/volatiletech/authboss/mocks"
    10  )
    11  
    12  func TestAuthInit(t *testing.T) {
    13  	t.Parallel()
    14  
    15  	ab := authboss.New()
    16  
    17  	router := &mocks.Router{}
    18  	renderer := &mocks.Renderer{}
    19  	errHandler := &mocks.ErrorHandler{}
    20  	ab.Config.Core.Router = router
    21  	ab.Config.Core.ViewRenderer = renderer
    22  	ab.Config.Core.ErrorHandler = errHandler
    23  
    24  	a := &Auth{}
    25  	if err := a.Init(ab); err != nil {
    26  		t.Fatal(err)
    27  	}
    28  
    29  	if err := renderer.HasLoadedViews(PageLogin); err != nil {
    30  		t.Error(err)
    31  	}
    32  
    33  	if err := router.HasGets("/login"); err != nil {
    34  		t.Error(err)
    35  	}
    36  	if err := router.HasPosts("/login"); err != nil {
    37  		t.Error(err)
    38  	}
    39  }
    40  
    41  func TestAuthGet(t *testing.T) {
    42  	t.Parallel()
    43  
    44  	ab := authboss.New()
    45  	responder := &mocks.Responder{}
    46  	ab.Config.Core.Responder = responder
    47  
    48  	a := &Auth{ab}
    49  
    50  	r := mocks.Request("GET")
    51  	r.URL.RawQuery = "redir=/redirectpage"
    52  	if err := a.LoginGet(nil, r); err != nil {
    53  		t.Error(err)
    54  	}
    55  
    56  	if responder.Page != PageLogin {
    57  		t.Error("wanted login page, got:", responder.Page)
    58  	}
    59  
    60  	if responder.Status != http.StatusOK {
    61  		t.Error("wanted ok status, got:", responder.Status)
    62  	}
    63  
    64  	if got := responder.Data[authboss.FormValueRedirect]; got != "/redirectpage" {
    65  		t.Error("redirect page was wrong:", got)
    66  	}
    67  }
    68  
    69  type testHarness struct {
    70  	auth *Auth
    71  	ab   *authboss.Authboss
    72  
    73  	bodyReader *mocks.BodyReader
    74  	responder  *mocks.Responder
    75  	redirector *mocks.Redirector
    76  	session    *mocks.ClientStateRW
    77  	storer     *mocks.ServerStorer
    78  }
    79  
    80  func testSetup() *testHarness {
    81  	harness := &testHarness{}
    82  
    83  	harness.ab = authboss.New()
    84  	harness.bodyReader = &mocks.BodyReader{}
    85  	harness.redirector = &mocks.Redirector{}
    86  	harness.responder = &mocks.Responder{}
    87  	harness.session = mocks.NewClientRW()
    88  	harness.storer = mocks.NewServerStorer()
    89  
    90  	harness.ab.Paths.AuthLoginOK = "/login/ok"
    91  
    92  	harness.ab.Config.Core.BodyReader = harness.bodyReader
    93  	harness.ab.Config.Core.Logger = mocks.Logger{}
    94  	harness.ab.Config.Core.Responder = harness.responder
    95  	harness.ab.Config.Core.Redirector = harness.redirector
    96  	harness.ab.Config.Storage.SessionState = harness.session
    97  	harness.ab.Config.Storage.Server = harness.storer
    98  
    99  	harness.auth = &Auth{harness.ab}
   100  
   101  	return harness
   102  }
   103  
   104  func TestAuthPostSuccess(t *testing.T) {
   105  	t.Parallel()
   106  
   107  	setupMore := func(h *testHarness) *testHarness {
   108  		h.bodyReader.Return = mocks.Values{
   109  			PID:      "test@test.com",
   110  			Password: "hello world",
   111  		}
   112  		h.storer.Users["test@test.com"] = &mocks.User{
   113  			Email:    "test@test.com",
   114  			Password: "$2a$10$IlfnqVyDZ6c1L.kaA/q3bu1nkAC6KukNUsizvlzay1pZPXnX2C9Ji", // hello world
   115  		}
   116  		h.session.ClientValues[authboss.SessionHalfAuthKey] = "true"
   117  
   118  		return h
   119  	}
   120  
   121  	t.Run("normal", func(t *testing.T) {
   122  		t.Parallel()
   123  		h := setupMore(testSetup())
   124  
   125  		var beforeCalled, afterCalled bool
   126  		var beforeHasValues, afterHasValues bool
   127  		h.ab.Events.Before(authboss.EventAuth, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
   128  			beforeCalled = true
   129  			beforeHasValues = r.Context().Value(authboss.CTXKeyValues) != nil
   130  			return false, nil
   131  		})
   132  		h.ab.Events.After(authboss.EventAuth, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
   133  			afterCalled = true
   134  			afterHasValues = r.Context().Value(authboss.CTXKeyValues) != nil
   135  			return false, nil
   136  		})
   137  
   138  		r := mocks.Request("POST")
   139  		resp := httptest.NewRecorder()
   140  		w := h.ab.NewResponse(resp)
   141  
   142  		if err := h.auth.LoginPost(w, r); err != nil {
   143  			t.Error(err)
   144  		}
   145  
   146  		if resp.Code != http.StatusTemporaryRedirect {
   147  			t.Error("code was wrong:", resp.Code)
   148  		}
   149  		if h.redirector.Options.RedirectPath != "/login/ok" {
   150  			t.Error("redirect path was wrong:", h.redirector.Options.RedirectPath)
   151  		}
   152  
   153  		if _, ok := h.session.ClientValues[authboss.SessionHalfAuthKey]; ok {
   154  			t.Error("half auth should have been deleted")
   155  		}
   156  		if pid := h.session.ClientValues[authboss.SessionKey]; pid != "test@test.com" {
   157  			t.Error("pid was wrong:", pid)
   158  		}
   159  
   160  		if !beforeCalled {
   161  			t.Error("before should have been called")
   162  		}
   163  		if !afterCalled {
   164  			t.Error("after should have been called")
   165  		}
   166  		if !beforeHasValues {
   167  			t.Error("before callback should have access to values")
   168  		}
   169  		if !afterHasValues {
   170  			t.Error("after callback should have access to values")
   171  		}
   172  	})
   173  
   174  	t.Run("handledBefore", func(t *testing.T) {
   175  		t.Parallel()
   176  		h := setupMore(testSetup())
   177  
   178  		var beforeCalled bool
   179  		h.ab.Events.Before(authboss.EventAuth, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
   180  			w.WriteHeader(http.StatusTeapot)
   181  			beforeCalled = true
   182  			return true, nil
   183  		})
   184  
   185  		r := mocks.Request("POST")
   186  		resp := httptest.NewRecorder()
   187  		w := h.ab.NewResponse(resp)
   188  
   189  		if err := h.auth.LoginPost(w, r); err != nil {
   190  			t.Error(err)
   191  		}
   192  
   193  		if h.responder.Status != 0 {
   194  			t.Error("a status should never have been sent back")
   195  		}
   196  		if _, ok := h.session.ClientValues[authboss.SessionKey]; ok {
   197  			t.Error("session key should not have been set")
   198  		}
   199  
   200  		if !beforeCalled {
   201  			t.Error("before should have been called")
   202  		}
   203  		if resp.Code != http.StatusTeapot {
   204  			t.Error("should have left the response alone once teapot was sent")
   205  		}
   206  	})
   207  
   208  	t.Run("handledAfter", func(t *testing.T) {
   209  		t.Parallel()
   210  		h := setupMore(testSetup())
   211  
   212  		var afterCalled bool
   213  		h.ab.Events.After(authboss.EventAuth, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
   214  			w.WriteHeader(http.StatusTeapot)
   215  			afterCalled = true
   216  			return true, nil
   217  		})
   218  
   219  		r := mocks.Request("POST")
   220  		resp := httptest.NewRecorder()
   221  		w := h.ab.NewResponse(resp)
   222  
   223  		if err := h.auth.LoginPost(w, r); err != nil {
   224  			t.Error(err)
   225  		}
   226  
   227  		if h.responder.Status != 0 {
   228  			t.Error("a status should never have been sent back")
   229  		}
   230  		if _, ok := h.session.ClientValues[authboss.SessionKey]; !ok {
   231  			t.Error("session key should have been set")
   232  		}
   233  
   234  		if !afterCalled {
   235  			t.Error("after should have been called")
   236  		}
   237  		if resp.Code != http.StatusTeapot {
   238  			t.Error("should have left the response alone once teapot was sent")
   239  		}
   240  	})
   241  }
   242  
   243  func TestAuthPostBadPassword(t *testing.T) {
   244  	t.Parallel()
   245  
   246  	setupMore := func(h *testHarness) *testHarness {
   247  		h.bodyReader.Return = mocks.Values{
   248  			PID:      "test@test.com",
   249  			Password: "world hello",
   250  		}
   251  		h.storer.Users["test@test.com"] = &mocks.User{
   252  			Email:    "test@test.com",
   253  			Password: "$2a$10$IlfnqVyDZ6c1L.kaA/q3bu1nkAC6KukNUsizvlzay1pZPXnX2C9Ji", // hello world
   254  		}
   255  
   256  		return h
   257  	}
   258  
   259  	t.Run("normal", func(t *testing.T) {
   260  		t.Parallel()
   261  		h := setupMore(testSetup())
   262  
   263  		r := mocks.Request("POST")
   264  		resp := httptest.NewRecorder()
   265  		w := h.ab.NewResponse(resp)
   266  
   267  		var afterCalled bool
   268  		h.ab.Events.After(authboss.EventAuthFail, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
   269  			afterCalled = true
   270  			return false, nil
   271  		})
   272  
   273  		if err := h.auth.LoginPost(w, r); err != nil {
   274  			t.Error(err)
   275  		}
   276  
   277  		if resp.Code != 200 {
   278  			t.Error("wanted a 200:", resp.Code)
   279  		}
   280  
   281  		if h.responder.Data[authboss.DataErr] != "Invalid Credentials" {
   282  			t.Error("wrong error:", h.responder.Data)
   283  		}
   284  
   285  		if _, ok := h.session.ClientValues[authboss.SessionKey]; ok {
   286  			t.Error("user should not be logged in")
   287  		}
   288  
   289  		if !afterCalled {
   290  			t.Error("after should have been called")
   291  		}
   292  	})
   293  
   294  	t.Run("handledAfter", func(t *testing.T) {
   295  		t.Parallel()
   296  		h := setupMore(testSetup())
   297  
   298  		r := mocks.Request("POST")
   299  		resp := httptest.NewRecorder()
   300  		w := h.ab.NewResponse(resp)
   301  
   302  		var afterCalled bool
   303  		h.ab.Events.After(authboss.EventAuthFail, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
   304  			w.WriteHeader(http.StatusTeapot)
   305  			afterCalled = true
   306  			return true, nil
   307  		})
   308  
   309  		if err := h.auth.LoginPost(w, r); err != nil {
   310  			t.Error(err)
   311  		}
   312  
   313  		if h.responder.Status != 0 {
   314  			t.Error("responder should not have been called to give a status")
   315  		}
   316  		if _, ok := h.session.ClientValues[authboss.SessionKey]; ok {
   317  			t.Error("user should not be logged in")
   318  		}
   319  
   320  		if !afterCalled {
   321  			t.Error("after should have been called")
   322  		}
   323  		if resp.Code != http.StatusTeapot {
   324  			t.Error("should have left the response alone once teapot was sent")
   325  		}
   326  	})
   327  }
   328  
   329  func TestAuthPostUserNotFound(t *testing.T) {
   330  	t.Parallel()
   331  
   332  	harness := testSetup()
   333  	harness.bodyReader.Return = mocks.Values{
   334  		PID:      "test@test.com",
   335  		Password: "world hello",
   336  	}
   337  
   338  	r := mocks.Request("POST")
   339  	resp := httptest.NewRecorder()
   340  	w := harness.ab.NewResponse(resp)
   341  
   342  	// This event is really the only thing that separates "user not found"
   343  	// from "bad password"
   344  	var afterCalled bool
   345  	harness.ab.Events.After(authboss.EventAuthFail, func(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
   346  		afterCalled = true
   347  		return false, nil
   348  	})
   349  
   350  	if err := harness.auth.LoginPost(w, r); err != nil {
   351  		t.Error(err)
   352  	}
   353  
   354  	if resp.Code != 200 {
   355  		t.Error("wanted a 200:", resp.Code)
   356  	}
   357  
   358  	if harness.responder.Data[authboss.DataErr] != "Invalid Credentials" {
   359  		t.Error("wrong error:", harness.responder.Data)
   360  	}
   361  
   362  	if _, ok := harness.session.ClientValues[authboss.SessionKey]; ok {
   363  		t.Error("user should not be logged in")
   364  	}
   365  
   366  	if afterCalled {
   367  		t.Error("after should not have been called")
   368  	}
   369  }