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 00000000000..604a02a24be
--- /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 eeae39dd71a..1e7bb584e02 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 00000000000..9af5c8c6b4f
--- /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 5cd4d92512d..62fff3b3f21 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 9ac1fbd431e..691f7754ddb 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 68f2312d6e9..9db1ee76674 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