From 8dd509aa08a5c706dada2bb9c8d70d5ddf000202 Mon Sep 17 00:00:00 2001 From: naiba Date: Sat, 24 Feb 2024 23:21:33 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=EF=B8=8F=20=E5=A2=9E=E5=BC=BA=20pi?= =?UTF-8?q?ng=20=E5=8E=86=E5=8F=B2=20API=20=E9=89=B4=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/dashboard/controller/api_v1.go | 35 +++++++++++++++++----- cmd/dashboard/controller/common_page.go | 30 ++----------------- cmd/dashboard/controller/guest_page.go | 10 +++---- cmd/dashboard/controller/member_api.go | 20 +++++++------ cmd/dashboard/controller/member_page.go | 10 +++---- pkg/mygin/auth.go | 40 ++++++++++++++++++------- 6 files changed, 82 insertions(+), 63 deletions(-) diff --git a/cmd/dashboard/controller/api_v1.go b/cmd/dashboard/controller/api_v1.go index da24b68..be2d1d5 100644 --- a/cmd/dashboard/controller/api_v1.go +++ b/cmd/dashboard/controller/api_v1.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" + "github.com/naiba/nezha/model" "github.com/naiba/nezha/pkg/mygin" "github.com/naiba/nezha/service/singleton" ) @@ -16,18 +17,28 @@ type apiV1 struct { func (v *apiV1) serve() { r := v.r.Group("") - // API + // 强制认证的 API r.Use(mygin.Authorize(mygin.AuthorizeOption{ - Member: true, - IsPage: false, - AllowAPI: true, - Msg: "访问此接口需要认证", - Btn: "点此登录", - Redirect: "/login", + MemberOnly: true, + AllowAPI: true, + IsPage: false, + Msg: "访问此接口需要认证", + Btn: "点此登录", + Redirect: "/login", })) r.GET("/server/list", v.serverList) r.GET("/server/details", v.serverDetails) + // 不强制认证的 API mr := v.r.Group("monitor") + mr.Use(mygin.Authorize(mygin.AuthorizeOption{ + MemberOnly: false, + IsPage: false, + ValidateViewPassword: true, + AllowAPI: true, + Msg: "访问此接口需要认证", + Btn: "点此登录", + Redirect: "/login", + })) mr.GET("/:id", v.monitorHistoriesById) } @@ -84,5 +95,15 @@ func (v *apiV1) monitorHistoriesById(c *gin.Context) { }) return } + + _, isMember := c.Get(model.CtxKeyAuthorizedUser) + _, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified) + authorized := isMember || isViewPasswordVerfied + + if server.HideForGuest && !authorized { + c.AbortWithStatusJSON(403, gin.H{"code": 403, "message": "需要认证"}) + return + } + c.JSON(200, singleton.MonitorAPI.GetMonitorHistories(map[string]any{"server_id": server.ID})) } diff --git a/cmd/dashboard/controller/common_page.go b/cmd/dashboard/controller/common_page.go index 26b4119..cf0b9a1 100644 --- a/cmd/dashboard/controller/common_page.go +++ b/cmd/dashboard/controller/common_page.go @@ -43,11 +43,12 @@ type commonPage struct { func (cp *commonPage) serve() { cr := cp.r.Group("") - cr.Use(mygin.Authorize(mygin.AuthorizeOption{})) + cr.Use(mygin.Authorize(mygin.AuthorizeOption{ + ValidateViewPassword: true, + })) cr.Use(mygin.PreferredTheme) cr.GET("/terminal/:id", cp.terminal) cr.POST("/view-password", cp.issueViewPassword) - cr.Use(cp.checkViewPassword) // 前端查看密码鉴权 cr.GET("/", cp.home) cr.GET("/service", cp.service) // TODO: 界面直接跳转使用该接口 @@ -86,31 +87,6 @@ func (p *commonPage) issueViewPassword(c *gin.Context) { c.Redirect(http.StatusFound, c.Request.Referer()) } -func (p *commonPage) checkViewPassword(c *gin.Context) { - if singleton.Conf.Site.ViewPassword == "" { - c.Next() - return - } - if _, authorized := c.Get(model.CtxKeyAuthorizedUser); authorized { - c.Next() - return - } - - // 验证查看密码 - viewPassword, _ := c.Cookie(singleton.Conf.Site.CookieName + "-vp") - if err := bcrypt.CompareHashAndPassword([]byte(viewPassword), []byte(singleton.Conf.Site.ViewPassword)); err != nil { - c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/viewpassword"), mygin.CommonEnvironment(c, gin.H{ - "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "VerifyPassword"}), - "CustomCode": singleton.Conf.Site.CustomCode, - })) - c.Abort() - return - } - - c.Set(model.CtxKeyViewPasswordVerified, true) - c.Next() -} - func (p *commonPage) service(c *gin.Context) { res, _, _ := p.requestGroup.Do("servicePage", func() (interface{}, error) { singleton.AlertsLock.RLock() diff --git a/cmd/dashboard/controller/guest_page.go b/cmd/dashboard/controller/guest_page.go index 835a3bb..e3351e9 100644 --- a/cmd/dashboard/controller/guest_page.go +++ b/cmd/dashboard/controller/guest_page.go @@ -19,11 +19,11 @@ type guestPage struct { func (gp *guestPage) serve() { gr := gp.r.Group("") gr.Use(mygin.Authorize(mygin.AuthorizeOption{ - Guest: true, - IsPage: true, - Msg: "您已登录", - Btn: "返回首页", - Redirect: "/", + GuestOnly: true, + IsPage: true, + Msg: "您已登录", + Btn: "返回首页", + Redirect: "/", })) gr.GET("/login", gp.login) diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index f1b5969..cf20e78 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -28,11 +28,11 @@ type memberAPI struct { func (ma *memberAPI) serve() { mr := ma.r.Group("") mr.Use(mygin.Authorize(mygin.AuthorizeOption{ - Member: true, - IsPage: false, - Msg: "访问此接口需要登录", - Btn: "点此登录", - Redirect: "/login", + MemberOnly: true, + IsPage: false, + Msg: "访问此接口需要登录", + Btn: "点此登录", + Redirect: "/login", })) mr.GET("/search-server", ma.searchServer) @@ -444,10 +444,12 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) { err = singleton.DB.Save(&m).Error } } - if m.Cover == 0 { - err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error - } else { - err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id not in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error + if err == nil { + if m.Cover == 0 { + err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error + } else { + err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id not in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error + } } } if err == nil { diff --git a/cmd/dashboard/controller/member_page.go b/cmd/dashboard/controller/member_page.go index 83c1115..66c734a 100644 --- a/cmd/dashboard/controller/member_page.go +++ b/cmd/dashboard/controller/member_page.go @@ -17,11 +17,11 @@ type memberPage struct { func (mp *memberPage) serve() { mr := mp.r.Group("") mr.Use(mygin.Authorize(mygin.AuthorizeOption{ - Member: true, - IsPage: true, - Msg: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "YouAreNotAuthorized"}), - Btn: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}), - Redirect: "/login", + MemberOnly: true, + IsPage: true, + Msg: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "YouAreNotAuthorized"}), + Btn: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}), + Redirect: "/login", })) mr.GET("/server", mp.server) mr.GET("/monitor", mp.monitor) diff --git a/pkg/mygin/auth.go b/pkg/mygin/auth.go index 0f28974..9549a0f 100644 --- a/pkg/mygin/auth.go +++ b/pkg/mygin/auth.go @@ -6,25 +6,28 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/crypto/bcrypt" "github.com/naiba/nezha/model" "github.com/naiba/nezha/service/singleton" ) type AuthorizeOption struct { - Guest bool - Member bool - IsPage bool - AllowAPI bool - Msg string - Redirect string - Btn string + GuestOnly bool + MemberOnly bool + ValidateViewPassword bool + IsPage bool + AllowAPI bool + Msg string + Redirect string + Btn string } func Authorize(opt AuthorizeOption) func(*gin.Context) { return func(c *gin.Context) { var code = http.StatusForbidden - if opt.Guest { + if opt.GuestOnly { code = http.StatusBadRequest } @@ -67,15 +70,32 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) { } } } + // 已登录且只能游客访问 - if isLogin && opt.Guest { + if isLogin && opt.GuestOnly { ShowErrorPage(c, commonErr, opt.IsPage) return } + // 未登录且需要登录 - if !isLogin && opt.Member { + if !isLogin && opt.MemberOnly { ShowErrorPage(c, commonErr, opt.IsPage) return } + + // 验证查看密码 + if opt.ValidateViewPassword && singleton.Conf.Site.ViewPassword != "" { + viewPassword, _ := c.Cookie(singleton.Conf.Site.CookieName + "-vp") + if err := bcrypt.CompareHashAndPassword([]byte(viewPassword), []byte(singleton.Conf.Site.ViewPassword)); err != nil { + c.HTML(http.StatusOK, GetPreferredTheme(c, "/viewpassword"), CommonEnvironment(c, gin.H{ + "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "VerifyPassword"}), + "CustomCode": singleton.Conf.Site.CustomCode, + })) + c.Abort() + return + } + + c.Set(model.CtxKeyViewPasswordVerified, true) + } } }