From 11b7aa859c4b5108207b1395a91ddbc988ccf00b Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 24 Mar 2017 16:42:05 -0400 Subject: Implement POST /users/email/verify/send endpoint for APIv4 (#5825) --- api4/user.go | 40 +++++++++++++++++++++++++++++++++------- api4/user_test.go | 31 +++++++++++++++++++++++++------ model/client4.go | 19 ++++++++++++++++--- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/api4/user.go b/api4/user.go index b22bc75f6..c04fa0d77 100644 --- a/api4/user.go +++ b/api4/user.go @@ -35,7 +35,8 @@ func InitUser() { BaseRoutes.User.Handle("/password", ApiSessionRequired(updatePassword)).Methods("PUT") BaseRoutes.Users.Handle("/password/reset", ApiHandler(resetPassword)).Methods("POST") BaseRoutes.Users.Handle("/password/reset/send", ApiHandler(sendPasswordReset)).Methods("POST") - BaseRoutes.User.Handle("/email/verify", ApiHandler(verify)).Methods("POST") + BaseRoutes.Users.Handle("/email/verify", ApiHandler(verifyUserEmail)).Methods("POST") + BaseRoutes.Users.Handle("/email/verify/send", ApiHandler(sendVerificationEmail)).Methods("POST") BaseRoutes.Users.Handle("/login", ApiHandler(login)).Methods("POST") BaseRoutes.Users.Handle("/logout", ApiHandler(logout)).Methods("POST") @@ -797,18 +798,18 @@ func getUserAudits(c *Context, w http.ResponseWriter, r *http.Request) { } } -func verify(c *Context, w http.ResponseWriter, r *http.Request) { +func verifyUserEmail(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) - userId := props["uid"] + userId := props["user_id"] if len(userId) != 26 { - c.SetInvalidParam("uid") + c.SetInvalidParam("user_id") return } - hashedId := props["hid"] + hashedId := props["hash_id"] if len(hashedId) == 0 { - c.SetInvalidParam("hid") + c.SetInvalidParam("hash_id") return } @@ -823,7 +824,32 @@ func verify(c *Context, w http.ResponseWriter, r *http.Request) { } } - c.Err = model.NewLocAppError("verifyEmail", "api.user.verify_email.bad_link.app_error", nil, "") + c.Err = model.NewLocAppError("verifyUserEmail", "api.user.verify_email.bad_link.app_error", nil, "") c.Err.StatusCode = http.StatusBadRequest return } + +func sendVerificationEmail(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) + + email := props["email"] + if len(email) == 0 { + c.SetInvalidParam("email") + return + } + + user, err := app.GetUserForLogin(email, false) + if err != nil { + // Don't want to leak whether the email is valid or not + ReturnStatusOK(w) + return + } + + if _, err := app.GetStatus(user.Id); err != nil { + go app.SendVerifyEmail(user.Id, user.Email, user.Locale, c.GetSiteURL()) + } else { + go app.SendEmailChangeVerifyEmail(user.Id, user.Email, user.Locale, c.GetSiteURL()) + } + + ReturnStatusOK(w) +} diff --git a/api4/user_test.go b/api4/user_test.go index 6a42d19d2..16ae8bdad 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -1303,7 +1303,7 @@ func TestGetUserAudits(t *testing.T) { CheckNoError(t, resp) } -func TestVerify(t *testing.T) { +func TestVerifyUserEmail(t *testing.T) { th := Setup().InitBasic() defer TearDown() Client := th.Client @@ -1320,15 +1320,34 @@ func TestVerify(t *testing.T) { _, resp = Client.VerifyUserEmail(ruser.Id, hashId) CheckBadRequestStatus(t, resp) - // Comment per request from Joram, he will investigate why it fail with a wrong status - // hashId = ruser.Id+GenerateTestId() - // _, resp = Client.VerifyUserEmail("", hashId) - // CheckBadRequestStatus(t, resp) - _, resp = Client.VerifyUserEmail(ruser.Id, "") CheckBadRequestStatus(t, resp) } +func TestSendVerificationEmail(t *testing.T) { + th := Setup().InitBasic() + defer TearDown() + Client := th.Client + + pass, resp := Client.SendVerificationEmail(th.BasicUser.Email) + CheckNoError(t, resp) + + if !pass { + t.Fatal("should have passed") + } + + _, resp = Client.SendVerificationEmail("") + CheckBadRequestStatus(t, resp) + + // Even non-existent emails should return 200 OK + _, resp = Client.SendVerificationEmail(GenerateTestEmail()) + CheckNoError(t, resp) + + Client.Logout() + _, resp = Client.SendVerificationEmail(th.BasicUser.Email) + CheckNoError(t, resp) +} + func TestSetProfileImage(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer TearDown() diff --git a/model/client4.go b/model/client4.go index 3aef5019c..b269f2f34 100644 --- a/model/client4.go +++ b/model/client4.go @@ -645,10 +645,23 @@ func (c *Client4) GetUserAudits(userId string, page int, perPage int, etag strin } } -// Verify user email user id and hash strings. +// VerifyUserEmail will verify a user's email using user id and hash strings. func (c *Client4) VerifyUserEmail(userId, hashId string) (bool, *Response) { - requestBody := map[string]string{"uid": userId, "hid": hashId} - if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/email/verify", MapToJson(requestBody)); err != nil { + requestBody := map[string]string{"user_id": userId, "hash_id": hashId} + if r, err := c.DoApiPost(c.GetUsersRoute()+"/email/verify", MapToJson(requestBody)); err != nil { + return false, &Response{StatusCode: r.StatusCode, Error: err} + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// SendVerificationEmail will send an email to the user with the provided email address, if +// that user exists. The email will contain a link that can be used to verify the user's +// email address. +func (c *Client4) SendVerificationEmail(email string) (bool, *Response) { + requestBody := map[string]string{"email": email} + if r, err := c.DoApiPost(c.GetUsersRoute()+"/email/verify/send", MapToJson(requestBody)); err != nil { return false, &Response{StatusCode: r.StatusCode, Error: err} } else { defer closeBody(r) -- cgit v1.2.3-1-g7c22