Add user email verification when registering (#5)

This commit is contained in:
konrad 2018-10-27 13:14:55 +00:00 committed by Gitea
parent d0c30cb089
commit 738c22b5f9
12 changed files with 153 additions and 7 deletions

View file

@ -233,7 +233,7 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
-> Soweit es geht und Sinnvoll ist auf den neuen Handler umziehen
-> Login/Register/Password-reset geht natürlich nicht
-> Bleibt noch Profile abrufen und Einstellungen -> Macht also keinen Sinn das auf den neuen Handler umzuziehen
* [ ] Email-Verifizierung beim Registrieren
* [x] Email-Verifizierung beim Registrieren
* [x] Password Reset -> Link via email oder so
* [ ] Settings

View file

@ -9,17 +9,15 @@ Content-Type: application/json
> {% client.global.set("auth_token", response.body.token); %}
###
## Register
### Register
POST http://localhost:8080/api/v1/register
Content-Type: application/json
{
"username": "user3",
"username": "user4",
"password": "1234",
"email": "3@knt.li"
"email": "4@knt.li"
}
###

View file

@ -30,7 +30,7 @@ Accept: application/json
"user_name": "user"
}
### Request a password to reset a password
### Request a token to reset a password
POST http://localhost:8080/api/v1/user/password/reset
Content-Type: application/json
Accept: application/json
@ -40,4 +40,14 @@ Accept: application/json
"new_password": "1234"
}
### Confirm a users email address
POST http://localhost:8080/api/v1/user/confirm
Content-Type: application/json
Accept: application/json
{
"token": "cqucJFxaTBwEZfuQATIBouCvpAfVUWXvrinRXSZpWpxikBMKyBtfNsZysvKOwCPMTsfmHZZeXiHhdBQyAUHFkMiXFAKqzMTWpTTJLkVeoKSkoinlsnxuPiqXjOHJNhnihRtRTdpQARQBlGHBrppojIJwZdKtmXsxwqMDwYKiTuHwjaOKYLeMLQaEWYpmedfvjtwSqhfuitguIatvLbVmtMfEAgwTcHscGeHpPsHFhLMXDqzwCmJYqsXoXxaumMaqaGOTguwvpWXCfvfBSXsjqiTwOcxhdYTRvQNoHijYkzshmrPDwiQcMNyCRzenxaKcrrVPcxJMmMGffjkRQlMtzUyBuHbHLbwQRaadLqPWuKJdXKSjMGiIFzyhCTzOSzMXgSCBtIfRFQaqsUss"
}
###

View file

@ -174,6 +174,29 @@ func IsErrInvalidPasswordResetToken(err error) bool {
return ok
}
// ErrInvalidEmailConfirmToken is an error where the email confirm token is invalid
type ErrInvalidEmailConfirmToken struct {
Token string
}
func (err ErrInvalidEmailConfirmToken) Error() string {
return fmt.Sprintf("Invalid email confirm token [Token: %s]", err.Token)
}
// ErrCodeInvalidEmailConfirmToken holds the unique world-error code of this error
const ErrCodeInvalidEmailConfirmToken = 1010
// HTTPError holds the http error description
func (err ErrInvalidEmailConfirmToken) HTTPError() HTTPError {
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidEmailConfirmToken, Message: "Invalid email confirm token."}
}
// IsErrInvalidEmailConfirmToken checks if an error is a ErrInvalidEmailConfirmToken.
func IsErrInvalidEmailConfirmToken(err error) bool {
_, ok := err.(ErrInvalidEmailConfirmToken)
return ok
}
// ===================
// Empty things errors
// ===================

View file

@ -18,8 +18,10 @@ type User struct {
Username string `xorm:"varchar(250) not null unique" json:"username"`
Password string `xorm:"varchar(250) not null" json:"-"`
Email string `xorm:"varchar(250)" json:"email"`
IsActive bool `json:"-"`
PasswordResetToken string `xorm:"varchar(450)" json:"-"`
EmailConfirmToken string `xorm:"varchar(450)" json:"-"`
Created int64 `xorm:"created" json:"-"`
Updated int64 `xorm:"updated" json:"-"`

View file

@ -1,6 +1,8 @@
package models
import (
"code.vikunja.io/api/models/mail"
"code.vikunja.io/api/models/utils"
"golang.org/x/crypto/bcrypt"
)
@ -48,6 +50,12 @@ func CreateUser(user User) (newUser User, err error) {
return User{}, err
}
// Generate a confirm token
newUser.EmailConfirmToken = utils.MakeRandomString(400)
// The new user should not be activated until it confirms his mail address
newUser.IsActive = false
// Insert it
_, err = x.Insert(newUser)
if err != nil {
@ -67,6 +75,13 @@ func CreateUser(user User) (newUser User, err error) {
return User{}, err
}
// Send the user a mail with a link to confirm the mail
data := map[string]interface{}{
"User": newUserOut,
}
mail.SendMailWithTemplate(user.Email, newUserOut.Username+" + Vikunja = <3", "confirm-email", data)
return newUserOut, err
}

View file

@ -0,0 +1,25 @@
package models
// EmailConfirm holds the token to confirm a mail address
type EmailConfirm struct {
Token string `json:"token"`
}
// UserEmailConfirm handles the confirmation of an email address
func UserEmailConfirm(c *EmailConfirm) (err error) {
user := User{}
has, err := x.Where("email_confirm_token = ?", c.Token).Get(&user)
if err != nil {
return
}
if !has {
return ErrInvalidEmailConfirmToken{Token: c.Token}
}
user.IsActive = true
user.EmailConfirmToken = ""
_, err = x.Where("id = ?", user.ID).Cols("is_active", "email_confirm_token").Update(&user)
return
}

View file

@ -46,4 +46,7 @@ type swaggerParameterBodies struct {
// in:body
PasswordTokenRequest models.PasswordTokenRequest
// in:body
EmailConfirm models.EmailConfirm
}

View file

@ -0,0 +1,46 @@
package v1
import (
"code.vikunja.io/api/models"
"code.vikunja.io/api/routes/crud"
"github.com/labstack/echo"
"net/http"
)
// UserConfirmEmail is the handler to confirm a user email
func UserConfirmEmail(c echo.Context) error {
// swagger:operation POST /user/confirm user confirmEmail
// ---
// summary: Confirms a users email address
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EmailConfirm"
// responses:
// "200":
// "$ref": "#/responses/Message"
// "400":
// "$ref": "#/responses/Message"
// "404":
// "$ref": "#/responses/Message"
// "500":
// "$ref": "#/responses/Message"
// Check for Request Content
var emailConfirm models.EmailConfirm
if err := c.Bind(&emailConfirm); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "No token provided.")
}
err := models.UserEmailConfirm(&emailConfirm)
if err != nil {
return crud.HandleHTTPError(err)
}
return c.JSON(http.StatusOK, models.Message{"The email was confirmed successfully."})
}

View file

@ -67,6 +67,7 @@ func RegisterRoutes(e *echo.Echo) {
a.POST("/register", apiv1.RegisterUser)
a.POST("/user/password/token", apiv1.UserRequestResetPasswordToken)
a.POST("/user/password/reset", apiv1.UserResetPassword)
a.POST("/user/confirm", apiv1.UserConfirmEmail)
// ===== Routes with Authetification =====
// Authetification

View file

@ -0,0 +1,16 @@
{{template "mail-header.tmpl" .}}
<p>
Hi {{.User.Username}},<br>
<br>
Welcome to Vikunja!
<br/>
To confirm you email address, click the link below:
</p>
<a href="{{.FrontendURL}}?userEmailConfirm={{.User.EmailConfirmToken}}" title="Confirm your email address" style="background: rgb(20, 131, 175); -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; border: 1px solid rgb(16, 106, 140); border-bottom-width: 3px; color: rgb(255, 255, 255); font-weight: 700; font-size: 13px; margin: 10px auto; padding: 5px 10px; text-decoration: none; text-align: center; text-rendering: optimizelegibility; text-transform: uppercase; display: block; width: 200px;">
Confirm your email address
</a>
<p>
If the button above doesn't work, copy the url below and paste it in your browsers address bar:<br/>
{{.FrontendURL}}?userEmailConfirm={{.User.EmailConfirmToken}}
</p>
{{template "mail-footer.tmpl"}}

View file

@ -0,0 +1,7 @@
Hi {{.User.Username}},
Welcome to Vikunja!
To confirm you email address, click the link below:
{{.User.EmailConfirmToken}}