mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-31 09:29:34 +00:00
feat(api-tokens): manage multiple named tokens; add tab/section anchor URLs
Replace the single regenerable API token with a named-token list: - New ApiToken model + service with constant-time auth matching - Seeder migrates the legacy `apiToken` setting into a "default" row - Security tab gets create/enable/delete UI; api-docs page links to it - Dedicated "API Tokens" section in the in-panel docs URL anchors now reflect the active tab/section on Settings, Xray, and API Docs pages, so deep links like `/panel/settings#security` work. Translations for the 8 new SecurityTab strings added across all locales.
This commit is contained in:
@@ -19,6 +19,7 @@ type APIController struct {
|
||||
nodeController *NodeController
|
||||
settingService service.SettingService
|
||||
userService service.UserService
|
||||
apiTokenService service.ApiTokenService
|
||||
Tgbot service.Tgbot
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ func (a *APIController) checkAPIAuth(c *gin.Context) {
|
||||
auth := c.GetHeader("Authorization")
|
||||
if strings.HasPrefix(auth, "Bearer ") {
|
||||
tok := strings.TrimPrefix(auth, "Bearer ")
|
||||
if a.settingService.MatchApiToken(tok) {
|
||||
if a.apiTokenService.Match(tok) {
|
||||
if u, err := a.userService.GetFirstUser(); err == nil {
|
||||
session.SetAPIAuthUser(c, u)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/util/crypto"
|
||||
@@ -22,9 +23,10 @@ type updateUserForm struct {
|
||||
|
||||
// SettingController handles settings and user management operations.
|
||||
type SettingController struct {
|
||||
settingService service.SettingService
|
||||
userService service.UserService
|
||||
panelService service.PanelService
|
||||
settingService service.SettingService
|
||||
userService service.UserService
|
||||
panelService service.PanelService
|
||||
apiTokenService service.ApiTokenService
|
||||
}
|
||||
|
||||
// NewSettingController creates a new SettingController and initializes its routes.
|
||||
@@ -44,8 +46,10 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/updateUser", a.updateUser)
|
||||
g.POST("/restartPanel", a.restartPanel)
|
||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||
g.GET("/getApiToken", a.getApiToken)
|
||||
g.POST("/regenerateApiToken", a.regenerateApiToken)
|
||||
g.GET("/apiTokens", a.listApiTokens)
|
||||
g.POST("/apiTokens/create", a.createApiToken)
|
||||
g.POST("/apiTokens/delete/:id", a.deleteApiToken)
|
||||
g.POST("/apiTokens/setEnabled/:id", a.setApiTokenEnabled)
|
||||
}
|
||||
|
||||
// getAllSetting retrieves all current settings.
|
||||
@@ -130,26 +134,56 @@ func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||
jsonObj(c, defaultJsonConfig, nil)
|
||||
}
|
||||
|
||||
// getApiToken returns the panel's API token used by remote central
|
||||
// panels to authenticate as Bearer tokens. The token is auto-generated
|
||||
// on first read so existing installs upgrade transparently.
|
||||
func (a *SettingController) getApiToken(c *gin.Context) {
|
||||
tok, err := a.settingService.GetApiToken()
|
||||
type apiTokenCreateForm struct {
|
||||
Name string `json:"name" form:"name"`
|
||||
}
|
||||
|
||||
type apiTokenEnabledForm struct {
|
||||
Enabled bool `json:"enabled" form:"enabled"`
|
||||
}
|
||||
|
||||
func (a *SettingController) listApiTokens(c *gin.Context) {
|
||||
rows, err := a.apiTokenService.List()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, tok, nil)
|
||||
jsonObj(c, rows, nil)
|
||||
}
|
||||
|
||||
// regenerateApiToken rotates the API token. Any central panel that had
|
||||
// the old value cached will start failing heartbeats until it is updated
|
||||
// with the new token — that's intentional, it's the whole point of rotation.
|
||||
func (a *SettingController) regenerateApiToken(c *gin.Context) {
|
||||
tok, err := a.settingService.RegenerateApiToken()
|
||||
func (a *SettingController) createApiToken(c *gin.Context) {
|
||||
form := &apiTokenCreateForm{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
row, err := a.apiTokenService.Create(form.Name)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, tok, nil)
|
||||
jsonObj(c, row, nil)
|
||||
}
|
||||
|
||||
func (a *SettingController) deleteApiToken(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), a.apiTokenService.Delete(id))
|
||||
}
|
||||
|
||||
func (a *SettingController) setApiTokenEnabled(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
form := &apiTokenEnabledForm{}
|
||||
if bindErr := c.ShouldBind(form); bindErr != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), bindErr)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), a.apiTokenService.SetEnabled(id, form.Enabled))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user