mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 11:27:17 +08:00
feat: 安全入口及应用安装检查中间件
This commit is contained in:
@@ -216,7 +216,7 @@ func (s *Service) SetRootPassword(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
if err = s.settingRepo.Set(biz.SettingKeyMySQLRootPassword, req.Password); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("设置保存失败:%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ func (s *Service) SaveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重载服务失败: %v", err))
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重载服务失败:%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d", s.version))
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载状态失败")
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("获取负载状态失败:%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -94,19 +94,19 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
|
||||
dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"}
|
||||
regexKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"}
|
||||
|
||||
data := make([]types.NV, len(dataKeys))
|
||||
loads := make([]types.NV, len(dataKeys))
|
||||
for i := range dataKeys {
|
||||
data[i].Name = dataKeys[i]
|
||||
loads[i].Name = dataKeys[i]
|
||||
|
||||
r := regexp.MustCompile(fmt.Sprintf("%s:\\s+(.*)", regexKeys[i]))
|
||||
match := r.FindStringSubmatch(raw)
|
||||
|
||||
if len(match) > 1 {
|
||||
data[i].Value = strings.TrimSpace(match[1])
|
||||
loads[i].Value = strings.TrimSpace(match[1])
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(w, data)
|
||||
service.Success(w, loads)
|
||||
}
|
||||
|
||||
func (s *Service) ErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -88,7 +88,7 @@ func (s *Service) UpdatePort(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败:%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败:%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
46
internal/http/middleware/entrance.go
Normal file
46
internal/http/middleware/entrance.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
)
|
||||
|
||||
// Entrance 确保通过正确的入口访问
|
||||
func Entrance(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sess, err := app.Session.GetSession(r)
|
||||
if err != nil {
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusInternalServerError)
|
||||
render.JSON(chix.M{
|
||||
"message": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
entrance := app.Conf.String("http.entrance")
|
||||
if strings.TrimSuffix(r.URL.Path, "/") == strings.TrimSuffix(entrance, "/") {
|
||||
sess.Put("verify_entrance", true)
|
||||
render := chix.NewRender(w, r)
|
||||
render.Redirect("/login")
|
||||
return
|
||||
}
|
||||
|
||||
if !app.Conf.Bool("app.debug") &&
|
||||
!cast.ToBool(sess.Get("verify_entrance", false)) &&
|
||||
r.URL.Path != "/robots.txt" {
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusTeapot)
|
||||
render.JSON(chix.M{
|
||||
"message": "请通过正确的入口访问",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -19,5 +19,8 @@ func GlobalMiddleware() []func(http.Handler) http.Handler {
|
||||
middleware.Logger,
|
||||
middleware.Recoverer,
|
||||
middleware.Compress(5),
|
||||
Entrance,
|
||||
Status,
|
||||
MustInstall,
|
||||
}
|
||||
}
|
||||
|
||||
52
internal/http/middleware/must_install.go
Normal file
52
internal/http/middleware/must_install.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
)
|
||||
|
||||
// MustInstall 确保已安装应用
|
||||
func MustInstall(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var slugs []string
|
||||
if strings.HasPrefix(r.URL.Path, "/api/website") {
|
||||
slugs = append(slugs, "openresty")
|
||||
} else if strings.HasPrefix(r.URL.Path, "/api/container") {
|
||||
slugs = append(slugs, "podman", "docker")
|
||||
} else if strings.HasPrefix(r.URL.Path, "/api/apps/") {
|
||||
pathArr := strings.Split(r.URL.Path, "/")
|
||||
if len(pathArr) < 4 {
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusForbidden)
|
||||
render.JSON(chix.M{
|
||||
"message": "应用不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
slugs = append(slugs, pathArr[3])
|
||||
}
|
||||
|
||||
flag := false
|
||||
for _, s := range slugs {
|
||||
if installed, _ := data.NewAppRepo().IsInstalled("slug = ?", s); installed {
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !flag && len(slugs) > 0 {
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusForbidden)
|
||||
render.JSON(chix.M{
|
||||
"message": fmt.Sprintf("应用 %s 未安装", slugs),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// MustLogin 确保已登录
|
||||
func MustLogin(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := app.Session.GetSession(r)
|
||||
sess, err := app.Session.GetSession(r)
|
||||
if err != nil {
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusInternalServerError)
|
||||
@@ -22,7 +22,7 @@ func MustLogin(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
if session.Missing("user_id") {
|
||||
if sess.Missing("user_id") {
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusUnauthorized)
|
||||
render.JSON(chix.M{
|
||||
@@ -31,7 +31,7 @@ func MustLogin(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
userID := cast.ToUint(session.Get("user_id"))
|
||||
userID := cast.ToUint(sess.Get("user_id"))
|
||||
if userID == 0 {
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusUnauthorized)
|
||||
|
||||
47
internal/http/middleware/status.go
Normal file
47
internal/http/middleware/status.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
// Status 检查程序状态
|
||||
func Status(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch types.Status {
|
||||
case types.StatusUpgrade:
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusServiceUnavailable)
|
||||
render.JSON(chix.M{
|
||||
"message": "面板升级中,请稍后刷新",
|
||||
})
|
||||
return
|
||||
case types.StatusMaintain:
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusServiceUnavailable)
|
||||
render.JSON(chix.M{
|
||||
"message": "面板正在运行维护任务,请稍后刷新",
|
||||
})
|
||||
return
|
||||
case types.StatusClosed:
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusForbidden)
|
||||
render.JSON(chix.M{
|
||||
"message": "面板已关闭",
|
||||
})
|
||||
return
|
||||
case types.StatusFailed:
|
||||
render := chix.NewRender(w)
|
||||
render.Status(http.StatusInternalServerError)
|
||||
render.JSON(chix.M{
|
||||
"message": "面板运行出错,请检查排除或联系支持",
|
||||
})
|
||||
return
|
||||
default:
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -47,9 +47,7 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/website", func(r chi.Router) {
|
||||
// TODO 修改前端
|
||||
r.Use(middleware.MustLogin)
|
||||
// r.Use(middleware.MustInstallWebServer)
|
||||
website := service.NewWebsiteService()
|
||||
r.Get("/defaultConfig", website.GetDefaultConfig)
|
||||
r.Post("/defaultConfig", website.UpdateDefaultConfig)
|
||||
|
||||
@@ -358,18 +358,18 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error {
|
||||
|
||||
settings := []biz.Setting{{Key: biz.SettingKeyName, Value: "耗子面板"}, {Key: biz.SettingKeyMonitor, Value: "1"}, {Key: biz.SettingKeyMonitorDays, Value: "30"}, {Key: biz.SettingKeyBackupPath, Value: filepath.Join(app.Root, "backup")}, {Key: biz.SettingKeyWebsitePath, Value: filepath.Join(app.Root, "wwwroot")}, {Key: biz.SettingKeyVersion, Value: app.Conf.String("app.version")}}
|
||||
if err := app.Orm.Create(&settings).Error; err != nil {
|
||||
return fmt.Errorf("初始化失败: %v", err)
|
||||
return fmt.Errorf("初始化失败:%v", err)
|
||||
}
|
||||
|
||||
value, err := hash.NewArgon2id().Make(str.RandomString(32))
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化失败: %v", err)
|
||||
return fmt.Errorf("初始化失败:%v", err)
|
||||
}
|
||||
|
||||
user := data.NewUserRepo()
|
||||
_, err = user.Create("admin", value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化失败: %v", err)
|
||||
return fmt.Errorf("初始化失败:%v", err)
|
||||
}
|
||||
|
||||
config := new(types.PanelConfig)
|
||||
|
||||
@@ -8,6 +8,7 @@ type PanelConfig struct {
|
||||
}
|
||||
|
||||
type PanelAppConfig struct {
|
||||
Debug bool `yaml:"debug"`
|
||||
Key string `yaml:"key"`
|
||||
Locale string `yaml:"locale"`
|
||||
Timezone string `yaml:"timezone"`
|
||||
|
||||
@@ -4,38 +4,38 @@ import { request } from '@/utils'
|
||||
|
||||
export default {
|
||||
// 负载状态
|
||||
load: (version: number): Promise<AxiosResponse<any>> => request.get(`/apps/php/${version}/load`),
|
||||
load: (version: number): Promise<AxiosResponse<any>> => request.get(`/apps/php${version}/load`),
|
||||
// 获取配置
|
||||
config: (version: number): Promise<AxiosResponse<any>> =>
|
||||
request.get(`/apps/php/${version}/config`),
|
||||
request.get(`/apps/php${version}/config`),
|
||||
// 保存配置
|
||||
saveConfig: (version: number, config: string): Promise<AxiosResponse<any>> =>
|
||||
request.post(`/apps/php/${version}/config`, { config }),
|
||||
request.post(`/apps/php${version}/config`, { config }),
|
||||
// 获取FPM配置
|
||||
fpmConfig: (version: number): Promise<AxiosResponse<any>> =>
|
||||
request.get(`/apps/php/${version}/fpmConfig`),
|
||||
request.get(`/apps/php${version}/fpmConfig`),
|
||||
// 保存FPM配置
|
||||
saveFPMConfig: (version: number, config: string): Promise<AxiosResponse<any>> =>
|
||||
request.post(`/apps/php/${version}/fpmConfig`, { config }),
|
||||
request.post(`/apps/php${version}/fpmConfig`, { config }),
|
||||
// 获取错误日志
|
||||
errorLog: (version: number): Promise<AxiosResponse<any>> =>
|
||||
request.get(`/apps/php/${version}/errorLog`),
|
||||
request.get(`/apps/php${version}/errorLog`),
|
||||
// 清空错误日志
|
||||
clearErrorLog: (version: number): Promise<AxiosResponse<any>> =>
|
||||
request.post(`/apps/php/${version}/clearErrorLog`),
|
||||
request.post(`/apps/php${version}/clearErrorLog`),
|
||||
// 获取慢日志
|
||||
slowLog: (version: number): Promise<AxiosResponse<any>> =>
|
||||
request.get(`/apps/php/${version}/slowLog`),
|
||||
request.get(`/apps/php${version}/slowLog`),
|
||||
// 清空慢日志
|
||||
clearSlowLog: (version: number): Promise<AxiosResponse<any>> =>
|
||||
request.post(`/apps/php/${version}/clearSlowLog`),
|
||||
request.post(`/apps/php${version}/clearSlowLog`),
|
||||
// 拓展列表
|
||||
extensions: (version: number): Promise<AxiosResponse<any>> =>
|
||||
request.get(`/apps/php/${version}/extensions`),
|
||||
request.get(`/apps/php${version}/extensions`),
|
||||
// 安装拓展
|
||||
installExtension: (version: number, slug: string): Promise<AxiosResponse<any>> =>
|
||||
request.post(`/apps/php/${version}/extensions`, { slug }),
|
||||
request.post(`/apps/php${version}/extensions`, { slug }),
|
||||
// 卸载拓展
|
||||
uninstallExtension: (version: number, slug: string): Promise<AxiosResponse<any>> =>
|
||||
request.delete(`/apps/php/${version}/extensions`, { params: { slug } })
|
||||
request.delete(`/apps/php${version}/extensions`, { params: { slug } })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user