diff --git a/pkg/routes/api/v1/user_update_email.go b/pkg/routes/api/v1/user_update_email.go new file mode 100644 index 0000000000..604a02a24b --- /dev/null +++ b/pkg/routes/api/v1/user_update_email.go @@ -0,0 +1,72 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package v1 + +import ( + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/web/handler" + "fmt" + "github.com/labstack/echo/v4" + "net/http" +) + +// UpdateUserEmail is the handler to let a user update their email address. +// @Summary Update email address +// @Description Lets the current user change their email address. +// @tags user +// @Accept json +// @Produce json +// @Param userEmailUpdate body user.EmailUpdate true "The new email address and current password." +// @Security JWTKeyAuth +// @Success 200 {object} models.Message +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Something's invalid." +// @Failure 404 {object} code.vikunja.io/web.HTTPError "User does not exist." +// @Failure 500 {object} models.Message "Internal server error." +// @Router /user/settings/email [post] +func UpdateUserEmail(c echo.Context) (err error) { + + var emailUpdate = &user.EmailUpdate{} + if err := c.Bind(emailUpdate); err != nil { + log.Debugf("Invalid model error. Internal error was: %s", err.Error()) + if he, is := err.(*echo.HTTPError); is { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)) + } + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided.")) + } + + emailUpdate.User, err = user.GetCurrentUser(c) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + emailUpdate.User, err = user.CheckUserCredentials(&user.Login{ + Username: emailUpdate.User.Username, + Password: emailUpdate.Password, + }) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + err = user.UpdateEmail(emailUpdate) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + return c.JSON(http.StatusOK, models.Message{Message: "We sent you email with a link to confirm your email address."}) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index eeae39dd71..1e7bb584e0 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -206,6 +206,7 @@ func registerAPIRoutes(a *echo.Group) { a.POST("/user/password", apiv1.UserChangePassword) a.GET("/users", apiv1.UserList) a.POST("/user/token", apiv1.RenewToken) + a.POST("/user/settings/email", apiv1.UpdateUserEmail) listHandler := &handler.WebHandler{ EmptyStruct: func() handler.CObject { diff --git a/pkg/user/update_email.go b/pkg/user/update_email.go new file mode 100644 index 0000000000..9af5c8c6b4 --- /dev/null +++ b/pkg/user/update_email.go @@ -0,0 +1,79 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package user + +import ( + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/mail" + "code.vikunja.io/api/pkg/utils" +) + +// EmailUpdate is the data structure to update a user's email address +type EmailUpdate struct { + User *User `json:"-"` + // The new email address. Needs to be a valid email address. + NewEmail string `json:"new_email" valid:"email,length(0|250),required"` + // The password of the user for confirmation. + Password string `json:"password"` +} + +// UpdateEmail lets a user update their email address +func UpdateEmail(update *EmailUpdate) (err error) { + + // Check the email is not already used + user := &User{} + has, err := x.Where("email = ?", update.NewEmail).Get(user) + if err != nil { + return + } + + if has { + return ErrUserEmailExists{UserID: user.ID, Email: update.NewEmail} + } + + // Set the user as unconfirmed and the new email address + update.User, err = GetUserWithEmail(&User{ID: update.User.ID}) + if err != nil { + return + } + + update.User.IsActive = false + update.User.Email = update.NewEmail + update.User.EmailConfirmToken = utils.MakeRandomString(64) + _, err = x. + Where("id = ?", update.User.ID). + Cols("email", "is_active", "email_confirm_token"). + Update(update.User) + if err != nil { + return + } + + // Send the confirmation mail + if !config.MailerEnabled.GetBool() { + return + } + + // Send the user a mail with a link to confirm the mail + data := map[string]interface{}{ + "User": update.User, + "IsNew": false, + } + + mail.SendMailWithTemplate(update.User.Email, update.User.Username+", please confirm your email address at Vikunja", "confirm-email", data) + + return +} diff --git a/pkg/user/user.go b/pkg/user/user.go index 5cd4d92512..62fff3b3f2 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -252,7 +252,7 @@ func CreateUser(user *User) (newUser *User, err error) { // The new user should not be activated until it confirms his mail address newUser.IsActive = false // Generate a confirm token - newUser.EmailConfirmToken = utils.MakeRandomString(400) + newUser.EmailConfirmToken = utils.MakeRandomString(60) } // Insert it @@ -277,7 +277,8 @@ func CreateUser(user *User) (newUser *User, err error) { // Send the user a mail with a link to confirm the mail data := map[string]interface{}{ - "User": newUserOut, + "User": newUserOut, + "IsNew": true, } mail.SendMailWithTemplate(user.Email, newUserOut.Username+" + Vikunja = <3", "confirm-email", data) diff --git a/templates/mail/confirm-email.html.tmpl b/templates/mail/confirm-email.html.tmpl index 9ac1fbd431..691f7754dd 100644 --- a/templates/mail/confirm-email.html.tmpl +++ b/templates/mail/confirm-email.html.tmpl @@ -1,10 +1,12 @@ {{template "mail-header.tmpl" .}}

Hi {{.User.Username}},
-
- Welcome to Vikunja! + {{if .IsNew}} +
+ Welcome to Vikunja! + {{end}}
- To confirm you email address, click the link below: + To confirm your email address, click the link below:

Confirm your email address diff --git a/templates/mail/confirm-email.plain.tmpl b/templates/mail/confirm-email.plain.tmpl index 68f2312d6e..9db1ee7667 100644 --- a/templates/mail/confirm-email.plain.tmpl +++ b/templates/mail/confirm-email.plain.tmpl @@ -1,7 +1,9 @@ Hi {{.User.Username}}, -Welcome to Vikunja! +{{if .IsNew}} + Welcome to Vikunja! -To confirm you email address, click the link below: +{{end}} +To confirm your email address, copy the link below and paste it in your browser: {{.FrontendURL}}?userEmailConfirm={{.User.EmailConfirmToken}} \ No newline at end of file