feat: refactor auth and xpack integration

This commit is contained in:
ssongliu
2026-04-23 17:00:50 +08:00
committed by zhengkunwang223
parent 69906046e8
commit a917ca00a1
175 changed files with 5676 additions and 3734 deletions

10
.gitignore vendored
View File

@@ -42,9 +42,17 @@ frontend/components.d.ts
frontend/src/xpack
frontend/src/xpack-ee
agent/xpack
agent/xpack-ee
agent/router/entry_xpack.go
agent/server/init_xpack.go
agent/utils/xpack/xpack.go
core/xpack
core/xpack-ee
core/router/entry_xpack.go
core/router/entry_xpackee.go
core/server/init_xpack.go
core/server/init_xpackee.go
core/utils/xpack/xpack.go
core/utils/xpack/xpackee.go
.history/
dist/

View File

@@ -3,9 +3,6 @@ package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
"github.com/gin-gonic/gin"
)
@@ -163,37 +160,6 @@ func (b *BaseApi) DeleteOllamaModel(c *gin.Context) {
helper.Success(c)
}
// @Tags AI
// @Summary Load gpu / xpu info
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/gpu/load [get]
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
ok, client := gpu.New()
if ok {
info, err := client.LoadGpuInfo()
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, info)
return
}
xpuOK, xpuClient := xpu.New()
if xpuOK {
info, err := xpuClient.LoadGpuInfo()
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, info)
return
}
helper.SuccessWithData(c, &common.GpuInfo{})
}
// @Tags AI
// @Summary Bind domain
// @Accept json

66
agent/app/api/v2/gpu.go Normal file
View File

@@ -0,0 +1,66 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
"github.com/gin-gonic/gin"
)
// @Tags AI
// @Summary Load gpu / xpu info
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/gpu/load [get]
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
ok, client := gpu.New()
if ok {
info, err := client.LoadGpuInfo()
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, info)
return
}
xpuOK, xpuClient := xpu.New()
if xpuOK {
info, err := xpuClient.LoadGpuInfo()
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, info)
return
}
helper.SuccessWithData(c, &common.GpuInfo{})
}
func (b *BaseApi) GetCPUOptions(c *gin.Context) {
helper.SuccessWithData(c, monitorService.LoadGPUOptions())
}
// @Tags Monitor
// @Summary Load monitor data
// @Param request body dto.MonitorGPUSearch true "request"
// @Success 200 {object} dto.MonitorGPUData
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/monitor/gpu/search [post]
func (b *BaseApi) LoadGPUMonitor(c *gin.Context) {
var req dto.MonitorGPUSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := monitorService.LoadGPUMonitorData(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
}

View File

@@ -31,27 +31,6 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
helper.SuccessWithDataGzipped(c, data)
}
// @Tags Monitor
// @Summary Load monitor data
// @Param request body dto.MonitorGPUSearch true "request"
// @Success 200 {object} dto.MonitorGPUData
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/monitor/gpu/search [post]
func (b *BaseApi) LoadGPUMonitor(c *gin.Context) {
var req dto.MonitorGPUSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := monitorService.LoadGPUMonitorData(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Monitor
// @Summary Clean monitor data
// @Success 200
@@ -126,7 +105,3 @@ func (b *BaseApi) GetIOOptions(c *gin.Context) {
sort.Strings(options)
helper.SuccessWithData(c, options)
}
func (b *BaseApi) GetCPUOptions(c *gin.Context) {
helper.SuccessWithData(c, monitorService.LoadGPUOptions())
}

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
@@ -19,117 +20,30 @@ import (
"github.com/pkg/errors"
)
func (b *BaseApi) WsSSH(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return
}
defer wsConn.Close()
if global.CONF.Base.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
}
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
var client *ssh.SSHClient
if hostID > 0 {
host, err := service.GetHostInfo(uint(hostID))
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
return
}
connInfo := ssh.ConnInfo{
Addr: host.Addr,
Port: int(host.Port),
User: host.User,
AuthMode: host.AuthMode,
Password: host.Password,
PrivateKey: []byte(host.PrivateKey),
}
if len(host.PassPhrase) != 0 {
connInfo.PassPhrase = []byte(host.PassPhrase)
}
client, err = ssh.NewClient(connInfo)
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
return
}
} else {
client, err = loadLocalConn()
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
return
}
}
defer client.Close()
command := c.DefaultQuery("command", "")
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command)
if wshandleError(wsConn, err) {
return
}
defer sws.Close()
quitChan := make(chan bool, 3)
sws.Start(quitChan)
go sws.Wait(quitChan)
<-quitChan
dt := time.Now().Add(time.Second)
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
func (b *BaseApi) WsLocalTerminal(c *gin.Context) {
client, err := loadLocalConn()
b.runSSHSession(c, client, err, c.DefaultQuery("command", ""))
}
func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
func (b *BaseApi) WsHostSSH(c *gin.Context) {
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
if hostID <= 0 {
b.runSSHSession(c, nil, errors.New("missing host id"), c.DefaultQuery("command", ""))
return
}
host, err := service.GetHostInfo(uint(hostID))
client, err := newHostSSHClient(host, err)
b.runSSHSession(c, client, err, c.DefaultQuery("command", ""))
}
func (b *BaseApi) WsContainerTerminal(c *gin.Context) {
wsConn, cols, rows, ok := prepareTerminalSession(c)
if !ok {
return
}
defer wsConn.Close()
if global.CONF.Base.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
}
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}
source := c.Query("source")
var initCmd []string
switch source {
case "redis", "redis-cluster":
initCmd, err = loadRedisInitCmd(c, source)
case "ollama":
initCmd, err = loadOllamaInitCmd(c)
case "container":
initCmd, err = loadContainerInitCmd(c)
case "database":
initCmd, err = loadDatabaseInitCmd(c)
default:
if wshandleError(wsConn, fmt.Errorf("not support such source %s", source)) {
return
}
}
if wshandleError(wsConn, err) {
return
}
slave, err := terminal.NewCommand("docker", initCmd...)
slave, err := loadContainerTerminalCommand(c)
if wshandleError(wsConn, err) {
return
}
@@ -147,10 +61,107 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
<-quitChan
global.LOG.Info("websocket finished")
closeTerminalConn(wsConn)
}
func prepareTerminalSession(c *gin.Context) (*websocket.Conn, int, int, bool) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
global.LOG.Errorf("gin context http handler failed, err: %v", err)
return nil, 0, 0, false
}
if global.CONF.Base.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return nil, 0, 0, false
}
}
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
return nil, 0, 0, false
}
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return nil, 0, 0, false
}
return wsConn, cols, rows, true
}
func (b *BaseApi) runSSHSession(c *gin.Context, client *ssh.SSHClient, clientErr error, command string) {
wsConn, cols, rows, ok := prepareTerminalSession(c)
if !ok {
return
}
defer wsConn.Close()
if wshandleError(wsConn, errors.WithMessage(clientErr, "failed to set up the connection. Please check the host information")) {
return
}
defer client.Close()
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command)
if wshandleError(wsConn, err) {
return
}
defer sws.Close()
quitChan := make(chan bool, 3)
sws.Start(quitChan)
go sws.Wait(quitChan)
<-quitChan
closeTerminalConn(wsConn)
}
func closeTerminalConn(wsConn *websocket.Conn) {
dt := time.Now().Add(time.Second)
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
}
func newHostSSHClient(host *model.Host, err error) (*ssh.SSHClient, error) {
if err != nil {
return nil, errors.WithMessage(err, "load host info by id failed")
}
connInfo := ssh.ConnInfo{
Addr: host.Addr,
Port: int(host.Port),
User: host.User,
AuthMode: host.AuthMode,
Password: host.Password,
PrivateKey: []byte(host.PrivateKey),
}
if len(host.PassPhrase) != 0 {
connInfo.PassPhrase = []byte(host.PassPhrase)
}
return ssh.NewClient(connInfo)
}
func loadContainerTerminalCommand(c *gin.Context) (*terminal.LocalCommand, error) {
source := c.Query("source")
var (
initCmd []string
err error
)
switch source {
case "redis", "redis-cluster":
initCmd, err = loadRedisInitCmd(c, source)
case "ollama":
initCmd, err = loadOllamaInitCmd(c)
case "container":
initCmd, err = loadContainerInitCmd(c)
case "database":
initCmd, err = loadDatabaseInitCmd(c)
default:
return nil, fmt.Errorf("not support such source %s", source)
}
if err != nil {
return nil, err
}
return terminal.NewCommand("docker", initCmd...)
}
func loadRedisInitCmd(c *gin.Context, redisType string) ([]string, error) {
name := c.Query("name")
from := c.Query("from")

View File

@@ -127,7 +127,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
if installs, _ := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(req.Name)); len(installs) > 0 {
return nil, buserr.New("ErrNameIsExist")
}
if !xpack.IsXpack() {
if !xpack.MultiNodeProvider.IsXpack() {
count, _, err := agentRepo.Page(1, 1)
if err != nil {
return nil, err

View File

@@ -501,7 +501,7 @@ func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
Body: i18n.GetMsgByKey("TestAlert"),
IsHTML: false,
}
transport := xpack.LoadRequestTransport()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
if err := email.SendMail(cfg, msg, transport); err != nil {
return false, err
}

View File

@@ -526,7 +526,7 @@ func loadSSHLogin(alert dto.AlertDTO) {
func loadNodeException(alert dto.AlertDTO) {
// only master alert
failCount, err := xpack.GetNodeErrorAlert()
failCount, err := xpack.AlertProvider.GetNodeErrorAlert()
if err != nil {
global.LOG.Errorf("error getting node, err: %s", err)
return
@@ -555,7 +555,7 @@ func loadNodeException(alert dto.AlertDTO) {
func loadLicenseException(alert dto.AlertDTO) {
// only master alert
failCount, err := xpack.GetLicenseErrorAlert()
failCount, err := xpack.AlertProvider.GetLicenseErrorAlert()
if err != nil {
global.LOG.Errorf("error getting license, err: %s", err)
return
@@ -604,7 +604,7 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
AlertId: alert.ID,
Count: todayCount + 1,
}
alertErr := xpack.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
alertErr := xpack.AlertProvider.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
if alertErr != nil {
global.LOG.Infof("%s alert sms push faild, err: %v", alertType, alertErr.Error())
continue
@@ -624,8 +624,8 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
alertInfo.Type = alertType
create.AlertRule = alertUtil.ProcessAlertRule(alert)
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Email)
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
alertErr := alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport, agentInfo)
if alertErr != nil {
global.LOG.Infof("%s alert email push faild, err: %v", alertType, alertErr.Error())
@@ -642,9 +642,9 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
AlertId: alert.ID,
Count: todayCount + 1,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
err := xpack.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, m, transport, agentInfo)
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
err := xpack.AlertProvider.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, m, transport, agentInfo)
if err != nil {
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, m, err)
continue
@@ -664,8 +664,8 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
alertInfo.Type = alertType
create.AlertRule = alertUtil.ProcessAlertRule(alert)
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, m)
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
alertErr := alertUtil.CreateBarkAlertLog(create, alertInfo, params, transport, agentInfo)
if alertErr != nil {
global.LOG.Infof("%s alert %s push failed, err: %v", alertType, m, alertErr.Error())

View File

@@ -72,7 +72,7 @@ func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
Type: s.alert.Type,
}
err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
if err != nil {
global.LOG.Errorf("%s alert sms push failed: %v", s.alert.Type, err)
return
@@ -95,8 +95,8 @@ func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo)
if err != nil {
global.LOG.Errorf("%s alert email push failed: %v", s.alert.Type, err)
@@ -120,8 +120,8 @@ func (s *AlertSender) sendBark(quota string, params []dto.Param) {
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo)
if err != nil {
global.LOG.Errorf("%s alert bark push failed: %v", s.alert.Type, err)
@@ -142,9 +142,9 @@ func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method strin
AlertId: s.alert.ID,
Type: s.alert.Type,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo)
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo)
if err != nil {
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
return
@@ -169,7 +169,7 @@ func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
Type: s.alert.Type,
}
if err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS); err != nil {
if err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS); err != nil {
global.LOG.Errorf("failed to send SMS alert: %v", err)
return
}
@@ -191,8 +191,8 @@ func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo); err != nil {
global.LOG.Errorf("failed to send Email alert: %v", err)
return
@@ -215,8 +215,8 @@ func (s *AlertSender) sendResourceBark(quota string, params []dto.Param) {
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
if err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo); err != nil {
global.LOG.Errorf("failed to send Bark alert: %v", err)
return
@@ -236,9 +236,9 @@ func (s *AlertSender) sendResourceWebhook(quota string, params []dto.Param, meth
AlertId: s.alert.ID,
Type: s.alert.Type,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
if err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo); err != nil {
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
if err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo); err != nil {
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
return
}

View File

@@ -966,7 +966,7 @@ func deleteCustomApp() {
}
func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
if xpack.IsUseCustomApp() {
if xpack.MultiNodeProvider.IsUseCustomApp() {
return nil
}

View File

@@ -82,7 +82,7 @@ func (a AppService) createSyncAppStoreTask(sharedCtx **appSyncContext) func(t *t
ctx := &appSyncContext{
task: t,
httpClient: http.Client{Timeout: time.Duration(constant.TimeOut20s) * time.Second, Transport: xpack.LoadRequestTransport()},
httpClient: http.Client{Timeout: time.Duration(constant.TimeOut20s) * time.Second, Transport: xpack.MultiNodeProvider.LoadRequestTransport()},
baseRemoteUrl: fmt.Sprintf("%s/%s/1panel", global.AppRepoURL(), global.CONF.Base.Mode),
systemVersion: setting.SystemVersion,
settingService: settingService,
@@ -279,9 +279,9 @@ func (c *appSyncContext) syncAppIconsAndDetails() error {
}()
var (
completed int
icon200Count int
icon304Count int
completed int
icon200Count int
icon304Count int
iconFailCount int
)
milestones := [4]int{totalWork / 4, totalWork / 2, totalWork * 3 / 4, totalWork}

View File

@@ -1178,7 +1178,7 @@ func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error
if err != nil {
return err
}
imagePrefix := xpack.GetImagePrefix()
imagePrefix := xpack.MultiNodeProvider.GetImagePrefix()
dockerCLi, err := docker.NewClient()
if err != nil {
return err
@@ -1778,7 +1778,7 @@ func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName
if !serviceValid {
return buserr.New("ErrFileParse")
}
imagePreFix := xpack.GetImagePrefix()
imagePreFix := xpack.MultiNodeProvider.GetImagePrefix()
if imagePreFix != "" {
for _, service := range services {
serviceValue := service.(map[string]interface{})

View File

@@ -179,7 +179,7 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
clam.InfectedDir = ""
}
if len(req.Spec) != 0 {
entryID, err := xpack.StartClam(&clam, false)
entryID, err := xpack.MultiNodeProvider.StartClam(&clam, false)
if err != nil {
return err
}
@@ -232,7 +232,7 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
upMap["entry_id"] = 0
}
if len(req.Spec) != 0 && clam.Status != constant.StatusDisable {
newEntryID, err := xpack.StartClam(&clamItem, true)
newEntryID, err := xpack.MultiNodeProvider.StartClam(&clamItem, true)
if err != nil {
return err
}
@@ -276,7 +276,7 @@ func (c *ClamService) UpdateStatus(id uint, status string) error {
err error
)
if status == constant.StatusEnable {
entryID, err = xpack.StartClam(&clam, true)
entryID, err = xpack.MultiNodeProvider.StartClam(&clam, true)
if err != nil {
return err
}

View File

@@ -39,12 +39,13 @@ var monitorCancel context.CancelFunc
type IMonitorService interface {
Run()
LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorData, error)
LoadGPUOptions() dto.MonitorGPUOptions
LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error)
LoadSetting() (*dto.MonitorSetting, error)
UpdateSetting(key, value string) error
CleanData() error
LoadGPUOptions() dto.MonitorGPUOptions
LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error)
saveIODataToDB(ctx context.Context, interval float64)
saveNetDataToDB(ctx context.Context, interval float64)
}

View File

@@ -436,7 +436,7 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
reloadSystemSSL(websiteSSL, logger)
if websiteSSL.PushNode {
printSSLLog(logger, "StartPushSSLToNode", nil)
if err = xpack.PushSSLToNode(websiteSSL); err != nil {
if err = xpack.MultiNodeProvider.PushSSLToNode(websiteSSL); err != nil {
printSSLLog(logger, "PushSSLToNodeFailed", map[string]interface{}{"err": err.Error()})
return
}

View File

@@ -576,7 +576,7 @@ func delNginxConfig(website model.Website, force bool) error {
}
sitePath := GetSiteDir(website.Alias)
if fileOp.Stat(sitePath) {
xpack.RemoveTamper(website.Alias)
xpack.MultiNodeProvider.RemoveTamper(website.Alias)
_ = fileOp.DeleteDir(sitePath)
}

View File

@@ -68,7 +68,6 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.16 // indirect
@@ -221,7 +220,6 @@ require (
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/sigstore/sigstore v1.10.5 // indirect
github.com/sigstore/sigstore-go v1.1.4 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.80 // indirect

View File

@@ -55,8 +55,6 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
@@ -348,8 +346,6 @@ github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg=
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
github.com/gammazero/toposort v0.2.0 h1:4wndQUKr24ALbkDnYag1bMGJBR+2y++cIGST1lb+L9w=
github.com/gammazero/toposort v0.2.0/go.mod h1:X4xn3isrX0SRJi+T0wfUkmzubOm2uOQynDzAsO4yiMQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
@@ -727,8 +723,6 @@ github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
github.com/mholt/archiver/v4 v4.0.0-alpha.9 h1:EZgAsW6DsuawxDgTtIdjCUBa2TQ6AOe9pnCidofSRtE=
github.com/mholt/archiver/v4 v4.0.0-alpha.9/go.mod h1:5D3uct315OMkMRXKwEuMB+wQi/2m5NQngKDmApqwVlo=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
@@ -921,8 +915,6 @@ github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7D
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/qiniu/go-sdk/v7 v7.26.4 h1:D/k6cPbhjKvSx9hVBQSh97GuPu1ZtM3OLPCclSpusjo=
github.com/qiniu/go-sdk/v7 v7.26.4/go.mod h1:ri7fGwbio0pRDFr8EK5TUpx0DbnpIMJ2bMSDxGWfCbk=
github.com/qiniu/go-sdk/v7 v7.26.9 h1:is9th0m6RCq9LBq7Cuh7mmGUTfR2xb68SGO3xH7ZrA0=
github.com/qiniu/go-sdk/v7 v7.26.9/go.mod h1:ri7fGwbio0pRDFr8EK5TUpx0DbnpIMJ2bMSDxGWfCbk=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
@@ -980,8 +972,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spdx/tools-golang v0.5.7 h1:+sWcKGnhwp3vLdMqPcLdA6QK679vd86cK9hQWH3AwCg=
github.com/spdx/tools-golang v0.5.7/go.mod h1:jg7w0LOpoNAw6OxKEzCoqPC2GCTj45LyTlVmXubDsYw=

View File

@@ -38,7 +38,7 @@ func initGlobalData() {
if err := settingRepo.Update("SystemStatus", "Free"); err != nil {
global.LOG.Fatalf("init service before start failed, err: %v", err)
}
node, _ := xpack.LoadNodeInfo(false)
node, _ := xpack.MultiNodeProvider.LoadNodeInfo(false)
if len(node.Version) != 0 {
_ = settingRepo.Update("SystemVersion", node.Version)
}

View File

@@ -97,7 +97,7 @@ var InitSetting = &gormigrate.Migration{
ID: "20240722-init-setting",
Migrate: func(tx *gorm.DB) error {
global.CONF.Base.EncryptKey = common.RandStr(16)
nodeInfo, err := xpack.LoadNodeInfo(true)
nodeInfo, err := xpack.MultiNodeProvider.LoadNodeInfo(true)
if err != nil {
return err
}

View File

@@ -56,7 +56,7 @@ func Init() {
}
func initBaseInfo() {
nodeInfo, err := xpack.LoadNodeInfo(true)
nodeInfo, err := xpack.MultiNodeProvider.LoadNodeInfo(true)
if err != nil {
panic(err)
}

View File

@@ -19,7 +19,7 @@ func Certificate() gin.HandlerFunc {
c.Next()
return
}
if !xpack.ValidateCertificate(c) {
if !xpack.MultiNodeProvider.ValidateCertificate(c) {
CloseDirectly(c)
return
}

View File

@@ -1,19 +0,0 @@
//go:build xpack
package router
import (
xpackRouter "github.com/1Panel-dev/1Panel/agent/xpack/router"
)
func RouterGroups() []CommonRouter {
baseRouter := commonGroups()
for _, ro := range xpackRouter.XpackGroups() {
if val, ok := ro.(CommonRouter); ok {
baseRouter = append(baseRouter, val)
}
}
return baseRouter
}
var RouterGroupApp = RouterGroups()

View File

@@ -1,19 +0,0 @@
//go:build xpackee
package router
import (
xpackRouter "github.com/1Panel-dev/1Panel/agent/xpack/router"
)
func RouterGroups() []CommonRouter {
baseRouter := commonGroups()
for _, ro := range xpackRouter.XpackGroups() {
if val, ok := ro.(CommonRouter); ok {
baseRouter = append(baseRouter, val)
}
}
return baseRouter
}
var RouterGroupApp = RouterGroups()

View File

@@ -20,11 +20,14 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/ollama/model/sync", baseApi.SyncOllamaModel)
aiToolsRouter.POST("/ollama/model/load", baseApi.LoadOllamaModelDetail)
aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel)
aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo)
aiToolsRouter.POST("/domain/bind", baseApi.BindDomain)
aiToolsRouter.POST("/domain/get", baseApi.GetBindDomain)
aiToolsRouter.POST("/domain/update", baseApi.UpdateBindDomain)
aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo)
aiToolsRouter.POST("/gpu/search", baseApi.LoadGPUMonitor)
aiToolsRouter.GET("/gpu/options", baseApi.GetCPUOptions)
aiToolsRouter.POST("/mcp/search", baseApi.PageMcpServers)
aiToolsRouter.POST("/mcp/server", baseApi.CreateMcpServer)
aiToolsRouter.POST("/mcp/server/update", baseApi.UpdateMcpServer)

View File

@@ -11,7 +11,6 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
baRouter := Router.Group("containers")
baseApi := v2.ApiGroupApp.BaseApi
{
baRouter.GET("/exec", baseApi.ContainerWsSSH)
baRouter.GET("/stats/:id", baseApi.ContainerStats)
baRouter.POST("", baseApi.ContainerCreate)

View File

@@ -39,9 +39,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter.POST("/firewall/filter/chain/status", baseApi.LoadChainStatus)
hostRouter.POST("/monitor/search", baseApi.LoadMonitor)
hostRouter.POST("/monitor/gpu/search", baseApi.LoadGPUMonitor)
hostRouter.POST("/monitor/clean", baseApi.CleanMonitor)
hostRouter.GET("/monitor/gpuoptions", baseApi.GetCPUOptions)
hostRouter.GET("/monitor/netoptions", baseApi.GetNetworkOptions)
hostRouter.GET("/monitor/iooptions", baseApi.GetIOOptions)
hostRouter.GET("/monitor/setting", baseApi.LoadMonitorSetting)
@@ -69,7 +67,9 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter.GET("/tool/supervisor/process", baseApi.GetProcess)
hostRouter.POST("/tool/supervisor/process/file", baseApi.GetProcessFile)
hostRouter.GET("/terminal", baseApi.WsSSH)
hostRouter.GET("/terminal/local", baseApi.WsLocalTerminal)
hostRouter.GET("/terminal/ssh", baseApi.WsHostSSH)
hostRouter.GET("/terminal/container", baseApi.WsContainerTerminal)
hostRouter.GET("/disks", baseApi.GetCompleteDiskInfo)
hostRouter.POST("/disks/partition", baseApi.PartitionDisk)

View File

@@ -1,11 +0,0 @@
//go:build xpack
package server
import (
xpack "github.com/1Panel-dev/1Panel/agent/xpack"
)
func InitOthers() {
xpack.Init()
}

View File

@@ -1,11 +0,0 @@
//go:build xpackee
package server
import (
xpack "github.com/1Panel-dev/1Panel/agent/xpack"
)
func InitOthers() {
xpack.Init()
}

View File

@@ -42,7 +42,7 @@ func PushAlert(pushAlert dto.PushAlert) error {
AlertId: alert.ID,
Count: todayCount + 1,
}
err = xpack.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, constant.SMS)
err = xpack.AlertProvider.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, constant.SMS)
if err != nil {
global.LOG.Errorf("%s alert sms push failed: %v", alert.Type, err)
continue
@@ -58,8 +58,8 @@ func PushAlert(pushAlert dto.PushAlert) error {
AlertId: alert.ID,
Count: todayCount + 1,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
err = alertUtil.CreateTaskScanEmailAlertLog(alert, create, pushAlert, constant.Email, transport, agentInfo)
if err != nil {
global.LOG.Errorf("%s alert email push failed: %v", alert.Type, err)
@@ -76,8 +76,8 @@ func PushAlert(pushAlert dto.PushAlert) error {
AlertId: alert.ID,
Count: todayCount + 1,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
params := alertUtil.CreateAlertParams(alertUtil.GetCronJobTypeName(pushAlert.Param))
alertDetail := alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, constant.Bark)
alertRule := alertUtil.ProcessAlertRule(alert)
@@ -99,9 +99,9 @@ func PushAlert(pushAlert dto.PushAlert) error {
AlertId: alert.ID,
Count: todayCount + 1,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
err = xpack.CreateTaskScanWebhookAlertLog(alert, alert.Type, create, pushAlert, m, transport, agentInfo)
transport := xpack.MultiNodeProvider.LoadRequestTransport()
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
err = xpack.AlertProvider.CreateTaskScanWebhookAlertLog(alert, alert.Type, create, pushAlert, m, transport, agentInfo)
if err != nil {
global.LOG.Errorf("%s alert %s webhook push failed: %v", alert.Type, m, err)
continue

View File

@@ -47,7 +47,7 @@ func HandleGet(url string) (*http.Response, error) {
}
func HandleRequest(url, method string, timeout int) (int, []byte, error) {
transport := xpack.LoadRequestTransport()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
client := http.Client{Timeout: time.Duration(timeout) * time.Second, Transport: transport}
return HandleRequestWithClient(&client, url, method, timeout)
}
@@ -130,7 +130,7 @@ func RequestFile(url, method string, timeout int) (io.ReadCloser, context.Cancel
return
}
}()
transport := xpack.LoadRequestTransport()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
client := http.Client{Timeout: time.Duration(timeout) * time.Second, Transport: transport}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
request, err := http.NewRequestWithContext(ctx, method, url, nil)

View File

@@ -2,92 +2,8 @@
package xpack
import (
"crypto/tls"
"net"
"net/http"
"time"
import "github.com/1Panel-dev/1Panel/agent/utils/xpack/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/gin-gonic/gin"
)
var AlertProvider = helper.NewIAlertProvider()
func RemoveTamper(website string) {}
func StartClam(startClam *model.Clam, isUpdate bool) (int, error) {
return 0, buserr.New("ErrXpackNotFound")
}
func LoadNodeInfo(isBase bool) (model.NodeInfo, error) {
var info model.NodeInfo
info.BaseDir = common.LoadParams("BASE_DIR")
info.Version = common.LoadParams("ORIGINAL_VERSION")
info.Scope = "master"
global.IsMaster = true
return info, nil
}
func GetImagePrefix() string {
return ""
}
func IsUseCustomApp() bool {
return false
}
func IsXpack() bool {
return false
}
func CreateTaskScanSMSAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error {
return nil
}
func CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error {
return nil
}
func CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
return nil
}
func CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
return nil
}
func GetLicenseErrorAlert() (uint, error) {
return 0, nil
}
func GetNodeErrorAlert() (uint, error) {
return 0, nil
}
func LoadRequestTransport() *http.Transport {
return &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DialContext: (&net.Dialer{
Timeout: 60 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
IdleConnTimeout: 15 * time.Second,
}
}
func ValidateCertificate(c *gin.Context) bool {
return true
}
func PushSSLToNode(websiteSSL *model.WebsiteSSL) error {
return nil
}
func GetAgentInfo() (*dto.AgentInfo, error) {
return nil, nil
}
var MultiNodeProvider = helper.NewIMultiNodeProvider()

View File

@@ -0,0 +1,38 @@
package helper
import (
"net/http"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/utils/xpack/providers"
)
type alertHelper struct{}
func NewIAlertProvider() providers.AlertProvider {
return &alertHelper{}
}
func (a *alertHelper) CreateTaskScanSMSAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error {
return nil
}
func (a *alertHelper) CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error {
return nil
}
func (a *alertHelper) CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
return nil
}
func (a *alertHelper) CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
return nil
}
func (a *alertHelper) GetLicenseErrorAlert() (uint, error) {
return 0, nil
}
func (a *alertHelper) GetNodeErrorAlert() (uint, error) {
return 0, nil
}

View File

@@ -0,0 +1,74 @@
package helper
import (
"crypto/tls"
"net"
"net/http"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/xpack/providers"
"github.com/gin-gonic/gin"
)
type multiNodeHelper struct{}
func NewIMultiNodeProvider() providers.MultiNodeProvider {
return &multiNodeHelper{}
}
func (m *multiNodeHelper) RemoveTamper(website string) {}
func (m *multiNodeHelper) StartClam(startClam *model.Clam, isUpdate bool) (int, error) {
return 0, buserr.New("ErrXpackNotFound")
}
func (m *multiNodeHelper) LoadNodeInfo(isBase bool) (model.NodeInfo, error) {
var info model.NodeInfo
info.BaseDir = common.LoadParams("BASE_DIR")
info.Version = common.LoadParams("ORIGINAL_VERSION")
info.Scope = "master"
global.IsMaster = true
return info, nil
}
func (m *multiNodeHelper) GetImagePrefix() string {
return ""
}
func (m *multiNodeHelper) IsUseCustomApp() bool {
return false
}
func (m *multiNodeHelper) IsXpack() bool {
return false
}
func (m *multiNodeHelper) LoadRequestTransport() *http.Transport {
return &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DialContext: (&net.Dialer{
Timeout: 60 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
IdleConnTimeout: 15 * time.Second,
}
}
func (m *multiNodeHelper) ValidateCertificate(c *gin.Context) bool {
return true
}
func (m *multiNodeHelper) PushSSLToNode(websiteSSL *model.WebsiteSSL) error {
return nil
}
func (m *multiNodeHelper) GetAgentInfo() (*dto.AgentInfo, error) {
return nil, nil
}

View File

@@ -0,0 +1,17 @@
package providers
import (
"net/http"
"github.com/1Panel-dev/1Panel/agent/app/dto"
)
type AlertProvider interface {
GetNodeErrorAlert() (uint, error)
GetLicenseErrorAlert() (uint, error)
CreateTaskScanSMSAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error
CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error
CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error
CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error
}

View File

@@ -0,0 +1,23 @@
package providers
import (
"net/http"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/gin-gonic/gin"
)
type MultiNodeProvider interface {
IsXpack() bool
IsUseCustomApp() bool
GetImagePrefix() string
RemoveTamper(website string)
StartClam(startClam *model.Clam, isUpdate bool) (int, error)
LoadNodeInfo(isBase bool) (model.NodeInfo, error)
LoadRequestTransport() *http.Transport
ValidateCertificate(c *gin.Context) bool
PushSSLToNode(websiteSSL *model.WebsiteSSL) error
GetAgentInfo() (*dto.AgentInfo, error)
}

View File

@@ -1,74 +0,0 @@
//go:build xpack
package xpack
import (
"net/http"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
edition "github.com/1Panel-dev/1Panel/agent/xpack/edition"
"github.com/gin-gonic/gin"
)
func RemoveTamper(website string) {
edition.RemoveTamper(website)
}
func StartClam(startClam *model.Clam, isUpdate bool) (int, error) {
return edition.StartClam(startClam, isUpdate)
}
func LoadNodeInfo(isBase bool) (model.NodeInfo, error) {
return edition.LoadNodeInfo(isBase)
}
func GetImagePrefix() string {
return edition.GetImagePrefix()
}
func IsUseCustomApp() bool {
return edition.IsUseCustomApp()
}
func IsXpack() bool {
return edition.IsXpack()
}
func CreateTaskScanSMSAlertLog(info dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error {
return edition.CreateTaskScanSMSAlertLog(info, alertType, create, pushAlert, method)
}
func CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error {
return edition.CreateSMSAlertLog(alertType, info, create, project, params, method)
}
func CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
return edition.CreateTaskScanWebhookAlertLog(alert, alertType, create, pushAlert, method, transport, agentInfo)
}
func CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
return edition.CreateWebhookAlertLog(alertType, info, create, project, params, method, transport, agentInfo)
}
func GetLicenseErrorAlert() (uint, error) {
return edition.GetLicenseErrorAlert()
}
func GetNodeErrorAlert() (uint, error) {
return edition.GetNodeErrorAlert()
}
func LoadRequestTransport() *http.Transport { return edition.LoadRequestTransport() }
func ValidateCertificate(c *gin.Context) bool {
return edition.ValidateCertificate(c)
}
func PushSSLToNode(websiteSSL *model.WebsiteSSL) error {
return edition.PushSSLToNode(websiteSSL)
}
func GetAgentInfo() (*dto.AgentInfo, error) {
return edition.GetAgentInfo()
}

View File

@@ -1,74 +0,0 @@
//go:build xpackee
package xpack
import (
"net/http"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
edition "github.com/1Panel-dev/1Panel/agent/xpack/edition"
"github.com/gin-gonic/gin"
)
func RemoveTamper(website string) {
edition.RemoveTamper(website)
}
func StartClam(startClam *model.Clam, isUpdate bool) (int, error) {
return edition.StartClam(startClam, isUpdate)
}
func LoadNodeInfo(isBase bool) (model.NodeInfo, error) {
return edition.LoadNodeInfo(isBase)
}
func GetImagePrefix() string {
return edition.GetImagePrefix()
}
func IsUseCustomApp() bool {
return edition.IsUseCustomApp()
}
func IsXpack() bool {
return edition.IsXpack()
}
func CreateTaskScanSMSAlertLog(info dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error {
return edition.CreateTaskScanSMSAlertLog(info, alertType, create, pushAlert, method)
}
func CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error {
return edition.CreateSMSAlertLog(alertType, info, create, project, params, method)
}
func CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
return edition.CreateTaskScanWebhookAlertLog(alert, alertType, create, pushAlert, method, transport, agentInfo)
}
func CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
return edition.CreateWebhookAlertLog(alertType, info, create, project, params, method, transport, agentInfo)
}
func GetLicenseErrorAlert() (uint, error) {
return edition.GetLicenseErrorAlert()
}
func GetNodeErrorAlert() (uint, error) {
return edition.GetNodeErrorAlert()
}
func LoadRequestTransport() *http.Transport { return edition.LoadRequestTransport() }
func ValidateCertificate(c *gin.Context) bool {
return edition.ValidateCertificate(c)
}
func PushSSLToNode(websiteSSL *model.WebsiteSSL) error {
return edition.PushSSLToNode(websiteSSL)
}
func GetAgentInfo() (*dto.AgentInfo, error) {
return edition.GetAgentInfo()
}

View File

@@ -15,6 +15,7 @@ import (
initauth "github.com/1Panel-dev/1Panel/core/init/auth"
"github.com/1Panel-dev/1Panel/core/utils/captcha"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/gin-gonic/gin"
)
@@ -58,7 +59,7 @@ func (b *BaseApi) Login(c *gin.Context) {
}
}
user, msgKey, err := authService.Login(c, req, string(entrance))
user, msgKey, err := xpack.AuthProvider.Login(c, req, string(entrance))
if user == nil || user.MfaStatus != constant.StatusEnable {
go saveLoginLogs(c, wrapLoginErr(msgKey, err))
}
@@ -106,7 +107,7 @@ func (b *BaseApi) MFALogin(c *gin.Context) {
entrance, _ = base64.StdEncoding.DecodeString(entranceItem)
}
user, msgKey, err := authService.MFALogin(c, req, string(entrance))
user, msgKey, err := xpack.AuthProvider.MFALogin(c, req, string(entrance))
go saveLoginLogs(c, wrapLoginErr(msgKey, err))
if msgKey == "ErrMFA" {
global.IPTracker.RecordFailure(ip)
@@ -134,7 +135,7 @@ func (b *BaseApi) MFALogin(c *gin.Context) {
// @Router /core/auth/passkey/begin [post]
func (b *BaseApi) PasskeyBeginLogin(c *gin.Context) {
entrance := loadEntranceFromRequest(c)
res, msgKey, err := authService.PasskeyBeginLogin(c, entrance)
res, msgKey, err := xpack.AuthProvider.PasskeyBeginLogin(c, entrance)
if msgKey != "" {
if msgKey == "ErrEntrance" {
helper.BadAuth(c, msgKey, err)
@@ -161,7 +162,7 @@ func (b *BaseApi) PasskeyBeginLogin(c *gin.Context) {
func (b *BaseApi) PasskeyFinishLogin(c *gin.Context) {
sessionID := c.GetHeader("Passkey-Session")
entrance := loadEntranceFromRequest(c)
user, msgKey, err := authService.PasskeyFinishLogin(c, sessionID, entrance)
user, msgKey, err := xpack.AuthProvider.PasskeyFinishLogin(c, sessionID, entrance)
go saveLoginLogs(c, wrapLoginErr(msgKey, err))
if msgKey == "ErrAuth" || msgKey == "ErrEntrance" {
if msgKey == "ErrAuth" {
@@ -248,10 +249,218 @@ func (b *BaseApi) GetLoginSetting(c *gin.Context) {
Theme: settingInfo.Theme,
NeedCaptcha: needCaptcha,
}
res.PasskeySetting = authService.PasskeyStatus(c)
res.PasskeySetting = xpack.AuthProvider.PasskeyStatus(c)
helper.SuccessWithData(c, res)
}
// @Tags Auth
// @Summary Begin passkey registration
// @Accept json
// @Param request body dto.PasskeyRegisterRequest true "request"
// @Success 200 {object} dto.PasskeyBeginResponse
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/passkey/register/begin [post]
func (b *BaseApi) PasskeyRegisterBegin(c *gin.Context) {
var req dto.PasskeyRegisterRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, msgKey, err := xpack.AuthProvider.PasskeyBeginRegister(c, req.Name)
if msgKey != "" {
helper.ErrorWithDetail(c, http.StatusBadRequest, msgKey, err)
return
}
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags Auth
// @Summary Finish passkey registration
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/passkey/register/finish [post]
func (b *BaseApi) PasskeyRegisterFinish(c *gin.Context) {
sessionID := c.GetHeader("Passkey-Session")
msgKey, err := xpack.AuthProvider.PasskeyFinishRegister(c, sessionID)
if msgKey != "" {
helper.ErrorWithDetail(c, http.StatusBadRequest, msgKey, err)
return
}
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Auth
// @Summary List passkeys
// @Success 200 {array} dto.PasskeyInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/passkey/list [get]
func (b *BaseApi) PasskeyList(c *gin.Context) {
list, err := xpack.AuthProvider.PasskeyList(c)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Auth
// @Summary Delete passkey
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/passkey/del [post]
func (b *BaseApi) PasskeyDelete(c *gin.Context) {
var req dto.PasskeyID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := xpack.AuthProvider.PasskeyDelete(c, req.ID); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags System Setting
// @Summary Load mfa info
// @Accept json
// @Param request body dto.MfaCredential true "request"
// @Success 200 {object} mfa.Otp
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/mfa [post]
func (b *BaseApi) LoadMFA(c *gin.Context) {
var req dto.MfaRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
otp, err := xpack.AuthProvider.LoadMFA(c, req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, otp)
}
// @Tags System Setting
// @Summary Bind mfa
// @Accept json
// @Param request body dto.MfaCredential true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/mfa/bind [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"mfa 绑定","formatEN":"bind mfa"}
func (b *BaseApi) MFABind(c *gin.Context) {
var req dto.MfaCredential
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := xpack.AuthProvider.MFABind(c, req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Auth
// @Summary generate api key
// @Accept json
// @Success 200 {string} key
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/api/generate [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 API 接口密钥","formatEN":"generate api key"}
func (b *BaseApi) GenerateApiKey(c *gin.Context) {
panelToken := c.GetHeader("1Panel-Token")
if panelToken != "" {
helper.BadAuth(c, "ErrApiConfigDisable", nil)
return
}
apiKey, err := xpack.AuthProvider.GenerateApiKey(c)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, apiKey)
}
// @Tags Auth
// @Summary Update api config
// @Accept json
// @Param request body dto.ApiInterfaceConfig true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/api/update [post]
// @x-panel-log {"bodyKeys":["ipWhiteList"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 API 接口配置 => IP 白名单: [ipWhiteList]","formatEN":"update api config => IP White List: [ipWhiteList]"}
func (b *BaseApi) UpdateApiConfig(c *gin.Context) {
panelToken := c.GetHeader("1Panel-Token")
if panelToken != "" {
helper.BadAuth(c, "ErrApiConfigDisable", nil)
return
}
var req dto.ApiInterfaceConfig
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := xpack.AuthProvider.UpdateApiConfig(c, req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Auth
// @Summary Load current user info
// @Success 200 {object} dto.CurrentUserInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/current [get]
func (b *BaseApi) GetCurrentUser(c *gin.Context) {
userInfo, err := xpack.AuthProvider.GetCurrentUserInfo(c)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, userInfo)
}
// @Tags Auth
// @Summary Update current user info
// @Accept json
// @Param request body dto.CurrentUserUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/current/update [post]
func (b *BaseApi) UpdateCurrentUser(c *gin.Context) {
var req dto.CurrentUserUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := xpack.AuthProvider.UpdateCurrentUserInfo(c, req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func saveLoginLogs(c *gin.Context, err error) {
var logs model.LoginLog
if err != nil {

View File

@@ -186,7 +186,7 @@ func (b *BaseApi) RunScript(c *gin.Context) {
tty.Start(quitChan)
go slave.Wait(quitChan)
} else {
connInfo, _, err := xpack.LoadNodeInfo(currentNode)
connInfo, _, err := xpack.MultiNodeProvider.LoadNodeInfo(currentNode)
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
return
}

View File

@@ -10,14 +10,14 @@ import (
"strings"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
appauth "github.com/1Panel-dev/1Panel/core/app/auth"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/mfa"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/gin-gonic/gin"
)
@@ -36,6 +36,21 @@ func (b *BaseApi) GetSettingInfo(c *gin.Context) {
helper.SuccessWithData(c, setting)
}
// @Tags System Setting
// @Summary Load base system setting info
// @Success 200 {object} dto.SettingBaseInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/search/base [post]
func (b *BaseApi) GetSettingBaseInfo(c *gin.Context) {
setting, err := settingService.GetSettingBaseInfo()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, setting)
}
// @Tags System Setting
// @Summary Load system setting by key
// @Success 200 {string} info
@@ -114,7 +129,7 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
return
}
if req.Key == "SecurityEntrance" {
service.SetSecurityEntranceCookie(c, req.Value)
appauth.SetSecurityEntranceCookie(c, req.Value)
}
helper.Success(c)
}
@@ -210,28 +225,6 @@ func (b *BaseApi) DefaultMenu(c *gin.Context) {
helper.Success(c)
}
// @Tags System Setting
// @Summary Update system password
// @Accept json
// @Param request body dto.PasswordUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/password/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统密码","formatEN":"update system password"}
func (b *BaseApi) UpdatePassword(c *gin.Context) {
var req dto.PasswordUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := settingService.UpdatePassword(c, req.OldPassword, req.NewPassword); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags System Setting
// @Summary Update system ssl
// @Accept json
@@ -360,149 +353,7 @@ func (b *BaseApi) HandlePasswordExpired(c *gin.Context) {
return
}
if err := settingService.HandlePasswordExpired(c, req.OldPassword, req.NewPassword); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags System Setting
// @Summary Load mfa info
// @Accept json
// @Param request body dto.MfaCredential true "request"
// @Success 200 {object} mfa.Otp
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/mfa [post]
func (b *BaseApi) LoadMFA(c *gin.Context) {
var req dto.MfaRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
otp, err := mfa.GetOtp("admin", req.Title, req.Interval)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, otp)
}
// @Tags System Setting
// @Summary Bind mfa
// @Accept json
// @Param request body dto.MfaCredential true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/mfa/bind [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"mfa 绑定","formatEN":"bind mfa"}
func (b *BaseApi) MFABind(c *gin.Context) {
var req dto.MfaCredential
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
success := mfa.ValidCode(req.Code, req.Interval, req.Secret)
if !success {
helper.InternalServer(c, errors.New("code is not valid"))
return
}
if err := settingService.Update(c, "MFAInterval", req.Interval); err != nil {
helper.InternalServer(c, err)
return
}
if err := settingService.Update(c, "MFAStatus", constant.StatusEnable); err != nil {
helper.InternalServer(c, err)
return
}
if err := settingService.Update(c, "MFASecret", req.Secret); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags System Setting
// @Summary Begin passkey registration
// @Accept json
// @Param request body dto.PasskeyRegisterRequest true "request"
// @Success 200 {object} dto.PasskeyBeginResponse
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/passkey/register/begin [post]
func (b *BaseApi) PasskeyRegisterBegin(c *gin.Context) {
var req dto.PasskeyRegisterRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, msgKey, err := authService.PasskeyBeginRegister(c, req.Name)
if msgKey != "" {
helper.ErrorWithDetail(c, http.StatusBadRequest, msgKey, err)
return
}
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags System Setting
// @Summary Finish passkey registration
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/passkey/register/finish [post]
func (b *BaseApi) PasskeyRegisterFinish(c *gin.Context) {
sessionID := c.GetHeader("Passkey-Session")
msgKey, err := authService.PasskeyFinishRegister(c, sessionID)
if msgKey != "" {
helper.ErrorWithDetail(c, http.StatusBadRequest, msgKey, err)
return
}
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags System Setting
// @Summary List passkeys
// @Success 200 {array} dto.PasskeyInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/passkey/list [get]
func (b *BaseApi) PasskeyList(c *gin.Context) {
list, err := authService.PasskeyList()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags System Setting
// @Summary Delete passkey
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/passkey/{id} [delete]
func (b *BaseApi) PasskeyDelete(c *gin.Context) {
id := c.Param("id")
if id == "" {
helper.BadRequest(c, errors.New("passkey id is required"))
return
}
if err := authService.PasskeyDelete(id); err != nil {
if err := xpack.AuthProvider.HandlePasswordExpired(c, req.OldPassword, req.NewPassword); err != nil {
helper.InternalServer(c, err)
return
}
@@ -522,55 +373,6 @@ func (b *BaseApi) ReloadSSL(c *gin.Context) {
helper.Success(c)
}
// @Tags System Setting
// @Summary generate api key
// @Accept json
// @Success 200 {string} key
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/api/config/generate/key [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 API 接口密钥","formatEN":"generate api key"}
func (b *BaseApi) GenerateApiKey(c *gin.Context) {
panelToken := c.GetHeader("1Panel-Token")
if panelToken != "" {
helper.BadAuth(c, "ErrApiConfigDisable", nil)
return
}
apiKey, err := settingService.GenerateApiKey()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, apiKey)
}
// @Tags System Setting
// @Summary Update api config
// @Accept json
// @Param request body dto.ApiInterfaceConfig true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/api/config/update [post]
// @x-panel-log {"bodyKeys":["ipWhiteList"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 API 接口配置 => IP 白名单: [ipWhiteList]","formatEN":"update api config => IP White List: [ipWhiteList]"}
func (b *BaseApi) UpdateApiConfig(c *gin.Context) {
panelToken := c.GetHeader("1Panel-Token")
if panelToken != "" {
helper.BadAuth(c, "ErrApiConfigDisable", nil)
return
}
var req dto.ApiInterfaceConfig
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := settingService.UpdateApiConfig(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags App
// @Summary Update appstore config
// @Accept json

View File

@@ -1,4 +1,4 @@
package middleware
package auth
import (
"crypto/md5"
@@ -9,13 +9,24 @@ import (
"time"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/gin-gonic/gin"
)
func ApiAuth() gin.HandlerFunc {
type APIAuthConfig struct {
ApiInterfaceStatus string
ApiKey string
IpWhiteList string
ApiKeyValidityTime string
}
type APIAuthConfigLoader func(c *gin.Context) (APIAuthConfig, error)
type APIAuthSuccessHandler func(c *gin.Context, config APIAuthConfig)
func APIAuthMiddleware(loadConfig APIAuthConfigLoader, onSuccess APIAuthSuccessHandler) gin.HandlerFunc {
return func(c *gin.Context) {
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") {
c.Next()
@@ -24,35 +35,61 @@ func ApiAuth() gin.HandlerFunc {
panelToken := c.GetHeader("1Panel-Token")
panelTimestamp := c.GetHeader("1Panel-Timestamp")
if panelToken != "" || panelTimestamp != "" {
if global.Api.ApiInterfaceStatus == constant.StatusEnable {
clientIP := c.ClientIP()
if !isValid1PanelTimestamp(panelTimestamp) {
helper.BadAuth(c, "ErrApiConfigKeyTimeInvalid", nil)
return
}
if !isValid1PanelToken(panelToken, panelTimestamp) {
helper.BadAuth(c, "ErrApiConfigKeyInvalid", nil)
return
}
if !isIPInWhiteList(clientIP) {
helper.BadAuth(c, "ErrApiConfigIPInvalid", nil)
return
}
c.Set("API_AUTH", true)
c.Next()
return
} else {
helper.BadAuth(c, "ErrApiConfigStatusInvalid", nil)
return
}
if panelToken == "" && panelTimestamp == "" {
c.Next()
return
}
config, err := loadConfig(c)
if err != nil {
helper.InternalServer(c, err)
return
}
if config.ApiInterfaceStatus != constant.StatusEnable {
helper.BadAuth(c, "ErrApiConfigStatusInvalid", nil)
return
}
if !isValid1PanelTimestamp(panelTimestamp, config.ApiKeyValidityTime) {
helper.BadAuth(c, "ErrApiConfigKeyTimeInvalid", nil)
return
}
if !isValid1PanelToken(panelToken, panelTimestamp, config.ApiKey) {
helper.BadAuth(c, "ErrApiConfigKeyInvalid", nil)
return
}
if !isIPInWhiteList(c.ClientIP(), config.IpWhiteList) {
helper.BadAuth(c, "ErrApiConfigIPInvalid", nil)
return
}
c.Set("API_AUTH", true)
if onSuccess != nil {
onSuccess(c, config)
}
c.Next()
}
}
func isValid1PanelTimestamp(panelTimestamp string) bool {
apiKeyValidityTime := global.Api.ApiKeyValidityTime
func LoadAPIAuthConfig(_ *gin.Context) (APIAuthConfig, error) {
settingRepo := repo.NewISettingRepo()
config := APIAuthConfig{}
var err error
if config.ApiInterfaceStatus, err = settingRepo.GetValueByKey("ApiInterfaceStatus"); err != nil {
return config, err
}
if config.ApiKey, err = settingRepo.GetValueByKey("ApiKey"); err != nil {
return config, err
}
if config.IpWhiteList, err = settingRepo.GetValueByKey("IpWhiteList"); err != nil {
return config, err
}
if config.ApiKeyValidityTime, err = settingRepo.GetValueByKey("ApiKeyValidityTime"); err != nil {
return config, err
}
return config, nil
}
func isValid1PanelTimestamp(panelTimestamp string, apiKeyValidityTime string) bool {
apiTime, err := strconv.Atoi(apiKeyValidityTime)
if err != nil || apiTime < 0 {
global.LOG.Errorf("apiTime %d, err: %v", apiTime, err)
@@ -75,13 +112,11 @@ func isValid1PanelTimestamp(panelTimestamp string) bool {
return nowTime-panelTime <= int64(apiTime)*60+tolerance
}
func isValid1PanelToken(panelToken string, panelTimestamp string) bool {
system1PanelToken := global.Api.ApiKey
return panelToken == GenerateMD5("1panel"+system1PanelToken+panelTimestamp)
func isValid1PanelToken(panelToken string, panelTimestamp string, apiKey string) bool {
return panelToken == GenerateMD5("1panel"+apiKey+panelTimestamp)
}
func isIPInWhiteList(clientIP string) bool {
ipWhiteString := global.Api.IpWhiteList
func isIPInWhiteList(clientIP string, ipWhiteString string) bool {
if len(ipWhiteString) == 0 {
global.LOG.Error("IP whitelist is empty")
return false

372
core/app/auth/auth.go Normal file
View File

@@ -0,0 +1,372 @@
package auth
import (
"crypto/hmac"
"encoding/base64"
"encoding/json"
"errors"
"strconv"
"time"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
initauth "github.com/1Panel-dev/1Panel/core/init/auth"
"github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/mfa"
"github.com/gin-gonic/gin"
)
func Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, string, error) {
settingRepo := repo.NewISettingRepo()
nameSetting, err := settingRepo.Get(repo.WithByKey("UserName"))
if err != nil {
return nil, "", buserr.New("ErrRecordNotFound")
}
if info.Name != nameSetting.Value {
return nil, "ErrAuth", buserr.New("ErrAuth")
}
priKey, _ := settingRepo.Get(repo.WithByKey("PASSWORD_PRIVATE_KEY"))
passwordSetting, err := settingRepo.Get(repo.WithByKey("Password"))
if err != nil {
return nil, "", err
}
if err = CheckPassword(priKey.Value, info.Password, passwordSetting.Value); err != nil {
return nil, "ErrAuth", err
}
entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance"))
if err != nil {
return nil, "", err
}
if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance {
return nil, "ErrEntrance", buserr.New("ErrEntrance")
}
mfaSetting, err := settingRepo.Get(repo.WithByKey("MFAStatus"))
if err != nil {
return nil, "", err
}
if err = settingRepo.Update("Language", info.Language); err != nil {
return nil, "", err
}
if mfaSetting.Value == constant.StatusEnable {
return BeginMFALogin(c, nameSetting.Value, entrance, mfaSetting.Value), "", nil
}
sessionUser := psession.SessionUser{ID: psession.SuperAdminSessionUserID, Name: nameSetting.Value, Role: "ADMIN"}
res, err := GenerateSession(c, sessionUser)
if err != nil {
return nil, "", err
}
if entrance != "" {
SetSecurityEntranceCookie(c, entrance)
}
return res, "", nil
}
func MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, string, error) {
name, errCode, err := VerifyMFALogin(c, info.SessionID, info.Code, entrance)
if errCode != "" {
return nil, errCode, err
}
sessionUser := psession.SessionUser{ID: psession.SuperAdminSessionUserID, Name: name, Role: "ADMIN"}
res, err := GenerateSession(c, sessionUser)
if err != nil {
return nil, "", err
}
if entrance != "" {
SetSecurityEntranceCookie(c, entrance)
}
return res, "", nil
}
func BeginMFALogin(c *gin.Context, name, entrance, mfaStatus string) *dto.UserLoginInfo {
ip := common.GetRealClientIP(c)
mfaSession := initauth.GetMFASessionStore().Set(name, entrance, ip)
return &dto.UserLoginInfo{Name: name, MfaStatus: mfaStatus, MfaSession: mfaSession}
}
func VerifyMFALogin(c *gin.Context, sessionID, code, entrance string) (string, string, error) {
settingRepo := repo.NewISettingRepo()
mfaSessions := initauth.GetMFASessionStore()
session, ok := mfaSessions.Get(sessionID)
if !ok {
return "", "ErrMFA", nil
}
if session.IP != common.GetRealClientIP(c) {
return "", "ErrMFA", nil
}
if session.Entrance != entrance {
return "", "", buserr.New("ErrEntrance")
}
mfaSecret, err := settingRepo.Get(repo.WithByKey("MFASecret"))
if err != nil {
return "", "", err
}
mfaInterval, err := settingRepo.Get(repo.WithByKey("MFAInterval"))
if err != nil {
return "", "", err
}
if !mfa.ValidCode(code, mfaInterval.Value, mfaSecret.Value) {
return "", "ErrMFA", nil
}
mfaSessions.Delete(sessionID)
return session.Name, "", nil
}
func GenerateSession(c *gin.Context, sessionUser psession.SessionUser) (*dto.UserLoginInfo, error) {
settingRepo := repo.NewISettingRepo()
setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout"))
if err != nil {
return nil, err
}
httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL"))
if err != nil {
return nil, err
}
lifeTime, err := strconv.Atoi(setting.Value)
if err != nil {
return nil, err
}
if err := global.SESSION.SetFresh(c, sessionUser, httpsSetting.Value == constant.StatusEnable, lifeTime); err != nil {
return nil, err
}
return &dto.UserLoginInfo{Name: sessionUser.Name, Role: sessionUser.Role}, nil
}
func SetSecurityEntranceCookie(c *gin.Context, entrance string) {
settingRepo := repo.NewISettingRepo()
entranceValue := base64.StdEncoding.EncodeToString([]byte(entrance))
sslEnabled := false
if setting, err := settingRepo.Get(repo.WithByKey("SSL")); err == nil {
sslEnabled = setting.Value == constant.StatusEnable
}
c.SetCookie("SecurityEntrance", entranceValue, 0, "/", "", sslEnabled, true)
}
func CheckEntrance(entrance string) error {
settingRepo := repo.NewISettingRepo()
entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance"))
if err != nil {
return err
}
if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance {
return buserr.New("ErrEntrance")
}
return nil
}
func CheckPassword(priKey, password, passwordFromDB string) error {
privateKey, err := encrypt.ParseRSAPrivateKey(priKey)
if err != nil {
return err
}
loginPassword, err := encrypt.DecryptPassword(password, privateKey)
if err != nil {
return err
}
existPassword, err := encrypt.StringDecrypt(passwordFromDB)
if err != nil {
return err
}
if !hmac.Equal([]byte(loginPassword), []byte(existPassword)) {
return buserr.New("ErrAuth")
}
return nil
}
func LoadMFA(req dto.MfaRequest) (mfa.Otp, error) {
settingRepo := repo.NewISettingRepo()
username, err := settingRepo.GetValueByKey("UserName")
if err != nil {
return mfa.Otp{}, err
}
otp, err := mfa.GetOtp(username, req.Title, req.Interval)
if err != nil {
return mfa.Otp{}, err
}
return otp, nil
}
func MFABind(req dto.MfaCredential) error {
success := mfa.ValidCode(req.Code, req.Interval, req.Secret)
if !success {
return errors.New("code is not valid")
}
settingRepo := repo.NewISettingRepo()
if err := settingRepo.Update("MFAInterval", req.Interval); err != nil {
return err
}
if err := settingRepo.Update("MFAStatus", constant.StatusEnable); err != nil {
return err
}
if err := settingRepo.Update("MFASecret", req.Secret); err != nil {
return err
}
return nil
}
func GetCurrentUserInfo() (*dto.CurrentUserInfo, error) {
setting, err := repo.NewISettingRepo().List()
if err != nil {
return nil, buserr.New("ErrRecordNotFound")
}
settingMap := make(map[string]string)
for _, set := range setting {
settingMap[set.Key] = set.Value
}
var info dto.CurrentUserInfo
stringSettingMap := make(map[string]string, len(settingMap))
for key, value := range settingMap {
stringSettingMap[key] = value
}
delete(stringSettingMap, "SessionTimeout")
delete(stringSettingMap, "ExpirationDays")
arr, err := json.Marshal(stringSettingMap)
if err != nil {
return nil, err
}
if err := json.Unmarshal(arr, &info); err != nil {
return nil, err
}
info.SessionTimeout, _ = strconv.Atoi(settingMap["SessionTimeout"])
info.ExpirationDays, _ = strconv.Atoi(settingMap["ExpirationDays"])
info.Name = settingMap["UserName"]
return &info, nil
}
func UpdateCurrentUserInfo(c *gin.Context, req dto.CurrentUserUpdate) error {
settingRepo := repo.NewISettingRepo()
if len(req.Password) != 0 {
if len(req.OldPassword) == 0 {
return buserr.New("ErrInitialPassword")
}
oldPassword, err := base64.StdEncoding.DecodeString(req.OldPassword)
if err != nil {
return err
}
newPassword, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
return err
}
if err := HandlePasswordExpired(c, string(oldPassword), string(newPassword)); err != nil {
return err
}
}
if err := settingRepo.Update("UserName", req.Name); err != nil {
return err
}
if err := settingRepo.Update("SessionTimeout", strconv.Itoa(req.SessionTimeout)); err != nil {
return err
}
if err := settingRepo.Update("ExpirationDays", strconv.Itoa(req.ExpirationDays)); err != nil {
return err
}
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, req.ExpirationDays).Format(constant.DateTimeLayout)); err != nil {
return err
}
deleteCurrentSession(c)
return nil
}
func GenerateApiKey() (string, error) {
apiKey := common.RandStr(32)
if err := repo.NewISettingRepo().Update("ApiKey", apiKey); err != nil {
return "", err
}
return apiKey, nil
}
func UpdateApiConfig(req dto.ApiInterfaceConfig) error {
settingRepo := repo.NewISettingRepo()
if err := settingRepo.UpdateOrCreate("ApiInterfaceStatus", req.ApiInterfaceStatus); err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("ApiKey", req.ApiKey); err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("IpWhiteList", req.IpWhiteList); err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("ApiKeyValidityTime", req.ApiKeyValidityTime); err != nil {
return err
}
return nil
}
func HandlePasswordExpired(c *gin.Context, old, new string) error {
settingRepo := repo.NewISettingRepo()
setting, err := settingRepo.Get(repo.WithByKey("Password"))
if err != nil {
return err
}
passwordFromDB, err := encrypt.StringDecrypt(setting.Value)
if err != nil {
return err
}
if passwordFromDB == old {
newPassword, err := encrypt.StringEncrypt(new)
if err != nil {
return err
}
if err := settingRepo.Update("Password", newPassword); err != nil {
return err
}
expiredSetting, err := settingRepo.Get(repo.WithByKey("ExpirationDays"))
if err != nil {
return err
}
timeout, _ := strconv.Atoi(expiredSetting.Value)
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format(constant.DateTimeLayout)); err != nil {
return err
}
return nil
}
return buserr.New("ErrInitialPassword")
}
func LoadSessionTimeout(sessionUser psession.SessionUser) (int, error) {
settingRepo := repo.NewISettingRepo()
sessionTimeout, err := settingRepo.GetValueByKey("SessionTimeout")
if err != nil {
return 0, err
}
lifeTime, _ := strconv.Atoi(sessionTimeout)
return lifeTime, nil
}
func LoadExpired(sessionUser psession.SessionUser) (bool, time.Time, error) {
settingRepo := repo.NewISettingRepo()
expirationDays, err := settingRepo.GetValueByKey("ExpirationDays")
if err != nil {
return true, time.Time{}, err
}
expiredDays, _ := strconv.Atoi(expirationDays)
if expiredDays == 0 {
return false, time.Time{}, nil
}
expirationTime, err := settingRepo.GetValueByKey("ExpirationTime")
if err != nil {
return true, time.Time{}, err
}
expiredTime, err := time.ParseInLocation(constant.DateTimeLayout, expirationTime, common.LoadExpiredLocation())
if err != nil {
return true, time.Time{}, err
}
return true, expiredTime, nil
}
func deleteCurrentSession(c *gin.Context) {
if c == nil {
return
}
sessionUser, err := global.SESSION.Get(c)
if err != nil || sessionUser.ID == "" {
return
}
_ = global.SESSION.DeleteByID(sessionUser.ID)
}

598
core/app/auth/passkey.go Normal file
View File

@@ -0,0 +1,598 @@
package auth
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net"
"strings"
"time"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/passkey"
"github.com/gin-gonic/gin"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"gorm.io/gorm"
)
func EvaluatePasskeyStatus(c *gin.Context, configured func() (bool, error)) bool {
enabled, err := PasskeyEnabled(c)
if err != nil {
global.LOG.Errorf("passkey enabled check failed, err: %v", err)
enabled = false
}
configuredOK, err := configured()
if err != nil {
global.LOG.Errorf("passkey config check failed, err: %v", err)
configuredOK = false
}
return enabled && configuredOK
}
func PasskeyStatus(c *gin.Context) bool {
return EvaluatePasskeyStatus(c, communityPasskeyConfigured)
}
func PasskeyBeginLogin(c *gin.Context, entrance string) (*dto.PasskeyBeginResponse, string, error) {
if err := CheckEntrance(entrance); err != nil {
return nil, "ErrEntrance", err
}
config, msgKey, err := PasskeyConfig(c)
if err != nil {
return nil, msgKey, err
}
records, err := loadCommunityPasskeyCredentialRecords()
if err != nil {
return nil, "", err
}
if len(records) == 0 {
return nil, "ErrPasskeyNotConfigured", buserr.New("ErrPasskeyNotConfigured")
}
user, err := communityPasskeyUser(records, true)
if err != nil {
return nil, "", err
}
wa, err := webauthn.New(config)
if err != nil {
return nil, "", err
}
assertion, sessionData, err := wa.BeginLogin(user)
if err != nil {
return nil, "", err
}
sessionID := passkey.GetPasskeySessionStore().Set(passkey.PasskeySessionKindLogin, "", *sessionData)
return &dto.PasskeyBeginResponse{SessionID: sessionID, PublicKey: assertion.Response}, "", nil
}
func PasskeyFinishLogin(c *gin.Context, sessionID, entrance string) (*dto.UserLoginInfo, string, error) {
if sessionID == "" {
return nil, "ErrPasskeySession", buserr.New("ErrPasskeySession")
}
if err := CheckEntrance(entrance); err != nil {
return nil, "ErrEntrance", err
}
config, msgKey, err := PasskeyConfig(c)
if err != nil {
return nil, msgKey, err
}
sessionStore := passkey.GetPasskeySessionStore()
session, ok := sessionStore.Get(sessionID)
if !ok || session.Kind != passkey.PasskeySessionKindLogin {
return nil, "ErrPasskeySession", buserr.New("ErrPasskeySession")
}
sessionStore.Delete(sessionID)
records, err := loadCommunityPasskeyCredentialRecords()
if err != nil {
return nil, "", err
}
if len(records) == 0 {
return nil, "ErrPasskeyNotConfigured", buserr.New("ErrPasskeyNotConfigured")
}
user, err := communityPasskeyUser(records, true)
if err != nil {
return nil, "", err
}
wa, err := webauthn.New(config)
if err != nil {
return nil, "", err
}
credential, err := wa.FinishLogin(user, session.Session, c.Request)
if err != nil {
return nil, "ErrAuth", err
}
if err := UpdatePasskeyCredentialRecord(records, credential); err != nil {
return nil, "ErrAuth", err
}
if err := saveCommunityPasskeyCredentialRecords(records); err != nil {
return nil, "", err
}
userSetting, err := repo.NewISettingRepo().Get(repo.WithByKey("UserName"))
if err != nil {
return nil, "", err
}
res, err := GenerateSession(c, psession.SessionUser{ID: psession.SuperAdminSessionUserID, Name: userSetting.Value, Role: "ADMIN"})
if err != nil {
return nil, "", err
}
if entrance != "" {
SetSecurityEntranceCookie(c, entrance)
}
return res, "", nil
}
func PasskeyBeginRegister(c *gin.Context, name string) (*dto.PasskeyBeginResponse, string, error) {
config, msgKey, err := PasskeyConfig(c)
if err != nil {
return nil, msgKey, err
}
records, err := loadCommunityPasskeyCredentialRecords()
if err != nil {
return nil, "", err
}
if len(records) >= passkey.PasskeyMaxCredentials {
return nil, "ErrPasskeyLimit", buserr.New("ErrPasskeyLimit")
}
user, err := communityPasskeyUser(records, true)
if err != nil {
return nil, "", err
}
wa, err := webauthn.New(config)
if err != nil {
return nil, "", err
}
exclusions := make([]protocol.CredentialDescriptor, len(user.Credentials))
for i, credential := range user.Credentials {
exclusions[i] = credential.Descriptor()
}
creation, sessionData, err := wa.BeginRegistration(user, webauthn.WithExclusions(exclusions))
if err != nil {
return nil, "", err
}
sessionID := passkey.GetPasskeySessionStore().Set(passkey.PasskeySessionKindRegister, strings.TrimSpace(name), *sessionData)
return &dto.PasskeyBeginResponse{SessionID: sessionID, PublicKey: creation.Response}, "", nil
}
func PasskeyFinishRegister(c *gin.Context, sessionID string) (string, error) {
if sessionID == "" {
return "ErrPasskeySession", buserr.New("ErrPasskeySession")
}
config, msgKey, err := PasskeyConfig(c)
if err != nil {
return msgKey, err
}
sessionStore := passkey.GetPasskeySessionStore()
session, ok := sessionStore.Get(sessionID)
if !ok || session.Kind != passkey.PasskeySessionKindRegister {
return "ErrPasskeySession", buserr.New("ErrPasskeySession")
}
sessionStore.Delete(sessionID)
records, err := loadCommunityPasskeyCredentialRecords()
if err != nil {
return "", err
}
if len(records) >= passkey.PasskeyMaxCredentials {
return "ErrPasskeyLimit", buserr.New("ErrPasskeyLimit")
}
user, err := communityPasskeyUser(records, true)
if err != nil {
return "", err
}
wa, err := webauthn.New(config)
if err != nil {
return "", err
}
credential, err := wa.FinishRegistration(user, session.Session, c.Request)
if err != nil {
return "ErrPasskeyVerify", err
}
if PasskeyCredentialExists(records, credential.ID) {
return "ErrPasskeyDuplicate", buserr.New("ErrPasskeyDuplicate")
}
displayName := strings.TrimSpace(session.Name)
if displayName == "" {
displayName = fmt.Sprintf("%s-%s", passkey.PasskeyCredentialNameDefault, time.Now().Format("20060102150405"))
}
records = append(records, passkey.PasskeyCredentialRecord{
ID: base64.RawURLEncoding.EncodeToString(credential.ID),
Name: displayName,
CreatedAt: time.Now().Format(constant.DateTimeLayout),
LastUsedAt: "",
FlagsValue: CredentialFlagsValue(credential.Flags),
Credential: *credential,
})
if err := saveCommunityPasskeyCredentialRecords(records); err != nil {
return "", err
}
return "", nil
}
func PasskeyList() ([]dto.PasskeyInfo, error) {
records, err := loadCommunityPasskeyCredentialRecords()
if err != nil {
return nil, err
}
list := make([]dto.PasskeyInfo, 0, len(records))
for _, record := range records {
list = append(list, dto.PasskeyInfo{ID: record.ID, Name: record.Name, CreatedAt: record.CreatedAt, LastUsedAt: record.LastUsedAt})
}
return list, nil
}
func PasskeyDelete(id string) error {
records, err := loadCommunityPasskeyCredentialRecords()
if err != nil {
return err
}
index := -1
for i, record := range records {
if record.ID == id {
index = i
break
}
}
if index == -1 {
return buserr.New("ErrRecordNotFound")
}
records = append(records[:index], records[index+1:]...)
return saveCommunityPasskeyCredentialRecords(records)
}
func ClearPasskeys() error {
settingRepo := repo.NewISettingRepo()
if err := settingRepo.Update(passkey.PasskeyUserIDSettingKey, ""); err != nil {
return err
}
return settingRepo.Update(passkey.PasskeyCredentialSettingKey, "")
}
func communityPasskeyConfigured() (bool, error) {
bindDomain, err := repo.NewISettingRepo().Get(repo.WithByKey("BindDomain"))
if err != nil {
return false, err
}
if strings.TrimSpace(bindDomain.Value) == "" {
return false, nil
}
records, err := loadCommunityPasskeyCredentialRecords()
if err != nil {
return false, err
}
return len(records) > 0, nil
}
func communityPasskeyUser(records []passkey.PasskeyCredentialRecord, allowCreate bool) (*passkey.PasskeyUser, error) {
settingRepo := repo.NewISettingRepo()
storedUserID, err := settingRepo.Get(repo.WithByKey(passkey.PasskeyUserIDSettingKey))
if err != nil {
return nil, err
}
rawUserID, encodedUserID, err := GeneratePasskeyUserID(storedUserID.Value, allowCreate)
if err != nil {
return nil, err
}
if storedUserID.Value == "" && encodedUserID != "" {
if err := settingRepo.Update(passkey.PasskeyUserIDSettingKey, encodedUserID); err != nil {
return nil, err
}
}
nameSetting, err := settingRepo.Get(repo.WithByKey("UserName"))
if err != nil {
return nil, err
}
return NewPasskeyUser(rawUserID, nameSetting.Value, records), nil
}
func loadCommunityPasskeyCredentialRecords() ([]passkey.PasskeyCredentialRecord, error) {
setting, err := repo.NewISettingRepo().Get(repo.WithByKey(passkey.PasskeyCredentialSettingKey))
if err != nil {
return nil, err
}
return LoadPasskeyCredentialRecords(setting.Value)
}
func saveCommunityPasskeyCredentialRecords(records []passkey.PasskeyCredentialRecord) error {
encoded, err := SavePasskeyCredentialRecords(records)
if err != nil {
return err
}
return repo.NewISettingRepo().Update(passkey.PasskeyCredentialSettingKey, encoded)
}
func PasskeyEnabled(c *gin.Context) (bool, error) {
return strings.EqualFold(PasskeyRequestScheme(c), "https"), nil
}
func PasskeyConfig(c *gin.Context) (*webauthn.Config, string, error) {
enabled, err := PasskeyEnabled(c)
if err != nil {
return nil, "", err
}
if !enabled {
return nil, "ErrPasskeyDisabled", buserr.New("ErrPasskeyDisabled")
}
origin, rpID, err := PasskeyOriginAndRPID(c)
if err != nil {
return nil, "", err
}
panelName, err := repo.NewISettingRepo().Get(repo.WithByKey("PanelName"))
if err != nil {
return nil, "", err
}
return &webauthn.Config{
RPID: rpID,
RPDisplayName: panelName.Value,
RPOrigins: []string{origin},
AuthenticatorSelection: protocol.AuthenticatorSelection{
UserVerification: protocol.VerificationRequired,
},
}, "", nil
}
func PasskeyOriginAndRPID(c *gin.Context) (string, string, error) {
host := passkeyRequestHost(c)
if host == "" {
return "", "", fmt.Errorf("missing request host")
}
scheme := PasskeyRequestScheme(c)
origin := fmt.Sprintf("%s://%s", scheme, host)
bindDomain, err := repo.NewISettingRepo().Get(repo.WithByKey("BindDomain"))
if err != nil {
return "", "", err
}
bindDomainValue := strings.TrimSpace(bindDomain.Value)
if bindDomainValue == "" {
return "", "", buserr.New("ErrPasskeyNotConfigured")
}
hostDomain := stripHostPort(host)
bindDomainValue = stripHostPort(bindDomainValue)
if hostDomain == "" || !strings.EqualFold(hostDomain, bindDomainValue) {
return "", "", buserr.New("ErrPasskeyDisabled")
}
return origin, bindDomainValue, nil
}
func NewPasskeyUser(userID []byte, name string, records []passkey.PasskeyCredentialRecord) *passkey.PasskeyUser {
credentials := make([]webauthn.Credential, len(records))
for i, record := range records {
credentials[i] = record.Credential
}
return &passkey.PasskeyUser{
ID: userID,
Name: name,
DisplayName: name,
Credentials: credentials,
}
}
func GeneratePasskeyUserID(encoded string, allowCreate bool) ([]byte, string, error) {
if encoded == "" {
if !allowCreate {
return nil, "", buserr.New("ErrPasskeyNotConfigured")
}
raw := make([]byte, 32)
if _, err := rand.Read(raw); err != nil {
return nil, "", err
}
return raw, base64.RawURLEncoding.EncodeToString(raw), nil
}
raw, err := base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
return nil, "", err
}
return raw, encoded, nil
}
func LoadPasskeyCredentialRecords(encryptedValue string) ([]passkey.PasskeyCredentialRecord, error) {
if encryptedValue == "" {
return []passkey.PasskeyCredentialRecord{}, nil
}
decrypted, err := encrypt.StringDecrypt(encryptedValue)
if err != nil {
return nil, err
}
var records []passkey.PasskeyCredentialRecord
if err := json.Unmarshal([]byte(decrypted), &records); err != nil {
return nil, err
}
for i := range records {
records[i].Credential.Flags = webauthn.NewCredentialFlags(protocol.AuthenticatorFlags(records[i].FlagsValue))
}
return records, nil
}
func SavePasskeyCredentialRecords(records []passkey.PasskeyCredentialRecord) (string, error) {
if len(records) == 0 {
return "", nil
}
copyRecords := make([]passkey.PasskeyCredentialRecord, len(records))
copy(copyRecords, records)
for i := range copyRecords {
copyRecords[i].FlagsValue = CredentialFlagsValue(copyRecords[i].Credential.Flags)
}
raw, err := json.Marshal(copyRecords)
if err != nil {
return "", err
}
return encrypt.StringEncrypt(string(raw))
}
func PasskeyCredentialExists(records []passkey.PasskeyCredentialRecord, credentialID []byte) bool {
encoded := base64.RawURLEncoding.EncodeToString(credentialID)
for _, record := range records {
if record.ID == encoded {
return true
}
}
return false
}
func UpdatePasskeyCredentialRecord(records []passkey.PasskeyCredentialRecord, credential *webauthn.Credential) error {
encoded := base64.RawURLEncoding.EncodeToString(credential.ID)
for i := range records {
if records[i].ID == encoded {
records[i].Credential = *credential
records[i].FlagsValue = CredentialFlagsValue(credential.Flags)
records[i].LastUsedAt = time.Now().Format(constant.DateTimeLayout)
return nil
}
}
return buserr.New("ErrPasskeyNotConfigured")
}
func CredentialFlagsValue(flags webauthn.CredentialFlags) uint8 {
var value protocol.AuthenticatorFlags
if flags.UserPresent {
value |= protocol.FlagUserPresent
}
if flags.UserVerified {
value |= protocol.FlagUserVerified
}
if flags.BackupEligible {
value |= protocol.FlagBackupEligible
}
if flags.BackupState {
value |= protocol.FlagBackupState
}
return uint8(value)
}
func PasskeyRequestScheme(c *gin.Context) string {
if c.Request.TLS != nil {
return "https"
}
if !passkeyIsFromTrustedProxy(c) {
return "http"
}
if proto := passkeyForwardedProto(c.GetHeader("Forwarded")); proto != "" {
return proto
}
if proto := passkeyXForwardedProto(c.GetHeader("X-Forwarded-Proto")); proto != "" {
return proto
}
return "http"
}
func passkeyRequestHost(c *gin.Context) string {
host := c.Request.Host
if strings.Contains(host, ",") {
host = strings.TrimSpace(strings.Split(host, ",")[0])
}
return strings.TrimSpace(host)
}
func passkeyIsFromTrustedProxy(c *gin.Context) bool {
remoteIP := passkeyRemoteIP(c.Request.RemoteAddr)
if remoteIP == nil {
return false
}
proxies, err := loadPasskeyTrustedProxies()
if err != nil {
global.LOG.Errorf("load passkey trusted proxies failed, err: %v", err)
return false
}
for _, cidr := range proxies {
if cidr.Contains(remoteIP) {
return true
}
}
return false
}
func passkeyRemoteIP(remoteAddr string) net.IP {
if host, _, err := net.SplitHostPort(strings.TrimSpace(remoteAddr)); err == nil {
return net.ParseIP(host)
}
return net.ParseIP(strings.TrimSpace(remoteAddr))
}
func loadPasskeyTrustedProxies() ([]*net.IPNet, error) {
setting, err := repo.NewISettingRepo().Get(repo.WithByKey("PasskeyTrustedProxies"))
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return parsePasskeyTrustedProxies("127.0.0.1\n::1")
}
return nil, err
}
return parsePasskeyTrustedProxies(setting.Value)
}
func parsePasskeyTrustedProxies(value string) ([]*net.IPNet, error) {
lines := strings.Split(value, "\n")
proxies := make([]*net.IPNet, 0, len(lines))
for _, line := range lines {
entry := strings.TrimSpace(line)
if entry == "" {
continue
}
if !strings.Contains(entry, "/") {
if strings.Contains(entry, ":") {
entry += "/128"
} else {
entry += "/32"
}
}
_, cidr, err := net.ParseCIDR(entry)
if err != nil {
return nil, err
}
proxies = append(proxies, cidr)
}
return proxies, nil
}
func passkeyForwardedProto(forwarded string) string {
for _, part := range strings.Split(forwarded, ";") {
item := strings.TrimSpace(part)
if len(item) < 6 || !strings.EqualFold(item[:6], "proto=") {
continue
}
proto := strings.Trim(strings.TrimSpace(item[6:]), `"`)
if strings.EqualFold(proto, "https") {
return "https"
}
if strings.EqualFold(proto, "http") {
return "http"
}
}
return ""
}
func passkeyXForwardedProto(forwarded string) string {
if forwarded == "" {
return ""
}
proto := strings.TrimSpace(strings.Split(forwarded, ",")[0])
if strings.EqualFold(proto, "https") {
return "https"
}
if strings.EqualFold(proto, "http") {
return "http"
}
return ""
}
func stripHostPort(host string) string {
host = strings.TrimSpace(host)
if host == "" {
return ""
}
if strings.HasPrefix(host, "[") {
if parsedHost, _, err := net.SplitHostPort(host); err == nil {
return strings.Trim(parsedHost, "[]")
}
}
if parsedHost, _, err := net.SplitHostPort(host); err == nil {
return parsedHost
}
return strings.Trim(host, "[]")
}

View File

@@ -47,3 +47,7 @@ type SystemSetting struct {
Language string `json:"language"`
IsIntl bool `json:"isIntl"`
}
type PasskeyID struct {
ID string `json:"id" validate:"required"`
}

View File

@@ -9,16 +9,15 @@ type SettingInfo struct {
DeveloperMode string `json:"developerMode"`
UpgradeBackupCopies string `json:"upgradeBackupCopies"`
SessionTimeout string `json:"sessionTimeout"`
Port string `json:"port"`
Ipv6 string `json:"ipv6"`
BindAddress string `json:"bindAddress"`
PanelName string `json:"panelName"`
Edition string `json:"edition"`
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
Language string `json:"language"`
DocSource string `json:"docSource"`
Port string `json:"port"`
Ipv6 string `json:"ipv6"`
BindAddress string `json:"bindAddress"`
PanelName string `json:"panelName"`
Edition string `json:"edition"`
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
Language string `json:"language"`
DocSource string `json:"docSource"`
ServerPort string `json:"serverPort"`
SSL string `json:"ssl"`
@@ -32,8 +31,6 @@ type SettingInfo struct {
ExpirationDays string `json:"expirationDays"`
ExpirationTime string `json:"expirationTime"`
ComplexityVerification string `json:"complexityVerification"`
MFAStatus string `json:"mfaStatus"`
MFAInterval string `json:"mfaInterval"`
AppStoreVersion string `json:"appStoreVersion"`
AppStoreLastModified string `json:"appStoreLastModified"`
@@ -48,12 +45,56 @@ type SettingInfo struct {
ProxyUser string `json:"proxyUser"`
ProxyPasswd string `json:"proxyPasswd"`
ProxyPasswdKeep string `json:"proxyPasswdKeep"`
}
type SettingBaseInfo struct {
SystemVersion string `json:"systemVersion"`
DeveloperMode string `json:"developerMode"`
UpgradeBackupCopies string `json:"upgradeBackupCopies"`
Port string `json:"port"`
Ipv6 string `json:"ipv6"`
BindAddress string `json:"bindAddress"`
PanelName string `json:"panelName"`
Edition string `json:"edition"`
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
Language string `json:"language"`
HideMenu string `json:"hideMenu"`
DocSource string `json:"docSource"`
ServerPort string `json:"serverPort"`
SecurityEntrance string `json:"securityEntrance"`
ComplexityVerification string `json:"complexityVerification"`
NoAuthSetting string `json:"noAuthSetting"`
ProxyType string `json:"proxyType"`
DashboardMemoVisible string `json:"dashboardMemoVisible"`
DashboardSimpleNodeVisible string `json:"dashboardSimpleNodeVisible"`
}
type CurrentUserInfo struct {
Name string `json:"name"`
SessionTimeout int `json:"sessionTimeout"`
MFAStatus string `json:"mfaStatus"`
MFAInterval string `json:"mfaInterval"`
ExpirationDays int `json:"expirationDays"`
ExpirationTime string `json:"expirationTime"`
ComplexitySetting string `json:"complexitySetting"`
ApiInterfaceStatus string `json:"apiInterfaceStatus"`
ApiKey string `json:"apiKey"`
IpWhiteList string `json:"ipWhiteList"`
ApiKeyValidityTime string `json:"apiKeyValidityTime"`
}
type CurrentUserUpdate struct {
Name string `json:"name" validate:"required"`
Password string `json:"password"`
OldPassword string `json:"oldPassword"`
SessionTimeout int `json:"sessionTimeout" validate:"required,min=300,max=864000"`
ExpirationDays int `json:"expirationDays" validate:"min=0,max=60"`
ExpirationTime string `json:"expirationTime"`
}
type SettingKey struct {
Key string `json:"key" validate:"required,oneof=ScriptSync"`

View File

@@ -8,22 +8,18 @@ import (
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/core/app/auth"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
initauth "github.com/1Panel-dev/1Panel/core/init/auth"
"github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/mfa"
"github.com/1Panel-dev/1Panel/core/utils/passkey"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/gin-gonic/gin"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
@@ -35,9 +31,7 @@ type AuthService struct{}
type IAuthService interface {
GetResponsePage() (string, error)
VerifyCode(code string) (bool, error)
Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, string, error)
LogOut(c *gin.Context) error
MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, string, error)
PasskeyBeginLogin(c *gin.Context, entrance string) (*dto.PasskeyBeginResponse, string, error)
PasskeyFinishLogin(c *gin.Context, sessionID, entrance string) (*dto.UserLoginInfo, string, error)
PasskeyBeginRegister(c *gin.Context, name string) (*dto.PasskeyBeginResponse, string, error)
@@ -53,104 +47,6 @@ func NewIAuthService() IAuthService {
return &AuthService{}
}
func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, string, error) {
nameSetting, err := settingRepo.Get(repo.WithByKey("UserName"))
if err != nil {
return nil, "", buserr.New("ErrRecordNotFound")
}
if nameSetting.Value != info.Name {
return xpack.Login(c, info, entrance)
}
if err = checkPassword(info.Password); err != nil {
return nil, "ErrAuth", err
}
entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance"))
if err != nil {
return nil, "", err
}
if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance {
return nil, "ErrEntrance", buserr.New("ErrEntrance")
}
mfa, err := settingRepo.Get(repo.WithByKey("MFAStatus"))
if err != nil {
return nil, "", err
}
if err = settingRepo.Update("Language", info.Language); err != nil {
return nil, "", err
}
if mfa.Value == constant.StatusEnable {
ip := common.GetRealClientIP(c)
mfaSession := initauth.GetMFASessionStore().Set(nameSetting.Value, entrance, ip)
return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value, MfaSession: mfaSession}, "", nil
}
res, err := u.generateSession(c, info.Name)
if err != nil {
return nil, "", err
}
if entrance != "" {
SetSecurityEntranceCookie(c, entrance)
}
return res, "", nil
}
func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, string, error) {
mfaSessions := initauth.GetMFASessionStore()
session, ok := mfaSessions.Get(info.SessionID)
if !ok {
return nil, "ErrMFA", nil
}
if session.IP != common.GetRealClientIP(c) {
return nil, "ErrMFA", nil
}
if session.Entrance != entrance {
return nil, "", buserr.New("ErrEntrance")
}
mfaSecret, err := settingRepo.Get(repo.WithByKey("MFASecret"))
if err != nil {
return nil, "", err
}
mfaInterval, err := settingRepo.Get(repo.WithByKey("MFAInterval"))
if err != nil {
return nil, "", err
}
success := mfa.ValidCode(info.Code, mfaInterval.Value, mfaSecret.Value)
if !success {
return nil, "ErrMFA", nil
}
res, err := u.generateSession(c, session.Name)
if err != nil {
return nil, "", err
}
mfaSessions.Delete(info.SessionID)
if entrance != "" {
SetSecurityEntranceCookie(c, entrance)
}
return res, "", nil
}
func (u *AuthService) generateSession(c *gin.Context, name string) (*dto.UserLoginInfo, error) {
setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout"))
if err != nil {
return nil, err
}
httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL"))
if err != nil {
return nil, err
}
lifeTime, err := strconv.Atoi(setting.Value)
if err != nil {
return nil, err
}
sessionUser := psession.SessionUser{ID: psession.SuperAdminSessionUserID, Name: name, Role: "ADMIN"}
lifeTime = xpack.LoadSessionTimeout(sessionUser, lifeTime)
if err := global.SESSION.SetFresh(c, sessionUser, httpsSetting.Value == constant.StatusEnable, lifeTime); err != nil {
return nil, err
}
return &dto.UserLoginInfo{Name: name, Role: "ADMIN"}, nil
}
func (u *AuthService) LogOut(c *gin.Context) error {
httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL"))
if err != nil {
@@ -304,7 +200,8 @@ func (u *AuthService) PasskeyFinishLogin(c *gin.Context, sessionID, entrance str
if err != nil {
return nil, "", err
}
res, err := u.generateSession(c, userSetting.Value)
sessionUser := psession.SessionUser{ID: psession.SuperAdminSessionUserID, Name: userSetting.Value, Role: "ADMIN"}
res, err := auth.GenerateSession(c, sessionUser)
if err != nil {
return nil, "", err
}
@@ -794,27 +691,3 @@ func stripHostPort(hostport string) string {
}
return strings.Trim(hostport, "[]")
}
func checkPassword(password string) error {
priKey, _ := settingRepo.Get(repo.WithByKey("PASSWORD_PRIVATE_KEY"))
privateKey, err := encrypt.ParseRSAPrivateKey(priKey.Value)
if err != nil {
return err
}
loginPassword, err := encrypt.DecryptPassword(password, privateKey)
if err != nil {
return err
}
passwordSetting, err := settingRepo.Get(repo.WithByKey("Password"))
if err != nil {
return err
}
existPassword, err := encrypt.StringDecrypt(passwordSetting.Value)
if err != nil {
return err
}
if !hmac.Equal([]byte(loginPassword), []byte(existPassword)) {
return buserr.New("ErrAuth")
}
return nil
}

View File

@@ -98,7 +98,7 @@ func (u *BackupService) Create(req dto.BackupOperate) error {
if err := backupRepo.Create(&backup); err != nil {
return err
}
if err := xpack.Sync(constant.SyncBackupAccounts); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncBackupAccounts); err != nil {
global.LOG.Errorf("sync backup account to node failed, err: %v", err)
}
return nil
@@ -119,7 +119,7 @@ func (u *BackupService) Delete(name string) error {
global.LOG.Errorf("check used of local cronjob failed, err: %v", err)
return buserr.New("ErrBackupInUsed")
}
if err := xpack.CheckBackupUsed(name); err != nil {
if err := xpack.MultiNodeProvider.CheckBackupUsed(name); err != nil {
global.LOG.Errorf("check used of node cronjob failed, err: %v", err)
return buserr.New("ErrBackupInUsed")
}
@@ -127,7 +127,7 @@ func (u *BackupService) Delete(name string) error {
if err := backupRepo.Delete(repo.WithByName(name)); err != nil {
return err
}
if err := xpack.Sync(constant.SyncBackupAccounts); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncBackupAccounts); err != nil {
global.LOG.Errorf("sync backup account to node failed, err: %v", err)
}
return nil
@@ -176,7 +176,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
if err := backupRepo.Save(&newBackup); err != nil {
return err
}
if err := xpack.Sync(constant.SyncBackupAccounts); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncBackupAccounts); err != nil {
global.LOG.Errorf("sync backup account to node failed, err: %v", err)
}
return nil
@@ -218,7 +218,7 @@ func (u *BackupService) RefreshToken(req dto.OperateByName) error {
if err := backupRepo.Save(&backup); err != nil {
return err
}
if err := xpack.Sync(constant.SyncBackupAccounts); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncBackupAccounts); err != nil {
global.LOG.Errorf("sync backup account to node failed, err: %v", err)
}
return nil

View File

@@ -105,13 +105,13 @@ func (u *GroupService) Delete(id uint) error {
case "command":
err = commandRepo.UpdateGroup(id, defaultGroup.ID)
case "node":
err = xpack.UpdateGroup("node", id, defaultGroup.ID)
err = xpack.MultiNodeProvider.UpdateGroup("node", id, defaultGroup.ID)
case "website":
bodyItem := []byte(fmt.Sprintf(`{"Group":%v, "NewGroup":%v}`, id, defaultGroup.ID))
if _, err := proxy_local.NewLocalClient("/api/v2/websites/group/change", http.MethodPost, bytes.NewReader(bodyItem), nil); err != nil {
return err
}
if err := xpack.UpdateGroup("node", id, defaultGroup.ID); err != nil {
if err := xpack.MultiNodeProvider.UpdateGroup("node", id, defaultGroup.ID); err != nil {
return err
}
}

View File

@@ -124,7 +124,7 @@ func (u *ScriptService) Create(req dto.ScriptOperate) error {
if req.IsInteractive {
return nil
}
if err := xpack.Sync(constant.SyncScripts); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncScripts); err != nil {
global.LOG.Errorf("sync scripts to node failed, err: %v", err)
}
return nil
@@ -140,7 +140,7 @@ func (u *ScriptService) Delete(req dto.OperateByIDs) error {
return err
}
}
if err := xpack.Sync(constant.SyncScripts); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncScripts); err != nil {
global.LOG.Errorf("sync scripts to node failed, err: %v", err)
}
return nil
@@ -160,7 +160,7 @@ func (u *ScriptService) Update(req dto.ScriptOperate) error {
if err := scriptRepo.Update(req.ID, updateMap); err != nil {
return err
}
if err := xpack.Sync(constant.SyncScripts); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncScripts); err != nil {
global.LOG.Errorf("sync scripts to node failed, err: %v", err)
}
return nil
@@ -276,7 +276,7 @@ func (u *ScriptService) Sync(req dto.OperateByTaskID) error {
if err := global.DB.Model(&model.Setting{}).Where("key = ?", "ScriptVersion").Updates(map[string]interface{}{"value": string(versionRes)}).Error; err != nil {
return fmt.Errorf("update script version in db failed, err: %v", err)
}
if err := xpack.Sync(constant.SyncScripts); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncScripts); err != nil {
global.LOG.Errorf("sync scripts to node failed, err: %v", err)
}
return nil

View File

@@ -43,16 +43,13 @@ type SettingService struct{}
type ISettingService interface {
GetSettingInfo() (*dto.SettingInfo, error)
GetSettingBaseInfo() (*dto.SettingBaseInfo, error)
LoadInterfaceAddr() ([]string, error)
Update(c *gin.Context, key, value string) error
UpdatePassword(c *gin.Context, old, new string) error
UpdatePort(port uint) error
UpdateBindInfo(req dto.BindInfo) error
UpdateSSL(c *gin.Context, req dto.SSLUpdate) error
LoadFromCert() (*dto.SSLInfo, error)
HandlePasswordExpired(c *gin.Context, old, new string) error
GenerateApiKey() (string, error)
UpdateApiConfig(req dto.ApiInterfaceConfig) error
UpdateProxy(req dto.ProxyUpdate) error
@@ -113,6 +110,40 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
return &info, err
}
func (u *SettingService) GetSettingBaseInfo() (*dto.SettingBaseInfo, error) {
setting, err := settingRepo.List()
if err != nil {
return nil, buserr.New("ErrRecordNotFound")
}
settingMap := make(map[string]string)
for _, set := range setting {
settingMap[set.Key] = set.Value
}
if hideMenu, ok := settingMap["HideMenu"]; ok && len(hideMenu) > 0 {
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(hideMenu), &menus); err == nil {
sortShowMenus(menus)
if sortedBytes, err := json.Marshal(menus); err == nil {
settingMap["HideMenu"] = string(sortedBytes)
}
}
}
var info dto.SettingBaseInfo
arr, err := json.Marshal(settingMap)
if err != nil {
return nil, err
}
if err := json.Unmarshal(arr, &info); err != nil {
return nil, err
}
if info.Edition == "" {
info.Edition = "cn"
_ = settingRepo.UpdateOrCreate("Edition", info.Edition)
}
return &info, err
}
func sortShowMenus(menus []dto.ShowMenu) {
for i := range menus {
if len(menus[i].Children) > 0 {
@@ -164,14 +195,6 @@ func (u *SettingService) Update(c *gin.Context, key, value string) error {
}
switch key {
case "ExpirationDays":
timeout, err := strconv.Atoi(value)
if err != nil {
return err
}
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format(constant.DateTimeLayout)); err != nil {
return err
}
case "BindDomain":
if len(value) != 0 {
_ = global.SESSION.Clean()
@@ -179,11 +202,9 @@ func (u *SettingService) Update(c *gin.Context, key, value string) error {
if err := u.clearPasskeySettings(); err != nil {
return err
}
case "UserName", "Password":
u.deleteCurrentSession(c)
case "Language":
i18n.SetCachedDBLanguage(value)
if err := xpack.Sync(constant.SyncLanguage); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncLanguage); err != nil {
global.LOG.Errorf("sync language to node failed, err: %v", err)
}
case "UpgradeBackupCopies":
@@ -196,7 +217,7 @@ func (u *SettingService) Update(c *gin.Context, key, value string) error {
}
case "Edition":
global.CONF.Base.Edition = value
if err := xpack.Sync(constant.SyncEdition); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncEdition); err != nil {
global.LOG.Errorf("sync edition to node failed, err: %v", err)
}
}
@@ -263,14 +284,14 @@ func (u *SettingService) UpdateProxy(req dto.ProxyUpdate) error {
if err := settingRepo.Update("ProxyPasswdKeep", req.ProxyPasswdKeep); err != nil {
return err
}
if err := xpack.ProxyDocker(loadDockerProxy(req)); err != nil {
if err := xpack.MultiNodeProvider.ProxyDocker(loadDockerProxy(req)); err != nil {
return err
}
syncScope := constant.SyncSystemProxy
if req.WithDockerRestart {
syncScope = constant.SyncSystemProxyWithRestartDocker
}
if err := xpack.Sync(syncScope); err != nil {
if err := xpack.MultiNodeProvider.Sync(syncScope); err != nil {
global.LOG.Errorf("sync proxy to node failed, err: %v", err)
}
return nil
@@ -410,7 +431,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := os.Rename(path.Join(secretDir, "server.key.tmp"), path.Join(secretDir, "server.key")); err != nil {
return err
}
status, _ := settingRepo.GetValueByKey("SSL")
status := global.CONF.Conn.SSL
if req.SSL != status {
go func() {
time.Sleep(1 * time.Second)
@@ -420,6 +441,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := settingRepo.Update("SSL", req.SSL); err != nil {
return err
}
global.CONF.Conn.SSL = req.SSL
return u.UpdateSystemSSL()
}
@@ -562,14 +584,6 @@ func (u *SettingService) UpdateTerminal(req dto.TerminalInfo) error {
return nil
}
func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
if err := u.HandlePasswordExpired(c, old, new); err != nil {
return err
}
u.deleteCurrentSession(c)
return nil
}
func (u *SettingService) deleteCurrentSession(c *gin.Context) {
if c == nil {
return
@@ -588,7 +602,7 @@ func (u *SettingService) clearPasskeySettings() error {
if err := settingRepo.Update(passkey.PasskeyCredentialSettingKey, ""); err != nil {
return err
}
return nil
return xpack.AuthProvider.ClearPasskeys()
}
func (u *SettingService) UpdateSystemSSL() error {
@@ -610,35 +624,6 @@ func (u *SettingService) UpdateSystemSSL() error {
return nil
}
func (u *SettingService) GenerateApiKey() (string, error) {
apiKey := common.RandStr(32)
if err := settingRepo.Update("ApiKey", apiKey); err != nil {
return global.Api.ApiKey, err
}
global.Api.ApiKey = apiKey
return apiKey, nil
}
func (u *SettingService) UpdateApiConfig(req dto.ApiInterfaceConfig) error {
if err := settingRepo.UpdateOrCreate("ApiInterfaceStatus", req.ApiInterfaceStatus); err != nil {
return err
}
global.Api.ApiInterfaceStatus = req.ApiInterfaceStatus
if err := settingRepo.UpdateOrCreate("ApiKey", req.ApiKey); err != nil {
return err
}
global.Api.ApiKey = req.ApiKey
if err := settingRepo.UpdateOrCreate("IpWhiteList", req.IpWhiteList); err != nil {
return err
}
global.Api.IpWhiteList = req.IpWhiteList
if err := settingRepo.UpdateOrCreate("ApiKeyValidityTime", req.ApiKeyValidityTime); err != nil {
return err
}
global.Api.ApiKeyValidityTime = req.ApiKeyValidityTime
return nil
}
func loadInfoFromCert() (dto.SSLInfo, error) {
var info dto.SSLInfo
certFile := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt")

View File

@@ -259,7 +259,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
global.LOG.Info("upgrade successful!")
dropBackupCopies()
xpack.AutoUpgradeWithMaster()
xpack.MultiNodeProvider.AutoUpgradeWithMaster()
go writeLogs(req.Version)
_ = settingRepo.Update("SystemVersion", req.Version)
_ = global.AgentDB.Model(&model.Setting{}).Where("key = ?", "SystemVersion").Updates(map[string]interface{}{"value": req.Version}).Error

View File

@@ -4,7 +4,7 @@ base:
is_demo: false
is_offline: false
is_fxplay: false
is_xpackee: true
is_xpackee: false
port: 9999
username: admin
password: admin123

View File

@@ -30,13 +30,6 @@ type Conn struct {
Entrance string `mapstructure:"entrance"`
}
type ApiInterface struct {
ApiKey string `mapstructure:"api_key"`
ApiInterfaceStatus string `mapstructure:"api_interface_status"`
IpWhiteList string `mapstructure:"ip_white_list"`
ApiKeyValidityTime string `mapstructure:"api_key_validity_time"`
}
type LogConfig struct {
Level string `mapstructure:"level"`
TimeZone string `mapstructure:"timeZone"`

View File

@@ -18,7 +18,6 @@ var (
AgentDB *gorm.DB
LOG *logrus.Logger
CONF ServerConfig
Api ApiInterface
VALID *validator.Validate
SESSION *psession.PSession
Viper *viper.Viper

View File

@@ -55,8 +55,6 @@ AppInstallCheck: 'Check application installation environment'
# backup
ErrBackupInUsed: "This backup account is used in scheduled tasks and cannot be deleted"
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrBackupCheck: "Backup account connection test failed {{ .err }}"
ErrBackupLocal: "Local server backup account does not support this operation!"
ErrBackupPublic: "Detected that this backup account is not public, check and try again!"
@@ -75,6 +73,7 @@ ErrLicenseExist: "This license record already exists. You can directly go to the
ErrXpackNotFound: "This section requires Business Edition. Import a license in Panel Settings > License."
ErrXpackExceptional: "This section requires Business Edition. Sync the license status in Panel Settings > License."
ErrXpackLost: "The license retry limit has been reached. Go to Panel Settings > License and run a manual sync."
ErrXpackEELicenseRequired: "Enterprise Edition license is not imported. Import a license first."
ErrDeviceLost: "Required files for license verification are missing, check and try again!"
ErrDeviceErr: "Current environment does not match the license import environment. Edit the license and re-import."
ErrXpackTimeout: "Request timeout, network connection might be unstable, try again later!"
@@ -92,6 +91,19 @@ ErrNodeBind: "This node is already bound to a license, check and retry!"
ErrNodeLocalRollback: "The primary node does not support direct rollback. Run '1pctl restore' manually to roll back."
ErrIntlLicense: "The current version does not support importing international licenses yet. Stay tuned!"
# user
ErrNoSuchUser: "User information not found, check and try again!"
ErrToMaster: "This user does not have permission to perform this operation on the master node. Check and try again!"
ErrToNode: "This user does not have permission to perform this operation on this node. Check and try again!"
ErrRBAC: "Insufficient user permissions. This operation cannot be performed. Check and try again!"
ErrNoneNode: "This user has no node permissions. Contact an administrator or check and try again!"
ErrOnlySuperAdmin: "Only super admins can use this feature!"
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRolePresetCannotUpdate: "System preset roles cannot be updated"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrRolePresetCannotBind: "System preset roles cannot be assigned to users"
ErrSuperAdminCannotDelete: "Super admin users cannot be deleted"
InvalidRequestBodyType: "Invalid request body format. Ensure content matches the required format and retry."
InvalidLicenseCodeType: "Invalid license code format provided, check and try again!"
LicenseNotFoundType: "License not found, no matching record exists in the system for the provided license. Check and try again!"

View File

@@ -54,8 +54,6 @@ AppInstallCheck: 'Verificar entorno de instalación de aplicación'
# backup
ErrBackupInUsed: 'Cuenta de respaldo en uso por tarea programada'
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrBackupCheck: 'Conexión de respaldo falló: {{ .err }}'
ErrBackupLocal: "La cuenta de respaldo del servidor local no admite esta operación"
ErrBackupPublic: "Se detectó que esta cuenta de respaldo no es pública, verifique e intente de nuevo"
@@ -75,6 +73,7 @@ ErrXpackNotFound: "Esta sección requiere la edición Business. Importa una lice
ErrXpackExceptional: "Esta sección requiere la edición Business. Sincroniza el estado de la licencia en Configuración del panel > Licencia."
ErrXpackOutOfDate: "La licencia actual ha expirado, importe nuevamente la licencia en Panel > Ajustes > Licencia"
ErrXpackLost: "La licencia alcanzó el límite de reintentos. Ve a Configuración del panel > Licencia y ejecuta una sincronización manual."
ErrXpackEELicenseRequired: "La licencia de la edición Enterprise no se ha importado. Importa primero una licencia."
ErrDeviceLost: "Faltan archivos necesarios para la verificación de licencia, verifique e intente de nuevo"
ErrDeviceErr: "El entorno actual no coincide con el entorno de importación de la licencia. Edite la licencia e impórtela de nuevo"
ErrXpackTimeout: "Tiempo de espera de la solicitud, puede que la conexión de red sea inestable, intente más tarde"
@@ -91,6 +90,20 @@ ErrNodeUnbind: "Este nodo no está dentro del rango de vinculación de la licenc
ErrNodeBind: "Este nodo ya está vinculado a una licencia, verifique e intente de nuevo"
ErrNodeLocalRollback: "El nodo principal no admite la reversión directa. Ejecuta manualmente '1pctl restore' para revertir."
ErrIntlLicense: "La versión actual aún no admite importar licencias de la edición internacional. Próximamente"
# user
ErrNoSuchUser: "No se encontró la información del usuario. Verifique e intente de nuevo."
ErrToMaster: "Este usuario no tiene permiso para realizar esta operación en el nodo principal. Verifique e intente de nuevo."
ErrToNode: "Este usuario no tiene permiso para realizar esta operación en este nodo. Verifique e intente de nuevo."
ErrRBAC: "Permisos de usuario insuficientes. No se puede realizar esta operación. Verifique e intente de nuevo."
ErrNoneNode: "Este usuario no tiene permisos sobre ningún nodo. Contacte al administrador o verifique e intente de nuevo."
ErrOnlySuperAdmin: "Solo los superadministradores pueden usar esta función."
ErrRolePresetCannotDelete: "No se pueden eliminar los roles predefinidos del sistema"
ErrRolePresetCannotUpdate: "No se pueden actualizar los roles predefinidos del sistema"
ErrRoleBoundToUser: "Este rol ya está vinculado a usuarios y no se puede eliminar"
ErrRolePresetCannotBind: "Los roles predefinidos del sistema no se pueden asignar a usuarios"
ErrSuperAdminCannotDelete: "No se pueden eliminar usuarios superadministradores"
InvalidRequestBodyType: "Formato del cuerpo de la solicitud no válido, por favor revisa y asegúrate de que el contenido cumpla con el formato requerido antes de reintentar."
InvalidLicenseCodeType: "Formato de código de licencia no válido, por favor revisa e inténtalo de nuevo."
LicenseNotFoundType: "Licencia no encontrada, no existe ningún registro coincidente en el sistema para la licencia proporcionada. Por favor revisa e inténtalo de nuevo."

View File

@@ -49,8 +49,6 @@ AppInstallCheck: 'アプリケーションインストール環境を確認'
# backup
ErrBackupInUsed: 'バックアップアカウントがスケジュールで使用中'
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrBackupCheck: '接続テストに失敗しました: {{ .err }}'
ErrBackupLocal: "ローカルサーバーバックアップアカウントはこの操作をサポートしていません!"
ErrBackupPublic: "このバックアップアカウントが公開されていないと検出されました。再確認してください!"
@@ -70,6 +68,7 @@ ErrLicenseExist: "このライセンス記録は既に存在します。ライ
ErrXpackNotFound: "この機能は Business Edition で利用できます。パネル設定 > ライセンス でライセンスをインポートしてください"
ErrXpackExceptional: "この機能は Business Edition で利用できます。パネル設定 > ライセンス でライセンス状態を同期してください"
ErrXpackLost: "ライセンスの再試行回数が上限に達しました。パネル設定 > ライセンス で手動同期を実行してください"
ErrXpackEELicenseRequired: "Enterprise Edition のライセンスがインポートされていません。先にライセンスをインポートしてください"
ErrDeviceLost: "ライセンス検証に必要なファイルが失われました。確認して再試行してください!"
ErrDeviceErr: "現在の環境とライセンスのインポート環境が一致しません。ライセンスを編集して再度インポートしてください!"
ErrXpackTimeout: "リクエストタイムアウト、ネットワーク接続が不安定な可能性があります。後で再試行してください"
@@ -87,6 +86,19 @@ ErrNodeBind: "そのノードはライセンスにバインドされています
ErrNodeLocalRollback: "マスターノードでは直接ロールバックできません。'1pctl restore' を手動で実行してロールバックしてください"
ErrIntlLicense: "現在のバージョンでは国際版ライセンスのインポートはまだサポートされていません。しばらくお待ちください!"
# user
ErrNoSuchUser: "ユーザー情報が見つかりません。確認して再試行してください!"
ErrToMaster: "このユーザーにはマスターノードでこの操作を実行する権限がありません。確認して再試行してください!"
ErrToNode: "このユーザーにはこのノードでこの操作を実行する権限がありません。確認して再試行してください!"
ErrRBAC: "ユーザー権限が不足しているため、この操作を実行できません。確認して再試行してください!"
ErrNoneNode: "このユーザーにはノード権限がありません。管理者に連絡するか、確認して再試行してください!"
ErrOnlySuperAdmin: "この機能はスーパー管理者のみ使用できます!"
ErrRolePresetCannotDelete: "システム既定ロールは削除できません"
ErrRolePresetCannotUpdate: "システム既定ロールは更新できません"
ErrRoleBoundToUser: "このロールはすでにユーザーに紐付いているため削除できません"
ErrRolePresetCannotBind: "システム既定ロールはユーザーに割り当てできません"
ErrSuperAdminCannotDelete: "スーパー管理者ユーザーは削除できません"
InvalidRequestBodyType: "リクエストボディの形式が無効です。内容が要求された形式に準拠しているか確認してから再試行してください!"
InvalidLicenseCodeType: "提供されたライセンスコードの形式が無効です。確認してから再試行してください!"
LicenseNotFoundType: "ライセンスが見つかりません。提供されたライセンスに一致する記録がシステム内に存在しません。確認してから再試行してください!"

View File

@@ -49,8 +49,6 @@ AppInstallCheck: '애플리케이션 설치 환경 확인'
# backup
ErrBackupInUsed: '백업 계정이 예약에 사용 중'
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrBackupCheck: '연결 테스트 실패: {{ .err }}'
ErrBackupLocal: "로컬 서버 백업 계정은 이 작업을 지원하지 않습니다"
ErrBackupPublic: "이 백업 계정이 공개된 것으로 감지되지 않았습니다. 다시 확인하십시오"
@@ -69,6 +67,7 @@ ErrLicenseExist: "해당 라이선스 기록이 이미 존재합니다. 라이
ErrXpackNotFound: "이 기능은 Business Edition에서 사용할 수 있습니다. 패널 설정 > 라이선스에서 라이선스를 가져오세요"
ErrXpackExceptional: "이 기능은 Business Edition에서 사용할 수 있습니다. 패널 설정 > 라이선스에서 라이선스 상태를 동기화하세요"
ErrXpackLost: "라이선스 재시도 한도에 도달했습니다. 패널 설정 > 라이선스에서 수동 동기화를 실행하세요"
ErrXpackEELicenseRequired: "Enterprise Edition 라이선스를 가져오지 않았습니다. 먼저 라이선스를 가져오세요"
ErrDeviceLost: "라이센스 검증에 필요한 파일이 누락되었습니다. 확인 후 다시 시도해 주세요"
ErrDeviceErr: "현재 환경이 라이선스 가져오기 환경과 일치하지 않습니다. 라이선스를 편집하고 다시 가져오십시오"
ErrXpackTimeout: "요청 시간 초과, 네트워크 연결이 불안정할 수 있습니다. 나중에 다시 시도해 주세요"
@@ -86,6 +85,19 @@ ErrNodeBind: "이 노드가 이미 라이선스에 바인딩되어 있음을 감
ErrNodeLocalRollback: "마스터 노드는 직접 롤백을 지원하지 않습니다. '1pctl restore'를 수동으로 실행해 롤백하세요"
ErrIntlLicense: "현재 버전에서는 국제판 라이선스 가져오기를 아직 지원하지 않습니다. 곧 제공될 예정입니다"
# user
ErrNoSuchUser: "사용자 정보를 찾을 수 없습니다. 확인 후 다시 시도하세요"
ErrToMaster: "이 사용자는 마스터 노드에서 이 작업을 수행할 권한이 없습니다. 확인 후 다시 시도하세요"
ErrToNode: "이 사용자는 이 노드에서 이 작업을 수행할 권한이 없습니다. 확인 후 다시 시도하세요"
ErrRBAC: "사용자 권한이 부족하여 이 작업을 수행할 수 없습니다. 확인 후 다시 시도하세요"
ErrNoneNode: "이 사용자에게는 노드 권한이 없습니다. 관리자에게 문의하거나 확인 후 다시 시도하세요"
ErrOnlySuperAdmin: "슈퍼 관리자만 이 기능을 사용할 수 있습니다"
ErrRolePresetCannotDelete: "시스템 기본 역할은 삭제할 수 없습니다"
ErrRolePresetCannotUpdate: "시스템 기본 역할은 수정할 수 없습니다"
ErrRoleBoundToUser: "이 역할은 이미 사용자에게 연결되어 있어 삭제할 수 없습니다"
ErrRolePresetCannotBind: "시스템 기본 역할은 사용자에게 할당할 수 없습니다"
ErrSuperAdminCannotDelete: "슈퍼 관리자 사용자는 삭제할 수 없습니다"
InvalidRequestBodyType: "요청 본문 형식이 잘못되었습니다. 내용이 형식 요구 사항을 충족하는지 확인한 후 다시 시도하세요"
InvalidLicenseCodeType: "제공된 라이선스 코드 형식이 잘못되었습니다. 확인 후 다시 시도하세요"
LicenseNotFoundType: "라이선스가 존재하지 않습니다. 시스템에서 제공된 라이선스와 일치하는 기록을 찾을 수 없습니다. 확인 후 다시 시도하세요"

View File

@@ -44,8 +44,6 @@ ErrFileNotFound: "Fail {{ .name }} tidak wujud"
# backup
ErrBackupInUsed: 'Akaun sandaran sedang digunakan oleh tugas'
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrBackupCheck: 'Ujian sambungan gagal: {{ .err }}'
ErrBackupLocal: "Akaun sandaran pelayan tempatan tidak menyokong operasi ini"
ErrBackupPublic: "Akaun sandaran ini dikesan tidak awam, sila semak semula dan cuba lagi"
@@ -68,6 +66,7 @@ ErrDeviceLost: "Fail yang diperlukan untuk pengesahan lesen hilang, sila semak d
ErrDeviceErr: "Persekitaran semasa tidak sepadan dengan persekitaran import lesen. Sila edit lesen dan import semula"
ErrXpackTimeout: "Permintaan tamat masa, sambungan rangkaian mungkin tidak stabil, sila cuba lagi kemudian"
ErrUnbindMaster: "Terdapat nod dalam pengurusan nod, sila keluarkan dahulu dan cuba lagi"
ErrXpackEELicenseRequired: "Lesen Edisi Enterprise belum diimport. Sila import lesen terlebih dahulu"
ErrFreeNodeLimit: "Had nod Edisi Community telah dicapai. Pergi ke www.lxware.cn/1panel untuk naik taraf dan cuba lagi"
ErrNodeBound: "Lesen ini telah diikat dengan nod lain, sila semak dan cuba lagi"
ErrNodeBoundDelete: "Lisensi ini telah diikat dan tidak menyokong operasi penghapusan. Sila semak dan cuba lagi"
@@ -81,6 +80,19 @@ ErrNodeBind: "Nod ini telah diikat dengan lesen, sila semak dan cuba lagi"
ErrNodeLocalRollback: "Nod utama tidak menyokong rollback secara langsung. Jalankan '1pctl restore' secara manual untuk rollback"
ErrIntlLicense: "Versi semasa belum menyokong import lesen edisi antarabangsa. Nantikan"
# user
ErrNoSuchUser: "Maklumat pengguna tidak ditemui, sila semak dan cuba lagi"
ErrToMaster: "Pengguna ini tiada kebenaran untuk melakukan operasi ini pada nod utama, sila semak dan cuba lagi"
ErrToNode: "Pengguna ini tiada kebenaran untuk melakukan operasi ini pada nod ini, sila semak dan cuba lagi"
ErrRBAC: "Kebenaran pengguna tidak mencukupi, operasi ini tidak boleh dilakukan. Sila semak dan cuba lagi"
ErrNoneNode: "Pengguna ini tidak mempunyai sebarang kebenaran nod. Sila hubungi pentadbir atau semak dan cuba lagi"
ErrOnlySuperAdmin: "Hanya super admin boleh menggunakan fungsi ini"
ErrRolePresetCannotDelete: "Peranan pratetap sistem tidak boleh dipadam"
ErrRolePresetCannotUpdate: "Peranan pratetap sistem tidak boleh dikemas kini"
ErrRoleBoundToUser: "Peranan ini sudah terikat kepada pengguna dan tidak boleh dipadam"
ErrRolePresetCannotBind: "Peranan pratetap sistem tidak boleh diberikan kepada pengguna"
ErrSuperAdminCannotDelete: "Pengguna super admin tidak boleh dipadam"
InvalidRequestBodyType: "Format badan permintaan tidak sah, sila periksa dan pastikan kandungan memenuhi format yang diperlukan sebelum mencuba semula"
InvalidLicenseCodeType: "Format kod lesen yang diberikan tidak sah, sila periksa dan cuba lagi"
LicenseNotFoundType: "Lesen tidak dijumpai, tiada rekod yang sepadan dengan lesen yang diberikan dalam sistem. Sila periksa dan cuba lagi"

View File

@@ -49,8 +49,6 @@ AppInstallCheck: 'Verificar ambiente de instalação da aplicação'
# backup
ErrBackupInUsed: 'Conta de backup em uso por tarefa'
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrBackupCheck: 'Teste de conexão falhou: {{ .err }}'
ErrBackupLocal: "A conta de backup do servidor local não suporta essa operação"
ErrBackupPublic: "A conta de backup detectada não é pública, por favor verifique e tente novamente"
@@ -69,6 +67,7 @@ ErrLicenseExist: "Este registro de licença já existe. Você pode ir diretament
ErrXpackNotFound: "Este recurso requer a edição Business. Importe uma licença em Configurações do Painel > Licença."
ErrXpackExceptional: "Este recurso requer a edição Business. Sincronize o status da licença em Configurações do Painel > Licença."
ErrXpackLost: "A licença atingiu o limite de tentativas. Acesse Configurações do Painel > Licença e execute uma sincronização manual."
ErrXpackEELicenseRequired: "A licença da edição Enterprise não foi importada. Importe uma licença primeiro."
ErrDeviceLost: "Arquivos necessários para a verificação da licença estão faltando, por favor verifique e tente novamente"
ErrDeviceErr: "O ambiente atual não corresponde ao ambiente de importação da licença. Por favor, edite a licença e reimporte"
ErrXpackTimeout: "Requisição expirou, a conexão de rede pode estar instável, por favor tente novamente mais tarde"
@@ -86,6 +85,19 @@ ErrNodeBind: "Este nó já está vinculado a uma licença, por favor verifique e
ErrNodeLocalRollback: "O nó principal não suporta rollback direto. Por favor, execute manualmente o comando '1pctl restore' para fazer o rollback"
ErrIntlLicense: "A versão atual ainda não oferece suporte à importação de licença da edição internacional. Em breve"
# user
ErrNoSuchUser: "Informações do usuário não encontradas. Verifique e tente novamente."
ErrToMaster: "Este usuário não tem permissão para executar esta operação no nó principal. Verifique e tente novamente."
ErrToNode: "Este usuário não tem permissão para executar esta operação neste nó. Verifique e tente novamente."
ErrRBAC: "Permissões de usuário insuficientes. Não é possível executar esta operação. Verifique e tente novamente."
ErrNoneNode: "Este usuário não tem permissões de nenhum nó. Contate o administrador ou verifique e tente novamente."
ErrOnlySuperAdmin: "Somente superadministradores podem usar este recurso."
ErrRolePresetCannotDelete: "Funções predefinidas do sistema não podem ser excluídas"
ErrRolePresetCannotUpdate: "Funções predefinidas do sistema não podem ser atualizadas"
ErrRoleBoundToUser: "Esta função já está vinculada a usuários e não pode ser excluída"
ErrRolePresetCannotBind: "Funções predefinidas do sistema não podem ser atribuídas a usuários"
ErrSuperAdminCannotDelete: "Usuários superadministradores não podem ser excluídos"
InvalidRequestBodyType: "Formato do corpo da requisição inválido. Verifique se o conteúdo está no formato correto e tente novamente"
InvalidLicenseCodeType: "Formato do código de licença inválido. Verifique e tente novamente"
LicenseNotFoundType: "Licença não encontrada. Não há registros correspondentes no sistema para a licença fornecida. Verifique e tente novamente"

View File

@@ -49,8 +49,6 @@ AppInstallCheck: 'Проверить среду установки прилож
# backup
ErrBackupInUsed: 'Аккаунт бэкапа занят задачей'
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrBackupCheck: 'Проверка подключения не удалась: {{ .err }}'
ErrBackupLocal: "Локальная учетная запись резервного копирования не поддерживает эту операцию"
ErrBackupPublic: "Обнаружено, что эта учетная запись резервного копирования не является публичной, проверьте и повторите попытку"
@@ -69,6 +67,7 @@ ErrLicenseExist: "Данная лицензия уже существует. В
ErrXpackNotFound: "Этот раздел доступен в Business Edition. Импортируйте лицензию в Настройки панели > Лицензия"
ErrXpackExceptional: "Этот раздел доступен в Business Edition. Синхронизируйте статус лицензии в Настройки панели > Лицензия"
ErrXpackLost: "Достигнут лимит повторных попыток по лицензии. Перейдите в Настройки панели > Лицензия и выполните ручную синхронизацию"
ErrXpackEELicenseRequired: "Лицензия Enterprise Edition не импортирована. Сначала импортируйте лицензию."
ErrDeviceLost: "Необходимые файлы для проверки лицензии отсутствуют, пожалуйста, проверьте и попробуйте снова"
ErrDeviceErr: "Текущая среда не соответствует среде импорта лицензии. Отредактируйте лицензию и повторите импорт"
ErrXpackTimeout: "Время ожидания запроса истекло, возможно нестабильное сетевое соединение, повторите попытку позже"
@@ -86,6 +85,19 @@ ErrNodeBind: "Этот узел уже связан с лицензией, пр
ErrNodeLocalRollback: "Прямой откат на основном узле не поддерживается. Выполните '1pctl restore' вручную для отката"
ErrIntlLicense: "Текущая версия пока не поддерживает импорт международной лицензии. Скоро будет доступно"
# user
ErrNoSuchUser: "Информация о пользователе не найдена. Проверьте и повторите попытку."
ErrToMaster: "У этого пользователя нет прав для выполнения этой операции на основном узле. Проверьте и повторите попытку."
ErrToNode: "У этого пользователя нет прав для выполнения этой операции на этом узле. Проверьте и повторите попытку."
ErrRBAC: "Недостаточно прав пользователя. Операция не может быть выполнена. Проверьте и повторите попытку."
ErrNoneNode: "У этого пользователя нет прав ни на один узел. Обратитесь к администратору или проверьте и повторите попытку."
ErrOnlySuperAdmin: "Эта функция доступна только супер администраторам."
ErrRolePresetCannotDelete: "Системные предустановленные роли нельзя удалять"
ErrRolePresetCannotUpdate: "Системные предустановленные роли нельзя обновлять"
ErrRoleBoundToUser: "Эта роль уже привязана к пользователям и не может быть удалена"
ErrRolePresetCannotBind: "Системные предустановленные роли нельзя назначать пользователям"
ErrSuperAdminCannotDelete: "Пользователей супер-администратора нельзя удалять"
InvalidRequestBodyType: "Неверный формат тела запроса. Проверьте, соответствует ли содержимое требуемому формату, и повторите попытку"
InvalidLicenseCodeType: "Указан неверный формат лицензионного кода. Проверьте и повторите попытку"
LicenseNotFoundType: "Лицензия не найдена. В системе нет записей, соответствующих указанной лицензии. Проверьте и повторите попытку"

View File

@@ -49,8 +49,6 @@ AppInstallCheck: 'Uygulama kurulum ortamını kontrol et'
# backup
ErrBackupInUsed: 'Yedek hesabı görevde kullanılıyor'
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
ErrBackupCheck: 'Bağlantı testi başarısız: {{ .err }}'
ErrBackupLocal: "Yerel sunucu yedekleme hesabı bu işlemi desteklemiyor"
ErrBackupPublic: "Bu yedekleme hesabının herkese açık olmadığı tespit edildi, lütfen kontrol edip tekrar deneyin"
@@ -69,6 +67,7 @@ ErrLicenseExist: "Bu lisans kaydı zaten mevcut. Düğüm bağlama için doğrud
ErrXpackNotFound: "Bu bölüm Business Sürüm gerektirir. Lisansı Panel Ayarları > Lisans bölümünden içe aktarın"
ErrXpackExceptional: "Bu bölüm Business Sürüm gerektirir. Lisans durumunu Panel Ayarları > Lisans bölümünden senkronize edin"
ErrXpackLost: "Lisans yeniden deneme sınırına ulaştı. Panel Ayarları > Lisans bölümüne gidip manuel senkronizasyon çalıştırın"
ErrXpackEELicenseRequired: "Enterprise Edition lisansı içe aktarılmamış. Önce bir lisans içe aktarın."
ErrDeviceLost: "Lisans doğrulama için gerekli dosyalar eksik, lütfen kontrol edip tekrar deneyin"
ErrXpackTimeout: "İstek zaman aşımı, ağ bağlantısı kararsız olabilir, lütfen daha sonra tekrar deneyin"
ErrUnbindMaster: "Düğüm yönetiminde düğümler tespit edildi, mevcut lisansın bağı çözülemiyor, lütfen önce düğümleri kaldırın ve tekrar deneyin"
@@ -85,6 +84,19 @@ ErrNodeBind: "Bu düğüm zaten bir lisansa bağlı, lütfen kontrol edip tekrar
ErrNodeLocalRollback: "Ana düğüm doğrudan rollback'i desteklemez. Rollback için '1pctl restore' komutunu manuel çalıştırın"
ErrIntlLicense: "Mevcut sürüm uluslararası sürüm lisansının içe aktarılmasını henüz desteklemiyor. Yakında"
# user
ErrNoSuchUser: "Kullanıcı bilgisi bulunamadı, lütfen kontrol edip tekrar deneyin"
ErrToMaster: "Bu kullanıcının ana düğümde bu işlemi yapma izni yok, lütfen kontrol edip tekrar deneyin"
ErrToNode: "Bu kullanıcının bu düğümde bu işlemi yapma izni yok, lütfen kontrol edip tekrar deneyin"
ErrRBAC: "Kullanıcı izinleri yetersiz, bu işlem yapılamaz. Lütfen kontrol edip tekrar deneyin"
ErrNoneNode: "Bu kullanıcının hiçbir düğüm izni yok. Lütfen yöneticiyle iletişime geçin veya kontrol edip tekrar deneyin"
ErrOnlySuperAdmin: "Bu özellik yalnızca süper yöneticiler tarafından kullanılabilir"
ErrRolePresetCannotDelete: "Sistem ön tanımlı rolleri silinemez"
ErrRolePresetCannotUpdate: "Sistem ön tanımlı rolleri güncellenemez"
ErrRoleBoundToUser: "Bu rol zaten kullanıcılara bağlı ve silinemez"
ErrRolePresetCannotBind: "Sistem ön tanımlı rolleri kullanıcılara atanamaz"
ErrSuperAdminCannotDelete: "Süper yönetici kullanıcılar silinemez"
InvalidRequestBodyType: "Geçersiz istek gövdesi formatı, lütfen içeriğin format gereksinimlerine uygun olduğunu kontrol edip tekrar deneyin"
InvalidLicenseCodeType: "Sağlanan lisans kodu formatı geçersiz, lütfen kontrol edip tekrar deneyin"
LicenseNotFoundType: "Lisans bulunamadı, sistemde sağlanan lisansla eşleşen bir kayıt yok. Lütfen kontrol edip tekrar deneyin"

View File

@@ -49,8 +49,6 @@ AppInstallCheck: '檢查應用安裝環境'
#backup
ErrBackupInUsed: "該備份帳號已在排程任務中使用,無法刪除"
ErrRolePresetCannotDelete: "系統預設角色,無法刪除"
ErrRoleBoundToUser: "角色已被使用者綁定,無法刪除"
ErrBackupCheck: "備份帳號測試連線失敗 {{ .err }}"
ErrBackupLocal: "本機伺服器備份帳號暫不支援該操作!"
ErrBackupPublic: "偵測到該備份帳號為非公用,請檢查後再試。"
@@ -69,6 +67,7 @@ ErrLicenseExist: "該許可證記錄已存在,您可直接前往許可證頁
ErrXpackNotFound: "此功能需要商業版。請在 面板設定 > 許可證 匯入許可證"
ErrXpackExceptional: "此功能需要商業版。請在 面板設定 > 許可證 同步許可證狀態"
ErrXpackLost: "許可證已達重試上限,請前往 面板設定 > 許可證 手動同步"
ErrXpackEELicenseRequired: "企業版許可證未匯入,請先匯入許可證"
ErrDeviceLost: "許可證驗證必要檔案遺失,請檢查後重試。"
ErrDeviceErr: "目前環境與許可證匯入環境不一致,請編輯許可證重新匯入!"
ErrXpackTimeout: "請求逾時,網路連接可能不穩定,請稍後再試。"
@@ -86,6 +85,19 @@ ErrNodeBind: "偵測到該節點已綁定許可證,請檢查後重試。"
ErrNodeLocalRollback: "主節點暫不支援直接回滾,請手動執行 '1pctl restore' 進行回滾"
ErrIntlLicense: "當前版本暫不支援匯入國際版許可證,敬請期待!"
# user
ErrNoSuchUser: "未能找到該使用者資訊,請檢查後重試!"
ErrToMaster: "該使用者無權限對主節點進行此操作,請檢查後重試!"
ErrToNode: "該使用者無權限對該節點進行此操作,請檢查後重試!"
ErrRBAC: "使用者權限不足,無法進行此操作,請檢查後重試!"
ErrNoneNode: "該使用者無任何節點權限,請聯絡管理員或檢查後重試!"
ErrOnlySuperAdmin: "僅超級管理員支援此功能!"
ErrRolePresetCannotDelete: "系統預設角色,無法刪除"
ErrRolePresetCannotUpdate: "系統預設角色,無法修改"
ErrRoleBoundToUser: "角色已被使用者綁定,無法刪除"
ErrRolePresetCannotBind: "系統預設角色,無法分配給使用者"
ErrSuperAdminCannotDelete: "超級管理員使用者無法刪除"
InvalidRequestBodyType: "請求內容格式錯誤,請檢查請求內容是否符合格式要求後重試!"
InvalidLicenseCodeType: "提供的許可證格式錯誤,請檢查後重試。"
LicenseNotFoundType: "許可證不存在,系統中未找到與提供許可證符合的紀錄,請檢查後重試。"

View File

@@ -55,8 +55,6 @@ AppInstallCheck: "检查应用安装环境"
#backup
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
ErrRolePresetCannotDelete: "系统预设角色,无法删除"
ErrRoleBoundToUser: "角色已被用户绑定,无法删除"
ErrBackupCheck: "备份账号测试连接失败 {{ .err }}"
ErrBackupLocal: "本地服务器备份账号暂不支持该操作!"
ErrBackupPublic: "检测到该备份账号为非公用,请检查后重试!"
@@ -72,20 +70,20 @@ ErrXpackVersion: "许可证校验失败,该许可证受版本限制,无法
ErrLicenseSave: "许可证信息保存失败,错误 {{ .err }},请重试!"
ErrLicenseSync: "许可证信息同步失败,数据库中未检测到许可证信息!"
ErrLicenseExist: "该许可证记录已存在,您可直接前往许可证页面进行节点绑定。"
ErrXpackNotFound: "该部分为业版功能,请先在 面板设置-许可证 界面导入许可证"
ErrXpackExceptional: "该部分为业版功能,请先在 面板设置-许可证 界面同步许可证状态"
ErrXpackLost: "许可证已达到最大重试次数,请进入【面板设置】【许可证】页面手动点击同步按钮,以确保业版功能正常使用"
ErrXpackNotFound: "该部分为业版功能,请先在 面板设置-许可证 界面导入许可证"
ErrXpackExceptional: "该部分为业版功能,请先在 面板设置-许可证 界面同步许可证状态"
ErrXpackLost: "许可证已达到最大重试次数,请进入【面板设置】【许可证】页面手动点击同步按钮,以确保业版功能正常使用"
ErrXpackEELicenseRequired: "企业版许可证未导入,请先导入许可证"
ErrDeviceLost: "许可证校验必要文件丢失,请检查后重试!"
ErrDeviceErr: "当前环境与许可证导入环境不一致,请编辑许可证重新导入!"
ErrXpackTimeout: "请求超时,网络连接可能不稳定,请稍后再试!"
ErrUnbindMaster: "检测到节点管理内存在业版节点,无法解绑当前许可证,请先移除或解绑后重试!"
ErrUnbindMaster: "检测到节点管理内存在业版节点,无法解绑当前许可证,请先移除或解绑后重试!"
ErrFreeNodeLimit: "社区版节点数量已达到免费上限,请前往 www.lxware.cn/1panel 购买后重试!"
ErrNodeBound: "该许可证已绑定到其他节点,请检查后重试!"
ErrNodeBoundDelete: "该许可证已被绑定,不支持删除操作,请检查后重试!"
ErrNodeBoundLimit: "当前免费节点已经达到上限,请检查后重试!"
ErrLicenseFree: "仅当许可证正常绑定到节点后,才能使用其免费节点,请检查后重试!"
ErrLicenseUnbind: "检测到该许可证存在社区版节点,请在 [ 面板设置 - 许可证 ] 中解绑后重试!"
ErrLicenseBindCount: "新许可证免费节点数量小于当前已绑定社区版节点数量,请检查后重试!"
ErrNoSuchNode: "未能找到该节点信息,请检查后重试!"
ErrNodeUnbind: "检测到该节点未在许可证绑定范围内,请检查后重试!"
ErrNodeBind: "检测到该节点已绑定许可证,请检查后重试!"
@@ -98,6 +96,12 @@ ErrToMaster: "该用户无权限对主节点进行该操作,请检查后重试
ErrToNode: "该用户无权限对该节点进行该操作,请检查后重试!"
ErrRBAC: "用户权限不足,无法进行该操作,请检查后重试!"
ErrNoneNode: "该用户无任何节点权限,请联系管理员或检查后重试!"
ErrOnlySuperAdmin: "仅超级管理员支持该功能!"
ErrRolePresetCannotDelete: "系统预设角色,无法删除"
ErrRolePresetCannotUpdate: "系统预设角色,无法修改"
ErrRoleBoundToUser: "角色已被用户绑定,无法删除"
ErrRolePresetCannotBind: "系统预设角色,无法分配给用户"
ErrSuperAdminCannotDelete: "超级管理员用户无法删除"
InvalidRequestBodyType: "请求体格式错误,请检查请求内容是否符合格式要求后重试!"
InvalidLicenseCodeType: "提供的许可证格式错误,请检查后重试!"

View File

@@ -3,8 +3,9 @@ package auth
import (
"crypto/rand"
"encoding/base64"
"sync"
"time"
"github.com/1Panel-dev/1Panel/core/utils/ttlstore"
)
const (
@@ -28,98 +29,42 @@ type mfaSession struct {
}
type mfaSessionStore struct {
mu sync.Mutex
items map[string]mfaSession
store *ttlstore.Store[mfaSession]
}
func newMFASessionStore() *mfaSessionStore {
return &mfaSessionStore{items: make(map[string]mfaSession)}
return &mfaSessionStore{
store: ttlstore.New[mfaSession](MFASessionTTL, MFASessionStoreMaxEntries, generateMFASessionID),
}
}
func (s *mfaSessionStore) Set(name, entrance, ip string) string {
s.mu.Lock()
defer s.mu.Unlock()
s.cleanupExpiredLocked()
if len(s.items) >= MFASessionStoreMaxEntries {
s.removeOldestLocked()
}
sessionID := generateMFASessionID()
s.items[sessionID] = mfaSession{
Name: name,
Entrance: entrance,
IP: ip,
ExpiresAt: time.Now().Add(MFASessionTTL),
}
return sessionID
return s.store.Set(mfaSession{
Name: name,
Entrance: entrance,
IP: ip,
})
}
func (s *mfaSessionStore) Get(sessionID string) (mfaSession, bool) {
s.mu.Lock()
defer s.mu.Unlock()
item, ok := s.items[sessionID]
if !ok {
return mfaSession{}, false
}
if time.Now().After(item.ExpiresAt) {
delete(s.items, sessionID)
return mfaSession{}, false
}
return item, true
return s.store.Get(sessionID)
}
func (s *mfaSessionStore) Delete(sessionID string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.items, sessionID)
s.store.Delete(sessionID)
}
func (s *mfaSessionStore) RecordFailure(sessionID string) int {
s.mu.Lock()
defer s.mu.Unlock()
item, ok := s.items[sessionID]
failures := 0
ok := s.store.Update(sessionID, func(item *mfaSession) bool {
item.Failures++
failures = item.Failures
return item.Failures < MFASessionMaxFailures
})
if !ok {
return 0
}
if time.Now().After(item.ExpiresAt) {
delete(s.items, sessionID)
return 0
}
item.Failures++
if item.Failures >= MFASessionMaxFailures {
delete(s.items, sessionID)
return item.Failures
}
s.items[sessionID] = item
return item.Failures
}
func (s *mfaSessionStore) cleanupExpiredLocked() {
now := time.Now()
for id, item := range s.items {
if now.After(item.ExpiresAt) {
delete(s.items, id)
}
}
}
func (s *mfaSessionStore) removeOldestLocked() {
var oldestID string
var oldestTime time.Time
for id, item := range s.items {
if oldestID == "" || item.ExpiresAt.Before(oldestTime) {
oldestID = id
oldestTime = item.ExpiresAt
}
}
if oldestID != "" {
delete(s.items, oldestID)
}
return failures
}
func generateMFASessionID() string {

View File

@@ -56,7 +56,7 @@ func (b *backup) Run() {
varsItem, _ := json.Marshal(varMap)
_ = global.DB.Model(&model.BackupAccount{}).Where("id = ?", backupItem.ID).Updates(map[string]interface{}{"vars": string(varsItem)}).Error
global.LOG.Infof("Refresh %s-%s access_token successful!", backupItem.Type, backupItem.Name)
if err := xpack.Sync(constant.SyncBackupAccounts); err != nil {
if err := xpack.MultiNodeProvider.Sync(constant.SyncBackupAccounts); err != nil {
global.LOG.Errorf("sync backup account to node failed, err: %v", err)
}
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/1Panel-dev/1Panel/core/utils/cmd"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
)
func Init() {
@@ -17,12 +18,6 @@ func Init() {
global.CONF.Conn.Port, _ = settingRepo.GetValueByKey("ServerPort")
global.CONF.Conn.Ipv6, _ = settingRepo.GetValueByKey("Ipv6")
global.CONF.Base.Edition, _ = settingRepo.GetValueByKey("Edition")
global.Api.ApiInterfaceStatus, _ = settingRepo.GetValueByKey("ApiInterfaceStatus")
if global.Api.ApiInterfaceStatus == constant.StatusEnable {
global.Api.ApiKey, _ = settingRepo.GetValueByKey("ApiKey")
global.Api.IpWhiteList, _ = settingRepo.GetValueByKey("IpWhiteList")
global.Api.ApiKeyValidityTime, _ = settingRepo.GetValueByKey("ApiKeyValidityTime")
}
global.CONF.Conn.BindAddress, _ = settingRepo.GetValueByKey("BindAddress")
global.CONF.Conn.SSL, _ = settingRepo.GetValueByKey("SSL")
global.CONF.Base.Version, _ = settingRepo.GetValueByKey("SystemVersion")
@@ -65,15 +60,24 @@ func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
if strings.Contains(global.CONF.Base.ChangeUserInfo, "entrance") {
settingMap["SecurityEntrance"] = common.RandStrAndNum(10)
}
for key, val := range settingMap {
if len(val) == 0 {
continue
if global.CONF.Base.IsXpackEE {
if len(settingMap["UserName"]) != 0 || len(settingMap["Password"]) != 0 {
if err := xpack.AuthProvider.ResetSuperAdminUser(settingMap["UserName"], settingMap["Password"]); err != nil {
global.LOG.Fatalf("reset xpackee super admin failed, err: %v", err)
return
}
}
if key == "Password" {
val, _ = encrypt.StringEncrypt(val)
}
if err := settingRepo.Update(key, val); err != nil {
global.LOG.Errorf("update %s before start failed, err: %v", key, err)
} else {
for key, val := range settingMap {
if len(val) == 0 {
continue
}
if key == "Password" {
val, _ = encrypt.StringEncrypt(val)
}
if err := settingRepo.Update(key, val); err != nil {
global.LOG.Errorf("update %s before start failed, err: %v", key, err)
}
}
}

View File

@@ -7,9 +7,37 @@ import (
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/global"
"gorm.io/gorm"
)
func UpsertMenuByLabel(children []dto.ShowMenu, newMenu dto.ShowMenu, afterLabel string) []dto.ShowMenu {
for i := range children {
if children[i].Label != newMenu.Label {
continue
}
children[i].Disabled = newMenu.Disabled
children[i].Title = newMenu.Title
children[i].Path = newMenu.Path
children[i].Sort = newMenu.Sort
children[i].IsShow = newMenu.IsShow
return children
}
insertIndex := len(children)
for i := range children {
if children[i].Label == afterLabel {
insertIndex = i + 1
break
}
}
children = append(children, dto.ShowMenu{})
copy(children[insertIndex+1:], children[insertIndex:])
children[insertIndex] = newMenu
return children
}
func LoadMenus() string {
item := []dto.ShowMenu{
{ID: "1", Disabled: true, Title: "menu.home", IsShow: true, Label: "Home-Menu", Path: "/", Sort: 100},
@@ -56,6 +84,23 @@ func LoadMenus() string {
{ID: "12", Disabled: false, Title: "menu.logs", IsShow: true, Label: "Log-Menu", Path: "/logs", Sort: 1200},
{ID: "13", Disabled: true, Title: "menu.settings", IsShow: true, Label: "Setting-Menu", Path: "/settings", Sort: 1300},
}
if global.CONF.Base.IsXpackEE {
for i := range item {
if item[i].Label != "Xpack-Menu" {
continue
}
item[i].Children = UpsertMenuByLabel(item[i].Children, dto.ShowMenu{
ID: "121",
Disabled: false,
Title: "xpack.user.userManage",
IsShow: true,
Label: "UserManagement",
Path: "/xpack-ee/users",
Sort: 350,
}, "NodeDashboard")
break
}
}
menu, _ := json.Marshal(item)
return string(menu)
}
@@ -89,6 +134,8 @@ func MenuSort() []dto.MenuLabelSort {
{Label: "XApp", Sort: 100},
{Label: "Dashboard", Sort: 200},
{Label: "Node", Sort: 300},
{Label: "NodeDashboard", Sort: 300},
{Label: "UserManagement", Sort: 350},
{Label: "Upage", Sort: 400},
{Label: "MonitorDashboard", Sort: 500},
{Label: "Tamper", Sort: 600},

View File

@@ -38,6 +38,7 @@ func Init() {
migrations.UpdateAiModelMenuStructure,
migrations.AddDocSourceSetting,
migrations.AddAppStoreInstallAllowPortSetting,
migrations.AddUserManagementMenu,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View File

@@ -41,17 +41,19 @@ var InitSetting = &gormigrate.Migration{
ID: "20200908-add-table-setting",
Migrate: func(tx *gorm.DB) error {
encryptKey := common.RandStr(16)
if err := tx.Create(&model.Setting{Key: "UserName", Value: global.CONF.Base.Username}).Error; err != nil {
return err
}
global.CONF.Base.EncryptKey = encryptKey
pass, _ := encrypt.StringEncrypt(global.CONF.Base.Password)
language := "en"
if global.CONF.Base.Language == "zh" {
language = "zh"
}
if err := tx.Create(&model.Setting{Key: "Password", Value: pass}).Error; err != nil {
return err
if !global.CONF.Base.IsXpackEE {
if err := tx.Create(&model.Setting{Key: "UserName", Value: global.CONF.Base.Username}).Error; err != nil {
return err
}
pass, _ := encrypt.StringEncrypt(global.CONF.Base.Password)
if err := tx.Create(&model.Setting{Key: "Password", Value: pass}).Error; err != nil {
return err
}
}
_, _ = cmd.RunDefaultWithStdoutBashCf("%s sed -i -e 's#ORIGINAL_PASSWORD=.*#ORIGINAL_PASSWORD=**********#g' /usr/local/bin/1pctl", cmd.SudoHandleCmd())
if err := tx.Create(&model.Setting{Key: "Theme", Value: "light"}).Error; err != nil {
@@ -990,3 +992,52 @@ func normalizeAiMenuChild(children []dto.ShowMenu, fallback dto.ShowMenu, labels
}
return fallback
}
var AddUserManagementMenu = &gormigrate.Migration{
ID: "20260414-add-user-management-menu",
Migrate: func(tx *gorm.DB) error {
if !global.CONF.Base.IsXpackEE {
return nil
}
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
if menuJSON == "" {
menuJSON = helper.LoadMenus()
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
newItem := dto.ShowMenu{
ID: "121",
Disabled: false,
Title: "xpack.user.userManage",
IsShow: true,
Label: "UserManagement",
Path: "/xpack-ee/users",
Sort: 350,
}
for i := range menus {
if menus[i].Label != "Xpack-Menu" {
continue
}
menus[i].Children = helper.UpsertMenuByLabel(menus[i].Children, newItem, "NodeDashboard")
break
}
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}

View File

@@ -3,15 +3,14 @@ package router
import (
"net/http"
"net/url"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/cmd/server/res"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/init/proxy"
psessionUtils "github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/gin-gonic/gin"
)
@@ -58,7 +57,7 @@ func Proxy() gin.HandlerFunc {
proxyLocalAgent(c)
return
}
xpack.Proxy(c, currentNode)
xpack.MultiNodeProvider.Proxy(c, currentNode)
c.Abort()
}
}
@@ -78,18 +77,13 @@ func checkSession(c *gin.Context) bool {
if err != nil {
return false
}
settingRepo := repo.NewISettingRepo()
sessionTimeout, err := settingRepo.GetValueByKey("SessionTimeout")
c.Set(psessionUtils.GinContextSessionUserKey, psession)
lifeTime, err := xpack.AuthProvider.LoadSessionTimeout(c, psession)
if err != nil {
global.LOG.Errorf("get session timeout failed, err: %v", err)
return false
}
lifeTime, _ := strconv.Atoi(sessionTimeout)
lifeTime = xpack.LoadSessionTimeout(psession, lifeTime)
ssl, err := settingRepo.GetValueByKey("SSL")
if err != nil {
return false
}
if _, err := global.SESSION.RefreshIfNeeded(c, psession, ssl == constant.StatusEnable, lifeTime); err != nil {
if _, err := global.SESSION.RefreshIfNeeded(c, psession, global.CONF.Conn.SSL == constant.StatusEnable, lifeTime); err != nil {
global.LOG.Warnf("proxy refresh session failed, path=%s, err=%v", c.Request.URL.Path, err)
return false
}

View File

@@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"
appauth "github.com/1Panel-dev/1Panel/core/app/auth"
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/cmd/server/docs"
"github.com/1Panel-dev/1Panel/core/cmd/server/web"
@@ -57,7 +58,7 @@ func setWebStatic(rootRouter *gin.RouterGroup) {
}
entrance = authService.GetSecurityEntrance()
if entrance != "" {
service.SetSecurityEntranceCookie(c, entrance)
appauth.SetSecurityEntranceCookie(c, entrance)
}
staticServer := http.FileServer(http.FS(web.IndexHtml))
staticServer.ServeHTTP(c.Writer, c.Request)
@@ -85,10 +86,10 @@ func Routers() *gin.Engine {
Router.Use(middleware.OperationLog())
Router.Use(middleware.GlobalLoading())
Router.Use(xpack.AuthProvider.CoreAPIAuthMiddleware())
Router.Use(middleware.PasswordExpired())
Router.Use(middleware.ApiAuth())
Router.Use(middleware.CSRFTokenGuard())
Router.Use(xpack.CoreRBACMiddlewares()...)
Router.Use(xpack.AuthProvider.CoreRBACMiddlewares()...)
Router.Use(Proxy())
PrivateGroup := Router.Group("/api/v2/core")

View File

@@ -20,6 +20,7 @@ type SessionUser struct {
}
const SuperAdminSessionUserID = "__super_admin__"
const GinContextSessionUserKey = "session_user"
type sessionItem struct {
CreatedAt time.Time
@@ -35,7 +36,7 @@ type PSession struct {
lastFullCleanup time.Time
}
const maxSessionEntries = 64
const maxSessionEntries = 1024
func NewPSession(_ string) *PSession {
return &PSession{

View File

@@ -2,53 +2,60 @@ package middleware
import (
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/global"
psessionUtils "github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/gin-gonic/gin"
)
var (
expiredLoc *time.Location
expiredLocOnce sync.Once
)
func PasswordExpired() gin.HandlerFunc {
return func(c *gin.Context) {
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") ||
c.Request.URL.Path == "/api/v2/core/settings/expired/handle" ||
c.Request.URL.Path == "/api/v2/core/settings/search" {
c.Request.URL.Path == "/api/v2/core/settings/search" ||
c.Request.URL.Path == "/api/v2/core/settings/search/base" {
c.Next()
return
}
settingRepo := repo.NewISettingRepo()
expirationDays, err := settingRepo.GetValueByKey("ExpirationDays")
if c.GetBool("API_AUTH") {
c.Next()
return
}
var err error
sessionUser, ok := c.Get(psessionUtils.GinContextSessionUserKey)
psession, typeOK := sessionUser.(psessionUtils.SessionUser)
if !ok || !typeOK {
psession, err = global.SESSION.Get(c)
}
if err != nil {
errItem := err.Error()
if errItem == "ErrSessionDataFormat" || errItem == "ErrSessionDataNotFound" {
helper.BadAuth(c, "ErrNotLogin", buserr.New(errItem))
return
}
helper.BadAuth(c, "ErrNotLogin", err)
return
}
c.Set(psessionUtils.GinContextSessionUserKey, psession)
if len(psession.Name) == 0 {
helper.BadAuth(c, "ErrNotLogin", err)
return
}
needCheck, expiredTime, err := xpack.AuthProvider.LoadExpired(c, psession)
if err != nil {
helper.ErrorWithDetail(c, http.StatusInternalServerError, "ErrPasswordExpired", err)
return
}
expiredDays, _ := strconv.Atoi(expirationDays)
if expiredDays == 0 {
if !needCheck {
c.Next()
return
}
expirationTime, err := settingRepo.GetValueByKey("ExpirationTime")
if err != nil {
helper.ErrorWithDetail(c, http.StatusInternalServerError, "ErrPasswordExpired", err)
return
}
expiredTime, err := time.ParseInLocation(constant.DateTimeLayout, expirationTime, loadExpiredLocation())
if err != nil {
helper.ErrorWithDetail(c, 313, "ErrPasswordExpired", err)
return
}
if time.Now().After(expiredTime) {
helper.ErrorWithDetail(c, 313, "ErrPasswordExpired", err)
return
@@ -56,18 +63,3 @@ func PasswordExpired() gin.HandlerFunc {
c.Next()
}
}
func loadExpiredLocation() *time.Location {
expiredLocOnce.Do(func() {
loc, err := time.LoadLocation(common.LoadTimeZoneByCmd())
if err != nil {
expiredLoc = time.Local
return
}
expiredLoc = loc
})
if expiredLoc == nil {
return time.Local
}
return expiredLoc
}

View File

@@ -1,14 +1,11 @@
package middleware
import (
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
psessionUtils "github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
"github.com/gin-gonic/gin"
)
@@ -16,7 +13,7 @@ import (
func SessionAuth() gin.HandlerFunc {
return func(c *gin.Context) {
apiReq := c.GetBool("API_AUTH")
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") || apiReq {
if isAnonymousAuthPath(c.Request.URL.Path) || apiReq {
c.Next()
return
}
@@ -31,24 +28,20 @@ func SessionAuth() gin.HandlerFunc {
helper.BadAuth(c, "ErrNotLogin", err)
return
}
if len(psession.Name) == 0 {
if len(psession.Name) == 0 || len(psession.ID) == 0 {
helper.BadAuth(c, "ErrNotLogin", err)
return
}
settingRepo := repo.NewISettingRepo()
sessionTimeout, err := settingRepo.GetValueByKey("SessionTimeout")
c.Set(psessionUtils.GinContextSessionUserKey, psession)
lifeTime, err := xpack.AuthProvider.LoadSessionTimeout(c, psession)
if err != nil {
global.LOG.Errorf("create operation record failed, err: %v", err)
global.LOG.Errorf("get session timeout failed, err: %v", err)
helper.InternalServer(c, err)
c.Abort()
return
}
lifeTime, _ := strconv.Atoi(sessionTimeout)
lifeTime = xpack.LoadSessionTimeout(psession, lifeTime)
ssl, err := settingRepo.GetValueByKey("SSL")
if err != nil {
global.LOG.Errorf("create operation record failed, err: %v", err)
return
}
if _, err := global.SESSION.RefreshIfNeeded(c, psession, ssl == constant.StatusEnable, lifeTime); err != nil {
if _, err := global.SESSION.RefreshIfNeeded(c, psession, global.CONF.Conn.SSL == constant.StatusEnable, lifeTime); err != nil {
errItem := err.Error()
if errItem == "ErrSessionDataFormat" || errItem == "ErrSessionDataNotFound" {
helper.BadAuth(c, "ErrNotLogin", buserr.New(errItem))
@@ -61,3 +54,19 @@ func SessionAuth() gin.HandlerFunc {
c.Next()
}
}
func isAnonymousAuthPath(path string) bool {
switch path {
case "/api/v2/core/auth/captcha",
"/api/v2/core/auth/passkey/begin",
"/api/v2/core/auth/passkey/finish",
"/api/v2/core/auth/mfalogin",
"/api/v2/core/auth/login",
"/api/v2/core/auth/logout",
"/api/v2/core/auth/setting",
"/api/v2/core/auth/welcome":
return true
default:
return false
}
}

View File

@@ -1,19 +0,0 @@
//go:build xpack
package router
import (
xpackRouter "github.com/1Panel-dev/1Panel/core/xpack/router"
)
func RouterGroups() []CommonRouter {
baseRouter := commonGroups()
for _, ro := range xpackRouter.XpackGroups() {
if val, ok := ro.(CommonRouter); ok {
baseRouter = append(baseRouter, val)
}
}
return baseRouter
}
var RouterGroupApp = RouterGroups()

View File

@@ -1,25 +0,0 @@
//go:build xpackee
package router
import (
xpackEERouter "github.com/1Panel-dev/1Panel/core/xpack-ee/router"
xpackRouter "github.com/1Panel-dev/1Panel/core/xpack/router"
)
func RouterGroups() []CommonRouter {
baseRouter := commonGroups()
for _, ro := range xpackRouter.XpackGroups() {
if val, ok := ro.(CommonRouter); ok {
baseRouter = append(baseRouter, val)
}
}
for _, ro := range xpackEERouter.XpackEEGroups() {
if val, ok := ro.(CommonRouter); ok {
baseRouter = append(baseRouter, val)
}
}
return baseRouter
}
var RouterGroupApp = RouterGroups()

View File

@@ -2,6 +2,7 @@ package router
import (
v2 "github.com/1Panel-dev/1Panel/core/app/api/v2"
"github.com/1Panel-dev/1Panel/core/middleware"
"github.com/gin-gonic/gin"
)
@@ -9,6 +10,9 @@ type BaseRouter struct{}
func (s *BaseRouter) InitRouter(Router *gin.RouterGroup) {
baseRouter := Router.Group("auth")
authRouter := Router.Group("auth").
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired())
baseApi := v2.ApiGroupApp.BaseApi
{
baseRouter.GET("/captcha", baseApi.Captcha)
@@ -19,5 +23,17 @@ func (s *BaseRouter) InitRouter(Router *gin.RouterGroup) {
baseRouter.POST("/logout", baseApi.LogOut)
baseRouter.GET("/setting", baseApi.GetLoginSetting)
baseRouter.GET("/welcome", baseApi.GetWelcomePage)
authRouter.POST("/mfa", baseApi.LoadMFA)
authRouter.POST("/mfa/bind", baseApi.MFABind)
authRouter.POST("/passkey/register/begin", baseApi.PasskeyRegisterBegin)
authRouter.POST("/passkey/register/finish", baseApi.PasskeyRegisterFinish)
authRouter.GET("/passkey/list", baseApi.PasskeyList)
authRouter.POST("/passkey/del", baseApi.PasskeyDelete)
authRouter.POST("/api/generate", baseApi.GenerateApiKey)
authRouter.POST("/api/update", baseApi.UpdateApiConfig)
authRouter.GET("/current", baseApi.GetCurrentUser)
authRouter.POST("/current/update", baseApi.UpdateCurrentUser)
}
}

View File

@@ -19,7 +19,8 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v2.ApiGroupApp.BaseApi
{
router.POST("/search", baseApi.GetSettingInfo)
router.POST("/expired/handle", baseApi.HandlePasswordExpired)
router.POST("/search/base", baseApi.GetSettingBaseInfo)
settingRouter.POST("/by", baseApi.GetSettingByKey)
settingRouter.POST("/terminal/search", baseApi.GetTerminalSettingInfo)
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
@@ -34,20 +35,10 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
settingRouter.POST("/ssl/update", baseApi.UpdateSSL)
settingRouter.GET("/ssl/info", baseApi.LoadFromCert)
settingRouter.POST("/ssl/download", baseApi.DownloadSSL)
settingRouter.POST("/password/update", baseApi.UpdatePassword)
settingRouter.POST("/mfa", baseApi.LoadMFA)
settingRouter.POST("/mfa/bind", baseApi.MFABind)
settingRouter.POST("/passkey/register/begin", baseApi.PasskeyRegisterBegin)
settingRouter.POST("/passkey/register/finish", baseApi.PasskeyRegisterFinish)
settingRouter.GET("/passkey/list", baseApi.PasskeyList)
settingRouter.DELETE("/passkey/:id", baseApi.PasskeyDelete)
settingRouter.POST("/upgrade", baseApi.Upgrade)
settingRouter.POST("/upgrade/notes", baseApi.GetNotesByVersion)
settingRouter.GET("/upgrade/releases", baseApi.LoadRelease)
settingRouter.GET("/upgrade", baseApi.GetUpgradeInfo)
settingRouter.POST("/api/config/generate/key", baseApi.GenerateApiKey)
settingRouter.POST("/api/config/update", baseApi.UpdateApiConfig)
noAuthRouter.POST("/ssl/reload", baseApi.ReloadSSL)

View File

@@ -1,11 +0,0 @@
//go:build xpack
package server
import (
xpack "github.com/1Panel-dev/1Panel/core/xpack"
)
func InitOthers() {
xpack.Init()
}

View File

@@ -1,13 +0,0 @@
//go:build xpackee
package server
import (
xpack "github.com/1Panel-dev/1Panel/core/xpack"
xpackEE "github.com/1Panel-dev/1Panel/core/xpack-ee"
)
func InitOthers() {
xpack.Init()
xpackEE.Init()
}

View File

@@ -83,7 +83,8 @@ func Start() {
type tcpKeepAliveListener struct {
*net.TCPListener
}
if global.CONF.Conn.SSL == constant.StatusEnable {
switch global.CONF.Conn.SSL {
case constant.StatusEnable:
constant.CertStore.Store(loadCert())
server.TLSConfig = &tls.Config{
@@ -96,8 +97,7 @@ func Start() {
if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, "", ""); err != nil {
panic(err)
}
return
} else if global.CONF.Conn.SSL == constant.StatusMux {
case constant.StatusMux:
constant.CertStore.Store(loadCert())
server.TLSConfig = &tls.Config{
@@ -144,13 +144,11 @@ func Start() {
if err := m.Serve(); err != nil {
panic(err)
}
return
} else {
default:
global.LOG.Infof("listen at http://%s:%s [%s]", global.CONF.Conn.BindAddress, global.CONF.Conn.Port, tcpItem)
if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil {
panic(err)
}
return
}
}
@@ -216,5 +214,4 @@ func handleMuxHttpConn(conn net.Conn) {
resp.Header.Set("Connection", "close")
_ = resp.Write(conn)
return
}

View File

@@ -9,6 +9,7 @@ import (
"net"
"os"
"path"
"sort"
"strconv"
"strings"
"time"
@@ -54,25 +55,6 @@ func Md5(val string) string {
return hex.EncodeToString(hash.Sum(nil))
}
func LoadTimeZoneByCmd() string {
loc := time.Now().Location().String()
if _, err := time.LoadLocation(loc); err != nil {
loc = "Asia/Shanghai"
}
std, err := cmd.RunDefaultWithStdoutBashC("timedatectl | grep 'Time zone'")
if err != nil {
return loc
}
fields := strings.Fields(string(std))
if len(fields) != 5 {
return loc
}
if _, err := time.LoadLocation(fields[2]); err != nil {
return loc
}
return fields[2]
}
func ScanPort(port int) bool {
ln, err := net.Listen("tcp", ":"+strconv.Itoa(port))
if err != nil {
@@ -145,6 +127,25 @@ func SplitStr(str string, spi ...string) []string {
return results
}
func UniqueUints(items []uint) []uint {
result := make([]uint, 0, len(items))
seen := make(map[uint]struct{}, len(items))
for _, item := range items {
if item == 0 {
continue
}
if _, ok := seen[item]; ok {
continue
}
seen[item] = struct{}{}
result = append(result, item)
}
sort.Slice(result, func(i, j int) bool {
return result[i] < result[j]
})
return result
}
func LoadArch() (string, error) {
std, err := cmd.RunDefaultWithStdoutBashC("uname -a")
if err != nil {

47
core/utils/common/time.go Normal file
View File

@@ -0,0 +1,47 @@
package common
import (
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/core/utils/cmd"
)
func LoadTimeZoneByCmd() string {
loc := time.Now().Location().String()
if _, err := time.LoadLocation(loc); err != nil {
loc = "Asia/Shanghai"
}
std, err := cmd.RunDefaultWithStdoutBashC("timedatectl | grep 'Time zone'")
if err != nil {
return loc
}
fields := strings.Fields(string(std))
if len(fields) != 5 {
return loc
}
if _, err := time.LoadLocation(fields[2]); err != nil {
return loc
}
return fields[2]
}
func LoadExpiredLocation() *time.Location {
var (
expiredLoc *time.Location
expiredLocOnce sync.Once
)
expiredLocOnce.Do(func() {
loc, err := time.LoadLocation(LoadTimeZoneByCmd())
if err != nil {
expiredLoc = time.Local
return
}
expiredLoc = loc
})
if expiredLoc == nil {
return time.Local
}
return expiredLoc
}

View File

@@ -3,22 +3,22 @@ package passkey
import (
"crypto/rand"
"encoding/base64"
"sync"
"time"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/ttlstore"
"github.com/go-webauthn/webauthn/webauthn"
)
const (
PasskeyUserIDSettingKey = "PasskeyUserID"
PasskeyCredentialSettingKey = "PasskeyCredentials"
PasskeyMaxCredentials = 5
PasskeySessionTTL = 5 * time.Minute
PasskeySessionKindLogin = "login"
PasskeySessionKindRegister = "register"
PasskeyCredentialNameDefault = "Passkey"
PasskeySessionStoreMaxEntries = 1024
PasskeyUserIDSettingKey = "PasskeyUserID"
PasskeyCredentialSettingKey = "PasskeyCredentials"
PasskeyMaxCredentials = 5
PasskeySessionTTL = 5 * time.Minute
PasskeySessionKindLogin = "login"
PasskeySessionKindRegister = "register"
PasskeyCredentialNameDefault = "Passkey"
PasskeySessionStoreMaxEntries = 1024
)
var passkeySessions = newPasskeySessionStore()
@@ -35,75 +35,29 @@ type passkeySession struct {
}
type passkeySessionStore struct {
mu sync.Mutex
items map[string]passkeySession
store *ttlstore.Store[passkeySession]
}
func newPasskeySessionStore() *passkeySessionStore {
return &passkeySessionStore{items: make(map[string]passkeySession)}
return &passkeySessionStore{
store: ttlstore.New[passkeySession](PasskeySessionTTL, PasskeySessionStoreMaxEntries, generatePasskeySessionID),
}
}
func (s *passkeySessionStore) Set(kind, name string, session webauthn.SessionData) string {
s.mu.Lock()
defer s.mu.Unlock()
s.cleanupExpiredLocked()
if len(s.items) >= PasskeySessionStoreMaxEntries {
s.removeOldestLocked()
}
sessionID := generatePasskeySessionID()
s.items[sessionID] = passkeySession{
Kind: kind,
Name: name,
Session: session,
ExpiresAt: time.Now().Add(PasskeySessionTTL),
}
return sessionID
return s.store.Set(passkeySession{
Kind: kind,
Name: name,
Session: session,
})
}
func (s *passkeySessionStore) Get(sessionID string) (passkeySession, bool) {
s.mu.Lock()
defer s.mu.Unlock()
item, ok := s.items[sessionID]
if !ok {
return passkeySession{}, false
}
if time.Now().After(item.ExpiresAt) {
delete(s.items, sessionID)
return passkeySession{}, false
}
return item, true
return s.store.Get(sessionID)
}
func (s *passkeySessionStore) Delete(sessionID string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.items, sessionID)
}
func (s *passkeySessionStore) cleanupExpiredLocked() {
now := time.Now()
for id, item := range s.items {
if now.After(item.ExpiresAt) {
delete(s.items, id)
}
}
}
func (s *passkeySessionStore) removeOldestLocked() {
var oldestID string
var oldestTime time.Time
for id, item := range s.items {
if oldestID == "" || item.ExpiresAt.Before(oldestTime) {
oldestID = id
oldestTime = item.ExpiresAt
}
}
if oldestID != "" {
delete(s.items, oldestID)
}
s.store.Delete(sessionID)
}
func generatePasskeySessionID() string {

View File

@@ -30,7 +30,7 @@ func HandleRequest(url, method string, timeout int) (int, []byte, error) {
}
func HandleRequestWithProxy(url, method string, timeout int) (int, []byte, error) {
transport := xpack.LoadRequestTransport()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
return handleRequestWithTransport(url, method, transport, timeout)
}
@@ -78,7 +78,7 @@ func HandleGet(url string) (*http.Response, error) {
}
func HandleGetWithProxy(url string) (*http.Response, error) {
transport := xpack.LoadRequestTransport()
transport := xpack.MultiNodeProvider.LoadRequestTransport()
return handleGetWithTransport(url, transport)
}

View File

@@ -0,0 +1,116 @@
package ttlstore
import (
"sync"
"time"
)
type Item[T any] struct {
Value T
ExpiresAt time.Time
}
type Store[T any] struct {
mu sync.Mutex
items map[string]Item[T]
ttl time.Duration
maxEntries int
newID func() string
now func() time.Time
}
func New[T any](ttl time.Duration, maxEntries int, newID func() string) *Store[T] {
return &Store[T]{
items: make(map[string]Item[T]),
ttl: ttl,
maxEntries: maxEntries,
newID: newID,
now: time.Now,
}
}
func (s *Store[T]) Set(value T) string {
s.mu.Lock()
defer s.mu.Unlock()
s.cleanupExpiredLocked()
if len(s.items) >= s.maxEntries {
s.removeOldestLocked()
}
itemID := s.newID()
s.items[itemID] = Item[T]{
Value: value,
ExpiresAt: s.now().Add(s.ttl),
}
return itemID
}
func (s *Store[T]) Get(itemID string) (T, bool) {
s.mu.Lock()
defer s.mu.Unlock()
item, ok := s.items[itemID]
if !ok {
var zero T
return zero, false
}
if s.now().After(item.ExpiresAt) {
delete(s.items, itemID)
var zero T
return zero, false
}
return item.Value, true
}
func (s *Store[T]) Update(itemID string, fn func(*T) bool) bool {
s.mu.Lock()
defer s.mu.Unlock()
item, ok := s.items[itemID]
if !ok {
return false
}
if s.now().After(item.ExpiresAt) {
delete(s.items, itemID)
return false
}
keep := fn(&item.Value)
if !keep {
delete(s.items, itemID)
return true
}
s.items[itemID] = item
return true
}
func (s *Store[T]) Delete(itemID string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.items, itemID)
}
func (s *Store[T]) cleanupExpiredLocked() {
now := s.now()
for id, item := range s.items {
if now.After(item.ExpiresAt) {
delete(s.items, id)
}
}
}
func (s *Store[T]) removeOldestLocked() {
var oldestID string
var oldestTime time.Time
for id, item := range s.items {
if oldestID == "" || item.ExpiresAt.Before(oldestTime) {
oldestID = id
oldestTime = item.ExpiresAt
}
}
if oldestID != "" {
delete(s.items, oldestID)
}
}

View File

@@ -2,88 +2,8 @@
package xpack
import (
"crypto/tls"
"errors"
"net"
"net/http"
"time"
import "github.com/1Panel-dev/1Panel/core/utils/xpack/helper"
baseDto "github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/init/proxy"
"github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/ssh"
"github.com/1Panel-dev/1Panel/core/xpack/app/model"
"github.com/gin-gonic/gin"
)
var AuthProvider = helper.NewIAuthProvider()
func Proxy(c *gin.Context, currentNode string) {
if currentNode != "local" && currentNode != "" {
c.Next()
return
}
defer func() {
if err := recover(); err != nil && err != http.ErrAbortHandler {
global.LOG.Debug(err)
}
}()
proxy.LocalAgentProxy.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
func CoreRBACMiddlewares() []gin.HandlerFunc { return nil }
func ProxyDocker(proxyURL string) error { return nil }
func UpdateGroup(name string, group, newGroup uint) error { return nil }
func CheckBackupUsed(name string) error { return nil }
func LoadRequestTransport() *http.Transport {
return &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DialContext: (&net.Dialer{
Timeout: 60 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
IdleConnTimeout: 15 * time.Second,
}
}
func LoadNodeInfo(currentNode string) (*ssh.ConnInfo, string, error) {
return nil, "", nil
}
func Sync(dataType string) error { return nil }
func AutoUpgradeWithMaster() {}
func Login(_ *gin.Context, _ baseDto.Login, _ string) (*baseDto.UserLoginInfo, string, error) {
return nil, "", errors.New("not xpackee build")
}
func LoadSessionTimeout(sessionUser psession.SessionUser, defaultTTL int) int { return defaultTTL }
func RemoveBindNode(nodeID uint) error { return nil }
func CheckLicenseStatus(isXpack bool, licenseID, nodeID uint) error {
return nil
}
func LoadLicenseByNodeID(nodeID uint) (bool, model.License, error) {
return false, model.License{}, nil
}
func Bind(node model.Node, license *model.License, withInit, withSync, withDryRun, withDockerRestart bool) error {
return nil
}
func BindFree(node model.Node, licenseID uint) error {
return nil
}
func Unbind(node model.Node, license model.License, withReset, withSync, withDockerRestart bool) error {
return nil
}
func UnbindFree(node model.Node, licenseID uint) error {
return nil
}
var MultiNodeProvider = helper.NewIMultiNodeProvider()

View File

@@ -0,0 +1,91 @@
package helper
import (
"time"
"github.com/1Panel-dev/1Panel/core/app/auth"
baseDto "github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/mfa"
"github.com/1Panel-dev/1Panel/core/utils/xpack/providers"
"github.com/gin-gonic/gin"
)
type authHelper struct{}
func NewIAuthProvider() providers.AuthProvider {
return &authHelper{}
}
func (a *authHelper) Login(c *gin.Context, info baseDto.Login, entrance string) (*baseDto.UserLoginInfo, string, error) {
return auth.Login(c, info, entrance)
}
func (a *authHelper) MFALogin(c *gin.Context, info baseDto.MFALogin, entrance string) (*baseDto.UserLoginInfo, string, error) {
return auth.MFALogin(c, info, entrance)
}
func (a *authHelper) PasskeyBeginLogin(c *gin.Context, entrance string) (*baseDto.PasskeyBeginResponse, string, error) {
return auth.PasskeyBeginLogin(c, entrance)
}
func (a *authHelper) PasskeyFinishLogin(c *gin.Context, sessionID, entrance string) (*baseDto.UserLoginInfo, string, error) {
return auth.PasskeyFinishLogin(c, sessionID, entrance)
}
func (a *authHelper) PasskeyBeginRegister(c *gin.Context, name string) (*baseDto.PasskeyBeginResponse, string, error) {
return auth.PasskeyBeginRegister(c, name)
}
func (a *authHelper) PasskeyFinishRegister(c *gin.Context, sessionID string) (string, error) {
return auth.PasskeyFinishRegister(c, sessionID)
}
func (a *authHelper) PasskeyList(c *gin.Context) ([]baseDto.PasskeyInfo, error) {
return auth.PasskeyList()
}
func (a *authHelper) PasskeyDelete(c *gin.Context, id string) error {
return auth.PasskeyDelete(id)
}
func (a *authHelper) PasskeyStatus(c *gin.Context) bool {
return auth.PasskeyStatus(c)
}
func (a *authHelper) ClearPasskeys() error {
return nil
}
func (a *authHelper) ResetSuperAdminUser(name, password string) error {
return nil
}
func (a *authHelper) LoadSessionTimeout(_ *gin.Context, sessionUser psession.SessionUser) (int, error) {
return auth.LoadSessionTimeout(sessionUser)
}
func (a *authHelper) LoadExpired(_ *gin.Context, sessionUser psession.SessionUser) (bool, time.Time, error) {
return auth.LoadExpired(sessionUser)
}
func (a *authHelper) CoreAPIAuthMiddleware() gin.HandlerFunc {
return auth.APIAuthMiddleware(auth.LoadAPIAuthConfig, nil)
}
func (a *authHelper) CoreRBACMiddlewares() []gin.HandlerFunc { return nil }
func (a *authHelper) LoadMFA(_ *gin.Context, req baseDto.MfaRequest) (mfa.Otp, error) {
return auth.LoadMFA(req)
}
func (a *authHelper) MFABind(_ *gin.Context, req baseDto.MfaCredential) error {
return auth.MFABind(req)
}
func (a *authHelper) GenerateApiKey(_ *gin.Context) (string, error) {
return auth.GenerateApiKey()
}
func (a *authHelper) UpdateApiConfig(c *gin.Context, req baseDto.ApiInterfaceConfig) error {
return auth.UpdateApiConfig(req)
}
func (a *authHelper) GetCurrentUserInfo(_ *gin.Context) (*baseDto.CurrentUserInfo, error) {
return auth.GetCurrentUserInfo()
}
func (a *authHelper) UpdateCurrentUserInfo(c *gin.Context, req baseDto.CurrentUserUpdate) error {
return auth.UpdateCurrentUserInfo(c, req)
}
func (a *authHelper) HandlePasswordExpired(c *gin.Context, old, new string) error {
return auth.HandlePasswordExpired(c, old, new)
}

View File

@@ -0,0 +1,60 @@
package helper
import (
"crypto/tls"
"net"
"net/http"
"time"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/init/proxy"
"github.com/1Panel-dev/1Panel/core/utils/ssh"
"github.com/gin-gonic/gin"
)
type multiNodeHelper struct{}
func NewIMultiNodeProvider() *multiNodeHelper {
return &multiNodeHelper{}
}
func (m *multiNodeHelper) Proxy(c *gin.Context, currentNode string) {
if currentNode != "local" && currentNode != "" {
c.Next()
return
}
defer func() {
if err := recover(); err != nil && err != http.ErrAbortHandler {
global.LOG.Debug(err)
}
}()
proxy.LocalAgentProxy.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
func (m *multiNodeHelper) ProxyDocker(proxyURL string) error { return nil }
func (m *multiNodeHelper) UpdateGroup(name string, group, newGroup uint) error { return nil }
func (m *multiNodeHelper) CheckBackupUsed(name string) error { return nil }
func (m *multiNodeHelper) LoadRequestTransport() *http.Transport {
return &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DialContext: (&net.Dialer{
Timeout: 60 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
IdleConnTimeout: 15 * time.Second,
}
}
func (m *multiNodeHelper) LoadNodeInfo(currentNode string) (*ssh.ConnInfo, string, error) {
return nil, "", nil
}
func (m *multiNodeHelper) Sync(dataType string) error { return nil }
func (m *multiNodeHelper) AutoUpgradeWithMaster() {}

View File

@@ -0,0 +1,42 @@
package providers
import (
"time"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/mfa"
"github.com/gin-gonic/gin"
)
type AuthProvider interface {
Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, string, error)
MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, string, error)
ResetSuperAdminUser(name, password string) error
LoadSessionTimeout(c *gin.Context, sessionUser psession.SessionUser) (int, error)
LoadExpired(c *gin.Context, sessionUser psession.SessionUser) (bool, time.Time, error)
LoadMFA(c *gin.Context, req dto.MfaRequest) (mfa.Otp, error)
MFABind(c *gin.Context, req dto.MfaCredential) error
GenerateApiKey(c *gin.Context) (string, error)
UpdateApiConfig(c *gin.Context, req dto.ApiInterfaceConfig) error
PasskeyBeginLogin(c *gin.Context, entrance string) (*dto.PasskeyBeginResponse, string, error)
PasskeyFinishLogin(c *gin.Context, sessionID, entrance string) (*dto.UserLoginInfo, string, error)
PasskeyBeginRegister(c *gin.Context, name string) (*dto.PasskeyBeginResponse, string, error)
PasskeyFinishRegister(c *gin.Context, sessionID string) (string, error)
PasskeyList(c *gin.Context) ([]dto.PasskeyInfo, error)
PasskeyDelete(c *gin.Context, id string) error
PasskeyStatus(c *gin.Context) bool
ClearPasskeys() error
GetCurrentUserInfo(c *gin.Context) (*dto.CurrentUserInfo, error)
UpdateCurrentUserInfo(c *gin.Context, req dto.CurrentUserUpdate) error
HandlePasswordExpired(c *gin.Context, old, new string) error
CoreAPIAuthMiddleware() gin.HandlerFunc
CoreRBACMiddlewares() []gin.HandlerFunc
}

View File

@@ -0,0 +1,20 @@
package providers
import (
"net/http"
"github.com/1Panel-dev/1Panel/core/utils/ssh"
"github.com/gin-gonic/gin"
)
type MultiNodeProvider interface {
Proxy(c *gin.Context, currentNode string)
ProxyDocker(proxyURL string) error
UpdateGroup(name string, group, newGroup uint) error
CheckBackupUsed(name string) error
LoadNodeInfo(currentNode string) (*ssh.ConnInfo, string, error)
Sync(dataType string) error
AutoUpgradeWithMaster()
LoadRequestTransport() *http.Transport
}

View File

@@ -1,68 +0,0 @@
//go:build xpack
package xpack
import (
"errors"
"net/http"
baseDto "github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/ssh"
"github.com/1Panel-dev/1Panel/core/xpack/app/model"
edition "github.com/1Panel-dev/1Panel/core/xpack/edition"
"github.com/gin-gonic/gin"
)
func Proxy(c *gin.Context, currentNode string) {
edition.Proxy(c, currentNode)
}
func CoreRBACMiddlewares() []gin.HandlerFunc { return nil }
func ProxyDocker(proxyURL string) error { return edition.ProxyDocker(proxyURL) }
func UpdateGroup(name string, group, newGroup uint) error {
return edition.UpdateGroup(name, group, newGroup)
}
func CheckBackupUsed(name string) error {
return edition.CheckBackupUsed(name)
}
func LoadRequestTransport() *http.Transport { return edition.LoadRequestTransport() }
func LoadNodeInfo(currentNode string) (*ssh.ConnInfo, string, error) {
return edition.LoadNodeInfo(currentNode)
}
func Sync(dataType string) error { return edition.Sync(dataType) }
func AutoUpgradeWithMaster() { edition.AutoUpgradeWithMaster() }
func Login(_ *gin.Context, _ baseDto.Login, _ string) (*baseDto.UserLoginInfo, string, error) {
return nil, "", errors.New("not xpackee build")
}
func LoadSessionTimeout(sessionUser psession.SessionUser, defaultTTL int) int { return defaultTTL }
func RemoveBindNode(nodeID uint) error { return nil }
func CheckLicenseStatus(isXpack bool, licenseID, nodeID uint) error {
return edition.CheckLicenseStatus(isXpack, licenseID, nodeID)
}
func LoadLicenseByNodeID(nodeID uint) (bool, model.License, error) {
return edition.LoadLicenseByNodeID(nodeID)
}
func Bind(node model.Node, license *model.License, withInit, withSync, withDryRun, withDockerRestart bool) error {
return edition.Bind(node, license, withInit, withSync, withDryRun, withDockerRestart)
}
func BindFree(node model.Node, licenseID uint) error {
return edition.BindFree(node, licenseID)
}
func Unbind(node model.Node, license model.License, withReset, withSync, withDockerRestart bool) error {
return edition.Unbind(node, license, withReset, withSync, withDockerRestart)
}
func UnbindFree(node model.Node, licenseID uint) error {
return edition.UnbindFree(node, licenseID)
}

View File

@@ -1,72 +0,0 @@
//go:build xpackee
package xpack
import (
"net/http"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/init/session/psession"
"github.com/1Panel-dev/1Panel/core/utils/ssh"
edition "github.com/1Panel-dev/1Panel/core/xpack-ee/edition"
xeemiddleware "github.com/1Panel-dev/1Panel/core/xpack-ee/router/middleware"
"github.com/1Panel-dev/1Panel/core/xpack/app/model"
"github.com/gin-gonic/gin"
)
func Proxy(c *gin.Context, currentNode string) {
edition.Proxy(c, currentNode)
}
func CoreRBACMiddlewares() []gin.HandlerFunc {
return []gin.HandlerFunc{xeemiddleware.RequireRBAC()}
}
func ProxyDocker(proxyURL string) error { return edition.ProxyDocker(proxyURL) }
func UpdateGroup(name string, group, newGroup uint) error {
return edition.UpdateGroup(name, group, newGroup)
}
func CheckBackupUsed(name string) error {
return edition.CheckBackupUsed(name)
}
func LoadRequestTransport() *http.Transport { return edition.LoadRequestTransport() }
func LoadNodeInfo(currentNode string) (*ssh.ConnInfo, string, error) {
return edition.LoadNodeInfo(currentNode)
}
func Sync(dataType string) error { return edition.Sync(dataType) }
func AutoUpgradeWithMaster() { edition.AutoUpgradeWithMaster() }
func Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, string, error) {
return edition.Login(c, info, entrance)
}
func LoadSessionTimeout(sessionUser psession.SessionUser, defaultTTL int) int {
return edition.LoadSessionTimeout(sessionUser, defaultTTL)
}
func RemoveBindNode(nodeID uint) error { return nil }
func CheckLicenseStatus(isXpack bool, licenseID, nodeID uint) error {
return edition.CheckLicenseStatus(isXpack, licenseID, nodeID)
}
func LoadLicenseByNodeID(nodeID uint) (bool, model.License, error) {
return edition.LoadLicenseByNodeID(nodeID)
}
func Bind(node model.Node, license *model.License, withInit, withSync, withDryRun, withDockerRestart bool) error {
return edition.Bind(node, license, withInit, withSync, withDryRun, withDockerRestart)
}
func BindFree(node model.Node, licenseID uint) error {
return edition.BindFree(node, licenseID)
}
func Unbind(node model.Node, license model.License, withReset, withSync, withDockerRestart bool) error {
return edition.Unbind(node, license, withReset, withSync, withDockerRestart)
}
func UnbindFree(node model.Node, licenseID uint) error {
return edition.UnbindFree(node, licenseID)
}

View File

@@ -92,6 +92,11 @@ class RequestHttp {
window.location.reload();
return Promise.reject(data);
}
if (data.code == ResultEnum.ERRXPACKEE) {
globalStore.isXpackEELicensed = false;
router.push({ name: 'XpackEELicenseRequired' });
return Promise.reject(data);
}
if (data.code == ResultEnum.NodeUnBind) {
changeToLocal();
window.location.reload();

View File

@@ -47,6 +47,44 @@ export namespace AI {
migMode: string;
processes: Process[];
}
export interface MonitorGPUSearch {
productName: string;
startTime: Date;
endTime: Date;
}
export interface MonitorGPUOptions {
gpuType: string;
options: Array<string>;
chartHide: Array<ChartHide>;
}
export interface ChartHide {
productName: string;
process: boolean;
gpu: boolean;
memory: boolean;
power: boolean;
temperature: boolean;
speed: boolean;
}
export interface MonitorGPUData {
date: Array<Date>;
gpuValue: Array<number>;
temperatureValue: Array<number>;
powerTotal: Array<number>;
powerUsed: Array<number>;
powerPercent: Array<number>;
memoryTotal: Array<number>;
memoryUsed: Array<number>;
memoryPercent: Array<number>;
speedValue: Array<number>;
gpuProcesses: Array<Array<GPUProcess>>;
}
export interface GPUProcess {
pid: string;
type: string;
processName: string;
usedMemory: string;
}
export interface Process {
pid: string;
type: string;

View File

@@ -48,15 +48,46 @@ export namespace Login {
export interface AuthInfo {
id: number;
name: string;
sessionTimeout: number;
expirationDays: number;
expirationTime: string;
mfaStatus: string;
mfaInterval: number;
role: string;
permissions: string[];
nodeScopes: number[];
nodeRoles: Array<{ nodeId: number; nodeName: string; roleId: number; roleName: string }>;
apiInterfaceStatus: string;
apiKey: string;
ipWhiteList: string;
apiKeyValidityTime: number;
}
export interface AuthInfoUpdate {
id: number;
name: string;
password: string;
oldPassword: string;
newPassword: string;
retryPassword: string;
sessionTimeout: number;
expirationDays: number;
expirationTime: string;
}
export interface MFARequest {
title: string;
interval: number;
}
export interface MFAInfo {
secret: string;
qrImage: string;
}
export interface MFABind {
secret: string;
code: string;
interval: string;
}
export interface ApiConfig {
apiInterfaceStatus: string;
apiKey: string;
ipWhiteList: string;
apiKeyValidityTime: number;
}
}

View File

@@ -160,45 +160,6 @@ export namespace Host {
endTime: Date;
}
export interface MonitorGPUSearch {
productName: string;
startTime: Date;
endTime: Date;
}
export interface MonitorGPUOptions {
gpuType: string;
options: Array<string>;
chartHide: Array<ChartHide>;
}
export interface ChartHide {
productName: string;
process: boolean;
gpu: boolean;
memory: boolean;
power: boolean;
temperature: boolean;
speed: boolean;
}
export interface MonitorGPUData {
date: Array<Date>;
gpuValue: Array<number>;
temperatureValue: Array<number>;
powerTotal: Array<number>;
powerUsed: Array<number>;
powerPercent: Array<number>;
memoryTotal: Array<number>;
memoryUsed: Array<number>;
memoryPercent: Array<number>;
speedValue: Array<number>;
gpuProcesses: Array<Array<GPUProcess>>;
}
export interface GPUProcess {
pid: string;
type: string;
processName: string;
usedMemory: string;
}
export interface SSHInfo {
autoStart: boolean;
isActive: boolean;

Some files were not shown because too many files have changed in this diff Show More