diff --git a/database/db.go b/database/db.go index 744f1401..2fe18478 100644 --- a/database/db.go +++ b/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 { diff --git a/database/model/model.go b/database/model/model.go index e9d1836f..7a20de16 100644 --- a/database/model/model.go +++ b/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 != "" { diff --git a/util/crypto/crypto.go b/util/crypto/crypto.go new file mode 100644 index 00000000..f600e7a6 --- /dev/null +++ b/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 +} diff --git a/web/controller/setting.go b/web/controller/setting.go index d04969dc..1ca65b07 100644 --- a/web/controller/setting.go +++ b/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) diff --git a/web/service/user.go b/web/service/user.go index 7438cf1a..72ae25a2 100644 --- a/web/service/user.go +++ b/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 }