mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-10-26 18:14:50 +00:00
Compare commits
3 commits
fb9839cb3d
...
254e5a6761
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
254e5a6761 | ||
|
|
d1aa040d7c | ||
|
|
8e62acc9e5 |
27 changed files with 463 additions and 147 deletions
|
|
@ -1,5 +1,11 @@
|
|||
# API 文档
|
||||
|
||||
要向 REST API v2 发送请求,您需要在每个请求中包含带有 Bearer 类型的 Authorization 头和令牌。
|
||||
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
## Inbounds
|
||||
|
||||
### 获取所有 Inbounds
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# API Documentation
|
||||
|
||||
To make requests to REST API v2, you need to include the Authorization header with the Bearer type and the token in each request.
|
||||
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
## Inbounds
|
||||
|
||||
### Get All Inbounds
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Documentación de la API
|
||||
|
||||
Para realizar solicitudes al REST API v2, es necesario incluir en cada solicitud el encabezado Authorization con el tipo Bearer y el token.
|
||||
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
## Inbounds
|
||||
|
||||
### Obtener todos los Inbounds
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# مستندات API
|
||||
|
||||
برای انجام درخواستها به REST API v2، در هر درخواست باید هدر Authorization با نوع Bearer و توکن ارسال شود.
|
||||
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
## ورودیها (Inbounds)
|
||||
|
||||
### دریافت تمام ورودیها
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# API Documentation
|
||||
|
||||
Для выполнения запросов к REST API v2 в каждом запросе необходимо передавать заголовок Authorization с типом Bearer и указанием токена.
|
||||
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
## Inbounds
|
||||
|
||||
### Get All Inbounds
|
||||
|
|
|
|||
|
|
@ -57,6 +57,20 @@ class HttpUtil {
|
|||
}
|
||||
}
|
||||
|
||||
static async delete(url, params, options = {}) {
|
||||
try {
|
||||
const resp = await axios.delete(url, { params, ...options });
|
||||
const msg = this._respToMsg(resp);
|
||||
this._handleMsg(msg);
|
||||
return msg;
|
||||
} catch (error) {
|
||||
console.error('DELETE request failed:', error);
|
||||
const errorMsg = new Msg(false, error.response?.data?.message || error.message || 'Request failed');
|
||||
this._handleMsg(errorMsg);
|
||||
return errorMsg;
|
||||
}
|
||||
}
|
||||
|
||||
static async postWithModal(url, data, modal) {
|
||||
if (modal) {
|
||||
modal.loading(true);
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ func NewAPIController(g *gin.RouterGroup) *APIController {
|
|||
}
|
||||
|
||||
func (controller *APIController) initRouter(router *gin.RouterGroup) {
|
||||
apiV1 := router.Group("/panel/api")
|
||||
apiV1.Use(controller.checkLogin)
|
||||
apiV1 := router.Group("/panel/api")
|
||||
apiV1.Use(controller.checkLogin)
|
||||
|
||||
inboundsApiGroup := apiV1.Group("/inbounds")
|
||||
inboundsApiGroup := apiV1.Group("/inbounds")
|
||||
controller.inbounds = NewInboundController(inboundsApiGroup)
|
||||
|
||||
inboundRoutes := []struct {
|
||||
|
|
@ -64,13 +64,13 @@ func (a *APIController) createBackup(c *gin.Context) {
|
|||
|
||||
func (controller *APIController) initApiV2Router(router *gin.RouterGroup) {
|
||||
apiV2 := router.Group("/api/v2")
|
||||
apiV2.Use(controller.checkLogin)
|
||||
apiV2.Use(controller.apiTokenGuard)
|
||||
|
||||
serverApiGroup := apiV2.Group("/server")
|
||||
inboundsApiGroup := apiV2.Group("/inbounds")
|
||||
|
||||
controller.inbounds = NewInboundController(inboundsApiGroup)
|
||||
controller.server = NewServerController(serverApiGroup)
|
||||
controller.inbounds = NewInboundController(inboundsApiGroup)
|
||||
controller.server = NewServerController(serverApiGroup)
|
||||
|
||||
/**
|
||||
* Inbounds
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"x-ui/logger"
|
||||
"x-ui/web/locale"
|
||||
"x-ui/web/session"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"x-ui/web/service"
|
||||
)
|
||||
|
||||
type BaseController struct{}
|
||||
type BaseController struct{
|
||||
settingService service.SettingService
|
||||
}
|
||||
|
||||
func (a *BaseController) checkLogin(c *gin.Context) {
|
||||
if !session.IsLogin(c) {
|
||||
|
|
@ -35,3 +40,39 @@ func I18nWeb(c *gin.Context, name string, params ...string) string {
|
|||
msg := i18nFunc(locale.Web, name, params...)
|
||||
return msg
|
||||
}
|
||||
|
||||
func (a *BaseController) apiTokenGuard(c *gin.Context) {
|
||||
bearerToken := c.Request.Header.Get("Authorization")
|
||||
tokenParts := strings.Split(bearerToken, " ")
|
||||
if len(tokenParts) != 2 {
|
||||
pureJsonMsg(c, http.StatusUnauthorized, false, "Invalid token format")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
reqToken := tokenParts[1]
|
||||
token, err := a.settingService.GetApiToken()
|
||||
|
||||
if err != nil {
|
||||
pureJsonMsg(c, http.StatusUnauthorized, false, err.Error())
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if reqToken != token {
|
||||
pureJsonMsg(c, http.StatusUnauthorized, false, "Auth failed")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
userService := service.UserService{}
|
||||
user, err := userService.GetFirstUser()
|
||||
if err != nil {
|
||||
fmt.Println("get current user info failed, error info:", err)
|
||||
}
|
||||
|
||||
session.SetSessionUser(c, user)
|
||||
|
||||
c.Next()
|
||||
|
||||
session.ClearSession(c)
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
func (a *InboundController) getInbounds(c *gin.Context) {
|
||||
user := session.GetLoginUser(c)
|
||||
user := session.GetSessionUser(c)
|
||||
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||
|
|
@ -54,10 +54,19 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
|||
jsonObj(c, inbounds, nil)
|
||||
}
|
||||
|
||||
func (a *InboundController) getAllInbounds(c *gin.Context) {
|
||||
inbounds, err := a.inboundService.GetAllInbounds()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, inbounds, nil)
|
||||
}
|
||||
|
||||
func (a *InboundController) getInbound(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "get"), err)
|
||||
jsonMsg(c, I18nWeb(c, "get"), errors.New("Invalid inbound id"))
|
||||
return
|
||||
}
|
||||
inbound, err := a.inboundService.GetInbound(id)
|
||||
|
|
@ -95,7 +104,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||
jsonMsg(c, I18nWeb(c, "pages.inbounds.create"), err)
|
||||
return
|
||||
}
|
||||
user := session.GetLoginUser(c)
|
||||
user := session.GetSessionUser(c)
|
||||
inbound.UserId = user.Id
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
|
|
@ -343,7 +352,7 @@ func (a *InboundController) importInbound(c *gin.Context) {
|
|||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
user := session.GetLoginUser(c)
|
||||
user := session.GetSessionUser(c)
|
||||
inbound.Id = 0
|
||||
inbound.UserId = user.Id
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ func (a *IndexController) login(c *gin.Context) {
|
|||
}
|
||||
|
||||
session.SetMaxAge(c, sessionMaxAge*60)
|
||||
session.SetLoginUser(c, user)
|
||||
session.SetSessionUser(c, user)
|
||||
if err := sessions.Default(c).Save(); err != nil {
|
||||
logger.Warning("Unable to save session: ", err)
|
||||
return
|
||||
|
|
@ -97,7 +97,7 @@ func (a *IndexController) login(c *gin.Context) {
|
|||
}
|
||||
|
||||
func (a *IndexController) logout(c *gin.Context) {
|
||||
user := session.GetLoginUser(c)
|
||||
user := session.GetSessionUser(c)
|
||||
if user != nil {
|
||||
logger.Infof("%s logged out successfully", user.Username)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package controller
|
|||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
|
||||
"x-ui/web/entity"
|
||||
"x-ui/web/service"
|
||||
|
|
@ -28,6 +31,10 @@ type SettingController struct {
|
|||
panelService service.PanelService
|
||||
}
|
||||
|
||||
type ApiTokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func NewSettingController(g *gin.RouterGroup) *SettingController {
|
||||
a := &SettingController{}
|
||||
a.initRouter(g)
|
||||
|
|
@ -45,6 +52,10 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
|||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||
g.POST("/updateUserSecret", a.updateSecret)
|
||||
g.POST("/getUserSecret", a.getUserSecret)
|
||||
|
||||
g.GET("/apiToken", a.getApiToken)
|
||||
g.POST("/apiToken", a.generateApiToken)
|
||||
g.DELETE("/apiToken", a.removeApiToken)
|
||||
}
|
||||
|
||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
|
|
@ -83,7 +94,7 @@ func (a *SettingController) updateUser(c *gin.Context) {
|
|||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
user := session.GetLoginUser(c)
|
||||
user := session.GetSessionUser(c)
|
||||
if user.Username != form.OldUsername || user.Password != form.OldPassword {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), errors.New(I18nWeb(c, "pages.settings.toasts.originalUserPassIncorrect")))
|
||||
return
|
||||
|
|
@ -96,7 +107,7 @@ func (a *SettingController) updateUser(c *gin.Context) {
|
|||
if err == nil {
|
||||
user.Username = form.NewUsername
|
||||
user.Password = form.NewPassword
|
||||
session.SetLoginUser(c, user)
|
||||
session.SetSessionUser(c, user)
|
||||
}
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err)
|
||||
}
|
||||
|
|
@ -112,17 +123,17 @@ func (a *SettingController) updateSecret(c *gin.Context) {
|
|||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
}
|
||||
user := session.GetLoginUser(c)
|
||||
user := session.GetSessionUser(c)
|
||||
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
|
||||
if err == nil {
|
||||
user.LoginSecret = form.LoginSecret
|
||||
session.SetLoginUser(c, user)
|
||||
session.SetSessionUser(c, user)
|
||||
}
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err)
|
||||
}
|
||||
|
||||
func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||
loginUser := session.GetLoginUser(c)
|
||||
loginUser := session.GetSessionUser(c)
|
||||
user := a.userService.GetUserSecret(loginUser.Id)
|
||||
if user != nil {
|
||||
jsonObj(c, user, nil)
|
||||
|
|
@ -137,3 +148,50 @@ func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
|
|||
}
|
||||
jsonObj(c, defaultJsonConfig, nil)
|
||||
}
|
||||
|
||||
func (a *SettingController) getApiToken(c *gin.Context) {
|
||||
response := &ApiTokenResponse{}
|
||||
token, err := a.settingService.GetApiToken()
|
||||
if err != nil {
|
||||
jsonObj(c, response , err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Token = token
|
||||
|
||||
jsonObj(c, response , nil)
|
||||
}
|
||||
|
||||
func (a *SettingController) generateApiToken(c *gin.Context) {
|
||||
response := &ApiTokenResponse{}
|
||||
randomBytes := make([]byte, 32)
|
||||
|
||||
_, err := rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
jsonObj(c, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
hash := sha512.Sum512(randomBytes)
|
||||
response.Token = hex.EncodeToString(hash[:])
|
||||
|
||||
saveErr := a.settingService.SaveApiToken(response.Token)
|
||||
|
||||
if saveErr != nil {
|
||||
jsonObj(c, nil, saveErr)
|
||||
return
|
||||
}
|
||||
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.settings.security.apiTokenGeneratedSuccess"), response, nil)
|
||||
}
|
||||
|
||||
func (a *SettingController) removeApiToken(c *gin.Context) {
|
||||
err := a.settingService.RemoveApiToken()
|
||||
|
||||
if err != nil {
|
||||
jsonObj(c, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonMsg(c, "Removed", nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ type AllSetting struct {
|
|||
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
||||
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
||||
Datepicker string `json:"datepicker" form:"datepicker"`
|
||||
ApiToken string `json:"apiToken" form:"apiToken"`
|
||||
}
|
||||
|
||||
func (s *AllSetting) CheckValid() error {
|
||||
|
|
|
|||
|
|
@ -235,6 +235,28 @@
|
|||
</a-list-item>
|
||||
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
||||
</a-form>
|
||||
|
||||
<a-divider>{{ i18n "pages.settings.security.apiTitle"}}</a-divider>
|
||||
<a-list-item>
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12" style="white-space: pre-line;">
|
||||
<a-list-item-meta description='{{ i18n "pages.settings.security.apiDescription" }}'>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-tag class="tr-info-tag" color="green" v-if="apiToken">[[ apiToken ]]</a-tag>
|
||||
<div style="display: flex; flex-direction: row; align-items: center; gap: 0.5rem; margin-top: 0.5rem">
|
||||
<a-tooltip title='{{ i18n "copy" }}' v-if="apiToken">
|
||||
<a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-api-token'" @click="copyApiToken"></a-button>
|
||||
</a-tooltip>
|
||||
<a-button @click="this.generateApiToken">{{ i18n "pages.settings.security.apiGenerateToken" }}</a-button>
|
||||
<a-tooltip title='{{ i18n "delete" }}' v-if="apiToken">
|
||||
<a-button type="danger" style="min-width: 24px;" size="small" icon="delete" :id="'copy-api-token'" @click="removeApiToken"></a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||
<a-list item-layout="horizontal">
|
||||
|
|
@ -401,6 +423,7 @@
|
|||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
|
||||
{{template "js" .}}
|
||||
<script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script>
|
||||
{{template "component/themeSwitcher" .}}
|
||||
|
|
@ -522,132 +545,166 @@
|
|||
sample = []
|
||||
this.remarkModel.forEach(r => sample.push(this.remarkModels[r]));
|
||||
this.remarkSample = sample.length == 0 ? '' : sample.join(this.remarkSeparator);
|
||||
}
|
||||
},
|
||||
apiToken: null,
|
||||
},
|
||||
methods: {
|
||||
loading(spinning = true) {
|
||||
this.spinning = spinning;
|
||||
},
|
||||
async getAllSetting() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/all");
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.oldAllSetting = new AllSetting(msg.obj);
|
||||
this.allSetting = new AllSetting(msg.obj);
|
||||
app.changeRemarkSample();
|
||||
this.saveBtnDisable = true;
|
||||
}
|
||||
await this.fetchUserSecret();
|
||||
},
|
||||
async updateAllSetting() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/update", this.allSetting);
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
await this.getAllSetting();
|
||||
}
|
||||
},
|
||||
async updateUser() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/updateUser", this.user);
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.user = {};
|
||||
window.location.replace(basePath + "logout");
|
||||
}
|
||||
},
|
||||
async restartPanel() {
|
||||
await new Promise(resolve => {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||
class: themeSwitcher.currentTheme,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
cancelText: '{{ i18n "cancel" }}',
|
||||
onOk: () => resolve(),
|
||||
});
|
||||
});
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/restartPanel");
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.loading(true);
|
||||
await PromiseUtil.sleep(5000);
|
||||
var { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
||||
if (host == this.oldAllSetting.webDomain) host = null;
|
||||
if (port == this.oldAllSetting.webPort) port = null;
|
||||
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
||||
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();
|
||||
},
|
||||
generateRandomString(length) {
|
||||
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||
let randomString = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
randomString += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
return randomString;
|
||||
},
|
||||
async getNewSecret() {
|
||||
if (!this.changeSecret) {
|
||||
this.changeSecret = true;
|
||||
this.user.loginSecret = '';
|
||||
const newSecret = this.generateRandomString(64);
|
||||
await PromiseUtil.sleep(1000);
|
||||
this.user.loginSecret = newSecret;
|
||||
this.changeSecret = false;
|
||||
}
|
||||
},
|
||||
async toggleToken(value) {
|
||||
if (value) {
|
||||
await this.getNewSecret();
|
||||
} else {
|
||||
this.user.loginSecret = "";
|
||||
}
|
||||
},
|
||||
addNoise() {
|
||||
const newNoise = { type: "rand", packet: "10-20", delay: "10-16" };
|
||||
this.noisesArray = [...this.noisesArray, newNoise];
|
||||
},
|
||||
removeNoise(index) {
|
||||
const newNoises = [...this.noisesArray];
|
||||
newNoises.splice(index, 1);
|
||||
this.noisesArray = newNoises;
|
||||
},
|
||||
updateNoiseType(index, value) {
|
||||
const updatedNoises = [...this.noisesArray];
|
||||
updatedNoises[index] = { ...updatedNoises[index], type: value };
|
||||
this.noisesArray = updatedNoises;
|
||||
},
|
||||
updateNoisePacket(index, value) {
|
||||
const updatedNoises = [...this.noisesArray];
|
||||
updatedNoises[index] = { ...updatedNoises[index], packet: value };
|
||||
this.noisesArray = updatedNoises;
|
||||
},
|
||||
updateNoiseDelay(index, value) {
|
||||
const updatedNoises = [...this.noisesArray];
|
||||
updatedNoises[index] = { ...updatedNoises[index], delay: value };
|
||||
this.noisesArray = updatedNoises;
|
||||
},
|
||||
methods: {
|
||||
loading(spinning = true) {
|
||||
this.spinning = spinning;
|
||||
},
|
||||
async getAllSetting() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/all");
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.oldAllSetting = new AllSetting(msg.obj);
|
||||
this.allSetting = new AllSetting(msg.obj);
|
||||
this.apiToken = msg.obj.apiToken;
|
||||
app.changeRemarkSample();
|
||||
this.saveBtnDisable = true;
|
||||
}
|
||||
await this.fetchUserSecret();
|
||||
},
|
||||
async updateAllSetting() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/update", this.allSetting);
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
await this.getAllSetting();
|
||||
}
|
||||
},
|
||||
async updateUser() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/updateUser", this.user);
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.user = {};
|
||||
window.location.replace(basePath + "logout");
|
||||
}
|
||||
},
|
||||
async restartPanel() {
|
||||
await new Promise(resolve => {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||
class: themeSwitcher.currentTheme,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
cancelText: '{{ i18n "cancel" }}',
|
||||
onOk: () => resolve(),
|
||||
});
|
||||
});
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/restartPanel");
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.loading(true);
|
||||
await PromiseUtil.sleep(5000);
|
||||
var { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
||||
if (host == this.oldAllSetting.webDomain) host = null;
|
||||
if (port == this.oldAllSetting.webPort) port = null;
|
||||
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
||||
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();
|
||||
},
|
||||
generateRandomString(length) {
|
||||
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||
let randomString = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
randomString += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
return randomString;
|
||||
},
|
||||
async getNewSecret() {
|
||||
if (!this.changeSecret) {
|
||||
this.changeSecret = true;
|
||||
this.user.loginSecret = '';
|
||||
const newSecret = this.generateRandomString(64);
|
||||
await PromiseUtil.sleep(1000);
|
||||
this.user.loginSecret = newSecret;
|
||||
this.changeSecret = false;
|
||||
}
|
||||
},
|
||||
async toggleToken(value) {
|
||||
if (value) {
|
||||
await this.getNewSecret();
|
||||
} else {
|
||||
this.user.loginSecret = "";
|
||||
}
|
||||
},
|
||||
addNoise() {
|
||||
const newNoise = { type: "rand", packet: "10-20", delay: "10-16" };
|
||||
this.noisesArray = [...this.noisesArray, newNoise];
|
||||
},
|
||||
removeNoise(index) {
|
||||
const newNoises = [...this.noisesArray];
|
||||
newNoises.splice(index, 1);
|
||||
this.noisesArray = newNoises;
|
||||
},
|
||||
updateNoiseType(index, value) {
|
||||
const updatedNoises = [...this.noisesArray];
|
||||
updatedNoises[index] = { ...updatedNoises[index], type: value };
|
||||
this.noisesArray = updatedNoises;
|
||||
},
|
||||
updateNoisePacket(index, value) {
|
||||
const updatedNoises = [...this.noisesArray];
|
||||
updatedNoises[index] = { ...updatedNoises[index], packet: value };
|
||||
this.noisesArray = updatedNoises;
|
||||
},
|
||||
updateNoiseDelay(index, value) {
|
||||
const updatedNoises = [...this.noisesArray];
|
||||
updatedNoises[index] = { ...updatedNoises[index], delay: value };
|
||||
this.noisesArray = updatedNoises;
|
||||
},
|
||||
async generateApiToken() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/apiToken");
|
||||
if (msg && msg.obj) {
|
||||
this.apiToken = msg.obj.token;
|
||||
}
|
||||
this.loading(false);
|
||||
},
|
||||
copyApiToken() {
|
||||
ClipboardJS.copy(this.apiToken);
|
||||
app.$message.success('{{ i18n "copied" }}')
|
||||
},
|
||||
async removeApiToken() {
|
||||
await new Promise(() => {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.settings.security.apiConfirmRemoveTokenTitle" }}',
|
||||
content: '{{ i18n "pages.settings.security.apiConfirmRemoveTokenText" }}',
|
||||
class: themeSwitcher.currentTheme,
|
||||
okText: '{{ i18n "delete"}}',
|
||||
cancelText: '{{ i18n "cancel" }}',
|
||||
onOk: async () => {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.delete("/panel/setting/apiToken");
|
||||
if (msg && msg.success) {
|
||||
app.$message.success('{{ i18n "deleted" }}')
|
||||
this.apiToken = null;
|
||||
}
|
||||
this.loading(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
fragment: {
|
||||
|
|
|
|||
|
|
@ -596,3 +596,46 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
|||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
func (s *SettingService) GetApiToken() (token string, err error) {
|
||||
db := database.GetDB()
|
||||
setting := &model.Setting{}
|
||||
err = db.Model(model.Setting{}).Where("key = 'apiToken'").Find(setting).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return setting.Value, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) SaveApiToken(token string) error {
|
||||
db := database.GetDB()
|
||||
setting := &model.Setting{}
|
||||
err := db.Model(model.Setting{}).Where("key = 'apiToken'").Find(setting).Error
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if setting.Value == "" {
|
||||
newSetting := model.Setting{
|
||||
Key: "apiToken",
|
||||
Value: token,
|
||||
}
|
||||
fmt.Println("New setting created")
|
||||
return db.Model(model.Setting{}).Create(&newSetting).Error
|
||||
}
|
||||
return db.Model(model.Setting{}).
|
||||
Where("key = 'apiToken'").
|
||||
Update("value", token).Error
|
||||
}
|
||||
|
||||
func (s *SettingService) RemoveApiToken() error {
|
||||
db := database.GetDB()
|
||||
setting := &model.Setting{}
|
||||
err := db.Model(model.Setting{}).Where("key = 'apiToken'").Find(setting).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Model(model.Setting{}).Delete(setting, setting.Id).Error
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ func init() {
|
|||
gob.Register(model.User{})
|
||||
}
|
||||
|
||||
func SetLoginUser(c *gin.Context, user *model.User) {
|
||||
func SetSessionUser(c *gin.Context, user *model.User) {
|
||||
if user == nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ func SetMaxAge(c *gin.Context, maxAge int) {
|
|||
})
|
||||
}
|
||||
|
||||
func GetLoginUser(c *gin.Context) *model.User {
|
||||
func GetSessionUser(c *gin.Context) *model.User {
|
||||
s := sessions.Default(c)
|
||||
obj := s.Get(loginUserKey)
|
||||
if obj == nil {
|
||||
|
|
@ -51,7 +51,7 @@ func GetLoginUser(c *gin.Context) *model.User {
|
|||
}
|
||||
|
||||
func IsLogin(c *gin.Context) bool {
|
||||
return GetLoginUser(c) != nil
|
||||
return GetSessionUser(c) != nil
|
||||
}
|
||||
|
||||
func ClearSession(c *gin.Context) {
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"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."
|
||||
"apiDescription" = "To make requests to REST API v2, you need to include the Authorization header with the Bearer type and the token in each request.\nExample: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "Generate token"
|
||||
"apiTokenGeneratedSuccess" = "Token generated"
|
||||
"apiConfirmRemoveTokenTitle" = "Confirm token deletion"
|
||||
"apiConfirmRemoveTokenText" = "After deleting the token, access to the API will be unavailable, and making requests will no longer be possible."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Modify Settings"
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"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."
|
||||
"apiDescription" = "Para realizar solicitudes al REST API v2, es necesario incluir en cada solicitud el encabezado Authorization con el tipo Bearer y el token.\nEjemplo: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "Generar token"
|
||||
"apiTokenGeneratedSuccess" = "Token generado con éxito"
|
||||
"apiConfirmRemoveTokenTitle" = "Confirmar eliminación del token"
|
||||
"apiConfirmRemoveTokenText" = "Después de eliminar el token, el acceso al API no estará disponible y no se podrán realizar solicitudes."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Modificar Configuraciones "
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند"
|
||||
"secretToken" = "توکن مخفی"
|
||||
"secretTokenDesc" = "لطفاً این توکن را در مکانی امن ذخیره کنید. این توکن برای ورود به سیستم مورد نیاز است و قابل بازیابی نیست"
|
||||
"apiDescription" = "برای انجام درخواستها به REST API v2، در هر درخواست باید هدر Authorization با نوع Bearer و توکن ارسال شود.\nمثال: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "توکن ایجاد کنید"
|
||||
"apiTokenGeneratedSuccessful" = "توکن با موفقیت ایجاد شد"
|
||||
"apiConfirmRemoveTokenTitle" = "تایید حذف توکن"
|
||||
"apiConfirmRemoveTokenText" = "پس از حذف توکن، دسترسی به API غیرفعال میشود و انجام درخواستها امکانپذیر نخواهد بود."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "ویرایش تنظیمات"
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"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."
|
||||
"apiDescription" = "Untuk melakukan permintaan ke REST API v2, Anda perlu menyertakan header Authorization dengan tipe Bearer dan token di setiap permintaan.\nContoh: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "Buat token"
|
||||
"apiTokenGeneratedSuccessful" = "Token berhasil dibuat"
|
||||
"apiConfirmRemoveTokenTitle" = "Konfirmasi penghapusan token"
|
||||
"apiConfirmRemoveTokenText" = "Setelah menghapus token, akses ke API akan tidak tersedia, dan permintaan tidak dapat dilakukan."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Ubah Pengaturan"
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる"
|
||||
"secretToken" = "セキュリティトークン"
|
||||
"secretTokenDesc" = "このトークンを安全な場所に保管してください。このトークンはログインに使用され、紛失すると回復できません。"
|
||||
"apiDescription" = "REST API v2 にリクエストを送信するには、Authorization ヘッダーに Bearer タイプのトークンを含める必要があります。\n例: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "トークンを生成"
|
||||
"apiTokenGeneratedSuccessful" = "トークンが正常に生成されました"
|
||||
"apiConfirmRemoveTokenTitle" = "トークン削除の確認"
|
||||
"apiConfirmRemoveTokenText" = "トークンを削除すると、API へのアクセスができなくなり、リクエストを送信できなくなります。"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "設定を変更"
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"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."
|
||||
"apiDescription" = "Para fazer solicitações à REST API v2, você precisa incluir o cabeçalho Authorization com o tipo Bearer e o token em cada solicitação.\nExemplo: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "Gerar token"
|
||||
"apiTokenGeneratedSuccessful" = "Token gerado com sucesso"
|
||||
"apiConfirmRemoveTokenTitle" = "Confirmar exclusão do token"
|
||||
"apiConfirmRemoveTokenText" = "Após excluir o token, o acesso à API não estará mais disponível e não será possível fazer solicitações."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Modificar Configurações"
|
||||
|
|
|
|||
|
|
@ -446,6 +446,12 @@
|
|||
"loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя"
|
||||
"secretToken" = "Секретный токен"
|
||||
"secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью инструмента x-ui"
|
||||
"apiTitle" = "REST API"
|
||||
"apiDescription" = "Для выполнения запросов к REST API v2 в каждом запросе необходимо передавать заголовок Authorization с типом Bearer и указанием токена.\nПример: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "Сгенерировать токен"
|
||||
"apiTokenGeneratedSuccess" = "Токен сгенерирован"
|
||||
"apiConfirmRemoveTokenTitle" = "Подтвердите удаление токена"
|
||||
"apiConfirmRemoveTokenText" = "После удаления токена доступ к API станет недоступным, и выполнение запросов будет невозможно."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Изменение настроек"
|
||||
|
|
@ -454,6 +460,8 @@
|
|||
"originalUserPassIncorrect" = "Неверное имя пользователя или пароль"
|
||||
"userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены"
|
||||
|
||||
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ Закрыта настраиваемая клавиатура!"
|
||||
"noResult" = "❗ Нет результатов!"
|
||||
|
|
@ -592,4 +600,4 @@
|
|||
"disableSuccess" = "✅ {{ .Email }}: Отключено успешно."
|
||||
"askToAddUserId" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваш идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаш идентификатор пользователя: <code>{{ .TgUserID }}</code>"
|
||||
"chooseClient" = "Выберите пользователя для подключения {{ .Inbound }}"
|
||||
"chooseInbound" = "Выберите подключение"
|
||||
"chooseInbound" = "Выберите подключение"
|
||||
|
|
@ -446,6 +446,11 @@
|
|||
"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."
|
||||
"apiDescription" = "REST API v2'ye istek gönderebilmek için, her isteğe Bearer türünde bir yetkilendirme (Authorization) başlığı ve token eklemeniz gerekir.\nÖrnek: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "Token oluştur"
|
||||
"apiTokenGeneratedSuccessful" = "Token başarıyla oluşturuldu"
|
||||
"apiConfirmRemoveTokenTitle" = "Token silme onayı"
|
||||
"apiConfirmRemoveTokenText" = "Token silindikten sonra API erişimi mümkün olmayacak ve istek gönderilemeyecektir."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Ayarları Değiştir"
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки."
|
||||
"secretToken" = "Секретний маркер"
|
||||
"secretTokenDesc" = "Будь ласка, надійно зберігайте цей маркер у безпечному місці. Цей маркер потрібен для входу, і його неможливо відновити."
|
||||
"apiDescription" = "Щоб надсилати запити до REST API v2, вам потрібно додати заголовок Authorization із типом Bearer і токен у кожен запит.\nПриклад: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "Створити токен"
|
||||
"apiTokenGeneratedSuccessful" = "Токен успішно створено"
|
||||
"apiConfirmRemoveTokenTitle" = "Підтвердження видалення токена"
|
||||
"apiConfirmRemoveTokenText" = "Після видалення токена доступ до API буде неможливим, і надсилати запити більше не вдасться."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Змінити налаштування"
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"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."
|
||||
"apiDescription" = "Để gửi yêu cầu đến REST API v2, bạn cần bao gồm tiêu đề Authorization với loại Bearer và token trong mỗi yêu cầu.\nVí dụ: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "Tạo token"
|
||||
"apiTokenGeneratedSuccessful" = "Token đã được tạo thành công"
|
||||
"apiConfirmRemoveTokenTitle" = "Xác nhận xóa token"
|
||||
"apiConfirmRemoveTokenText" = "Sau khi xóa token, quyền truy cập API sẽ không còn khả dụng và bạn sẽ không thể gửi yêu cầu nữa."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Chỉnh sửa cài đặt "
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"loginSecurityDesc" = "添加额外的身份验证以提高安全性"
|
||||
"secretToken" = "安全令牌"
|
||||
"secretTokenDesc" = "请将此令牌存储在安全的地方。此令牌用于登录,丢失无法恢复。"
|
||||
"apiDescription" = "要向 REST API v2 发送请求,您需要在每个请求中包含带有 Bearer 类型的 Authorization 头和令牌。\n示例: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "生成令牌"
|
||||
"apiTokenGeneratedSuccessful" = "令牌生成成功"
|
||||
"apiConfirmRemoveTokenTitle" = "确认删除令牌"
|
||||
"apiConfirmRemoveTokenText" = "删除令牌后,将无法访问 API,并且无法再发送请求。"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "修改设置"
|
||||
|
|
|
|||
|
|
@ -446,6 +446,11 @@
|
|||
"loginSecurityDesc" = "新增額外的身份驗證以提高安全性"
|
||||
"secretToken" = "安全令牌"
|
||||
"secretTokenDesc" = "請將此令牌儲存在安全的地方。此令牌用於登入,丟失無法恢復。"
|
||||
"apiDescription" = "要向 REST API v2 發送請求,您需要在每個請求中包含帶有 Bearer 類型的 Authorization 標頭和權杖。\n示例: Authorization: Bearer {token}"
|
||||
"apiGenerateToken" = "生成權杖"
|
||||
"apiTokenGeneratedSuccessful" = "權杖生成成功"
|
||||
"apiConfirmRemoveTokenTitle" = "確認刪除權杖"
|
||||
"apiConfirmRemoveTokenText" = "刪除權杖後,將無法存取 API,並且無法再發送請求。"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "修改設定"
|
||||
|
|
|
|||
Loading…
Reference in a new issue