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 }