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
|
-> Soweit es geht und Sinnvoll ist auf den neuen Handler umziehen
|
||||||
-> Login/Register/Password-reset geht natürlich nicht
|
-> Login/Register/Password-reset geht natürlich nicht
|
||||||
-> Bleibt noch Profile abrufen und Einstellungen -> Macht also keinen Sinn das auf den neuen Handler umzuziehen
|
-> 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
|
* [x] Password Reset -> Link via email oder so
|
||||||
* [ ] Settings
|
* [ ] Settings
|
||||||
|
|
||||||
|
|
|
@ -9,17 +9,15 @@ Content-Type: application/json
|
||||||
|
|
||||||
> {% client.global.set("auth_token", response.body.token); %}
|
> {% client.global.set("auth_token", response.body.token); %}
|
||||||
|
|
||||||
###
|
### Register
|
||||||
|
|
||||||
## Register
|
|
||||||
|
|
||||||
POST http://localhost:8080/api/v1/register
|
POST http://localhost:8080/api/v1/register
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"username": "user3",
|
"username": "user4",
|
||||||
"password": "1234",
|
"password": "1234",
|
||||||
"email": "3@knt.li"
|
"email": "4@knt.li"
|
||||||
}
|
}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
|
@ -30,7 +30,7 @@ Accept: application/json
|
||||||
"user_name": "user"
|
"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
|
POST http://localhost:8080/api/v1/user/password/reset
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
|
@ -40,4 +40,14 @@ Accept: application/json
|
||||||
"new_password": "1234"
|
"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
|
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
|
// Empty things errors
|
||||||
// ===================
|
// ===================
|
||||||
|
|
|
@ -18,8 +18,10 @@ type User struct {
|
||||||
Username string `xorm:"varchar(250) not null unique" json:"username"`
|
Username string `xorm:"varchar(250) not null unique" json:"username"`
|
||||||
Password string `xorm:"varchar(250) not null" json:"-"`
|
Password string `xorm:"varchar(250) not null" json:"-"`
|
||||||
Email string `xorm:"varchar(250)" json:"email"`
|
Email string `xorm:"varchar(250)" json:"email"`
|
||||||
|
IsActive bool `json:"-"`
|
||||||
|
|
||||||
PasswordResetToken string `xorm:"varchar(450)" json:"-"`
|
PasswordResetToken string `xorm:"varchar(450)" json:"-"`
|
||||||
|
EmailConfirmToken string `xorm:"varchar(450)" json:"-"`
|
||||||
|
|
||||||
Created int64 `xorm:"created" json:"-"`
|
Created int64 `xorm:"created" json:"-"`
|
||||||
Updated int64 `xorm:"updated" json:"-"`
|
Updated int64 `xorm:"updated" json:"-"`
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"code.vikunja.io/api/models/mail"
|
||||||
|
"code.vikunja.io/api/models/utils"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,6 +50,12 @@ func CreateUser(user User) (newUser User, err error) {
|
||||||
return User{}, err
|
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
|
// Insert it
|
||||||
_, err = x.Insert(newUser)
|
_, err = x.Insert(newUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -67,6 +75,13 @@ func CreateUser(user User) (newUser User, err error) {
|
||||||
return User{}, err
|
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
|
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
|
// in:body
|
||||||
PasswordTokenRequest models.PasswordTokenRequest
|
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("/register", apiv1.RegisterUser)
|
||||||
a.POST("/user/password/token", apiv1.UserRequestResetPasswordToken)
|
a.POST("/user/password/token", apiv1.UserRequestResetPasswordToken)
|
||||||
a.POST("/user/password/reset", apiv1.UserResetPassword)
|
a.POST("/user/password/reset", apiv1.UserResetPassword)
|
||||||
|
a.POST("/user/confirm", apiv1.UserConfirmEmail)
|
||||||
|
|
||||||
// ===== Routes with Authetification =====
|
// ===== Routes with Authetification =====
|
||||||
// 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