func TestRequestPasswordResetHandler(t *testing.T) { var tests = []struct { descr string mailer *testMailer userEmail string reqEmail string remoteAddr string remoteHost string sentMailTo string }{ { descr: "Email does not match user", mailer: &testMailer{}, userEmail: "*****@*****.**", reqEmail: "*****@*****.**", remoteAddr: "192.168.0.1:54678", remoteHost: "192.168.0.1/32", }, { descr: "Email matches user", mailer: &testMailer{}, userEmail: "*****@*****.**", reqEmail: "*****@*****.**", remoteAddr: "192.168.0.1:54678", remoteHost: "192.168.0.1/32", sentMailTo: "*****@*****.**", }, } for _, tt := range tests { pool := newConnPool(t) user := &data.User{ Name: data.NewString("test"), Email: data.NewString(tt.userEmail), } SetPassword(user, "password") userID, err := data.CreateUser(pool, user) if err != nil { t.Errorf("%s: repo.CreateUser returned error: %v", tt.descr, err) continue } buf := bytes.NewBufferString(`{"email": "` + tt.reqEmail + `"}`) req, err := http.NewRequest("POST", "http://example.com/", buf) if err != nil { t.Errorf("%s: http.NewRequest returned error: %v", tt.descr, err) continue } req.RemoteAddr = tt.remoteAddr env := &environment{user: user, pool: pool, logger: getLogger(t), mailer: tt.mailer} w := httptest.NewRecorder() RequestPasswordResetHandler(w, req, env) if w.Code != 200 { t.Errorf("%s: Expected HTTP status %d, instead received %d", tt.descr, 200, w.Code) continue } // Need to reach down pgx because repo interface doesn't need any get // interface besides by token, but for this test we need to know the token var token string err = pool.QueryRow("select token from password_resets").Scan(&token) if err != nil { t.Errorf("%s: pool.QueryRow Scan returned error: %v", tt.descr, err) continue } pwr, err := data.SelectPasswordResetByPK(env.pool, token) if err != nil { t.Errorf("%s: repo.GetPasswordReset returned error: %v", tt.descr, err) continue } if pwr.Email.Value != tt.reqEmail { t.Errorf("%s: PasswordReset.Email should be %s, but instead is %v", tt.descr, tt.reqEmail, pwr.Email) } if pwr.RequestIP.Value.String() != tt.remoteHost { t.Errorf("%s: PasswordReset.RequestIP should be %s, but instead is %v", tt.descr, tt.remoteHost, pwr.RequestIP) } if tt.reqEmail == tt.userEmail && userID != pwr.UserID.Value { t.Errorf("%s: PasswordReset.UserID should be %d, but instead is %v", tt.descr, userID, pwr.UserID) } if tt.reqEmail != tt.userEmail && pwr.UserID.Status != data.Null { t.Errorf("%s: PasswordReset.UserID should be nil, but instead is %v", tt.descr, pwr.UserID) } sentMails := tt.mailer.sentPasswordResetMails if tt.sentMailTo == "" { if len(sentMails) != 0 { t.Errorf("%s: Expected to not send any reset mails, instead sent %d", tt.descr, len(sentMails)) } continue } if len(sentMails) != 1 { t.Errorf("%s: Expected to send 1 reset mail, instead sent %d", tt.descr, len(sentMails)) continue } if sentMails[0].to != tt.sentMailTo { t.Errorf("%s: Expected to send reset mail to %s, instead sent it to %d", tt.descr, tt.sentMailTo, sentMails[0].to) } if sentMails[0].token != pwr.Token.Value { t.Errorf("%s: Reset mail (%v) and password reset (%v) do not have the same token", tt.descr, sentMails[0].token, pwr.Token) } } }
func ResetPasswordHandler(w http.ResponseWriter, req *http.Request, env *environment) { var resetPassword struct { Token string `json:"token"` Password string `json:"password"` } decoder := json.NewDecoder(req.Body) if err := decoder.Decode(&resetPassword); err != nil { w.WriteHeader(422) fmt.Fprintf(w, "Error decoding request: %v", err) return } pwr, err := data.SelectPasswordResetByPK(env.pool, resetPassword.Token) if err == data.ErrNotFound { w.WriteHeader(404) return } else if err != nil { w.WriteHeader(500) fmt.Fprintf(w, "Error decoding request: %v", err) return } if pwr.UserID.Status != data.Present { w.WriteHeader(404) return } if pwr.CompletionTime.Status == data.Present { w.WriteHeader(404) return } attrs := &data.User{} SetPassword(attrs, resetPassword.Password) err = data.UpdateUser(env.pool, pwr.UserID.Value, attrs) if err != nil { w.WriteHeader(500) return } user, err := data.SelectUserByPK(env.pool, pwr.UserID.Value) if err != nil { w.WriteHeader(500) return } sessionID, err := genSessionID() if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } err = data.InsertSession(env.pool, &data.Session{ ID: data.NewBytes(sessionID), UserID: user.ID, }, ) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") var response struct { Name string `json:"name"` SessionID string `json:"sessionID"` } response.Name = user.Name.Value response.SessionID = hex.EncodeToString(sessionID) encoder := json.NewEncoder(w) encoder.Encode(response) }