浏览代码

feat: hashing user passwords

solves problems #2944, #2783
Columbiysky 3 周之前
父节点
当前提交
85cbad3ef4
共有 5 个文件被更改,包括 101 次插入10 次删除
  1. 54 3
      database/db.go
  2. 5 0
      database/model/model.go
  3. 15 0
      util/crypto/crypto.go
  4. 3 2
      web/controller/setting.go
  5. 24 5
      web/service/user.go

+ 54 - 3
database/db.go

@@ -7,9 +7,11 @@ import (
 	"log"
 	"os"
 	"path"
+	"slices"
 
 	"x-ui/config"
 	"x-ui/database/model"
+	"x-ui/util/crypto"
 	"x-ui/xray"
 
 	"gorm.io/driver/sqlite"
@@ -33,6 +35,7 @@ func initModels() error {
 		&model.Setting{},
 		&model.InboundClientIps{},
 		&xray.ClientTraffic{},
+		&model.HistoryOfSeeders{},
 	}
 	for _, model := range models {
 		if err := db.AutoMigrate(model); err != nil {
@@ -50,9 +53,16 @@ func initUser() error {
 		return err
 	}
 	if empty {
+		hashedPassword, err := crypto.HashPasswordAsBcrypt(defaultPassword)
+
+		if err != nil {
+			log.Printf("Error hashing default password: %v", err)
+			return err
+		}
+
 		user := &model.User{
 			Username:    defaultUsername,
-			Password:    defaultPassword,
+			Password:    hashedPassword,
 			LoginSecret: defaultSecret,
 		}
 		return db.Create(user).Error
@@ -60,6 +70,45 @@ func initUser() error {
 	return nil
 }
 
+func runSeeders(isUsersEmpty bool) error {
+	empty, err := isTableEmpty("history_of_seeders")
+	if err != nil {
+		log.Printf("Error checking if users table is empty: %v", err)
+		return err
+	}
+
+	if empty && isUsersEmpty {
+		hashSeeder := &model.HistoryOfSeeders{
+			SeederName: "UserPasswordHash",
+		}
+		return db.Create(hashSeeder).Error
+	} else {
+		var seedersHistory []string
+		db.Model(&model.HistoryOfSeeders{}).Pluck("seeder_name", &seedersHistory)
+
+		if !slices.Contains(seedersHistory, "UserPasswordHash") && !isUsersEmpty {
+			var users []model.User
+			db.Find(&users)
+
+			for _, user := range users {
+				hashedPassword, err := crypto.HashPasswordAsBcrypt(user.Password)
+				if err != nil {
+					log.Printf("Error hashing password for user '%s': %v", user.Username, err)
+					return err
+				}
+				db.Model(&user).Update("password", hashedPassword)
+			}
+
+			hashSeeder := &model.HistoryOfSeeders{
+				SeederName: "UserPasswordHash",
+			}
+			return db.Create(hashSeeder).Error
+		}
+	}
+
+	return nil
+}
+
 func isTableEmpty(tableName string) (bool, error) {
 	var count int64
 	err := db.Table(tableName).Count(&count).Error
@@ -92,11 +141,13 @@ func InitDB(dbPath string) error {
 	if err := initModels(); err != nil {
 		return err
 	}
+
+	isUsersEmpty, err := isTableEmpty("users")
+
 	if err := initUser(); err != nil {
 		return err
 	}
-
-	return nil
+	return runSeeders(isUsersEmpty)
 }
 
 func CloseDB() error {

+ 5 - 0
database/model/model.go

@@ -63,6 +63,11 @@ type InboundClientIps struct {
 	Ips         string `json:"ips" form:"ips"`
 }
 
+type HistoryOfSeeders struct {
+	Id         int    `json:"id" gorm:"primaryKey;autoIncrement"`
+	SeederName string `json:"seederName"`
+}
+
 func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
 	listen := i.Listen
 	if listen != "" {

+ 15 - 0
util/crypto/crypto.go

@@ -0,0 +1,15 @@
+package crypto
+
+import (
+	"golang.org/x/crypto/bcrypt"
+)
+
+func HashPasswordAsBcrypt(password string) (string, error) {
+	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+	return string(hash), err
+}
+
+func CheckPasswordHash(hash, password string) bool {
+	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
+	return err == nil
+}

+ 3 - 2
web/controller/setting.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"time"
 
+	"x-ui/util/crypto"
 	"x-ui/web/entity"
 	"x-ui/web/service"
 	"x-ui/web/session"
@@ -84,7 +85,7 @@ func (a *SettingController) updateUser(c *gin.Context) {
 		return
 	}
 	user := session.GetLoginUser(c)
-	if user.Username != form.OldUsername || user.Password != form.OldPassword {
+	if user.Username != form.OldUsername || !crypto.CheckPasswordHash(user.Password, form.OldPassword) {
 		jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), errors.New(I18nWeb(c, "pages.settings.toasts.originalUserPassIncorrect")))
 		return
 	}
@@ -95,7 +96,7 @@ func (a *SettingController) updateUser(c *gin.Context) {
 	err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword)
 	if err == nil {
 		user.Username = form.NewUsername
-		user.Password = form.NewPassword
+		user.Password, _ = crypto.HashPasswordAsBcrypt(form.NewPassword)
 		session.SetLoginUser(c, user)
 	}
 	jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err)

+ 24 - 5
web/service/user.go

@@ -6,6 +6,7 @@ import (
 	"x-ui/database"
 	"x-ui/database/model"
 	"x-ui/logger"
+	"x-ui/util/crypto"
 
 	"gorm.io/gorm"
 )
@@ -29,8 +30,9 @@ func (s *UserService) CheckUser(username string, password string, secret string)
 	db := database.GetDB()
 
 	user := &model.User{}
+
 	err := db.Model(model.User{}).
-		Where("username = ? and password = ? and login_secret = ?", username, password, secret).
+		Where("username = ? and login_secret = ?", username, secret).
 		First(user).
 		Error
 	if err == gorm.ErrRecordNotFound {
@@ -39,14 +41,25 @@ func (s *UserService) CheckUser(username string, password string, secret string)
 		logger.Warning("check user err:", err)
 		return nil
 	}
-	return user
+
+	if crypto.CheckPasswordHash(user.Password, password) {
+		return user
+	}
+
+	return nil
 }
 
 func (s *UserService) UpdateUser(id int, username string, password string) error {
 	db := database.GetDB()
+	hashedPassword, err := crypto.HashPasswordAsBcrypt(password)
+
+	if err != nil {
+		return err
+	}
+
 	return db.Model(model.User{}).
 		Where("id = ?", id).
-		Updates(map[string]any{"username": username, "password": password}).
+		Updates(map[string]any{"username": username, "password": hashedPassword}).
 		Error
 }
 
@@ -100,17 +113,23 @@ func (s *UserService) UpdateFirstUser(username string, password string) error {
 	} else if password == "" {
 		return errors.New("password can not be empty")
 	}
+	hashedPassword, er := crypto.HashPasswordAsBcrypt(password)
+
+	if er != nil {
+		return er
+	}
+
 	db := database.GetDB()
 	user := &model.User{}
 	err := db.Model(model.User{}).First(user).Error
 	if database.IsNotFound(err) {
 		user.Username = username
-		user.Password = password
+		user.Password = hashedPassword
 		return db.Model(model.User{}).Create(user).Error
 	} else if err != nil {
 		return err
 	}
 	user.Username = username
-	user.Password = password
+	user.Password = hashedPassword
 	return db.Save(user).Error
 }