🔒️ 增强 ping 历史 API 鉴权

This commit is contained in:
naiba 2024-02-24 23:21:33 +08:00
parent 99ac12c9fd
commit 8dd509aa08
6 changed files with 82 additions and 63 deletions

View File

@ -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}))
}

View File

@ -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()

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}
}
}