Add user email verification when registering (#5)
This commit is contained in:
parent
d0c30cb089
commit
738c22b5f9
12 changed files with 153 additions and 7 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
###
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
###
|
||||
|
|
|
@ -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
|
||||
// ===================
|
||||
|
|
|
@ -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:"-"`
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
25
models/user_email_confirm.go
Normal file
25
models/user_email_confirm.go
Normal 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
|
||||
}
|
|
@ -46,4 +46,7 @@ type swaggerParameterBodies struct {
|
|||
|
||||
// in:body
|
||||
PasswordTokenRequest models.PasswordTokenRequest
|
||||
|
||||
// in:body
|
||||
EmailConfirm models.EmailConfirm
|
||||
}
|
||||
|
|
46
routes/api/v1/user_confirm_email.go
Normal file
46
routes/api/v1/user_confirm_email.go
Normal 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."})
|
||||
}
|
|
@ -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
|
||||
|
|
16
templates/mail/confirm-email.html.tmpl
Normal file
16
templates/mail/confirm-email.html.tmpl
Normal 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"}}
|
7
templates/mail/confirm-email.plain.tmpl
Normal file
7
templates/mail/confirm-email.plain.tmpl
Normal file
|
@ -0,0 +1,7 @@
|
|||
Hi {{.User.Username}},
|
||||
|
||||
Welcome to Vikunja!
|
||||
|
||||
To confirm you email address, click the link below:
|
||||
|
||||
{{.User.EmailConfirmToken}}
|
Loading…
Reference in a new issue