Merge branch 'main' into bot_develop

This commit is contained in:
Sanaei 2025-05-06 18:27:03 +02:00 committed by GitHub
commit 00dfdce79d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 305 additions and 65 deletions

View file

@ -33,7 +33,7 @@
لتثبيت المشروع أو تحديثه، نفذ الأمر ده: لتثبيت المشروع أو تحديثه، نفذ الأمر ده:
```bash ```bash
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/refs/tags/v2.5.8/install.sh)
``` ```
## تثبيت النسخة القديمة (مش موصى بيها) ## تثبيت النسخة القديمة (مش موصى بيها)

View file

@ -32,7 +32,7 @@
## Instalar y Actualizar ## Instalar y Actualizar
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/refs/tags/v2.5.8/install.sh)
``` ```
## Instalar versión antigua (no recomendamos) ## Instalar versión antigua (no recomendamos)

View file

@ -32,7 +32,7 @@
## نصب و ارتقا ## نصب و ارتقا
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/refs/tags/v2.5.8/install.sh)
``` ```
## نصب نسخه‌های قدیمی (توصیه نمی‌شود) ## نصب نسخه‌های قدیمی (توصیه نمی‌شود)

View file

@ -32,7 +32,7 @@
## Install & Upgrade ## Install & Upgrade
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/refs/tags/v2.5.8/install.sh)
``` ```
## Install legacy Version (we don't recommend) ## Install legacy Version (we don't recommend)

View file

@ -32,7 +32,7 @@
## Установка и обновление ## Установка и обновление
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/refs/tags/v2.5.8/install.sh)
``` ```
## Установить старую версию (мы не рекомендуем) ## Установить старую версию (мы не рекомендуем)

View file

@ -32,7 +32,7 @@
## 安装 & 升级 ## 安装 & 升级
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/refs/tags/v2.5.8/install.sh)
``` ```
## 安装旧版本 (我们不建议) ## 安装旧版本 (我们不建议)

View file

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

View file

@ -63,6 +63,11 @@ type InboundClientIps struct {
Ips string `json:"ips" form:"ips"` 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 { func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
listen := i.Listen listen := i.Listen
if listen != "" { if listen != "" {

View file

@ -82,14 +82,13 @@ gen_random_string() {
} }
config_after_install() { config_after_install() {
local existing_username=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'username: .+' | awk '{print $2}') local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
local existing_password=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'password: .+' | awk '{print $2}')
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}') local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}') local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local server_ip=$(curl -s https://api.ipify.org) local server_ip=$(curl -s https://api.ipify.org)
if [[ ${#existing_webBasePath} -lt 4 ]]; then if [[ ${#existing_webBasePath} -lt 4 ]]; then
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then if [[ "$existing_hasDefaultCredential" == "true" ]]; then
local config_webBasePath=$(gen_random_string 15) local config_webBasePath=$(gen_random_string 15)
local config_username=$(gen_random_string 10) local config_username=$(gen_random_string 10)
local config_password=$(gen_random_string 10) local config_password=$(gen_random_string 10)
@ -112,7 +111,6 @@ config_after_install() {
echo -e "${green}WebBasePath: ${config_webBasePath}${plain}" echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
echo -e "${green}Access URL: http://${server_ip}:${config_port}/${config_webBasePath}${plain}" echo -e "${green}Access URL: http://${server_ip}:${config_port}/${config_webBasePath}${plain}"
echo -e "###############################################" echo -e "###############################################"
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
else else
local config_webBasePath=$(gen_random_string 15) local config_webBasePath=$(gen_random_string 15)
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}" echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
@ -121,7 +119,7 @@ config_after_install() {
echo -e "${green}Access URL: http://${server_ip}:${existing_port}/${config_webBasePath}${plain}" echo -e "${green}Access URL: http://${server_ip}:${existing_port}/${config_webBasePath}${plain}"
fi fi
else else
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then if [[ "$existing_hasDefaultCredential" == "true" ]]; then
local config_username=$(gen_random_string 10) local config_username=$(gen_random_string 10)
local config_password=$(gen_random_string 10) local config_password=$(gen_random_string 10)
@ -132,7 +130,6 @@ config_after_install() {
echo -e "${green}Username: ${config_username}${plain}" echo -e "${green}Username: ${config_username}${plain}"
echo -e "${green}Password: ${config_password}${plain}" echo -e "${green}Password: ${config_password}${plain}"
echo -e "###############################################" echo -e "###############################################"
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
else else
echo -e "${green}Username, Password, and WebBasePath are properly set. Exiting...${plain}" echo -e "${green}Username, Password, and WebBasePath are properly set. Exiting...${plain}"
fi fi

13
main.go
View file

@ -16,6 +16,7 @@ import (
"x-ui/web" "x-ui/web"
"x-ui/web/global" "x-ui/web/global"
"x-ui/web/service" "x-ui/web/service"
"x-ui/util/crypto"
"github.com/op/go-logging" "github.com/op/go-logging"
) )
@ -151,9 +152,7 @@ func showSetting(show bool) {
fmt.Println("get current user info failed, error info:", err) fmt.Println("get current user info failed, error info:", err)
} }
username := userModel.Username if userModel.Username == "" || userModel.Password == "" {
userpasswd := userModel.Password
if username == "" || userpasswd == "" {
fmt.Println("current username or password is empty") fmt.Println("current username or password is empty")
} }
@ -163,8 +162,12 @@ func showSetting(show bool) {
} else { } else {
fmt.Println("Panel is secure with SSL") fmt.Println("Panel is secure with SSL")
} }
fmt.Println("username:", username)
fmt.Println("password:", userpasswd) hasDefaultCredential := func() bool {
return userModel.Username == "admin" && crypto.CheckPasswordHash(userModel.Password, "admin")
}()
fmt.Println("hasDefaultCredential:", hasDefaultCredential)
fmt.Println("port:", port) fmt.Println("port:", port)
fmt.Println("webBasePath:", webBasePath) fmt.Println("webBasePath:", webBasePath)
} }

15
util/crypto/crypto.go Normal file
View file

@ -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
}

View file

@ -44,6 +44,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/stopXrayService", a.stopXrayService) g.POST("/stopXrayService", a.stopXrayService)
g.POST("/restartXrayService", a.restartXrayService) g.POST("/restartXrayService", a.restartXrayService)
g.POST("/installXray/:version", a.installXray) g.POST("/installXray/:version", a.installXray)
g.POST("/updateGeofile/:fileName", a.updateGeofile)
g.POST("/logs/:count", a.getLogs) g.POST("/logs/:count", a.getLogs)
g.POST("/getConfigJson", a.getConfigJson) g.POST("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb) g.GET("/getDb", a.getDb)
@ -95,7 +96,13 @@ func (a *ServerController) getXrayVersion(c *gin.Context) {
func (a *ServerController) installXray(c *gin.Context) { func (a *ServerController) installXray(c *gin.Context) {
version := c.Param("version") version := c.Param("version")
err := a.serverService.UpdateXray(version) err := a.serverService.UpdateXray(version)
jsonMsg(c, I18nWeb(c, "install")+" xray", err) jsonMsg(c, I18nWeb(c, "pages.index.xraySwitchVersionPopover"), err)
}
func (a *ServerController) updateGeofile(c *gin.Context) {
fileName := c.Param("fileName")
err := a.serverService.UpdateGeofile(fileName)
jsonMsg(c, I18nWeb(c, "pages.index.geofileUpdatePopover"), err)
} }
func (a *ServerController) stopXrayService(c *gin.Context) { func (a *ServerController) stopXrayService(c *gin.Context) {

View file

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

View file

@ -22,11 +22,14 @@
.ant-backup-list-item { .ant-backup-list-item {
gap: 10px; gap: 10px;
} }
.ant-xray-version-list-item { .ant-version-list-item {
--padding: 12px; --padding: 12px;
padding: var(--padding) !important; padding: var(--padding) !important;
gap: var(--padding); gap: var(--padding);
} }
.dark .ant-version-list-item svg{
color: var(--dark-color-text-primary);
}
.dark .ant-backup-list-item svg, .dark .ant-backup-list-item svg,
.dark .ant-badge-status-text, .dark .ant-badge-status-text,
.dark .ant-card-extra { .dark .ant-card-extra {
@ -43,7 +46,7 @@
border-color: var(--color-primary-100); border-color: var(--color-primary-100);
} }
.dark .ant-backup-list, .dark .ant-backup-list,
.dark .ant-xray-version-list, .dark .ant-version-list,
.dark .ant-card-actions, .dark .ant-card-actions,
.dark .ant-card-actions>li:not(:last-child) { .dark .ant-card-actions>li:not(:last-child) {
border-color: var(--dark-color-stroke); border-color: var(--dark-color-stroke);
@ -353,14 +356,25 @@
</a-layout> </a-layout>
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true" <a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer=""> @ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
<a-alert type="warning" :style="{ marginBottom: '12px', width: '100%' }" <a-collapse default-active-key="1">
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert> <a-collapse-panel key="1" header='Xray'>
<a-list class="ant-xray-version-list" bordered :style="{ width: '100%' }"> <a-alert type="warning" :style="{ marginBottom: '12px', width: '100%' }" message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
<a-list-item class="ant-xray-version-list-item" v-for="version, index in versionModal.versions"> <a-list class="ant-version-list" bordered :style="{ width: '100%' }">
<a-list-item class="ant-version-list-item" v-for="version, index in versionModal.versions">
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ version ]]</a-tag> <a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ version ]]</a-tag>
<a-radio :class="themeSwitcher.currentTheme" :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio> <a-radio :class="themeSwitcher.currentTheme" :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
</a-list-item> </a-list-item>
</a-list> </a-list>
</a-collapse-panel>
<a-collapse-panel key="2" header='Geofiles'>
<a-list class="ant-version-list" bordered :style="{ width: '100%' }">
<a-list-item class="ant-version-list-item" v-for="file, index in ['geosite.dat', 'geoip.dat', 'geosite_IR.dat', 'geoip_IR.dat', 'geosite_RU.dat', 'geoip_RU.dat']">
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ file ]]</a-tag>
<a-icon type="reload" @click="updateGeofile(file)" :style="{ marginRight: '8px' }"/>
</a-list-item>
</a-list>
</a-collapse-panel>
</a-collapse>
</a-modal> </a-modal>
<a-modal id="log-modal" v-model="logModal.visible" <a-modal id="log-modal" v-model="logModal.visible"
:closable="true" @cancel="() => logModal.visible = false" :closable="true" @cancel="() => logModal.visible = false"
@ -645,7 +659,7 @@
switchV2rayVersion(version) { switchV2rayVersion(version) {
this.$confirm({ this.$confirm({
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}', title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`, content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}'.replace('#version#', version),
okText: '{{ i18n "confirm"}}', okText: '{{ i18n "confirm"}}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
cancelText: '{{ i18n "cancel"}}', cancelText: '{{ i18n "cancel"}}',
@ -657,6 +671,21 @@
}, },
}); });
}, },
updateGeofile(fileName) {
this.$confirm({
title: '{{ i18n "pages.index.geofileUpdateDialog" }}',
content: '{{ i18n "pages.index.geofileUpdateDialogDesc" }}'.replace("#filename#", fileName),
okText: '{{ i18n "confirm"}}',
class: themeSwitcher.currentTheme,
cancelText: '{{ i18n "cancel"}}',
onOk: async () => {
versionModal.hide();
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
await HttpUtil.post(`/server/updateGeofile/${fileName}`);
this.loading(false);
},
});
},
async stopXrayService() { async stopXrayService() {
this.loading(true); this.loading(true);
const msg = await HttpUtil.post('server/stopXrayService'); const msg = await HttpUtil.post('server/stopXrayService');

View file

@ -287,6 +287,7 @@
{ label: 'Malware 🇮🇷', value: 'ext:geosite_IR.dat:malware' }, { label: 'Malware 🇮🇷', value: 'ext:geosite_IR.dat:malware' },
{ label: 'Phishing 🇮🇷', value: 'ext:geosite_IR.dat:phishing' }, { label: 'Phishing 🇮🇷', value: 'ext:geosite_IR.dat:phishing' },
{ label: 'Cryptominers 🇮🇷', value: 'ext:geosite_IR.dat:cryptominers' }, { label: 'Cryptominers 🇮🇷', value: 'ext:geosite_IR.dat:cryptominers' },
{ label: 'Adult +18', value: 'geosite:category-porn' },
{ label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' }, { label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' },
{ label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' }, { label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' },
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' }, { label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },

View file

@ -591,6 +591,66 @@ func (s *ServerService) ImportDB(file multipart.File) error {
return nil return nil
} }
func (s *ServerService) UpdateGeofile(fileName string) error {
files := []struct {
URL string
FileName string
}{
{"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
{"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
{"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
{"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
}
downloadFile := func(url, destPath string) error {
resp, err := http.Get(url)
if err != nil {
return common.NewErrorf("Failed to download Geofile from %s: %v", url, err)
}
defer resp.Body.Close()
file, err := os.Create(destPath)
if err != nil {
return common.NewErrorf("Failed to create Geofile %s: %v", destPath, err)
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
return common.NewErrorf("Failed to save Geofile %s: %v", destPath, err)
}
return nil
}
var fileURL string
for _, file := range files {
if file.FileName == fileName {
fileURL = file.URL
break
}
}
if fileURL == "" {
return common.NewErrorf("File '%s' not found in the list of Geofiles", fileName)
}
destPath := fmt.Sprintf("%s/%s", config.GetBinFolderPath(), fileName)
if err := downloadFile(fileURL, destPath); err != nil {
return common.NewErrorf("Error downloading Geofile '%s': %v", fileName, err)
}
err := s.RestartXrayService()
if err != nil {
return common.NewErrorf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err)
}
return nil
}
func (s *ServerService) GetNewX25519Cert() (any, error) { func (s *ServerService) GetNewX25519Cert() (any, error) {
// Run the command // Run the command
cmd := exec.Command(xray.GetBinaryPath(), "x25519") cmd := exec.Command(xray.GetBinaryPath(), "x25519")

View file

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

View file

@ -122,8 +122,12 @@
"totalData" = "إجمالي البيانات" "totalData" = "إجمالي البيانات"
"sent" = "مرسل" "sent" = "مرسل"
"received" = "مستقبل" "received" = "مستقبل"
"xraySwitchVersionDialog" = "تغيير نسخة Xray" "xraySwitchVersionDialog" = "هل تريد حقًا تغيير إصدار Xray؟"
"xraySwitchVersionDialogDesc" = "متأكد إنك عايز تغير نسخة Xray لـ" "xraySwitchVersionDialogDesc" = "سيؤدي هذا إلى تغيير إصدار Xray إلى #version#."
"xraySwitchVersionPopover" = "تم تحديث Xray بنجاح"
"geofileUpdateDialog" = "هل تريد حقًا تحديث ملف الجغرافيا؟"
"geofileUpdateDialogDesc" = "سيؤدي هذا إلى تحديث ملف #filename#."
"geofileUpdatePopover" = "تم تحديث ملف الجغرافيا بنجاح"
"dontRefresh" = "التثبيت شغال، متعملش Refresh للصفحة" "dontRefresh" = "التثبيت شغال، متعملش Refresh للصفحة"
"logs" = "السجلات" "logs" = "السجلات"
"config" = "الإعدادات" "config" = "الإعدادات"

View file

@ -122,8 +122,12 @@
"totalData" = "Total Data" "totalData" = "Total Data"
"sent" = "Sent" "sent" = "Sent"
"received" = "Received" "received" = "Received"
"xraySwitchVersionDialog" = "Change Xray Version" "xraySwitchVersionDialog" = "Do you really want to change the Xray version?"
"xraySwitchVersionDialogDesc" = "Are you sure you want to change the Xray version to" "xraySwitchVersionDialogDesc" = "This will change the Xray version to #version#."
"xraySwitchVersionPopover" = "Xray updated successfully"
"geofileUpdateDialog" = "Do you really want to update the geofile?"
"geofileUpdateDialogDesc" = "This will update the #filename# file."
"geofileUpdatePopover" = "Geofile updated successfully"
"dontRefresh" = "Installation is in progress, please do not refresh this page" "dontRefresh" = "Installation is in progress, please do not refresh this page"
"logs" = "Logs" "logs" = "Logs"
"config" = "Config" "config" = "Config"

View file

@ -124,8 +124,12 @@
"totalData" = "Datos totales" "totalData" = "Datos totales"
"sent" = "Enviado" "sent" = "Enviado"
"received" = "Recibido" "received" = "Recibido"
"xraySwitchVersionDialog" = "Cambiar Versión de Xray" "xraySwitchVersionDialog" = "¿Realmente deseas cambiar la versión de Xray?"
"xraySwitchVersionDialogDesc" = "¿Estás seguro de que deseas cambiar la versión de Xray a" "xraySwitchVersionDialogDesc" = "Esto cambiará la versión de Xray a #version#."
"xraySwitchVersionPopover" = "Xray se actualizó correctamente"
"geofileUpdateDialog" = "¿Realmente deseas actualizar el geofichero?"
"geofileUpdateDialogDesc" = "Esto actualizará el archivo #filename#."
"geofileUpdatePopover" = "Geofichero actualizado correctamente"
"dontRefresh" = "La instalación está en progreso, por favor no actualices esta página." "dontRefresh" = "La instalación está en progreso, por favor no actualices esta página."
"logs" = "Registros" "logs" = "Registros"
"config" = "Configuración" "config" = "Configuración"

View file

@ -124,8 +124,12 @@
"totalData" = "داده‌های کل" "totalData" = "داده‌های کل"
"sent" = "ارسال شده" "sent" = "ارسال شده"
"received" = "دریافت شده" "received" = "دریافت شده"
"xraySwitchVersionDialog" = "تغییر نسخه ایکس‌ری" "xraySwitchVersionDialog" = "آیا واقعاً می‌خواهید نسخه Xray را تغییر دهید؟"
"xraySwitchVersionDialogDesc" = "آیا از تغییر نسخه‌ مطمئن هستید؟" "xraySwitchVersionDialogDesc" = "این کار نسخه Xray را به #version# تغییر می‌دهد."
"xraySwitchVersionPopover" = "Xray با موفقیت به‌روز شد"
"geofileUpdateDialog" = "آیا واقعاً می‌خواهید فایل جغرافیایی را به‌روز کنید؟"
"geofileUpdateDialogDesc" = "این عمل فایل #filename# را به‌روز می‌کند."
"geofileUpdatePopover" = "فایل جغرافیایی با موفقیت به‌روز شد"
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید" "dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
"logs" = "گزارش‌ها" "logs" = "گزارش‌ها"
"config" = "پیکربندی" "config" = "پیکربندی"

View file

@ -124,8 +124,12 @@
"totalData" = "Total data" "totalData" = "Total data"
"sent" = "Dikirim" "sent" = "Dikirim"
"received" = "Diterima" "received" = "Diterima"
"xraySwitchVersionDialog" = "Ganti Versi Xray" "xraySwitchVersionDialog" = "Apakah Anda yakin ingin mengubah versi Xray?"
"xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi" "xraySwitchVersionDialogDesc" = "Ini akan mengubah versi Xray ke #version#."
"xraySwitchVersionPopover" = "Xray berhasil diperbarui"
"geofileUpdateDialog" = "Apakah Anda yakin ingin memperbarui geofile?"
"geofileUpdateDialogDesc" = "Ini akan memperbarui file #filename#."
"geofileUpdatePopover" = "Geofile berhasil diperbarui"
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini" "dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
"logs" = "Log" "logs" = "Log"
"config" = "Konfigurasi" "config" = "Konfigurasi"

View file

@ -124,8 +124,12 @@
"totalData" = "総データ量" "totalData" = "総データ量"
"sent" = "送信" "sent" = "送信"
"received" = "受信" "received" = "受信"
"xraySwitchVersionDialog" = "Xrayバージョン切り替え" "xraySwitchVersionDialog" = "Xrayのバージョンを本当に変更しますか"
"xraySwitchVersionDialogDesc" = "Xrayのバージョンを切り替えますか" "xraySwitchVersionDialogDesc" = "Xrayのバージョンが#version#に変更されます。"
"xraySwitchVersionPopover" = "Xrayの更新が成功しました"
"geofileUpdateDialog" = "ジオファイルを本当に更新しますか?"
"geofileUpdateDialogDesc" = "これにより#filename#ファイルが更新されます。"
"geofileUpdatePopover" = "ジオファイルの更新が成功しました"
"dontRefresh" = "インストール中、このページをリロードしないでください" "dontRefresh" = "インストール中、このページをリロードしないでください"
"logs" = "ログ" "logs" = "ログ"
"config" = "設定" "config" = "設定"

View file

@ -124,8 +124,12 @@
"totalData" = "Dados totais" "totalData" = "Dados totais"
"sent" = "Enviado" "sent" = "Enviado"
"received" = "Recebido" "received" = "Recebido"
"xraySwitchVersionDialog" = "Alterar Versão do Xray" "xraySwitchVersionDialog" = "Você realmente deseja alterar a versão do Xray?"
"xraySwitchVersionDialogDesc" = "Tem certeza de que deseja alterar a versão do Xray para" "xraySwitchVersionDialogDesc" = "Isso mudará a versão do Xray para #version#."
"xraySwitchVersionPopover" = "Xray atualizado com sucesso"
"geofileUpdateDialog" = "Você realmente deseja atualizar o geofile?"
"geofileUpdateDialogDesc" = "Isso atualizará o arquivo #filename#."
"geofileUpdatePopover" = "Geofile atualizado com sucesso"
"dontRefresh" = "Instalação em andamento, por favor não atualize a página" "dontRefresh" = "Instalação em andamento, por favor não atualize a página"
"logs" = "Logs" "logs" = "Logs"
"config" = "Configuração" "config" = "Configuração"

View file

@ -124,8 +124,12 @@
"totalData" = "Общий объем данных" "totalData" = "Общий объем данных"
"sent" = "Отправлено" "sent" = "Отправлено"
"received" = "Получено" "received" = "Получено"
"xraySwitchVersionDialog" = "Переключить версию Xray" "xraySwitchVersionDialog" = "Вы действительно хотите изменить версию Xray?"
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?" "xraySwitchVersionDialogDesc" = "Это изменит версию Xray на #version#."
"xraySwitchVersionPopover" = "Xray успешно обновлён"
"geofileUpdateDialog" = "Вы действительно хотите обновить геофайл?"
"geofileUpdateDialogDesc" = "Это обновит файл #filename#."
"geofileUpdatePopover" = "Геофайл успешно обновлён"
"dontRefresh" = "Установка в процессе. Не обновляйте страницу" "dontRefresh" = "Установка в процессе. Не обновляйте страницу"
"logs" = "Журнал" "logs" = "Журнал"
"config" = "Конфигурация" "config" = "Конфигурация"

View file

@ -124,8 +124,12 @@
"totalData" = "Toplam veri" "totalData" = "Toplam veri"
"sent" = "Gönderilen" "sent" = "Gönderilen"
"received" = "Alınan" "received" = "Alınan"
"xraySwitchVersionDialog" = "Xray Sürümünü Değiştir" "xraySwitchVersionDialog" = "Xray sürümünü gerçekten değiştirmek istiyor musunuz?"
"xraySwitchVersionDialogDesc" = "Xray sürümünü değiştirmek istediğinizden emin misiniz" "xraySwitchVersionDialogDesc" = "Bu işlem Xray sürümünü #version# olarak değiştirecektir."
"xraySwitchVersionPopover" = "Xray başarıyla güncellendi"
"geofileUpdateDialog" = "Geofile'ı gerçekten güncellemek istiyor musunuz?"
"geofileUpdateDialogDesc" = "Bu işlem #filename# dosyasını güncelleyecektir."
"geofileUpdatePopover" = "Geofile başarıyla güncellendi"
"dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin" "dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin"
"logs" = "Günlükler" "logs" = "Günlükler"
"config" = "Yapılandırma" "config" = "Yapılandırma"

View file

@ -124,8 +124,12 @@
"totalData" = "Загальний обсяг даних" "totalData" = "Загальний обсяг даних"
"sent" = "Відправлено" "sent" = "Відправлено"
"received" = "Отримано" "received" = "Отримано"
"xraySwitchVersionDialog" = "Змінити версію Xray" "xraySwitchVersionDialog" = "Ви дійсно хочете змінити версію Xray?"
"xraySwitchVersionDialogDesc" = "Ви впевнені, що бажаєте змінити версію Xray на" "xraySwitchVersionDialogDesc" = "Це змінить версію Xray на #version#."
"xraySwitchVersionPopover" = "Xray успішно оновлено"
"geofileUpdateDialog" = "Ви дійсно хочете оновити геофайл?"
"geofileUpdateDialogDesc" = "Це оновить файл #filename#."
"geofileUpdatePopover" = "Геофайл успішно оновлено"
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку" "dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
"logs" = "Журнали" "logs" = "Журнали"
"config" = "Конфігурація" "config" = "Конфігурація"

View file

@ -124,8 +124,12 @@
"totalData" = "Tổng dữ liệu" "totalData" = "Tổng dữ liệu"
"sent" = "Đã gửi" "sent" = "Đã gửi"
"received" = "Đã nhận" "received" = "Đã nhận"
"xraySwitchVersionDialog" = "Chuyển đổi Phiên bản Xray" "xraySwitchVersionDialog" = "Bạn có chắc chắn muốn thay đổi phiên bản Xray không?"
"xraySwitchVersionDialogDesc" = "Bạn có chắc chắn muốn chuyển đổi phiên bản Xray sang" "xraySwitchVersionDialogDesc" = "Hành động này sẽ thay đổi phiên bản Xray thành #version#."
"xraySwitchVersionPopover" = "Xray đã được cập nhật thành công"
"geofileUpdateDialog" = "Bạn có chắc chắn muốn cập nhật geofile không?"
"geofileUpdateDialogDesc" = "Hành động này sẽ cập nhật tệp #filename#."
"geofileUpdatePopover" = "Geofile đã được cập nhật thành công"
"dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này." "dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này."
"logs" = "Nhật ký" "logs" = "Nhật ký"
"config" = "Cấu hình" "config" = "Cấu hình"

View file

@ -124,8 +124,12 @@
"totalData" = "总数据" "totalData" = "总数据"
"sent" = "已发送" "sent" = "已发送"
"received" = "已接收" "received" = "已接收"
"xraySwitchVersionDialog" = "切换 Xray 版本" "xraySwitchVersionDialog" = "您确定要更改Xray版本吗"
"xraySwitchVersionDialogDesc" = "是否切换 Xray 版本至" "xraySwitchVersionDialogDesc" = "这将把Xray版本更改为#version#。"
"xraySwitchVersionPopover" = "Xray 更新成功"
"geofileUpdateDialog" = "您确定要更新地理文件吗?"
"geofileUpdateDialogDesc" = "这将更新 #filename# 文件。"
"geofileUpdatePopover" = "地理文件更新成功"
"dontRefresh" = "安装中,请勿刷新此页面" "dontRefresh" = "安装中,请勿刷新此页面"
"logs" = "日志" "logs" = "日志"
"config" = "配置" "config" = "配置"

View file

@ -124,8 +124,12 @@
"totalData" = "總數據" "totalData" = "總數據"
"sent" = "已發送" "sent" = "已發送"
"received" = "已接收" "received" = "已接收"
"xraySwitchVersionDialog" = "切換 Xray 版本" "xraySwitchVersionDialog" = "您確定要變更Xray版本嗎"
"xraySwitchVersionDialogDesc" = "是否切換 Xray 版本至" "xraySwitchVersionDialogDesc" = "這將會把Xray版本變更為#version#。"
"xraySwitchVersionPopover" = "Xray 更新成功"
"geofileUpdateDialog" = "您確定要更新地理檔案嗎?"
"geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。"
"geofileUpdatePopover" = "地理檔案更新成功"
"dontRefresh" = "安裝中,請勿重新整理此頁面" "dontRefresh" = "安裝中,請勿重新整理此頁面"
"logs" = "日誌" "logs" = "日誌"
"config" = "配置" "config" = "配置"