package recover import ( "bytes" "fmt" "html/template" "log" "net/http" "net/http/httptest" "regexp" "strings" "testing" "time" "gopkg.in/authboss.v0" "gopkg.in/authboss.v0/internal/mocks" ) const ( testURLBase64Token = "MTIzNA==" testStdBase64Token = "gdyb21LQTcIANtvYMT7QVQ==" ) func testSetup() (r *Recover, s *mocks.MockStorer, l *bytes.Buffer) { s = mocks.NewMockStorer() l = &bytes.Buffer{} ab := authboss.New() ab.Layout = template.Must(template.New("").Parse(`{{template "authboss" .}}`)) ab.LayoutHTMLEmail = template.Must(template.New("").Parse(`{{template "authboss" .}}`)) ab.LayoutTextEmail = template.Must(template.New("").Parse(`{{template "authboss" .}}`)) ab.Storer = s ab.XSRFName = "xsrf" ab.XSRFMaker = func(_ http.ResponseWriter, _ *http.Request) string { return "xsrfvalue" } ab.PrimaryID = authboss.StoreUsername ab.LogWriter = l r = &Recover{} if err := r.Initialize(ab); err != nil { panic(err) } return r, s, l } func testRequest(ab *authboss.Authboss, method string, postFormValues ...string) (*authboss.Context, *httptest.ResponseRecorder, *http.Request, authboss.ClientStorerErr) { r, err := http.NewRequest(method, "", nil) if err != nil { panic(err) } sessionStorer := mocks.NewMockClientStorer() ctx := mocks.MockRequestContext(ab, postFormValues...) ctx.SessionStorer = sessionStorer return ctx, httptest.NewRecorder(), r, sessionStorer } func TestRecover(t *testing.T) { t.Parallel() r, _, _ := testSetup() storage := r.Storage() if storage[r.PrimaryID] != authboss.String { t.Error("Expected storage KV:", r.PrimaryID, authboss.String) } if storage[authboss.StoreEmail] != authboss.String { t.Error("Expected storage KV:", authboss.StoreEmail, authboss.String) } if storage[authboss.StorePassword] != authboss.String { t.Error("Expected storage KV:", authboss.StorePassword, authboss.String) } if storage[StoreRecoverToken] != authboss.String { t.Error("Expected storage KV:", StoreRecoverToken, authboss.String) } if storage[StoreRecoverTokenExpiry] != authboss.String { t.Error("Expected storage KV:", StoreRecoverTokenExpiry, authboss.String) } routes := r.Routes() if routes["/recover"] == nil { t.Error("Expected route '/recover' with handleFunc") } if routes["/recover/complete"] == nil { t.Error("Expected route '/recover/complete' with handleFunc") } } func TestRecover_startHandlerFunc_GET(t *testing.T) { t.Parallel() rec, _, _ := testSetup() ctx, w, r, _ := testRequest(rec.Authboss, "GET") if err := rec.startHandlerFunc(ctx, w, r); err != nil { t.Error("Unexpected error:", err) } if w.Code != http.StatusOK { t.Error("Unexpected status:", w.Code) } body := w.Body.String() if !strings.Contains(body, `
Unexpected error: %s", i, err) } if http.StatusMethodNotAllowed != w.Code { t.Errorf("%d> Expected status code %d, got %d", i, http.StatusMethodNotAllowed, w.Code) continue } } } func TestRecover_newToken(t *testing.T) { t.Parallel() regexURL := regexp.MustCompile(`^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=)?$`) regexSTD := regexp.MustCompile(`^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$`) encodedToken, encodedSum, _ := newToken() if !regexURL.MatchString(encodedToken) { t.Error("Expected encodedToken to be base64 encoded") } if !regexSTD.MatchString(encodedSum) { t.Error("Expected encodedSum to be base64 encoded") } } func TestRecover_sendRecoverMail_FailToSend(t *testing.T) { t.Parallel() r, _, logger := testSetup() mailer := mocks.NewMockMailer() mailer.SendErr = "failed to send" r.Mailer = mailer r.sendRecoverEmail("", "") if !strings.Contains(logger.String(), "failed to send") { t.Error("Expected logged to have msg:", "failed to send") } } func TestRecover_sendRecoverEmail(t *testing.T) { t.Parallel() r, _, _ := testSetup() mailer := mocks.NewMockMailer() r.EmailSubjectPrefix = "foo " r.RootURL = "bar" r.Mailer = mailer r.sendRecoverEmail("a@b.c", "abc=") if len(mailer.Last.To) != 1 { t.Error("Expected 1 to email") } if mailer.Last.To[0] != "a@b.c" { t.Error("Unexpected to email:", mailer.Last.To[0]) } if mailer.Last.Subject != "foo Password Reset" { t.Error("Unexpected subject:", mailer.Last.Subject) } url := fmt.Sprintf("%s/recover/complete?token=abc=", r.RootURL) if !strings.Contains(mailer.Last.HTMLBody, url) { t.Error("Expected HTMLBody to contain url:", url) } if !strings.Contains(mailer.Last.TextBody, url) { t.Error("Expected TextBody to contain url:", url) } } func TestRecover_completeHandlerFunc_GET_VerifyFails(t *testing.T) { t.Parallel() rec, storer, _ := testSetup() ctx, w, r, _ := testRequest(rec.Authboss, "GET", "token", testURLBase64Token) err := rec.completeHandlerFunc(ctx, w, r) rerr, ok := err.(authboss.ErrAndRedirect) if !ok { t.Error("Expected ErrAndRedirect") } if rerr.Location != "/" { t.Error("Unexpected location:", rerr.Location) } var zeroTime time.Time storer.Users["john"] = authboss.Attributes{StoreRecoverToken: testStdBase64Token, StoreRecoverTokenExpiry: zeroTime} ctx, w, r, _ = testRequest(rec.Authboss, "GET", "token", testURLBase64Token) err = rec.completeHandlerFunc(ctx, w, r) rerr, ok = err.(authboss.ErrAndRedirect) if !ok { t.Error("Expected ErrAndRedirect") } if rerr.Location != "/recover" { t.Error("Unexpected location:", rerr.Location) } if rerr.FlashError != recoverTokenExpiredFlash { t.Error("Unexpcted flash error:", rerr.FlashError) } } func TestRecover_completeHandlerFunc_GET(t *testing.T) { t.Parallel() rec, storer, _ := testSetup() storer.Users["john"] = authboss.Attributes{StoreRecoverToken: testStdBase64Token, StoreRecoverTokenExpiry: time.Now().Add(1 * time.Hour)} ctx, w, r, _ := testRequest(rec.Authboss, "GET", "token", testURLBase64Token) if err := rec.completeHandlerFunc(ctx, w, r); err != nil { t.Error("Unexpected error:", err) } if w.Code != http.StatusOK { t.Error("Unexpected status:", w.Code) } body := w.Body.String() if !strings.Contains(body, `