github.com/volatiletech/authboss@v2.4.1+incompatible/otp/twofactor/twofactor_verify_test.go (about) 1 package twofactor 2 3 import ( 4 "context" 5 "net/http" 6 "net/http/httptest" 7 "regexp" 8 "testing" 9 10 "github.com/volatiletech/authboss" 11 "github.com/volatiletech/authboss/mocks" 12 ) 13 14 func TestSetupEmailVerify(t *testing.T) { 15 t.Parallel() 16 17 router := &mocks.Router{} 18 renderer := &mocks.Renderer{} 19 mailRenderer := &mocks.Renderer{} 20 21 ab := &authboss.Authboss{} 22 ab.Config.Core.Router = router 23 ab.Config.Core.ViewRenderer = renderer 24 ab.Config.Core.MailRenderer = mailRenderer 25 ab.Config.Core.ErrorHandler = &mocks.ErrorHandler{} 26 27 ab.Config.Modules.MailRouteMethod = http.MethodGet 28 29 if _, err := SetupEmailVerify(ab, "totp", "/2fa/totp/setup"); err != nil { 30 t.Error(err) 31 } 32 33 if err := router.HasGets("/2fa/totp/email/verify", "/2fa/totp/email/verify/end"); err != nil { 34 t.Error(err) 35 } 36 if err := router.HasPosts("/2fa/totp/email/verify"); err != nil { 37 t.Error(err) 38 } 39 40 if err := renderer.HasLoadedViews(PageVerify2FA); err != nil { 41 t.Error(err) 42 } 43 44 if err := mailRenderer.HasLoadedViews(EmailVerifyHTML, EmailVerifyTxt); err != nil { 45 t.Error(err) 46 } 47 } 48 49 type testEmailVerifyHarness struct { 50 emailverify EmailVerify 51 ab *authboss.Authboss 52 53 bodyReader *mocks.BodyReader 54 mailer *mocks.Emailer 55 responder *mocks.Responder 56 renderer *mocks.Renderer 57 redirector *mocks.Redirector 58 session *mocks.ClientStateRW 59 storer *mocks.ServerStorer 60 } 61 62 func testEmailVerifySetup() *testEmailVerifyHarness { 63 harness := &testEmailVerifyHarness{} 64 65 harness.ab = authboss.New() 66 harness.bodyReader = &mocks.BodyReader{} 67 harness.mailer = &mocks.Emailer{} 68 harness.redirector = &mocks.Redirector{} 69 harness.renderer = &mocks.Renderer{} 70 harness.responder = &mocks.Responder{} 71 harness.session = mocks.NewClientRW() 72 harness.storer = mocks.NewServerStorer() 73 74 harness.ab.Config.Core.BodyReader = harness.bodyReader 75 harness.ab.Config.Core.Logger = mocks.Logger{} 76 harness.ab.Config.Core.Responder = harness.responder 77 harness.ab.Config.Core.Redirector = harness.redirector 78 harness.ab.Config.Core.Mailer = harness.mailer 79 harness.ab.Config.Core.MailRenderer = harness.renderer 80 harness.ab.Config.Storage.SessionState = harness.session 81 harness.ab.Config.Storage.Server = harness.storer 82 83 harness.ab.Config.Modules.TwoFactorEmailAuthRequired = true 84 harness.ab.Config.Modules.MailNoGoroutine = true 85 86 harness.emailverify = EmailVerify{ 87 Authboss: harness.ab, 88 TwofactorKind: "totp", 89 TwofactorSetupURL: "/2fa/totp/setup", 90 } 91 92 return harness 93 } 94 95 func (h *testEmailVerifyHarness) loadClientState(w http.ResponseWriter, r **http.Request) { 96 req, err := h.ab.LoadClientState(w, *r) 97 if err != nil { 98 panic(err) 99 } 100 101 *r = req 102 } 103 104 func (h *testEmailVerifyHarness) putUserInCtx(u *mocks.User, r **http.Request) { 105 req := (*r).WithContext(context.WithValue((*r).Context(), authboss.CTXKeyUser, u)) 106 *r = req 107 } 108 109 func TestEmailVerifyGetStart(t *testing.T) { 110 t.Parallel() 111 112 h := testEmailVerifySetup() 113 114 rec := httptest.NewRecorder() 115 r := mocks.Request("GET") 116 w := h.ab.NewResponse(rec) 117 118 u := &mocks.User{Email: "test@test.com"} 119 h.putUserInCtx(u, &r) 120 h.loadClientState(w, &r) 121 122 if err := h.emailverify.GetStart(w, r); err != nil { 123 t.Fatal(err) 124 } 125 126 if got := h.responder.Data["email"]; got != "test@test.com" { 127 t.Error("email was wrong:", got) 128 } 129 130 if got := h.responder.Page; got != PageVerify2FA { 131 t.Error("page was wrong:", got) 132 } 133 } 134 135 func TestEmailVerifyPostStart(t *testing.T) { 136 t.Parallel() 137 h := testEmailVerifySetup() 138 139 rec := httptest.NewRecorder() 140 r := mocks.Request("POST") 141 w := h.ab.NewResponse(rec) 142 143 u := &mocks.User{Email: "test@test.com"} 144 h.putUserInCtx(u, &r) 145 h.loadClientState(w, &r) 146 147 if err := h.emailverify.PostStart(w, r); err != nil { 148 t.Fatal(err) 149 } 150 151 ro := h.redirector.Options 152 if ro.Code != http.StatusTemporaryRedirect { 153 t.Error("code wrong:", ro.Code) 154 } 155 156 if ro.Success != "An e-mail has been sent to confirm 2FA activation." { 157 t.Error("message was wrong:", ro.Success) 158 } 159 160 mail := h.mailer.Email 161 if mail.To[0] != "test@test.com" { 162 t.Error("email was sent to wrong person:", mail.To) 163 } 164 165 if mail.Subject != "Add 2FA to Account" { 166 t.Error("subject wrong:", mail.Subject) 167 } 168 169 urlRgx := regexp.MustCompile(`^http://localhost:8080/auth/2fa/totp/email/verify/end\?token=[\-_a-zA-Z0-9=%]+$`) 170 171 data := h.renderer.Data 172 if !urlRgx.MatchString(data[DataVerifyURL].(string)) { 173 t.Error("url is wrong:", data[DataVerifyURL]) 174 } 175 } 176 177 func TestEmailVerifyEnd(t *testing.T) { 178 t.Parallel() 179 180 h := testEmailVerifySetup() 181 182 rec := httptest.NewRecorder() 183 r := mocks.Request("POST") 184 w := h.ab.NewResponse(rec) 185 186 h.bodyReader.Return = mocks.Values{Token: "abc"} 187 188 h.session.ClientValues[authboss.Session2FAAuthToken] = "abc" 189 h.loadClientState(w, &r) 190 191 if err := h.emailverify.End(w, r); err != nil { 192 t.Error(err) 193 } 194 195 ro := h.redirector.Options 196 if ro.Code != http.StatusTemporaryRedirect { 197 t.Error("code wrong:", ro.Code) 198 } 199 200 if ro.RedirectPath != "/2fa/totp/setup" { 201 t.Error("redir path wrong:", ro.RedirectPath) 202 } 203 204 // Flush session state 205 w.WriteHeader(http.StatusOK) 206 207 if h.session.ClientValues[authboss.Session2FAAuthed] != "true" { 208 t.Error("authed value not set") 209 } 210 211 if h.session.ClientValues[authboss.Session2FAAuthToken] != "" { 212 t.Error("auth token not removed") 213 } 214 } 215 216 func TestEmailVerifyEndFail(t *testing.T) { 217 t.Parallel() 218 219 h := testEmailVerifySetup() 220 221 rec := httptest.NewRecorder() 222 r := mocks.Request("POST") 223 w := h.ab.NewResponse(rec) 224 225 h.bodyReader.Return = mocks.Values{Token: "abc"} 226 227 h.session.ClientValues[authboss.Session2FAAuthToken] = "notabc" 228 h.loadClientState(w, &r) 229 230 if err := h.emailverify.End(w, r); err != nil { 231 t.Error(err) 232 } 233 234 ro := h.redirector.Options 235 if ro.Code != http.StatusTemporaryRedirect { 236 t.Error("code wrong:", ro.Code) 237 } 238 239 if ro.RedirectPath != "/" { 240 t.Error("redir path wrong:", ro.RedirectPath) 241 } 242 243 if ro.Failure != "invalid 2fa e-mail verification token" { 244 t.Error("did not get correct failure") 245 } 246 247 if h.session.ClientValues[authboss.Session2FAAuthed] != "" { 248 t.Error("should not be authed") 249 } 250 } 251 252 func TestEmailVerifyWrap(t *testing.T) { 253 t.Parallel() 254 255 t.Run("NotRequired", func(t *testing.T) { 256 h := testEmailVerifySetup() 257 258 rec := httptest.NewRecorder() 259 r := mocks.Request("POST") 260 w := h.ab.NewResponse(rec) 261 262 h.ab.Config.Modules.TwoFactorEmailAuthRequired = false 263 264 called := false 265 server := h.emailverify.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 266 called = true 267 })) 268 269 server.ServeHTTP(w, r) 270 if !called { 271 t.Error("should have called the handler") 272 } 273 }) 274 t.Run("Success", func(t *testing.T) { 275 h := testEmailVerifySetup() 276 277 rec := httptest.NewRecorder() 278 r := mocks.Request("POST") 279 w := h.ab.NewResponse(rec) 280 281 h.session.ClientValues[authboss.Session2FAAuthed] = "true" 282 h.loadClientState(w, &r) 283 284 called := false 285 server := h.emailverify.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 286 called = true 287 })) 288 289 server.ServeHTTP(w, r) 290 if !called { 291 t.Error("should have called the handler") 292 } 293 }) 294 t.Run("Fail", func(t *testing.T) { 295 h := testEmailVerifySetup() 296 297 rec := httptest.NewRecorder() 298 r := mocks.Request("POST") 299 w := h.ab.NewResponse(rec) 300 301 called := false 302 server := h.emailverify.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 303 called = true 304 })) 305 306 server.ServeHTTP(w, r) 307 if called { 308 t.Error("should not have called the handler") 309 } 310 311 ro := h.redirector.Options 312 if ro.Code != http.StatusTemporaryRedirect { 313 t.Error("code wrong:", ro.Code) 314 } 315 316 if ro.RedirectPath != "/auth/2fa/totp/email/verify" { 317 t.Error("redir path wrong:", ro.RedirectPath) 318 } 319 320 if ro.Failure != "You must first authorize adding 2fa by e-mail." { 321 t.Error("did not get correct failure") 322 } 323 }) 324 }