diff --git a/core/app/service/auth.go b/core/app/service/auth.go index 4788839d3..2f59bcdfd 100644 --- a/core/app/service/auth.go +++ b/core/app/service/auth.go @@ -25,6 +25,8 @@ type IAuthService interface { Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error) LogOut(c *gin.Context) error MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, error) + GetSecurityEntrance() string + IsLogin(c *gin.Context) bool } func NewIAuthService() IAuthService { @@ -192,3 +194,19 @@ func (u *AuthService) GetResponsePage() (string, error) { } return pageCode.Value, nil } + +func (u *AuthService) GetSecurityEntrance() string { + status, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) + if err != nil { + return "" + } + if len(status.Value) == 0 { + return "" + } + return status.Value +} + +func (u *AuthService) IsLogin(c *gin.Context) bool { + _, err := global.SESSION.Get(c) + return err == nil +} diff --git a/core/cmd/server/res/error_msg.go b/core/cmd/server/res/error_msg.go new file mode 100644 index 000000000..fb958d1e6 --- /dev/null +++ b/core/cmd/server/res/error_msg.go @@ -0,0 +1,6 @@ +package res + +import "embed" + +//go:embed html/* +var ErrorMsg embed.FS diff --git a/core/cmd/server/res/html/200.html b/core/cmd/server/res/html/200.html new file mode 100644 index 000000000..62598c189 --- /dev/null +++ b/core/cmd/server/res/html/200.html @@ -0,0 +1,55 @@ + + + + + + Access Temporarily Unavailable + + + +
+

Access Temporarily Unavailable

+

The current environment has enabled secure login access.

+

Run the following SSH command to view the panel login URL:

+

1pctl user-info

+
+ + diff --git a/core/cmd/server/res/html/200_err_domain.html b/core/cmd/server/res/html/200_err_domain.html new file mode 100644 index 000000000..8f48fc532 --- /dev/null +++ b/core/cmd/server/res/html/200_err_domain.html @@ -0,0 +1,55 @@ + + + + + + Access Temporarily Unavailable + + + +
+

Access Temporarily Unavailable

+

The current environment has enabled domain name binding.

+

Run the following SSH command to reset the binding information:

+

1pctl reset domain

+
+ + diff --git a/core/cmd/server/res/html/200_err_ip_limit.html b/core/cmd/server/res/html/200_err_ip_limit.html new file mode 100644 index 000000000..662704ec0 --- /dev/null +++ b/core/cmd/server/res/html/200_err_ip_limit.html @@ -0,0 +1,55 @@ + + + + + + Access Temporarily Unavailable + + + +
+

Access Temporarily Unavailable

+

The current environment has enabled authorized IP access.

+

Run the following SSH command to reset the binding information:

+

1pctl reset ips

+
+ + diff --git a/core/cmd/server/res/html/400.html b/core/cmd/server/res/html/400.html new file mode 100644 index 000000000..0d502a0a3 --- /dev/null +++ b/core/cmd/server/res/html/400.html @@ -0,0 +1,7 @@ + + +400 Bad Request + +

400 Bad Request

+
nginx
+ \ No newline at end of file diff --git a/core/cmd/server/res/html/401.html b/core/cmd/server/res/html/401.html new file mode 100644 index 000000000..60e1498f7 --- /dev/null +++ b/core/cmd/server/res/html/401.html @@ -0,0 +1,7 @@ + + +401 Unauthorized + +

401 Unauthorized

+
nginx
+ \ No newline at end of file diff --git a/core/cmd/server/res/html/403.html b/core/cmd/server/res/html/403.html new file mode 100644 index 000000000..a77b7cb64 --- /dev/null +++ b/core/cmd/server/res/html/403.html @@ -0,0 +1,7 @@ + + +403 Forbidden + +

403 Forbidden

+
nginx
+ \ No newline at end of file diff --git a/core/cmd/server/res/html/404.html b/core/cmd/server/res/html/404.html new file mode 100644 index 000000000..6748be25f --- /dev/null +++ b/core/cmd/server/res/html/404.html @@ -0,0 +1,7 @@ + + +404 Not Found + +

404 Not Found

+
nginx
+ \ No newline at end of file diff --git a/core/cmd/server/res/html/408.html b/core/cmd/server/res/html/408.html new file mode 100644 index 000000000..15ba0cdaa --- /dev/null +++ b/core/cmd/server/res/html/408.html @@ -0,0 +1,7 @@ + + +408 Request Timeout + +

408 Request Timeout

+
nginx
+ \ No newline at end of file diff --git a/core/cmd/server/res/html/416.html b/core/cmd/server/res/html/416.html new file mode 100644 index 000000000..8104e724c --- /dev/null +++ b/core/cmd/server/res/html/416.html @@ -0,0 +1,7 @@ + + +416 Requested Not Satisfiable + +

416 Requested Not Satisfiable

+
nginx
+ \ No newline at end of file diff --git a/core/cmd/server/res/html/500.html b/core/cmd/server/res/html/500.html new file mode 100644 index 000000000..c580daa08 --- /dev/null +++ b/core/cmd/server/res/html/500.html @@ -0,0 +1,7 @@ + + +Internal Server Error + +

Internal Server Error

+
nginx
+ \ No newline at end of file diff --git a/core/constant/common.go b/core/constant/common.go index e2fd84720..4da179844 100644 --- a/core/constant/common.go +++ b/core/constant/common.go @@ -40,4 +40,105 @@ const ( FilePerm = 0644 ) +var WebUrlMap = map[string]struct{}{ + "/apps": {}, + "/apps/all": {}, + "/apps/installed": {}, + "/apps/upgrade": {}, + + "/containers": {}, + "/containers/container": {}, + "/containers/image": {}, + "/containers/network": {}, + "/containers/volume": {}, + "/containers/repo": {}, + "/containers/compose": {}, + "/containers/template": {}, + "/containers/setting": {}, + + "/cronjobs": {}, + + "/databases": {}, + "/databases/mysql": {}, + "/databases/mysql/remote": {}, + "/databases/postgresql": {}, + "/databases/postgresql/remote": {}, + "/databases/redis": {}, + "/databases/redis/remote": {}, + + "/hosts": {}, + "/hosts/files": {}, + "/hosts/monitor/monitor": {}, + "/hosts/monitor/setting": {}, + "/hosts/terminal": {}, + "/hosts/firewall/port": {}, + "/hosts/firewall/forward": {}, + "/hosts/firewall/ip": {}, + "/hosts/process/process": {}, + "/hosts/process/network": {}, + "/hosts/ssh/ssh": {}, + "/hosts/ssh/log": {}, + "/hosts/ssh/session": {}, + + "/logs": {}, + "/logs/operation": {}, + "/logs/login": {}, + "/logs/website": {}, + "/logs/system": {}, + "/logs/ssh": {}, + + "/settings": {}, + "/settings/panel": {}, + "/settings/backupaccount": {}, + "/settings/license": {}, + "/settings/about": {}, + "/settings/safe": {}, + "/settings/snapshot": {}, + "/settings/expired": {}, + + "/toolbox": {}, + "/toolbox/device": {}, + "/toolbox/supervisor": {}, + "/toolbox/clam": {}, + "/toolbox/clam/setting": {}, + "/toolbox/ftp": {}, + "/toolbox/fail2ban": {}, + "/toolbox/clean": {}, + + "/websites": {}, + "/websites/ssl": {}, + "/websites/runtimes/php": {}, + "/websites/runtimes/node": {}, + "/websites/runtimes/java": {}, + "/websites/runtimes/go": {}, + "/websites/runtimes/python": {}, + "/websites/runtimes/dotnet": {}, + + "/login": {}, + + "/xpack": {}, + "/xpack/waf/dashboard": {}, + "/xpack/waf/global": {}, + "/xpack/waf/websites": {}, + "/xpack/waf/log": {}, + "/xpack/waf/block": {}, + "/xpack/monitor/dashboard": {}, + "/xpack/monitor/setting": {}, + "/xpack/monitor/rank": {}, + "/xpack/monitor/log": {}, + "/xpack/tamper": {}, + "/xpack/gpu": {}, + "/xpack/alert/dashboard": {}, + "/xpack/alert/log": {}, + "/xpack/alert/setting": {}, + "/xpack/setting": {}, +} + +var DynamicRoutes = []string{ + `^/containers/composeDetail/[^/]+$`, + `^/databases/mysql/setting/[^/]+/[^/]+$`, + `^/databases/postgresql/setting/[^/]+/[^/]+$`, + `^/websites/[^/]+/config/[^/]+$`, +} + var CertStore atomic.Value diff --git a/core/init/router/router.go b/core/init/router/router.go index b40a11495..6712ae4ab 100644 --- a/core/init/router/router.go +++ b/core/init/router/router.go @@ -1,8 +1,15 @@ package router import ( + "encoding/base64" "fmt" + "github.com/1Panel-dev/1Panel/core/app/service" + "github.com/1Panel-dev/1Panel/core/cmd/server/res" + "github.com/1Panel-dev/1Panel/core/constant" "net/http" + "regexp" + "strconv" + "strings" "github.com/1Panel-dev/1Panel/core/cmd/server/docs" "github.com/1Panel-dev/1Panel/core/cmd/server/web" @@ -21,8 +28,91 @@ var ( Router *gin.Engine ) +func toIndexHtml(c *gin.Context) { + c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") + c.Writer.WriteHeader(http.StatusOK) + _, _ = c.Writer.Write(web.IndexByte) + c.Writer.Flush() +} + +func isEntrancePath(c *gin.Context) bool { + entrance := service.NewIAuthService().GetSecurityEntrance() + if entrance != "" && strings.TrimSuffix(c.Request.URL.Path, "/") == "/"+entrance { + return true + } + return false +} + +func checkEntrance(c *gin.Context) bool { + authService := service.NewIAuthService() + entrance := authService.GetSecurityEntrance() + if entrance == "" { + return true + } + + cookieValue, err := c.Cookie("SecurityEntrance") + if err != nil { + return false + } + entranceValue, err := base64.StdEncoding.DecodeString(cookieValue) + if err != nil { + return false + } + return string(entranceValue) == entrance +} + +func handleNoRoute(c *gin.Context) { + resPage, err := service.NewIAuthService().GetResponsePage() + if err != nil { + c.String(http.StatusInternalServerError, "Internal Server Error") + return + } + if resPage == "444" { + c.String(444, "") + return + } + + file := fmt.Sprintf("html/%s.html", resPage) + data, err := res.ErrorMsg.ReadFile(file) + if err != nil { + c.String(http.StatusInternalServerError, "Internal Server Error") + return + } + statusCode, err := strconv.Atoi(resPage) + if err != nil { + c.String(http.StatusInternalServerError, "Internal Server Error") + return + } + c.Data(statusCode, "text/html; charset=utf-8", data) +} + +func isFrontendPath(c *gin.Context) bool { + reqUri := strings.TrimSuffix(c.Request.URL.Path, "/") + if _, ok := constant.WebUrlMap[reqUri]; ok { + return true + } + for _, route := range constant.DynamicRoutes { + if match, _ := regexp.MatchString(route, reqUri); match { + return true + } + } + return false +} + +func checkFrontendPath(c *gin.Context) bool { + if !isFrontendPath(c) { + return false + } + authService := service.NewIAuthService() + if authService.GetSecurityEntrance() != "" { + return authService.IsLogin(c) + } + return true +} + func setWebStatic(rootRouter *gin.RouterGroup) { rootRouter.StaticFS("/public", http.FS(web.Favicon)) + rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon)) rootRouter.Static("/api/v2/images", "./uploads") rootRouter.Use(func(c *gin.Context) { c.Next() @@ -32,7 +122,23 @@ func setWebStatic(rootRouter *gin.RouterGroup) { staticServer := http.FileServer(http.FS(web.Assets)) staticServer.ServeHTTP(c.Writer, c.Request) }) + authService := service.NewIAuthService() + entrance := authService.GetSecurityEntrance() + if entrance != "" { + rootRouter.GET("/"+entrance, func(c *gin.Context) { + entrance = authService.GetSecurityEntrance() + if entrance == "" { + handleNoRoute(c) + return + } + toIndexHtml(c) + }) + } rootRouter.GET("/", func(c *gin.Context) { + if !checkEntrance(c) { + handleNoRoute(c) + return + } staticServer := http.FileServer(http.FS(web.IndexHtml)) staticServer.ServeHTTP(c.Writer, c.Request) }) @@ -69,10 +175,15 @@ func Routers() *gin.Engine { } Router.NoRoute(func(c *gin.Context) { - c.Writer.WriteHeader(http.StatusOK) - _, _ = c.Writer.Write(web.IndexByte) - c.Writer.Header().Add("Accept", "text/html") - c.Writer.Flush() + if checkFrontendPath(c) { + toIndexHtml(c) + return + } + if isEntrancePath(c) { + toIndexHtml(c) + return + } + handleNoRoute(c) }) return Router diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index a4063314c..2cb084a5f 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -53,22 +53,6 @@ class RequestHttp { }); return Promise.reject(data); } - if (data.code == ResultEnum.NOTFOUND) { - globalStore.errStatus = 'err-found'; - return; - } - if (data.code == ResultEnum.ERRIP) { - globalStore.errStatus = 'err-ip'; - return; - } - if (data.code == ResultEnum.ERRDOMAIN) { - globalStore.errStatus = 'err-domain'; - return; - } - if (data.code == ResultEnum.UNSAFETY) { - globalStore.errStatus = 'err-unsafe'; - return; - } if (data.code == ResultEnum.EXPIRED) { router.push({ name: 'Expired' }); return; @@ -106,27 +90,6 @@ class RequestHttp { if (error.message.indexOf('timeout') !== -1) MsgError('请求超时!请您稍后重试'); if (response) { switch (response.status) { - case 310: - globalStore.errStatus = 'err-ip'; - router.push({ - name: 'entrance', - params: { code: globalStore.entrance }, - }); - return; - case 311: - globalStore.errStatus = 'err-domain'; - router.push({ - name: 'entrance', - params: { code: globalStore.entrance }, - }); - return; - case 312: - globalStore.errStatus = 'err-entrance'; - router.push({ - name: 'entrance', - params: { code: globalStore.entrance }, - }); - return; case 313: router.push({ name: 'Expired' }); return; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index cc7339e04..ff9715563 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1638,6 +1638,8 @@ const message = { error404: 'Not Found', error408: 'Request Timeout', error416: 'Range Not Satisfiable', + error444: 'Connection Closed', + error500: 'Internal Server Error', https: 'Setting up HTTPS protocol access for the panel can enhance the security of panel access.', certType: 'Certificate type', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 9175d4c97..e1116f370 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1606,6 +1606,8 @@ const message = { error404: '未找到', error408: '請求超時', error416: '無效請求', + error444: '連線已關閉', + error500: '內部伺服器錯誤', https: '為面板設置 https 協議訪問,提升面板訪問安全性', certType: '證書類型', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 6e1b09004..b74a818f6 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1604,6 +1604,8 @@ const message = { error404: '未找到', error408: '请求超时', error416: '无效请求', + error444: '连接被关闭', + error500: '内部错误', https: '为面板设置 https 协议访问,提升面板访问安全性', certType: '证书类型', diff --git a/frontend/src/routers/router.ts b/frontend/src/routers/router.ts index fc319d367..314a57f3f 100644 --- a/frontend/src/routers/router.ts +++ b/frontend/src/routers/router.ts @@ -72,12 +72,6 @@ export const routes: RouteRecordRaw[] = [ key: 'login', }, }, - { - path: '/:code?', - name: 'entrance', - component: () => import('@/views/login/entrance/index.vue'), - props: true, - }, ...routerArray, { path: '/:pathMatch(.*)', diff --git a/frontend/src/views/login/entrance/index.vue b/frontend/src/views/login/entrance/index.vue deleted file mode 100644 index af74d26af..000000000 --- a/frontend/src/views/login/entrance/index.vue +++ /dev/null @@ -1,186 +0,0 @@ - - - - - diff --git a/frontend/src/views/setting/safe/response/index.vue b/frontend/src/views/setting/safe/response/index.vue index cdb2f8e67..b85d5325c 100644 --- a/frontend/src/views/setting/safe/response/index.vue +++ b/frontend/src/views/setting/safe/response/index.vue @@ -63,6 +63,14 @@ const options = [ value: '416', label: '416 - ' + i18n.global.t('setting.error416'), }, + { + value: '444', + label: '444 - ' + i18n.global.t('setting.error444'), + }, + { + value: '500', + label: '500 - ' + i18n.global.t('setting.error500'), + }, ]; interface DialogProps {