chore: implement 2fa auth (#2968)

* chore: implement 2fa auth

from #2786

* chore: format code

* chore: replace two factor token input with qr-code

* chore: requesting confirmation of setting/removing two-factor authentication

otpauth library was taken from cdnjs

* chore: revert changes in `ClipboardManager`

don't need it.

* chore: removing twoFactor prop in settings page

* chore: remove `twoFactorQr` object in `mounted` function
This commit is contained in:
Shishkevich D.
2025-05-08 21:20:58 +07:00
committed by GitHub
parent d39ccf4b8f
commit fe3b1c9b52
31 changed files with 452 additions and 302 deletions

View File

@@ -8,10 +8,13 @@ import (
"x-ui/logger"
"x-ui/util/crypto"
"github.com/xlzd/gotp"
"gorm.io/gorm"
)
type UserService struct{}
type UserService struct {
settingService SettingService
}
func (s *UserService) GetFirstUser() (*model.User, error) {
db := database.GetDB()
@@ -26,13 +29,13 @@ func (s *UserService) GetFirstUser() (*model.User, error) {
return user, nil
}
func (s *UserService) CheckUser(username string, password string, secret string) *model.User {
func (s *UserService) CheckUser(username string, password string, twoFactorCode string) *model.User {
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).
Where("username = ? and login_secret = ?", username, secret).
Where("username = ?", username).
First(user).
Error
if err == gorm.ErrRecordNotFound {
@@ -42,11 +45,30 @@ func (s *UserService) CheckUser(username string, password string, secret string)
return nil
}
if crypto.CheckPasswordHash(user.Password, password) {
return user
if !crypto.CheckPasswordHash(user.Password, password) {
return nil
}
return nil
twoFactorEnable, err := s.settingService.GetTwoFactorEnable()
if err != nil {
logger.Warning("check two factor err:", err)
return nil
}
if twoFactorEnable {
twoFactorToken, err := s.settingService.GetTwoFactorToken()
if err != nil {
logger.Warning("check two factor token err:", err)
return nil
}
if gotp.NewDefaultTOTP(twoFactorToken).Now() != twoFactorCode {
return nil
}
}
return user
}
func (s *UserService) UpdateUser(id int, username string, password string) error {
@@ -63,50 +85,6 @@ func (s *UserService) UpdateUser(id int, username string, password string) error
Error
}
func (s *UserService) UpdateUserSecret(id int, secret string) error {
db := database.GetDB()
return db.Model(model.User{}).
Where("id = ?", id).
Update("login_secret", secret).
Error
}
func (s *UserService) RemoveUserSecret() error {
db := database.GetDB()
return db.Model(model.User{}).
Where("1 = 1").
Update("login_secret", "").
Error
}
func (s *UserService) GetUserSecret(id int) *model.User {
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).
Where("id = ?", id).
First(user).
Error
if err == gorm.ErrRecordNotFound {
return nil
}
return user
}
func (s *UserService) CheckSecretExistence() (bool, error) {
db := database.GetDB()
var count int64
err := db.Model(model.User{}).
Where("login_secret IS NOT NULL").
Count(&count).
Error
if err != nil {
return false, err
}
return count > 0, nil
}
func (s *UserService) UpdateFirstUser(username string, password string) error {
if username == "" {
return errors.New("username can not be empty")