chore: implement 2fa auth

from #2786
This commit is contained in:
Shishkevich D. 2025-05-03 08:03:09 +00:00
parent 3d54e33051
commit 2c53007580
29 changed files with 193 additions and 290 deletions

View file

@ -22,7 +22,6 @@ var db *gorm.DB
const (
defaultUsername = "admin"
defaultPassword = "admin"
defaultSecret = ""
)
func initModels() error {
@ -53,7 +52,6 @@ func initUser() error {
user := &model.User{
Username: defaultUsername,
Password: defaultPassword,
LoginSecret: defaultSecret,
}
return db.Create(user).Error
}

View file

@ -24,7 +24,6 @@ type User struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
Username string `json:"username"`
Password string `json:"password"`
LoginSecret string `json:"loginSecret"`
}
type Inbound struct {

1
go.mod
View file

@ -78,6 +78,7 @@ require (
github.com/valyala/fastjson v1.6.4 // indirect
github.com/vishvananda/netlink v1.3.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/xlzd/gotp v0.1.0 // indirect
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect

2
go.sum
View file

@ -187,6 +187,8 @@ github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
github.com/xtls/xray-core v1.250306.1-0.20250430044058-87ab8e512882 h1:O/aN4TCrJ+fmaDOBoQhtTRev2hVHIENy2EJ70jQcyEY=

35
main.go
View file

@ -343,36 +343,6 @@ func migrateDb() {
fmt.Println("Migration done!")
}
func removeSecret() {
userService := service.UserService{}
secretExists, err := userService.CheckSecretExistence()
if err != nil {
fmt.Println("Error checking secret existence:", err)
return
}
if !secretExists {
fmt.Println("No secret exists to remove.")
return
}
err = userService.RemoveUserSecret()
if err != nil {
fmt.Println("Error removing secret:", err)
return
}
settingService := service.SettingService{}
err = settingService.SetSecretStatus(false)
if err != nil {
fmt.Println("Error updating secret status:", err)
return
}
fmt.Println("Secret removed successfully.")
}
func main() {
if len(os.Args) < 2 {
runWebServer()
@ -400,10 +370,8 @@ func main() {
var reset bool
var show bool
var getCert bool
var remove_secret bool
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
settingCmd.BoolVar(&show, "show", false, "Display current settings")
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "Remove secret key")
settingCmd.IntVar(&port, "port", 0, "Set panel port number")
settingCmd.StringVar(&username, "username", "", "Set login username")
settingCmd.StringVar(&password, "password", "", "Set login password")
@ -467,9 +435,6 @@ func main() {
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
}
if remove_secret {
removeSecret()
}
if enabletgbot {
updateTgbotEnableSts(enabletgbot)
}

View file

@ -23,8 +23,9 @@ class AllSetting {
this.tgBotLoginNotify = true;
this.tgCpu = 80;
this.tgLang = "en-US";
this.twoFactorEnable = false;
this.twoFactorToken = "";
this.xrayTemplateConfig = "";
this.secretEnable = false;
this.subEnable = false;
this.subTitle = "";
this.subListen = "";

View file

@ -145,6 +145,33 @@ class RandomUtil {
return Base64.alternativeEncode(String.fromCharCode(...array));
}
static randomBase32String(length = 16) {
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
const base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let result = '';
let bits = 0;
let buffer = 0;
for (let i = 0; i < array.length; i++) {
buffer = (buffer << 8) | array[i];
bits += 8;
while (bits >= 5) {
bits -= 5;
result += base32Chars[(buffer >>> bits) & 0x1F];
}
}
if (bits > 0) {
result += base32Chars[(buffer << (5 - bits)) & 0x1F];
}
return result;
}
}
class ObjectUtil {

View file

@ -14,9 +14,9 @@ import (
)
type LoginForm struct {
Username string `json:"username" form:"username"`
Password string `json:"password" form:"password"`
LoginSecret string `json:"loginSecret" form:"loginSecret"`
Username string `json:"username" form:"username"`
Password string `json:"password" form:"password"`
TwoFactorCode string `json:"twoFactorCode" form:"twoFactorCode"`
}
type IndexController struct {
@ -37,7 +37,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)
g.POST("/getTwoFactorEnable", a.getTwoFactorEnable)
}
func (a *IndexController) index(c *gin.Context) {
@ -64,14 +64,13 @@ func (a *IndexController) login(c *gin.Context) {
return
}
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
user := a.userService.CheckUser(form.Username, form.Password, form.TwoFactorCode)
timeStr := time.Now().Format("2006-01-02 15:04:05")
safeUser := template.HTMLEscapeString(form.Username)
safePass := template.HTMLEscapeString(form.Password)
safeSecret := template.HTMLEscapeString(form.LoginSecret)
if user == nil {
logger.Warningf("wrong username: \"%s\", password: \"%s\", secret: \"%s\", IP: \"%s\"", safeUser, safePass, safeSecret, getRemoteIp(c))
logger.Warningf("wrong username: \"%s\", password: \"%s\", IP: \"%s\"", safeUser, safePass, getRemoteIp(c))
a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0)
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
return
@ -108,8 +107,8 @@ func (a *IndexController) logout(c *gin.Context) {
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
}
func (a *IndexController) getSecretStatus(c *gin.Context) {
status, err := a.settingService.GetSecretStatus()
func (a *IndexController) getTwoFactorEnable(c *gin.Context) {
status, err := a.settingService.GetTwoFactorEnable()
if err == nil {
jsonObj(c, status, nil)
}

View file

@ -18,10 +18,6 @@ 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
@ -43,8 +39,6 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
g.POST("/updateUser", a.updateUser)
g.POST("/restartPanel", a.restartPanel)
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
g.POST("/updateUserSecret", a.updateSecret)
g.POST("/getUserSecret", a.getUserSecret)
}
func (a *SettingController) getAllSetting(c *gin.Context) {
@ -106,29 +100,6 @@ func (a *SettingController) restartPanel(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "pages.settings.restartPanel"), err)
}
func (a *SettingController) updateSecret(c *gin.Context) {
form := &updateSecretForm{}
err := c.ShouldBind(form)
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), 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, I18nWeb(c, "pages.settings.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)
}
}
func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
defaultJsonConfig, err := a.settingService.GetDefaultXrayConfig()
if err != nil {

View file

@ -38,7 +38,8 @@ type AllSetting struct {
TgCpu int `json:"tgCpu" form:"tgCpu"`
TgLang string `json:"tgLang" form:"tgLang"`
TimeLocation string `json:"timeLocation" form:"timeLocation"`
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"`
TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"`
SubEnable bool `json:"subEnable" form:"subEnable"`
SubTitle string `json:"subTitle" form:"subTitle"`
SubListen string `json:"subListen" form:"subListen"`

View file

@ -512,11 +512,11 @@
<a-icon slot="prefix" type="lock" :style="{ fontSize: '1rem' }"></a-icon>
</a-input-password>
</a-form-item>
<a-form-item v-if="secretEnable">
<a-input-password autocomplete="secret" name="secret" v-model.trim="user.loginSecret"
placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login">
<a-form-item v-if="twoFactorEnable">
<a-input autocomplete="totp" name="twoFactorCode" v-model.trim="user.twoFactorCode"
placeholder='{{ i18n "twoFactorCode" }}' @keydown.enter.native="login">
<a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon>
</a-input-password>
</a-input>
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
@ -549,14 +549,14 @@
user: {
username: "",
password: "",
loginSecret: ""
twoFactorCode: ""
},
secretEnable: false,
twoFactorEnable: false,
lang: ""
},
async mounted() {
this.lang = LanguageManager.getLanguage();
this.secretEnable = await this.getSecretStatus();
this.twoFactorEnable = await this.getTwoFactorEnable();
},
methods: {
async login() {
@ -567,12 +567,12 @@
location.href = basePath + 'panel/';
}
},
async getSecretStatus() {
async getTwoFactorEnable() {
this.loading = true;
const msg = await HttpUtil.post('/getSecretStatus');
const msg = await HttpUtil.post('/getTwoFactorEnable');
this.loading = false;
if (msg.success) {
this.secretEnable = msg.obj;
this.twoFactorEnable = msg.obj;
return msg.obj;
}
},

View file

@ -133,7 +133,6 @@
data: {
themeSwitcher,
spinning: false,
changeSecret: false,
oldAllSetting: new AllSetting(),
allSetting: new AllSetting(),
saveBtnDisable: true,
@ -258,7 +257,6 @@
app.changeRemarkSample();
this.saveBtnDisable = true;
}
await this.fetchUserSecret();
},
async updateAllSetting() {
this.loading(true);
@ -302,38 +300,11 @@
window.location.replace(url);
}
},
async fetchUserSecret() {
this.loading(true);
const userMessage = await HttpUtil.post("/panel/setting/getUserSecret", this.user);
if (userMessage.success) {
this.user = userMessage.obj;
}
this.loading(false);
},
async updateSecret() {
this.loading(true);
const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
if (msg && msg.obj) {
this.user = msg.obj;
}
this.loading(false);
await this.updateAllSetting();
},
async getNewSecret() {
if (!this.changeSecret) {
this.changeSecret = true;
this.user.loginSecret = '';
const newSecret = RandomUtil.randomSeq(64);
await PromiseUtil.sleep(1000);
this.user.loginSecret = newSecret;
this.changeSecret = false;
}
},
async toggleToken(value) {
if (value) {
await this.getNewSecret();
toggleTwoFactor(newValue) {
if (newValue) {
this.allSetting.twoFactorToken = RandomUtil.randomBase32String();
} else {
this.user.loginSecret = "";
this.allSetting.twoFactorToken = "";
}
},
addNoise() {

View file

@ -31,30 +31,23 @@
</a-space>
</a-list-item>
</a-collapse-panel>
<a-collapse-panel key="2" header='{{ i18n "pages.settings.security.secret"}}'>
<a-collapse-panel key="2" header='{{ i18n "pages.settings.security.twoFactor" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.security.loginSecurity" }}</template>
<template #description>{{ i18n "pages.settings.security.loginSecurityDesc" }}</template>
<template #title>{{ i18n "pages.settings.security.twoFactorEnable" }}</template>
<template #description>{{ i18n "pages.settings.security.twoFactorEnableDesc" }}</template>
<template #control>
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
<a-icon :style="{ marginLeft: '1rem' }" v-if="allSetting.secretEnable" :spin="this.changeSecret" type="sync"
@click="getNewSecret"></a-icon>
<a-switch @change="toggleTwoFactor" v-model="allSetting.twoFactorEnable"></a-switch>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.security.secretToken" }}</template>
<template #description>{{ i18n "pages.settings.security.secretTokenDesc" }}</template>
<a-setting-list-item paddings="small" v-if="allSetting.twoFactorEnable">
<template #title>{{ i18n "pages.settings.security.twoFactorToken" }}</template>
<template #description>{{ i18n "pages.settings.security.twoFactorTokenDesc" }}</template>
<template #control>
<a-textarea type="text" :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
<a-input disabled="disabled" v-model="allSetting.twoFactorToken" :style="{ cursor: 'text' }">
<a-icon slot="addonAfter" type="copy" @click="ClipboardManager.copyText(allSetting.twoFactorToken)"/>
</a-input>
</template>
</a-setting-list-item>
<a-list-item>
<a-space direction="horizontal" :style="{ padding: '0 20px' }">
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">
<span>{{ i18n "confirm"}}</span>
</a-button>
</a-space>
</a-list-item>
</a-collapse-panel>
</a-collapse>
{{end}}

View file

@ -48,7 +48,8 @@ var defaultValueMap = map[string]string{
"tgBotLoginNotify": "true",
"tgCpu": "80",
"tgLang": "en-US",
"secretEnable": "false",
"twoFactorEnable": "false",
"twoFactorToken": "",
"subEnable": "false",
"subTitle": "",
"subListen": "",
@ -166,8 +167,7 @@ func (s *SettingService) ResetSettings() error {
return err
}
return db.Model(model.User{}).
Where("1 = 1").
Update("login_secret", "").Error
Where("1 = 1").Error
}
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
@ -318,6 +318,14 @@ func (s *SettingService) GetTgLang() (string, error) {
return s.getString("tgLang")
}
func (s *SettingService) GetTwoFactorEnable() (bool, error) {
return s.getBool("twoFactorEnable")
}
func (s *SettingService) GetTwoFactorToken() (string, error) {
return s.getString("twoFactorToken")
}
func (s *SettingService) GetPort() (int, error) {
return s.getInt("webPort")
}
@ -358,14 +366,6 @@ func (s *SettingService) GetRemarkModel() (string, error) {
return s.getString("remarkModel")
}
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"] {

View file

@ -8,9 +8,12 @@ import (
"x-ui/logger"
"gorm.io/gorm"
"github.com/xlzd/gotp"
)
type UserService struct{}
type UserService struct {
settingService SettingService
}
func (s *UserService) GetFirstUser() (*model.User, error) {
db := database.GetDB()
@ -25,12 +28,12 @@ func (s *UserService) GetFirstUser() (*model.User, error) {
return user, nil
}
func (s *UserService) CheckUser(username string, password string, secret string) *model.User {
func (s *UserService) CheckUser(username string, password string, twoFactorCode string) *model.User {
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).
Where("username = ? and password = ? and login_secret = ?", username, password, secret).
Where("username = ? and password = ?", username, password).
First(user).
Error
if err == gorm.ErrRecordNotFound {
@ -39,6 +42,24 @@ func (s *UserService) CheckUser(username string, password string, secret string)
logger.Warning("check user err:", err)
return nil
}
twoFactorEnable, err := s.settingService.GetTwoFactorEnable();
if err != nil {
logger.Warning("check two factor err:", err)
return nil
}
if twoFactorEnable {
twoFactorToken, err := s.settingService.GetTwoFactorToken();
if err != nil {
logger.Warning("check two factor token err:", err)
return nil
}
if gotp.NewDefaultTOTP(twoFactorToken).Now() != twoFactorCode {
return nil
}
}
return user
}
@ -50,50 +71,6 @@ 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) CheckSecretExistence() (bool, error) {
db := database.GetDB()
var count int64
err := db.Model(model.User{}).
Where("login_secret IS NOT NULL").
Count(&count).
Error
if err != nil {
return false, err
}
return count > 0, nil
}
func (s *UserService) UpdateFirstUser(username string, password string) error {
if username == "" {
return errors.New("username can not be empty")

View file

@ -51,7 +51,7 @@
"install" = "تثبيت"
"clients" = "عملاء"
"usage" = "استخدام"
"secretToken" = "توكن سري"
"twoFactorCode" = "الكود"
"remained" = "المتبقي"
"security" = "أمان"
"secAlertTitle" = "تنبيه أمني"
@ -87,7 +87,7 @@
"invalidFormData" = "تنسيق البيانات المدخلة مش صحيح."
"emptyUsername" = "اسم المستخدم مطلوب"
"emptyPassword" = "الباسورد مطلوب"
"wrongUsernameOrPassword" = "اسم المستخدم أو الباسورد أو السر مش صحيح."
"wrongUsernameOrPassword" = "اسم المستخدم أو كلمة المرور أو كود المصادقة الثنائية غير صحيح."
"successLogin" = "تسجيل دخول ناجح"
[pages.index]
@ -497,11 +497,11 @@
[pages.settings.security]
"admin" = "بيانات الأدمن"
"secret" = "توكن سري"
"loginSecurity" = "أمان تسجيل الدخول"
"loginSecurityDesc" = "بيضيف طبقة مصادقة إضافية لزيادة الأمان."
"secretToken" = "توكن سري"
"secretTokenDesc" = "احتفظ بالتوكن ده في مكان آمن. التوكن ده مطلوب لتسجيل الدخول ومش ممكن تسترجعه لو ضاع."
"twoFactor" = "المصادقة الثنائية"
"twoFactorEnable" = "تفعيل المصادقة الثنائية"
"twoFactorEnableDesc" = "يضيف طبقة إضافية من المصادقة لتعزيز الأمان."
"twoFactorToken" = "رمز المصادقة الثنائية"
"twoFactorTokenDesc" = "يرجى حفظ هذا الرمز بشكل آمن في تطبيق المصادقة."
[pages.settings.toasts]
"modifySettings" = "تعديل الإعدادات"

View file

@ -51,7 +51,7 @@
"install" = "Install"
"clients" = "Clients"
"usage" = "Usage"
"secretToken" = "Secret Token"
"twoFactorCode" = "Code"
"remained" = "Remained"
"security" = "Security"
"secAlertTitle" = "Security Alert"
@ -87,7 +87,7 @@
"invalidFormData" = "The Input data format is invalid."
"emptyUsername" = "Username is required"
"emptyPassword" = "Password is required"
"wrongUsernameOrPassword" = "Invalid username or password or secret."
"wrongUsernameOrPassword" = "Invalid username or password or two-factor code."
"successLogin" = "Login"
[pages.index]
@ -497,11 +497,11 @@
[pages.settings.security]
"admin" = "Admin credentials"
"secret" = "Secret Token"
"loginSecurity" = "Secure Login"
"loginSecurityDesc" = "Adds an additional layer of authentication to provide more security."
"secretToken" = "Secret Token"
"secretTokenDesc" = "Please securely store this token in a safe place. This token is required for login and cannot be recovered."
"twoFactor" = "Two-factor authentication"
"twoFactorEnable" = "Enable 2FA"
"twoFactorEnableDesc" = "Adds an additional layer of authentication to provide more security."
"twoFactorToken" = "2FA token"
"twoFactorTokenDesc" = "Please securely store this token in a authentication app."
[pages.settings.toasts]
"modifySettings" = "Modify Settings"

View file

@ -51,7 +51,7 @@
"install" = "Instalar"
"clients" = "Clientes"
"usage" = "Uso"
"secretToken" = "Token Secreto"
"twoFactorCode" = "Código"
"remained" = "Restante"
"security" = "Seguridad"
"secAlertTitle" = "Alerta de Seguridad"
@ -87,7 +87,7 @@
"invalidFormData" = "El formato de los datos de entrada es inválido."
"emptyUsername" = "Por favor ingresa el nombre de usuario."
"emptyPassword" = "Por favor ingresa la contraseña."
"wrongUsernameOrPassword" = "Nombre de usuario o contraseña inválidos."
"wrongUsernameOrPassword" = "Nombre de usuario, contraseña o código de dos factores incorrecto."
"successLogin" = "Inicio de Sesión Exitoso"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "Credenciales de administrador"
"secret" = "Token Secreto"
"loginSecurity" = "Seguridad de Inicio de Sesión"
"loginSecurityDesc" = "Habilitar un paso adicional de seguridad para el inicio de sesión de usuarios."
"secretToken" = "Token Secreto"
"secretTokenDesc" = "Por favor, copia y guarda este token de forma segura en un lugar seguro. Este token es necesario para iniciar sesión y no se puede recuperar con la herramienta de comando x-ui."
"twoFactor" = "Autenticación de dos factores"
"twoFactorEnable" = "Habilitar 2FA"
"twoFactorEnableDesc" = "Añade una capa adicional de autenticación para mayor seguridad."
"twoFactorToken" = "Token de 2FA"
"twoFactorTokenDesc" = "Guarde este token de forma segura en una aplicación de autenticación."
[pages.settings.toasts]
"modifySettings" = "Modificar Configuraciones "

View file

@ -51,7 +51,7 @@
"install" = "نصب"
"clients" = "کاربران"
"usage" = "استفاده"
"secretToken" = "توکن امنیتی"
"twoFactorCode" = "کد"
"remained" = "باقی‌مانده"
"security" = "امنیت"
"secAlertTitle" = "هشدار‌امنیتی"
@ -87,7 +87,7 @@
"invalidFormData" = "اطلاعات به‌درستی وارد نشده‌است"
"emptyUsername" = "لطفا یک نام‌کاربری وارد کنید‌"
"emptyPassword" = "لطفا یک رمزعبور وارد کنید"
"wrongUsernameOrPassword" = "نام‌کاربری یا رمزعبور‌اشتباه‌است"
"wrongUsernameOrPassword" = "نام کاربری، رمز عبور یا کد دو مرحله‌ای نامعتبر است."
"successLogin" = "ورود"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "اعتبارنامه‌های ادمین"
"secret" = "توکن مخفی"
"loginSecurity" = "ورود ایمن"
"loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند"
"secretToken" = "توکن مخفی"
"secretTokenDesc" = "لطفاً این توکن را در مکانی امن ذخیره کنید. این توکن برای ورود به سیستم مورد نیاز است و قابل بازیابی نیست"
"twoFactor" = "احراز هویت دو مرحله‌ای"
"twoFactorEnable" = "فعال‌سازی 2FA"
"twoFactorEnableDesc" = "یک لایه اضافی امنیتی برای احراز هویت فراهم می‌کند."
"twoFactorToken" = "توکن 2FA"
"twoFactorTokenDesc" = "لطفاً این توکن را در یک برنامه احراز هویت ذخیره کنید."
[pages.settings.toasts]
"modifySettings" = "ویرایش تنظیمات"

View file

@ -51,7 +51,7 @@
"install" = "Instal"
"clients" = "Klien"
"usage" = "Penggunaan"
"secretToken" = "Token Rahasia"
"twoFactorCode" = "Kode"
"remained" = "Tersisa"
"security" = "Keamanan"
"secAlertTitle" = "Peringatan keamanan"
@ -87,7 +87,7 @@
"invalidFormData" = "Format data input tidak valid."
"emptyUsername" = "Nama Pengguna diperlukan"
"emptyPassword" = "Kata Sandi diperlukan"
"wrongUsernameOrPassword" = "Nama pengguna atau kata sandi tidak valid."
"wrongUsernameOrPassword" = "Username, kata sandi, atau kode dua faktor tidak valid."
"successLogin" = "Login berhasil"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "Kredensial admin"
"secret" = "Token Rahasia"
"loginSecurity" = "Login Aman"
"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
"secretToken" = "Token Rahasia"
"secretTokenDesc" = "Simpan token ini dengan aman di tempat yang aman. Token ini diperlukan untuk login dan tidak dapat dipulihkan."
"twoFactor" = "Autentikasi dua faktor"
"twoFactorEnable" = "Aktifkan 2FA"
"twoFactorEnableDesc" = "Menambahkan lapisan autentikasi tambahan untuk keamanan lebih."
"twoFactorToken" = "Token 2FA"
"twoFactorTokenDesc" = "Harap simpan token ini dengan aman di aplikasi autentikasi."
[pages.settings.toasts]
"modifySettings" = "Ubah Pengaturan"

View file

@ -51,7 +51,7 @@
"install" = "インストール"
"clients" = "クライアント"
"usage" = "利用状況"
"secretToken" = "シークレットトークン"
"twoFactorCode" = "コード"
"remained" = "残り"
"security" = "セキュリティ"
"secAlertTitle" = "セキュリティアラート"
@ -87,7 +87,7 @@
"invalidFormData" = "データ形式エラー"
"emptyUsername" = "ユーザー名を入力してください"
"emptyPassword" = "パスワードを入力してください"
"wrongUsernameOrPassword" = "ユーザー名またはパスワードが間違っています"
"wrongUsernameOrPassword" = "ユーザー名、パスワード、または二段階認証コードが無効です。"
"successLogin" = "ログイン成功"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "管理者の資格情報"
"secret" = "セキュリティトークン"
"loginSecurity" = "ログインセキュリティ"
"loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる"
"secretToken" = "セキュリティトークン"
"secretTokenDesc" = "このトークンを安全な場所に保管してください。このトークンはログインに使用され、紛失すると回復できません。"
"twoFactor" = "二段階認証"
"twoFactorEnable" = "2FAを有効化"
"twoFactorEnableDesc" = "セキュリティを強化するために追加の認証層を追加します。"
"twoFactorToken" = "2FAトークン"
"twoFactorTokenDesc" = "このトークンを認証アプリに安全に保存してください。"
[pages.settings.toasts]
"modifySettings" = "設定を変更"

View file

@ -51,7 +51,7 @@
"install" = "Instalar"
"clients" = "Clientes"
"usage" = "Uso"
"secretToken" = "Token Secreto"
"twoFactorCode" = "Código"
"remained" = "Restante"
"security" = "Segurança"
"secAlertTitle" = "Alerta de Segurança"
@ -87,7 +87,7 @@
"invalidFormData" = "O formato dos dados de entrada é inválido."
"emptyUsername" = "Nome de usuário é obrigatório"
"emptyPassword" = "Senha é obrigatória"
"wrongUsernameOrPassword" = "Nome de usuário, senha ou segredo inválidos."
"wrongUsernameOrPassword" = "Nome de usuário, senha ou código de dois fatores inválido."
"successLogin" = "Login realizado com sucesso"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "Credenciais de administrador"
"secret" = "Token Secreto"
"loginSecurity" = "Login Seguro"
"loginSecurityDesc" = "Adiciona uma camada extra de autenticação para fornecer mais segurança."
"secretToken" = "Token Secreto"
"secretTokenDesc" = "Por favor, armazene este token em um local seguro. Este token é necessário para o login e não pode ser recuperado."
"twoFactor" = "Autenticação de dois fatores"
"twoFactorEnable" = "Ativar 2FA"
"twoFactorEnableDesc" = "Adiciona uma camada extra de autenticação para mais segurança."
"twoFactorToken" = "Token 2FA"
"twoFactorTokenDesc" = "Guarde este token com segurança em um aplicativo de autenticação."
[pages.settings.toasts]
"modifySettings" = "Modificar Configurações"

View file

@ -51,7 +51,7 @@
"install" = "Установка"
"clients" = "Клиенты"
"usage" = "Использование"
"secretToken" = "Секретный токен"
"twoFactorCode" = "Код"
"remained" = "Остаток"
"security" = "Безопасность"
"secAlertTitle" = "Предупреждение системы безопасности"
@ -87,7 +87,7 @@
"invalidFormData" = "Недопустимый формат данных"
"emptyUsername" = "Введите имя пользователя"
"emptyPassword" = "Введите пароль"
"wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или секретный токен."
"wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или код двухфакторной аутентификации."
"successLogin" = "Успешный вход"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "Учетные данные администратора"
"secret" = "Секретный токен"
"loginSecurity" = "Безопасность входа"
"loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя"
"secretToken" = "Секретный токен"
"secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью инструмента x-ui"
"twoFactor" = "Двухфакторная аутентификация"
"twoFactorEnable" = "Включить 2FA"
"twoFactorEnableDesc" = "Добавляет дополнительный уровень аутентификации для повышения безопасности."
"twoFactorToken" = "Токен 2FA"
"twoFactorTokenDesc" = "Пожалуйста, сохраните этот токен в приложении для аутентификации."
[pages.settings.toasts]
"modifySettings" = "Настройки изменены"

View file

@ -51,7 +51,7 @@
"install" = "Yükle"
"clients" = "Müşteriler"
"usage" = "Kullanım"
"secretToken" = "Gizli Anahtar"
"twoFactorCode" = "Kod"
"remained" = "Kalan"
"security" = "Güvenlik"
"secAlertTitle" = "Güvenlik Uyarısı"
@ -87,7 +87,7 @@
"invalidFormData" = "Girdi verisi formatı geçersiz."
"emptyUsername" = "Kullanıcı adı gerekli"
"emptyPassword" = "Şifre gerekli"
"wrongUsernameOrPassword" = "Geçersiz kullanıcı adı veya şifre veya gizli anahtar."
"wrongUsernameOrPassword" = "Geçersiz kullanıcı adı, şifre veya iki adımlı doğrulama kodu."
"successLogin" = "Giriş Başarılı"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "Yönetici kimlik bilgileri"
"secret" = "Gizli Anahtar"
"loginSecurity" = "Güvenli Giriş"
"loginSecurityDesc" = "Daha fazla güvenlik sağlamak için ek bir kimlik doğrulama katmanı ekler."
"secretToken" = "Gizli Anahtar"
"secretTokenDesc" = "Bu anahtarı güvenli bir yerde saklayın. Bu anahtar giriş için gereklidir ve geri alınamaz."
"twoFactor" = "İki adımlı doğrulama"
"twoFactorEnable" = "2FA'yı Etkinleştir"
"twoFactorEnableDesc" = "Daha fazla güvenlik için ek bir doğrulama katmanı ekler."
"twoFactorToken" = "2FA Token"
"twoFactorTokenDesc" = "Lütfen bu token'ı bir doğrulama uygulamasında güvenle saklayın."
[pages.settings.toasts]
"modifySettings" = "Ayarları Değiştir"

View file

@ -51,7 +51,7 @@
"install" = "Встановити"
"clients" = "Клієнти"
"usage" = "Використання"
"secretToken" = "Секретний маркер"
"twoFactorCode" = "Код"
"remained" = "Залишилося"
"security" = "Беспека"
"secAlertTitle" = "Попередження системи безпеки"
@ -87,7 +87,7 @@
"invalidFormData" = "Формат вхідних даних недійсний."
"emptyUsername" = "Потрібне ім'я користувача"
"emptyPassword" = "Потрібен пароль"
"wrongUsernameOrPassword" = "Невірне ім'я користувача або пароль."
"wrongUsernameOrPassword" = "Невірне ім’я користувача, пароль або код двофакторної аутентифікації."
"successLogin" = "Вхід"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "Облікові дані адміністратора"
"secret" = "Секретний маркер"
"loginSecurity" = "Безпечний вхід"
"loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки."
"secretToken" = "Секретний маркер"
"secretTokenDesc" = "Будь ласка, надійно зберігайте цей маркер у безпечному місці. Цей маркер потрібен для входу, і його неможливо відновити."
"twoFactor" = "Двофакторна аутентифікація"
"twoFactorEnable" = "Увімкнути 2FA"
"twoFactorEnableDesc" = "Додає додатковий рівень аутентифікації для підвищення безпеки."
"twoFactorToken" = "Токен 2FA"
"twoFactorTokenDesc" = "Будь ласка, збережіть цей токен у захищеному додатку для аутентифікації."
[pages.settings.toasts]
"modifySettings" = "Змінити налаштування"

View file

@ -51,7 +51,7 @@
"install" = "Cài đặt"
"clients" = "Các khách hàng"
"usage" = "Sử dụng"
"secretToken" = "Mã bí mật"
"twoFactorCode" = "Mã"
"remained" = "Còn lại"
"security" = "Bảo vệ"
"secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7"
@ -87,7 +87,7 @@
"invalidFormData" = "Dạng dữ liệu nhập không hợp lệ."
"emptyUsername" = "Vui lòng nhập tên người dùng."
"emptyPassword" = "Vui lòng nhập mật khẩu."
"wrongUsernameOrPassword" = "Tên người dùng hoặc mật khẩu không đúng."
"wrongUsernameOrPassword" = "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ."
"successLogin" = "Đăng nhập thành công."
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "Thông tin đăng nhập quản trị viên"
"secret" = "Mã thông báo bí mật"
"loginSecurity" = "Bảo mật đăng nhập"
"loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng"
"secretToken" = "Mã bí mật"
"secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã này một cách an toàn ở nơi an toàn. Mã này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui."
"twoFactor" = "Xác thực hai yếu tố"
"twoFactorEnable" = "Bật 2FA"
"twoFactorEnableDesc" = "Thêm một lớp bảo mật bổ sung để tăng cường an toàn."
"twoFactorToken" = "Token 2FA"
"twoFactorTokenDesc" = "Vui lòng lưu token này vào ứng dụng xác thực một cách an toàn."
[pages.settings.toasts]
"modifySettings" = "Chỉnh sửa cài đặt "

View file

@ -51,7 +51,7 @@
"install" = "安装"
"clients" = "客户端"
"usage" = "使用情况"
"secretToken" = "安全密钥"
"twoFactorCode" = "代码"
"remained" = "剩余"
"security" = "安全"
"secAlertTitle" = "安全警报"
@ -87,7 +87,7 @@
"invalidFormData" = "数据格式错误"
"emptyUsername" = "请输入用户名"
"emptyPassword" = "请输入密码"
"wrongUsernameOrPassword" = "用户名或密码错误"
"wrongUsernameOrPassword" = "用户名、密码或双重验证码无效。"
"successLogin" = "登录"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "管理员凭据"
"secret" = "安全令牌"
"loginSecurity" = "登录安全"
"loginSecurityDesc" = "添加额外的身份验证以提高安全性"
"secretToken" = "安全令牌"
"secretTokenDesc" = "请将此令牌存储在安全的地方。此令牌用于登录,丢失无法恢复。"
"twoFactor" = "双重验证"
"twoFactorEnable" = "启用2FA"
"twoFactorEnableDesc" = "增加额外的验证层以提高安全性。"
"twoFactorToken" = "2FA令牌"
"twoFactorTokenDesc" = "请将此令牌安全地保存在验证应用中。"
[pages.settings.toasts]
"modifySettings" = "修改设置"

View file

@ -51,7 +51,7 @@
"install" = "安裝"
"clients" = "客戶端"
"usage" = "使用情況"
"secretToken" = "安全金鑰"
"twoFactorCode" = "代碼"
"remained" = "剩餘"
"security" = "安全"
"secAlertTitle" = "安全警報"
@ -87,7 +87,7 @@
"invalidFormData" = "資料格式錯誤"
"emptyUsername" = "請輸入使用者名稱"
"emptyPassword" = "請輸入密碼"
"wrongUsernameOrPassword" = "使用者名稱或密碼錯誤"
"wrongUsernameOrPassword" = "用戶名、密碼或雙重驗證碼無效。"
"successLogin" = "登入"
[pages.index]
@ -499,11 +499,11 @@
[pages.settings.security]
"admin" = "管理員憑證"
"secret" = "安全令牌"
"loginSecurity" = "登入安全"
"loginSecurityDesc" = "新增額外的身份驗證以提高安全性"
"secretToken" = "安全令牌"
"secretTokenDesc" = "請將此令牌儲存在安全的地方。此令牌用於登入,丟失無法恢復。"
"twoFactor" = "雙重驗證"
"twoFactorEnable" = "啟用2FA"
"twoFactorEnableDesc" = "增加額外的驗證層以提高安全性。"
"twoFactorToken" = "2FA令牌"
"twoFactorTokenDesc" = "請將此令牌安全地保存在驗證應用中。"
[pages.settings.toasts]
"modifySettings" = "修改設定"

View file

@ -184,10 +184,8 @@ reset_user() {
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
}
@ -1731,7 +1729,7 @@ show_menu() {
${green}4.${plain} Legacy Version │
${green}5.${plain} Uninstall │
│────────────────────────────────────────────────│
${green}6.${plain} Reset Username & Password & Secret Token
${green}6.${plain} Reset Username & Password
${green}7.${plain} Reset Web Base Path │
${green}8.${plain} Reset Settings │
${green}9.${plain} Change Port │