Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColumbiysky <c.7843543@gmail.com>2025-05-03 12:27:53 +0300
committerGitHub <noreply@github.com>2025-05-03 12:27:53 +0300
commit85cbad3ef420ffdd7fec8657d247fdfe5e03903d (patch)
tree56036fc74abb46dd5de231bd8eeeeea50e6fcf0c
parent3d54e330514293e9385258da773be1a0e927a7f5 (diff)
feat: hashing user passwords
solves problems #2944, #2783
-rw-r--r--database/db.go57
-rw-r--r--database/model/model.go5
-rw-r--r--util/crypto/crypto.go15
-rw-r--r--web/controller/setting.go5
-rw-r--r--web/service/user.go29
5 files changed, 101 insertions, 10 deletions
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
}