Browse Source

secret token thanks to @HarlyquinForest

MHSanaei 1 year ago
parent
commit
b0f974a94d

+ 3 - 2
database/db.go

@@ -27,8 +27,9 @@ func initUser() error {
 	}
 	if count == 0 {
 		user := &model.User{
-			Username: "admin",
-			Password: "admin",
+			Username:    "admin",
+			Password:    "admin",
+			LoginSecret: "",
 		}
 		return db.Create(user).Error
 	}

+ 4 - 3
database/model/model.go

@@ -18,9 +18,10 @@ const (
 )
 
 type User struct {
-	Id       int    `json:"id" gorm:"primaryKey;autoIncrement"`
-	Username string `json:"username"`
-	Password string `json:"password"`
+	Id          int    `json:"id" gorm:"primaryKey;autoIncrement"`
+	Username    string `json:"username"`
+	Password    string `json:"password"`
+	LoginSecret string `json:"loginSecret"`
 }
 
 type Inbound struct {

+ 15 - 24
install.sh

@@ -23,23 +23,14 @@ else
 fi
 echo "The OS release is: $release"
 
-arch=$(arch)
-
-if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then
-    arch="amd64"
-elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then
-    arch="arm64"
-else
-    arch="amd64"
-    echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
-fi
-
-echo "arch: ${arch}"
-
-if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
-    echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!"
-    exit -1
-fi
+arch3xui() {
+    case "$(uname -m)" in
+        x86_64 | x64 | amd64 ) echo 'amd64' ;;
+        armv8 | arm64 | aarch64 ) echo 'arm64' ;;
+        * ) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
+    esac
+}
+echo "arch: $(arch3xui)"
 
 os_version=""
 os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
@@ -122,18 +113,18 @@ install_x-ui() {
             exit 1
         fi
         echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
-        wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
+        wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz
         if [[ $? -ne 0 ]]; then
             echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}"
             exit 1
         fi
     else
         last_version=$1
-        url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
+        url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz"
         echo -e "Begining to install x-ui $1"
-        wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
+        wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz ${url}
         if [[ $? -ne 0 ]]; then
-            echo -e "${red}Download x-ui $1 failed,please check the version exists${plain}"
+            echo -e "${red}Download x-ui $1 failed,please check the version exists ${plain}"
             exit 1
         fi
     fi
@@ -142,10 +133,10 @@ install_x-ui() {
         rm /usr/local/x-ui/ -rf
     fi
 
-    tar zxvf x-ui-linux-${arch}.tar.gz
-    rm x-ui-linux-${arch}.tar.gz -f
+    tar zxvf x-ui-linux-$(arch3xui).tar.gz
+    rm x-ui-linux-$(arch3xui).tar.gz -f
     cd x-ui
-    chmod +x x-ui bin/xray-linux-${arch}
+    chmod +x x-ui bin/xray-linux-$(arch3xui)
     cp -f x-ui.service /etc/systemd/system/
     wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
     chmod +x /usr/local/x-ui/x-ui.sh

+ 22 - 0
main.go

@@ -204,6 +204,24 @@ func updateSetting(port int, username string, password string) {
 	}
 }
 
+func removeSecret() {
+	err := database.InitDB(config.GetDBPath())
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	userService := service.UserService{}
+	err = userService.RemoveUserSecret()
+	if err != nil {
+		fmt.Println(err)
+	}
+	settingService := service.SettingService{}
+	err = settingService.SetSecretStatus(false)
+	if err != nil {
+		fmt.Println(err)
+	}
+}
+
 func main() {
 	if len(os.Args) < 2 {
 		runWebServer()
@@ -229,6 +247,7 @@ func main() {
 	var tgbotRuntime string
 	var reset bool
 	var show bool
+	var remove_secret bool
 	settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
 	settingCmd.BoolVar(&show, "show", false, "show current settings")
 	settingCmd.IntVar(&port, "port", 0, "set panel port")
@@ -290,6 +309,9 @@ func main() {
 		if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
 			updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
 		}
+		if remove_secret {
+			removeSecret()
+		}
 		if enabletgbot {
 			updateTgbotEnableSts(enabletgbot)
 		}

+ 2 - 0
web/assets/js/model/models.js

@@ -3,6 +3,7 @@ class User {
     constructor() {
         this.username = "";
         this.password = "";
+        this.LoginSecret = "";
     }
 }
 
@@ -180,6 +181,7 @@ class AllSetting {
         this.tgBotBackup = false;
         this.tgCpu = "";
         this.xrayTemplateConfig = "";
+        this.secretEnable = false;
 
         this.timeLocation = "Asia/Tehran";
 

+ 16 - 5
web/controller/index.go

@@ -11,15 +11,17 @@ import (
 )
 
 type LoginForm struct {
-	Username string `json:"username" form:"username"`
-	Password string `json:"password" form:"password"`
+	Username    string `json:"username" form:"username"`
+	Password    string `json:"password" form:"password"`
+	LoginSecret string `json:"loginSecret" form:"loginSecret"`
 }
 
 type IndexController struct {
 	BaseController
 
-	userService service.UserService
-	tgbot       service.Tgbot
+	settingService service.SettingService
+	userService    service.UserService
+	tgbot          service.Tgbot
 }
 
 func NewIndexController(g *gin.RouterGroup) *IndexController {
@@ -32,6 +34,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) {
 	g.GET("/", a.index)
 	g.POST("/login", a.login)
 	g.GET("/logout", a.logout)
+	g.POST("/getSecretStatus", a.getSecretStatus)
 }
 
 func (a *IndexController) index(c *gin.Context) {
@@ -57,7 +60,7 @@ func (a *IndexController) login(c *gin.Context) {
 		pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword"))
 		return
 	}
-	user := a.userService.CheckUser(form.Username, form.Password)
+	user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
 	timeStr := time.Now().Format("2006-01-02 15:04:05")
 	if user == nil {
 		a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
@@ -82,3 +85,11 @@ func (a *IndexController) logout(c *gin.Context) {
 	session.ClearSession(c)
 	c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
 }
+
+func (a *IndexController) getSecretStatus(c *gin.Context) {
+	status, err := a.settingService.GetSecretStatus()
+	if err == nil {
+		jsonObj(c, status, nil)
+	}
+
+}

+ 28 - 0
web/controller/setting.go

@@ -17,6 +17,10 @@ type updateUserForm struct {
 	NewPassword string `json:"newPassword" form:"newPassword"`
 }
 
+type updateSecretForm struct {
+	LoginSecret string `json:"loginSecret" form:"loginSecret"`
+}
+
 type SettingController struct {
 	settingService service.SettingService
 	userService    service.UserService
@@ -38,6 +42,8 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
 	g.POST("/updateUser", a.updateUser)
 	g.POST("/restartPanel", a.restartPanel)
 	g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig)
+	g.POST("/updateUserSecret", a.updateSecret)
+	g.POST("/getUserSecret", a.getUserSecret)
 }
 
 func (a *SettingController) getAllSetting(c *gin.Context) {
@@ -128,3 +134,25 @@ func (a *SettingController) restartPanel(c *gin.Context) {
 	err := a.panelService.RestartPanel(time.Second * 3)
 	jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err)
 }
+
+func (a *SettingController) updateSecret(c *gin.Context) {
+	form := &updateSecretForm{}
+	err := c.ShouldBind(form)
+	if err != nil {
+		jsonMsg(c, I18n(c, "pages.setting.toasts.modifySetting"), err)
+	}
+	user := session.GetLoginUser(c)
+	err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
+	if err == nil {
+		user.LoginSecret = form.LoginSecret
+		session.SetLoginUser(c, user)
+	}
+	jsonMsg(c, I18n(c, "pages.setting.toasts.modifyUser"), err)
+}
+func (a *SettingController) getUserSecret(c *gin.Context) {
+	loginUser := session.GetLoginUser(c)
+	user := a.userService.GetUserSecret(loginUser.Id)
+	if user != nil {
+		jsonObj(c, user, nil)
+	}
+}

+ 1 - 0
web/entity/entity.go

@@ -42,6 +42,7 @@ type AllSetting struct {
 	TgCpu              int    `json:"tgCpu" form:"tgCpu"`
 	XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
 	TimeLocation       string `json:"timeLocation" form:"timeLocation"`
+	SecretEnable       bool   `json:"secretEnable" form:"secretEnable"`
 }
 
 func (s *AllSetting) CheckValid() error {

+ 16 - 0
web/html/login.html

@@ -57,6 +57,11 @@
                                 <a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/>
                             </a-input>
                         </a-form-item>
+                        <a-form-item v-if="secretEnable">
+                            <a-input type="text" placeholder='{{ i18n "secretToken" }}' v-model.trim="user.loginSecret" @keydown.enter.native="login">
+                            <a-icon slot="prefix" type="key" style="color: rgba(0,0,0,.25)"/>
+                        </a-input>
+                        </a-form-item>
                         <a-form-item>
                             <a-button block @click="login" :loading="loading">{{ i18n "login" }}</a-button>
                         </a-form-item>
@@ -98,10 +103,12 @@
         data: {
             loading: false,
             user: new User(),
+            secretEnable: false,
             lang : ""
         },
         created(){
           this.lang = getLang();
+          this.secretEnable = this.getSecretStatus();
         },
         methods: {
             async login() {
@@ -111,6 +118,15 @@
                 if (msg.success) {
                     location.href = basePath + 'xui/';
                 }
+            },
+            async getSecretStatus() {
+                this.loading= true;
+                const msg = await HttpUtil.post('/getSecretStatus');
+                this.loading = false;
+                if (msg.success){
+                    this.secretEnable = msg.obj;
+                    return msg.obj;
+                }
             }
         }
     });

+ 83 - 15
web/html/xui/setting.html

@@ -91,8 +91,39 @@
                                         <a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
                                     </a-form-item>
                                 </a-form>
+                                <a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
+                                    <a-list-item style="padding: 20px">
+                                       <a-row>
+                                         <a-col :lg="24" :xl="12">
+                                            <a-list-item-meta title='{{ i18n "pages.setting.loginSecurity" }}' description='{{ i18n "pages.setting.loginSecurityDesc" }}'/>
+                                         </a-col>
+                                         <a-col :lg="24" :xl="12">
+                                            <template>
+                                                <a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
+                                            </template>
+                                         </a-col>
+                                       </a-row>
+                                    </a-list-item>
+                                    <a-list-item style="padding: 20px">
+                                      <a-row>
+                                        <a-col :lg="24" :xl="12">
+                                          <a-list-item-meta title='{{ i18n "pages.setting.secretToken" }}' description='{{ i18n "pages.setting.secretTokenDesc" }}'/>
+                                
+                                        </a-col>
+                                        <a-col :lg="24" :xl="12">
+                                           <svg 
+                                                  @click="getNewSecret"
+                                                  xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="anticon anticon-question-circle" viewBox="0 0 16 16"> <path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/> <path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/> 
+                                           </svg>
+                                           <template>
+                                               <a-textarea type="text" id='token' :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
+                                           </template>
+                                        </a-col>
+                                      </a-row>
+                                    </a-list-item>
+                                    <a-button type="primary" @click="updateSecret">{{ i18n "confirm" }}</a-button>
+                                </a-form>
                             </a-tab-pane>
-
                             <a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
                                 <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
                                     <a-divider>{{ i18n "pages.setting.actions"}}</a-divider>
@@ -205,7 +236,7 @@
                 oldAllSetting: new AllSetting(),
                 allSetting: new AllSetting(),
                 saveBtnDisable: true,
-                user: {},
+                user: new User(),
                 lang: getLang(),
                 ipv4Settings: {
                     tag: "IPv4",
@@ -262,31 +293,33 @@
                 }
             },
             methods: {
-                loading(spinning = true) {
-                    this.spinning = spinning;
+                loading(spinning = true , obj) {
+                if(obj == null)
+                  this.spinning = spinning;
                 },
                 async getAllSetting() {
-                    this.loading(true);
+                    this.loading(true,{});
                     const msg = await HttpUtil.post("/xui/setting/all");
-                    this.loading(false);
+                    this.loading(false,null);
                     if (msg.success) {
                         this.oldAllSetting = new AllSetting(msg.obj);
                         this.allSetting = new AllSetting(msg.obj);
                         this.saveBtnDisable = true;
                     }
+                    await this.getUserSecret();
                 },
                 async updateAllSetting() {
-                    this.loading(true);
+                    this.loading(true,{});
                     const msg = await HttpUtil.post("/xui/setting/update", this.allSetting);
-                    this.loading(false);
+                    this.loading(false,null);
                     if (msg.success) {
                         await this.getAllSetting();
                     }
                 },
                 async updateUser() {
-                    this.loading(true);
+                    this.loading(true,{});
                     const msg = await HttpUtil.post("/xui/setting/updateUser", this.user);
-                    this.loading(false);
+                    this.loading(false,null);
                     if (msg.success) {
                         this.user = {};
                     }
@@ -301,19 +334,54 @@
                             onOk: () => resolve(),
                         });
                     });
-                    this.loading(true);
+                    this.loading(true,{});
                     const msg = await HttpUtil.post("/xui/setting/restartPanel");
-                    this.loading(false);
+                    this.loading(false,null);
                     if (msg.success) {
-                        this.loading(true);
+                        this.loading(true,{});
                         await PromiseUtil.sleep(5000);
                         location.reload();
                     }
                 },
+                async getUserSecret(){
+                const user_msg = await HttpUtil.post("/xui/setting/getUserSecret", this.user);
+                if (user_msg.success){
+                    this.user = user_msg.obj;
+                }
+                this.loading(false);
+            },
+            async updateSecret(){
+                this.loading(true,{});
+                const msg = await HttpUtil.post("/xui/setting/updateUserSecret", this.user);
+                if (msg.success){
+                    this.user = msg.obj;
+                }
+                this.loading(false,null);
+                await this.updateAllSetting();
+            },
+            async getNewSecret(){
+                this.loading(true,{});
+                await PromiseUtil.sleep(1000);
+                var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
+                var string = '';
+                var len = 64;
+                for(var ii=0; ii<len; ii++){
+                    string += chars[Math.floor(Math.random() * chars.length)];
+                }
+                this.user.loginSecret = string;
+                document.getElementById('token').value =this.user.loginSecret; 
+                this.loading(false,null);
+            },
+            async toggleToken(value){
+                if(value)
+                  this.getNewSecret();
+                else 
+                  this.user.loginSecret = "";
+            },
                 async resetXrayConfigToDefault() {
-                    this.loading(true);
+                    this.loading(true,{});
                     const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
-                    this.loading(false);
+                    this.loading(false,null);
                     if (msg.success) {
                         this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
                         this.saveBtnDisable = true;

+ 16 - 1
web/service/setting.go

@@ -38,6 +38,7 @@ var defaultValueMap = map[string]string{
 	"tgRunTime":          "@daily",
 	"tgBotBackup":        "false",
 	"tgCpu":              "0",
+	"secretEnable":       "false",
 }
 
 type SettingService struct {
@@ -129,7 +130,13 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
 
 func (s *SettingService) ResetSettings() error {
 	db := database.GetDB()
-	return db.Where("1 = 1").Delete(model.Setting{}).Error
+	err := db.Where("1 = 1").Delete(model.Setting{}).Error
+	if err != nil {
+		return err
+	}
+	return db.Model(model.User{}).
+		Where("1 = 1").
+		Update("login_secret", "").Error
 }
 
 func (s *SettingService) getSetting(key string) (*model.Setting, error) {
@@ -288,6 +295,14 @@ func (s *SettingService) SetgetTrafficDiff(value int) error {
 	return s.setInt("trafficDiff", value)
 }
 
+func (s *SettingService) GetSecretStatus() (bool, error) {
+	return s.getBool("secretEnable")
+}
+
+func (s *SettingService) SetSecretStatus(value bool) error {
+	return s.setBool("secretEnable", value)
+}
+
 func (s *SettingService) GetSecret() ([]byte, error) {
 	secret, err := s.getString("secret")
 	if secret == defaultValueMap["secret"] {

+ 31 - 2
web/service/user.go

@@ -25,12 +25,12 @@ func (s *UserService) GetFirstUser() (*model.User, error) {
 	return user, nil
 }
 
-func (s *UserService) CheckUser(username string, password string) *model.User {
+func (s *UserService) CheckUser(username string, password string, secret string) *model.User {
 	db := database.GetDB()
 
 	user := &model.User{}
 	err := db.Model(model.User{}).
-		Where("username = ? and password = ?", username, password).
+		Where("username = ? and password = ? and login_secret = ?", username, password, secret).
 		First(user).
 		Error
 	if err == gorm.ErrRecordNotFound {
@@ -50,6 +50,35 @@ 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) UpdateFirstUser(username string, password string) error {
 	if username == "" {
 		return errors.New("username can not be empty")

+ 5 - 0
web/translation/translate.en_US.toml

@@ -48,6 +48,7 @@
 "install" = "Install"
 "clients" = "Clients"
 "usage" = "Usage"
+"secretToken" = "Secret token"
 
 [menu]
 "dashboard" = "System Status"
@@ -288,6 +289,10 @@
 "tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
 "timeZonee" = "Time Zone"
 "timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect"
+"loginSecurity" = "Login security"
+"loginSecurityDesc" = "Toggle additional step in user login page"
+"secretToken" = "Secret Token"
+"secretTokenDesc" = "Copy this secret token and keep it in a safe place; without this you won't be able to login. This can not be recovered from x-ui command tool neither" 
 
 [pages.setting.toasts]
 "modifySetting" = "Modify setting"

+ 5 - 0
web/translation/translate.fa_IR.toml

@@ -48,6 +48,7 @@
 "install" = "نصب"
 "clients" = "کاربران"
 "usage" = "استفاده"
+"secretToken" = "توکن امنیتی"
 
 [menu]
 "dashboard" = "وضعیت سیستم"
@@ -286,6 +287,10 @@
 "tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
 "timeZonee" = "منظقه زمانی"
 "timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
+"loginSecurity" = "لاگین ایمن"
+"loginSecurityDesc" = "افزودن یک مرحله دیگر به فرآیند لاگین"
+"secretToken" = "توکن امنیتی"
+"secretTokenDesc" = "این کد امنیتی را نزد خود در این جای امن نگه داری، بدون این کد امکان ورود به پنل را نخواهید داشت. امکان بازیابی آن وجود ندارد!" 
 
 [pages.setting.toasts]
 "modifySetting" = "ویرایش تنظیمات"

+ 5 - 0
web/translation/translate.zh_Hans.toml

@@ -48,6 +48,7 @@
 "install" = "安装"
 "clients" = "客户端"
 "usage" = "用法"
+"secretToken" = "秘密令牌"
 
 [menu]
 "dashboard" = "系统状态"
@@ -286,6 +287,10 @@
 "tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
 "timeZonee" = "时区"
 "timeZoneDesc" = "定时任务按照该时区的时间运行,重启面板生效"
+"loginSecurity" = "登录安全"
+"loginSecurityDesc" = "在用户登录页面中切换附加步骤"
+"secretToken" = "秘密令牌"
+"secretTokenDesc" = "复制此秘密令牌并将其保存在安全的地方;没有这个你将无法登录。这也无法从 x-ui 命令工具中恢复" 
 
 [pages.setting.toasts]
 "modifySetting" = "修改设置"

+ 62 - 15
x-ui.sh

@@ -56,6 +56,14 @@ elif [[ "${release}" == "debian" ]]; then
     fi
 fi
 
+arch3xui() {
+    case "$(uname -m)" in
+        x86_64 | x64 | amd64 ) echo 'amd64' ;;
+        armv8 | arm64 | aarch64 ) echo 'arm64' ;;
+        * ) echo -e "${red} Unsupported CPU architecture!${plain}" && exit 1 ;;
+    esac
+}
+
 confirm() {
     if [[ $# > 1 ]]; then
         echo && read -p "$1 [Default$2]: " temp
@@ -98,18 +106,49 @@ install() {
 }
 
 update() {
-    confirm "This function will forcefully reinstall the latest version, and the data will not be lost. Do you want to continue?" "n"
-    if [[ $? != 0 ]]; then
-        LOGE "Cancelled"
-        if [[ $# == 0 ]]; then
-            before_show_menu
+    read -rp "This function will update the X-UI panel to the latest version. Data will not be lost. Whether to continues? [Y/N]: " yn
+    if [[ $yn =~ "Y"|"y" ]]; then
+        systemctl stop x-ui
+        if [[ -e /usr/local/x-ui/ ]]; then
+            cd
+            rm -rf /usr/local/x-ui/
         fi
-        return 0
-    fi
-    bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/main/install.sh)
-    if [[ $? == 0 ]]; then
-        LOGI "Update is complete, Panel has automatically restarted "
-        exit 0
+        
+        last_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') || last_version=$(curl -sm8 https://raw.githubusercontent.com/MHSanaei/3x-ui/main/config/version)
+        if [[ -z "$last_version" ]]; then
+            echo -e "${red}Detecting the X-UI version failed, please make sure your server can connect to the GitHub API ${plain}"
+            exit 1
+        fi
+        
+        echo -e "${yellow}The latest version of X-UI is: ${last_version}, starting update...${plain}"
+        wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz
+        if [[ $? -ne 0 ]]; then
+            echo -e "${red}Download the X-UI failure, please make sure your server can connect and download the files from github ${plain}"
+            exit 1
+        fi
+        
+        cd /usr/local/
+        tar zxvf x-ui-linux-$(arch3xui).tar.gz
+        rm -f x-ui-linux-$(arch3xui).tar.gz
+        
+        cd x-ui
+        chmod +x x-ui bin/xray-linux-$(arch3xui)
+        cp -f x-ui.service /etc/systemd/system/
+        
+        wget -N --no-check-certificate https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh -O /usr/bin/x-ui
+        chmod +x /usr/local/x-ui/x-ui.sh
+        chmod +x /usr/bin/x-ui
+        
+        systemctl daemon-reload
+        systemctl enable x-ui >/dev/null 2>&1
+        systemctl start x-ui
+        systemctl restart x-ui
+        
+        echo -e "${green}The update is completed, and the X-UI panel has been automatically restarted ${plain}"
+        exit 1
+    else
+        echo -e "${red}The upgrade X-UI panel has been canceled! ${plain}"
+        exit 1
     fi
 }
 
@@ -139,15 +178,23 @@ uninstall() {
 }
 
 reset_user() {
-    confirm "Reset your username and password to admin?" "n"
+    confirm "Are you sure to reset the username and password of the panel?" "n"
     if [[ $? != 0 ]]; then
         if [[ $# == 0 ]]; then
             show_menu
         fi
         return 0
     fi
-    /usr/local/x-ui/x-ui setting -username admin -password admin
-    echo -e "Username and password have been reset to ${green}admin${plain}, Please restart the panel now."
+    read -rp "Please set the login username [default is a random username]: " config_account
+    [[ -z $config_account ]] && config_account=$(date +%s%N | md5sum | cut -c 1-8)
+    read -rp "Please set the login password [default is a random password]: " config_password
+    [[ -z $config_password ]] && config_password=$(date +%s%N | md5sum | cut -c 1-8)
+    /usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password} >/dev/null 2>&1
+    /usr/local/x-ui/x-ui setting -remove_secret >/dev/null 2>&1
+    echo -e "Panel login username has been reset to: ${green} ${config_account} ${plain}"
+    echo -e "Panel login password has been reset to: ${green} ${config_password} ${plain}"
+    echo -e "${yellow} Panel login secret token disabled ${plain}"
+    echo -e "${green} Please use the new login username and password to access the X-UI panel. Also remember them! ${plain}"
     confirm_restart
 }
 
@@ -781,7 +828,7 @@ show_menu() {
   ${green}2.${plain} Update x-ui
   ${green}3.${plain} Uninstall x-ui
 ————————————————
-  ${green}4.${plain} Reset Username And Password
+  ${green}4.${plain} Reset Username & Password & Secret Token
   ${green}5.${plain} Reset Panel Settings
   ${green}6.${plain} Change Panel Port
   ${green}7.${plain} View Current Panel Settings