github.com/volatiletech/authboss@v2.4.1+incompatible/confirm/confirm_test.go (about) 1 package confirm 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/sha512" 7 "encoding/base64" 8 "errors" 9 "net/http" 10 "net/http/httptest" 11 "testing" 12 13 "github.com/volatiletech/authboss" 14 "github.com/volatiletech/authboss/mocks" 15 ) 16 17 func TestInit(t *testing.T) { 18 t.Parallel() 19 20 ab := authboss.New() 21 22 router := &mocks.Router{} 23 renderer := &mocks.Renderer{} 24 errHandler := &mocks.ErrorHandler{} 25 ab.Config.Core.Router = router 26 ab.Config.Core.MailRenderer = renderer 27 ab.Config.Core.ErrorHandler = errHandler 28 29 c := &Confirm{} 30 if err := c.Init(ab); err != nil { 31 t.Fatal(err) 32 } 33 34 if err := renderer.HasLoadedViews(EmailConfirmHTML, EmailConfirmTxt); err != nil { 35 t.Error(err) 36 } 37 38 if err := router.HasGets("/confirm"); err != nil { 39 t.Error(err) 40 } 41 } 42 43 type testHarness struct { 44 confirm *Confirm 45 ab *authboss.Authboss 46 47 bodyReader *mocks.BodyReader 48 mailer *mocks.Emailer 49 redirector *mocks.Redirector 50 renderer *mocks.Renderer 51 responder *mocks.Responder 52 session *mocks.ClientStateRW 53 storer *mocks.ServerStorer 54 } 55 56 func testSetup() *testHarness { 57 harness := &testHarness{} 58 59 harness.ab = authboss.New() 60 harness.bodyReader = &mocks.BodyReader{} 61 harness.mailer = &mocks.Emailer{} 62 harness.redirector = &mocks.Redirector{} 63 harness.renderer = &mocks.Renderer{} 64 harness.responder = &mocks.Responder{} 65 harness.session = mocks.NewClientRW() 66 harness.storer = mocks.NewServerStorer() 67 68 harness.ab.Paths.ConfirmOK = "/confirm/ok" 69 harness.ab.Paths.ConfirmNotOK = "/confirm/not/ok" 70 harness.ab.Modules.MailNoGoroutine = true 71 72 harness.ab.Config.Core.BodyReader = harness.bodyReader 73 harness.ab.Config.Core.Logger = mocks.Logger{} 74 harness.ab.Config.Core.Mailer = harness.mailer 75 harness.ab.Config.Core.Redirector = harness.redirector 76 harness.ab.Config.Core.MailRenderer = harness.renderer 77 harness.ab.Config.Core.Responder = harness.responder 78 harness.ab.Config.Storage.SessionState = harness.session 79 harness.ab.Config.Storage.Server = harness.storer 80 81 harness.confirm = &Confirm{harness.ab} 82 83 return harness 84 } 85 86 func TestPreventAuthAllow(t *testing.T) { 87 t.Parallel() 88 89 harness := testSetup() 90 91 user := &mocks.User{ 92 Confirmed: true, 93 } 94 95 r := mocks.Request("GET") 96 r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user)) 97 w := httptest.NewRecorder() 98 99 handled, err := harness.confirm.PreventAuth(w, r, false) 100 if err != nil { 101 t.Error(err) 102 } 103 104 if handled { 105 t.Error("it should not have been handled") 106 } 107 } 108 109 func TestPreventDisallow(t *testing.T) { 110 t.Parallel() 111 112 harness := testSetup() 113 114 user := &mocks.User{ 115 Confirmed: false, 116 } 117 118 r := mocks.Request("GET") 119 r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user)) 120 w := httptest.NewRecorder() 121 122 handled, err := harness.confirm.PreventAuth(w, r, false) 123 if err != nil { 124 t.Error(err) 125 } 126 127 if !handled { 128 t.Error("it should have been handled") 129 } 130 131 if w.Code != http.StatusTemporaryRedirect { 132 t.Error("redirect did not occur") 133 } 134 135 if p := harness.redirector.Options.RedirectPath; p != "/confirm/not/ok" { 136 t.Error("redirect path was wrong:", p) 137 } 138 } 139 140 func TestStartConfirmationWeb(t *testing.T) { 141 t.Parallel() 142 143 harness := testSetup() 144 145 user := &mocks.User{Email: "test@test.com"} 146 harness.storer.Users["test@test.com"] = user 147 148 r := mocks.Request("GET") 149 r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user)) 150 w := httptest.NewRecorder() 151 152 handled, err := harness.confirm.StartConfirmationWeb(w, r, false) 153 if err != nil { 154 t.Error(err) 155 } 156 157 if !handled { 158 t.Error("it should always be handled") 159 } 160 161 if w.Code != http.StatusTemporaryRedirect { 162 t.Error("redirect did not occur") 163 } 164 165 if p := harness.redirector.Options.RedirectPath; p != "/confirm/not/ok" { 166 t.Error("redirect path was wrong:", p) 167 } 168 169 if to := harness.mailer.Email.To[0]; to != "test@test.com" { 170 t.Error("mailer sent e-mail to wrong person:", to) 171 } 172 } 173 174 func TestGetSuccess(t *testing.T) { 175 t.Parallel() 176 177 harness := testSetup() 178 179 selector, verifier, token, err := GenerateConfirmCreds() 180 if err != nil { 181 t.Fatal(err) 182 } 183 184 user := &mocks.User{Email: "test@test.com", Confirmed: false, ConfirmSelector: selector, ConfirmVerifier: verifier} 185 harness.storer.Users["test@test.com"] = user 186 harness.bodyReader.Return = mocks.Values{ 187 Token: token, 188 } 189 190 r := mocks.Request("GET") 191 w := httptest.NewRecorder() 192 193 if err := harness.confirm.Get(w, r); err != nil { 194 t.Error(err) 195 } 196 197 if w.Code != http.StatusTemporaryRedirect { 198 t.Error("expected a redirect, got:", w.Code) 199 } 200 if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmOK { 201 t.Error("redir path was wrong:", p) 202 } 203 204 if len(user.ConfirmSelector) != 0 { 205 t.Error("the confirm selector should have been erased") 206 } 207 if len(user.ConfirmVerifier) != 0 { 208 t.Error("the confirm verifier should have been erased") 209 } 210 if !user.Confirmed { 211 t.Error("the user should have been confirmed") 212 } 213 } 214 215 func TestGetValidationFailure(t *testing.T) { 216 t.Parallel() 217 218 harness := testSetup() 219 220 harness.bodyReader.Return = mocks.Values{ 221 Errors: []error{errors.New("fail")}, 222 } 223 224 r := mocks.Request("GET") 225 w := httptest.NewRecorder() 226 227 if err := harness.confirm.Get(w, r); err != nil { 228 t.Error(err) 229 } 230 231 if w.Code != http.StatusTemporaryRedirect { 232 t.Error("expected a redirect, got:", w.Code) 233 } 234 if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK { 235 t.Error("redir path was wrong:", p) 236 } 237 if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" { 238 t.Error("reason for failure was wrong:", reason) 239 } 240 } 241 242 func TestGetBase64DecodeFailure(t *testing.T) { 243 t.Parallel() 244 245 harness := testSetup() 246 247 harness.bodyReader.Return = mocks.Values{ 248 Token: "5", 249 } 250 251 r := mocks.Request("GET") 252 w := httptest.NewRecorder() 253 254 if err := harness.confirm.Get(w, r); err != nil { 255 t.Error(err) 256 } 257 258 if w.Code != http.StatusTemporaryRedirect { 259 t.Error("expected a redirect, got:", w.Code) 260 } 261 if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK { 262 t.Error("redir path was wrong:", p) 263 } 264 if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" { 265 t.Error("reason for failure was wrong:", reason) 266 } 267 } 268 269 func TestGetUserNotFoundFailure(t *testing.T) { 270 t.Parallel() 271 272 harness := testSetup() 273 274 _, _, token, err := GenerateConfirmCreds() 275 if err != nil { 276 t.Fatal(err) 277 } 278 279 harness.bodyReader.Return = mocks.Values{ 280 Token: token, 281 } 282 283 r := mocks.Request("GET") 284 w := httptest.NewRecorder() 285 286 if err := harness.confirm.Get(w, r); err != nil { 287 t.Error(err) 288 } 289 290 if w.Code != http.StatusTemporaryRedirect { 291 t.Error("expected a redirect, got:", w.Code) 292 } 293 if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK { 294 t.Error("redir path was wrong:", p) 295 } 296 if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" { 297 t.Error("reason for failure was wrong:", reason) 298 } 299 } 300 301 func TestMiddlewareAllow(t *testing.T) { 302 t.Parallel() 303 304 ab := authboss.New() 305 called := false 306 server := Middleware(ab)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 307 called = true 308 })) 309 310 user := &mocks.User{ 311 Confirmed: true, 312 } 313 314 r := mocks.Request("GET") 315 r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user)) 316 w := httptest.NewRecorder() 317 318 server.ServeHTTP(w, r) 319 320 if !called { 321 t.Error("The user should have been allowed through") 322 } 323 } 324 325 func TestMiddlewareDisallow(t *testing.T) { 326 t.Parallel() 327 328 ab := authboss.New() 329 redirector := &mocks.Redirector{} 330 ab.Config.Paths.ConfirmNotOK = "/confirm/not/ok" 331 ab.Config.Core.Logger = mocks.Logger{} 332 ab.Config.Core.Redirector = redirector 333 334 called := false 335 server := Middleware(ab)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 336 called = true 337 })) 338 339 user := &mocks.User{ 340 Confirmed: false, 341 } 342 343 r := mocks.Request("GET") 344 r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user)) 345 w := httptest.NewRecorder() 346 347 server.ServeHTTP(w, r) 348 349 if called { 350 t.Error("The user should not have been allowed through") 351 } 352 if redirector.Options.Code != http.StatusTemporaryRedirect { 353 t.Error("expected a redirect, but got:", redirector.Options.Code) 354 } 355 if p := redirector.Options.RedirectPath; p != "/confirm/not/ok" { 356 t.Error("redirect path wrong:", p) 357 } 358 } 359 360 func TestMailURL(t *testing.T) { 361 t.Parallel() 362 363 h := testSetup() 364 h.ab.Config.Paths.RootURL = "https://api.test.com:6343" 365 h.ab.Config.Paths.Mount = "/v1/auth" 366 367 want := "https://api.test.com:6343/v1/auth/confirm?cnf=abc" 368 if got := h.confirm.mailURL("abc"); got != want { 369 t.Error("want:", want, "got:", got) 370 } 371 372 h.ab.Config.Mail.RootURL = "https://test.com:3333/testauth" 373 374 want = "https://test.com:3333/testauth/confirm?cnf=abc" 375 if got := h.confirm.mailURL("abc"); got != want { 376 t.Error("want:", want, "got:", got) 377 } 378 } 379 380 func TestGenerateRecoverCreds(t *testing.T) { 381 t.Parallel() 382 383 selector, verifier, token, err := GenerateConfirmCreds() 384 if err != nil { 385 t.Error(err) 386 } 387 388 if verifier == selector { 389 t.Error("the verifier and selector should be different") 390 } 391 392 // base64 length: n = 64; 4*(64/3) = 85.3; round to nearest 4: 88 393 if len(verifier) != 88 { 394 t.Errorf("verifier length was wrong (%d): %s", len(verifier), verifier) 395 } 396 397 // base64 length: n = 64; 4*(64/3) = 85.3; round to nearest 4: 88 398 if len(selector) != 88 { 399 t.Errorf("selector length was wrong (%d): %s", len(selector), selector) 400 } 401 402 // base64 length: n = 64; 4*(64/3) = 85.33; round to nearest 4: 88 403 if len(token) != 88 { 404 t.Errorf("token length was wrong (%d): %s", len(token), token) 405 } 406 407 rawToken, err := base64.URLEncoding.DecodeString(token) 408 if err != nil { 409 t.Error(err) 410 } 411 412 rawSelector, err := base64.StdEncoding.DecodeString(selector) 413 if err != nil { 414 t.Error(err) 415 } 416 rawVerifier, err := base64.StdEncoding.DecodeString(verifier) 417 if err != nil { 418 t.Error(err) 419 } 420 421 checkSelector := sha512.Sum512(rawToken[:confirmTokenSplit]) 422 if 0 != bytes.Compare(checkSelector[:], rawSelector) { 423 t.Error("expected selector to match") 424 } 425 checkVerifier := sha512.Sum512(rawToken[confirmTokenSplit:]) 426 if 0 != bytes.Compare(checkVerifier[:], rawVerifier) { 427 t.Error("expected verifier to match") 428 } 429 }