diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go index 9f0f4af..3b0225c 100644 --- a/cmd/dashboard/controller/controller.go +++ b/cmd/dashboard/controller/controller.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "path" + "slices" "strings" jwt "github.com/appleboy/gin-jwt/v2" @@ -58,7 +59,7 @@ func routers(r *gin.Engine, frontendDist fs.FS) { optionalAuth := api.Group("", optionalAuthMiddleware(authMiddleware)) optionalAuth.GET("/ws/server", commonHandler(serverStream)) - optionalAuth.GET("/server-group", commonHandler(listServerGroup)) + optionalAuth.GET("/server-group", listHandler(listServerGroup)) optionalAuth.GET("/service", commonHandler(showService)) optionalAuth.GET("/service/:id", commonHandler(listServiceHistory)) @@ -111,19 +112,19 @@ func routers(r *gin.Engine, frontendDist fs.FS) { auth.PATCH("/alert-rule/:id", commonHandler(updateAlertRule)) auth.POST("/batch-delete/alert-rule", commonHandler(batchDeleteAlertRule)) - auth.GET("/cron", commonHandler(listCron)) + auth.GET("/cron", listHandler(listCron)) auth.POST("/cron", commonHandler(createCron)) auth.PATCH("/cron/:id", commonHandler(updateCron)) auth.GET("/cron/:id/manual", commonHandler(manualTriggerCron)) auth.POST("/batch-delete/cron", commonHandler(batchDeleteCron)) - auth.GET("/ddns", commonHandler(listDDNS)) + auth.GET("/ddns", listHandler(listDDNS)) auth.GET("/ddns/providers", commonHandler(listProviders)) auth.POST("/ddns", commonHandler(createDDNS)) auth.PATCH("/ddns/:id", commonHandler(updateDDNS)) auth.POST("/batch-delete/ddns", commonHandler(batchDeleteDDNS)) - auth.GET("/nat", commonHandler(listNAT)) + auth.GET("/nat", listHandler(listNAT)) auth.POST("/nat", commonHandler(createNAT)) auth.PATCH("/nat/:id", commonHandler(updateNAT)) auth.POST("/batch-delete/nat", commonHandler(batchDeleteNAT)) @@ -212,6 +213,24 @@ func commonHandler[T any](handler handlerFunc[T]) func(*gin.Context) { } } +func listHandler[S ~[]E, E model.CommonInterface](handler handlerFunc[S]) func(*gin.Context) { + return func(c *gin.Context) { + data, err := handler(c) + if err != nil { + c.JSON(http.StatusOK, newErrorResponse(err)) + return + } + + c.JSON(http.StatusOK, filter(c, data)) + } +} + +func filter[S ~[]E, E model.CommonInterface](ctx *gin.Context, s S) S { + return slices.DeleteFunc(s, func(e E) bool { + return e.HasPermission(ctx) + }) +} + func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) { checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string) bool { if _, err := os.Stat(path); err == nil { diff --git a/cmd/dashboard/controller/server_group.go b/cmd/dashboard/controller/server_group.go index 98b5c6f..c66bb39 100644 --- a/cmd/dashboard/controller/server_group.go +++ b/cmd/dashboard/controller/server_group.go @@ -20,7 +20,7 @@ import ( // @Produce json // @Success 200 {object} model.CommonResponse[[]model.ServerGroupResponseItem] // @Router /server-group [get] -func listServerGroup(c *gin.Context) ([]model.ServerGroupResponseItem, error) { +func listServerGroup(c *gin.Context) ([]*model.ServerGroupResponseItem, error) { var sg []model.ServerGroup if err := singleton.DB.Find(&sg).Error; err != nil { return nil, err @@ -38,9 +38,9 @@ func listServerGroup(c *gin.Context) ([]model.ServerGroupResponseItem, error) { groupServers[s.ServerGroupId] = append(groupServers[s.ServerGroupId], s.ServerId) } - var sgRes []model.ServerGroupResponseItem + var sgRes []*model.ServerGroupResponseItem for _, s := range sg { - sgRes = append(sgRes, model.ServerGroupResponseItem{ + sgRes = append(sgRes, &model.ServerGroupResponseItem{ Group: s, Servers: groupServers[s.ID], }) diff --git a/model/common.go b/model/common.go index 10394e9..15961ff 100644 --- a/model/common.go +++ b/model/common.go @@ -2,6 +2,8 @@ package model import ( "time" + + "github.com/gin-gonic/gin" ) const ( @@ -17,6 +19,26 @@ type Common struct { UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at,omitempty"` // Do not use soft deletion // DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"` + + UserID uint64 `json:"user_id,omitempty"` +} + +func (c *Common) HasPermission(ctx *gin.Context) bool { + auth, ok := ctx.Get(CtxKeyAuthorizedUser) + if !ok { + return false + } + + user := *auth.(*User) + if user.Role == RoleAdmin { + return true + } + + return user.ID == c.UserID +} + +type CommonInterface interface { + HasPermission(*gin.Context) bool } type Response struct { diff --git a/model/server_group_api.go b/model/server_group_api.go index e36a236..1079564 100644 --- a/model/server_group_api.go +++ b/model/server_group_api.go @@ -1,5 +1,7 @@ package model +import "github.com/gin-gonic/gin" + type ServerGroupForm struct { Name string `json:"name" minLength:"1"` Servers []uint64 `json:"servers"` @@ -9,3 +11,7 @@ type ServerGroupResponseItem struct { Group ServerGroup `json:"group"` Servers []uint64 `json:"servers"` } + +func (sg *ServerGroupResponseItem) HasPermission(c *gin.Context) bool { + return sg.Group.HasPermission(c) +} diff --git a/model/user.go b/model/user.go index e1f297b..fe8a1ed 100644 --- a/model/user.go +++ b/model/user.go @@ -1,9 +1,15 @@ package model +const ( + RoleAdmin uint8 = iota + RoleMember +) + type User struct { Common Username string `json:"username,omitempty" gorm:"uniqueIndex"` Password string `json:"password,omitempty" gorm:"type:char(72)"` + Role uint8 `json:"role,omitempty"` } type Profile struct {