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 }