Move user email confirm to new token
This commit is contained in:
parent
d26f81162f
commit
ff68c88a7f
@ -63,19 +63,17 @@ var (
|
||||
PasswordResetToken: "passwordresettesttoken",
|
||||
}
|
||||
testuser4 = user.User{
|
||||
ID: 4,
|
||||
Username: "user4",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user4@example.com",
|
||||
EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael",
|
||||
ID: 4,
|
||||
Username: "user4",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user4@example.com",
|
||||
}
|
||||
testuser5 = user.User{
|
||||
ID: 4,
|
||||
Username: "user5",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user5@example.com",
|
||||
EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael",
|
||||
IsActive: false,
|
||||
ID: 4,
|
||||
Username: "user5",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user5@example.com",
|
||||
IsActive: false,
|
||||
}
|
||||
)
|
||||
|
||||
|
100
pkg/migration/20210711173657.go
Normal file
100
pkg/migration/20210711173657.go
Normal file
@ -0,0 +1,100 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type user20210711173657 struct {
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"`
|
||||
PasswordResetToken string `xorm:"varchar(450) null" json:"-"`
|
||||
EmailConfirmToken string `xorm:"varchar(450) null" json:"-"`
|
||||
}
|
||||
|
||||
func (u user20210711173657) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
type userTokens20210711173657 struct {
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk"`
|
||||
UserID int64 `xorm:"not null"`
|
||||
Token string `xorm:"not null"`
|
||||
Kind int `xorm:"not null"`
|
||||
Created time.Time `xorm:"created not null"`
|
||||
}
|
||||
|
||||
func (userTokens20210711173657) TableName() string {
|
||||
return "user_tokens"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20210711173657",
|
||||
Description: "Add user tokens table",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
err := tx.Sync2(userTokens20210711173657{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
users := []*user20210711173657{}
|
||||
err = tx.Where(`password_reset_token != "" OR email_confirm_token != ""`).Find(&users)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const tokenPasswordReset = 1
|
||||
const tokenEmailConfirm = 2
|
||||
|
||||
for _, user := range users {
|
||||
if user.PasswordResetToken != "" {
|
||||
_, err = tx.Insert(&userTokens20210711173657{
|
||||
UserID: user.ID,
|
||||
Token: user.PasswordResetToken,
|
||||
Kind: tokenPasswordReset,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if user.EmailConfirmToken != "" {
|
||||
_, err = tx.Insert(&userTokens20210711173657{
|
||||
UserID: user.ID,
|
||||
Token: user.EmailConfirmToken,
|
||||
Kind: tokenEmailConfirm,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = dropTableColum(tx, "users", "password_reset_token")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dropTableColum(tx, "users", "email_confirm_token")
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
@ -62,7 +62,6 @@ func TestListUsersFromList(t *testing.T) {
|
||||
Username: "user4",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: false,
|
||||
EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael",
|
||||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
@ -74,7 +73,6 @@ func TestListUsersFromList(t *testing.T) {
|
||||
Username: "user5",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: false,
|
||||
EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael",
|
||||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
|
@ -23,8 +23,9 @@ import (
|
||||
|
||||
// EmailConfirmNotification represents a EmailConfirmNotification notification
|
||||
type EmailConfirmNotification struct {
|
||||
User *User
|
||||
IsNew bool
|
||||
User *User
|
||||
IsNew bool
|
||||
ConfirmToken string
|
||||
}
|
||||
|
||||
// ToMail returns the mail notification for EmailConfirmNotification
|
||||
@ -45,7 +46,7 @@ func (n *EmailConfirmNotification) ToMail() *notifications.Mail {
|
||||
|
||||
return nn.
|
||||
Line("To confirm your email address, click the link below:").
|
||||
Action("Confirm your email address", config.ServiceFrontendurl.GetString()+"?userEmailConfirm="+n.User.EmailConfirmToken).
|
||||
Action("Confirm your email address", config.ServiceFrontendurl.GetString()+"?userEmailConfirm="+n.ConfirmToken).
|
||||
Line("Have a nice day!")
|
||||
}
|
||||
|
||||
|
71
pkg/user/token.go
Normal file
71
pkg/user/token.go
Normal file
@ -0,0 +1,71 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 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 Affero General Public Licensee 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 Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type TokenKind int
|
||||
|
||||
const (
|
||||
TokenUnknown TokenKind = iota
|
||||
TokenPasswordReset
|
||||
TokenEmailConfirm
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk"`
|
||||
UserID int64 `xorm:"not null"`
|
||||
Token string `xorm:"not null"`
|
||||
Kind TokenKind `xorm:"not null"`
|
||||
Created time.Time `xorm:"created not null"`
|
||||
}
|
||||
|
||||
func (t *Token) TableName() string {
|
||||
return "user_tokens"
|
||||
}
|
||||
|
||||
func generateNewToken(s *xorm.Session, u *User, kind TokenKind) (token *Token, err error) {
|
||||
token = &Token{
|
||||
UserID: u.ID,
|
||||
Kind: kind,
|
||||
Token: utils.MakeRandomString(60),
|
||||
}
|
||||
|
||||
_, err = s.Insert(token)
|
||||
return
|
||||
}
|
||||
|
||||
func getToken(s *xorm.Session, token string, kind TokenKind) (t *Token, err error) {
|
||||
t = &Token{}
|
||||
has, err := s.Where("user_id = ? AND kind = ? AND token = ?", t.UserID, t.Kind, t.Token).
|
||||
Get(t)
|
||||
if err != nil || !has {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func removeTokens(s *xorm.Session, u *User, kind TokenKind) (err error) {
|
||||
_, err = s.Where("user_id = ? AND kind = ?", u.ID, kind).
|
||||
Delete(&Token{})
|
||||
return
|
||||
}
|
@ -19,7 +19,6 @@ package user
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/notifications"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@ -52,26 +51,35 @@ func UpdateEmail(s *xorm.Session, update *EmailUpdate) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
update.User.IsActive = false
|
||||
update.User.Email = update.NewEmail
|
||||
update.User.EmailConfirmToken = utils.MakeRandomString(64)
|
||||
|
||||
// Send the confirmation mail
|
||||
if !config.MailerEnabled.GetBool() {
|
||||
_, err = s.
|
||||
Where("id = ?", update.User.ID).
|
||||
Cols("email").
|
||||
Update(update.User)
|
||||
return
|
||||
}
|
||||
|
||||
update.User.IsActive = false
|
||||
token, err := generateNewToken(s, update.User, TokenEmailConfirm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = s.
|
||||
Where("id = ?", update.User.ID).
|
||||
Cols("email", "is_active", "email_confirm_token").
|
||||
Cols("email", "is_active"). // TODO: Status change
|
||||
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
|
||||
n := &EmailConfirmNotification{
|
||||
User: update.User,
|
||||
IsNew: false,
|
||||
User: update.User,
|
||||
IsNew: false,
|
||||
ConfirmToken: token.Token,
|
||||
}
|
||||
|
||||
err = notifications.Notify(update.User, n)
|
||||
|
@ -58,7 +58,6 @@ type User struct {
|
||||
IsActive bool `xorm:"null" json:"-"`
|
||||
|
||||
PasswordResetToken string `xorm:"varchar(450) null" json:"-"`
|
||||
EmailConfirmToken string `xorm:"varchar(450) null" json:"-"`
|
||||
|
||||
AvatarProvider string `xorm:"varchar(255) null" json:"-"`
|
||||
AvatarFileID int64 `xorm:"null" json:"-"`
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/events"
|
||||
"code.vikunja.io/api/pkg/notifications"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
@ -55,13 +54,6 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
|
||||
}
|
||||
|
||||
user.IsActive = true
|
||||
if config.MailerEnabled.GetBool() && user.Issuer == issuerLocal {
|
||||
// The new user should not be activated until it confirms his mail address
|
||||
user.IsActive = false
|
||||
// Generate a confirm token
|
||||
user.EmailConfirmToken = utils.MakeRandomString(60)
|
||||
}
|
||||
|
||||
user.AvatarProvider = "initials"
|
||||
|
||||
// Insert it
|
||||
@ -84,13 +76,29 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
|
||||
}
|
||||
|
||||
// Dont send a mail if no mailer is configured
|
||||
if !config.MailerEnabled.GetBool() {
|
||||
if !config.MailerEnabled.GetBool() || user.Issuer != issuerLocal {
|
||||
return newUserOut, err
|
||||
}
|
||||
|
||||
// TODO: Proper status change
|
||||
user.IsActive = false
|
||||
token, err := generateNewToken(s, user, TokenEmailConfirm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = s.
|
||||
Where("id = ?", user.ID).
|
||||
Cols("email", "is_active").
|
||||
Update(user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n := &EmailConfirmNotification{
|
||||
User: user,
|
||||
IsNew: false,
|
||||
User: user,
|
||||
IsNew: true,
|
||||
ConfirmToken: token.Token,
|
||||
}
|
||||
|
||||
err = notifications.Notify(user, n)
|
||||
|
@ -16,7 +16,9 @@
|
||||
|
||||
package user
|
||||
|
||||
import "xorm.io/xorm"
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// EmailConfirm holds the token to confirm a mail address
|
||||
type EmailConfirm struct {
|
||||
@ -32,24 +34,28 @@ func ConfirmEmail(s *xorm.Session, c *EmailConfirm) (err error) {
|
||||
return ErrInvalidEmailConfirmToken{}
|
||||
}
|
||||
|
||||
// Check if the token is valid
|
||||
user := User{}
|
||||
has, err := s.
|
||||
Where("email_confirm_token = ?", c.Token).
|
||||
Get(&user)
|
||||
token, err := getToken(s, c.Token, TokenEmailConfirm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if token == nil {
|
||||
return ErrInvalidEmailConfirmToken{Token: c.Token}
|
||||
}
|
||||
|
||||
user, err := GetUserByID(s, token.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !has {
|
||||
return ErrInvalidEmailConfirmToken{Token: c.Token}
|
||||
}
|
||||
|
||||
// TODO: proper user status
|
||||
user.IsActive = true
|
||||
user.EmailConfirmToken = ""
|
||||
err = removeTokens(s, user, TokenEmailConfirm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = s.
|
||||
Where("id = ?", user.ID).
|
||||
Cols("is_active", "email_confirm_token").
|
||||
Update(&user)
|
||||
Cols("is_active").
|
||||
Update(user)
|
||||
return
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user