Compare commits

...

4 Commits

Author SHA1 Message Date
UUBulb
1cabe975f2
Merge 0caca56dfd into a9c2abe71e 2024-12-17 06:10:01 +00:00
uubulb
0caca56dfd update 2024-12-17 14:09:49 +08:00
uubulb
2c8ab28efe update 2024-12-16 23:43:01 +08:00
uubulb
af7427666a [WIP] feat: user roles 2024-12-16 19:40:50 +08:00
12 changed files with 284 additions and 40 deletions

View File

@ -50,6 +50,9 @@ func createAlertRule(c *gin.Context) (uint64, error) {
return 0, err return 0, err
} }
uid := getUid(c)
r.UserID = uid
r.Name = arf.Name r.Name = arf.Name
r.Rules = arf.Rules r.Rules = arf.Rules
r.FailTriggerTasks = arf.FailTriggerTasks r.FailTriggerTasks = arf.FailTriggerTasks
@ -100,6 +103,10 @@ func updateAlertRule(c *gin.Context) (any, error) {
return nil, singleton.Localizer.ErrorT("alert id %d does not exist", id) return nil, singleton.Localizer.ErrorT("alert id %d does not exist", id)
} }
if !r.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
r.Name = arf.Name r.Name = arf.Name
r.Rules = arf.Rules r.Rules = arf.Rules
r.FailTriggerTasks = arf.FailTriggerTasks r.FailTriggerTasks = arf.FailTriggerTasks
@ -133,12 +140,24 @@ func updateAlertRule(c *gin.Context) (any, error) {
// @Success 200 {object} model.CommonResponse[any] // @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/alert-rule [post] // @Router /batch-delete/alert-rule [post]
func batchDeleteAlertRule(c *gin.Context) (any, error) { func batchDeleteAlertRule(c *gin.Context) (any, error) {
var ar []uint64 var arr []uint64
if err := c.ShouldBindJSON(&arr); err != nil {
if err := c.ShouldBindJSON(&ar); err != nil {
return nil, err return nil, err
} }
var ars []model.AlertRule
if err := singleton.DB.Where("id in (?)", arr).Find(&ars).Error; err != nil {
return nil, err
}
var ar []uint64
for _, a := range ars {
if !a.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
ar = append(ar, a.ID)
}
if err := singleton.DB.Unscoped().Delete(&model.AlertRule{}, "id in (?)", ar).Error; err != nil { if err := singleton.DB.Unscoped().Delete(&model.AlertRule{}, "id in (?)", ar).Error; err != nil {
return nil, newGormError("%v", err) return nil, newGormError("%v", err)
} }

View File

@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"slices"
"strings" "strings"
jwt "github.com/appleboy/gin-jwt/v2" jwt "github.com/appleboy/gin-jwt/v2"
@ -82,7 +83,7 @@ func routers(r *gin.Engine, frontendDist fs.FS) {
auth.POST("/user", commonHandler(createUser)) auth.POST("/user", commonHandler(createUser))
auth.POST("/batch-delete/user", commonHandler(batchDeleteUser)) auth.POST("/batch-delete/user", commonHandler(batchDeleteUser))
auth.GET("/service/list", commonHandler(listService)) auth.GET("/service/list", listHandler(listService))
auth.POST("/service", commonHandler(createService)) auth.POST("/service", commonHandler(createService))
auth.PATCH("/service/:id", commonHandler(updateService)) auth.PATCH("/service/:id", commonHandler(updateService))
auth.POST("/batch-delete/service", commonHandler(batchDeleteService)) auth.POST("/batch-delete/service", commonHandler(batchDeleteService))
@ -96,34 +97,34 @@ func routers(r *gin.Engine, frontendDist fs.FS) {
auth.PATCH("/notification-group/:id", commonHandler(updateNotificationGroup)) auth.PATCH("/notification-group/:id", commonHandler(updateNotificationGroup))
auth.POST("/batch-delete/notification-group", commonHandler(batchDeleteNotificationGroup)) auth.POST("/batch-delete/notification-group", commonHandler(batchDeleteNotificationGroup))
auth.GET("/server", commonHandler(listServer)) auth.GET("/server", listHandler(listServer))
auth.PATCH("/server/:id", commonHandler(updateServer)) auth.PATCH("/server/:id", commonHandler(updateServer))
auth.POST("/batch-delete/server", commonHandler(batchDeleteServer)) auth.POST("/batch-delete/server", commonHandler(batchDeleteServer))
auth.POST("/force-update/server", commonHandler(forceUpdateServer)) auth.POST("/force-update/server", commonHandler(forceUpdateServer))
auth.GET("/notification", commonHandler(listNotification)) auth.GET("/notification", listHandler(listNotification))
auth.POST("/notification", commonHandler(createNotification)) auth.POST("/notification", commonHandler(createNotification))
auth.PATCH("/notification/:id", commonHandler(updateNotification)) auth.PATCH("/notification/:id", commonHandler(updateNotification))
auth.POST("/batch-delete/notification", commonHandler(batchDeleteNotification)) auth.POST("/batch-delete/notification", commonHandler(batchDeleteNotification))
auth.GET("/alert-rule", commonHandler(listAlertRule)) auth.GET("/alert-rule", listHandler(listAlertRule))
auth.POST("/alert-rule", commonHandler(createAlertRule)) auth.POST("/alert-rule", commonHandler(createAlertRule))
auth.PATCH("/alert-rule/:id", commonHandler(updateAlertRule)) auth.PATCH("/alert-rule/:id", commonHandler(updateAlertRule))
auth.POST("/batch-delete/alert-rule", commonHandler(batchDeleteAlertRule)) auth.POST("/batch-delete/alert-rule", commonHandler(batchDeleteAlertRule))
auth.GET("/cron", commonHandler(listCron)) auth.GET("/cron", listHandler(listCron))
auth.POST("/cron", commonHandler(createCron)) auth.POST("/cron", commonHandler(createCron))
auth.PATCH("/cron/:id", commonHandler(updateCron)) auth.PATCH("/cron/:id", commonHandler(updateCron))
auth.GET("/cron/:id/manual", commonHandler(manualTriggerCron)) auth.GET("/cron/:id/manual", commonHandler(manualTriggerCron))
auth.POST("/batch-delete/cron", commonHandler(batchDeleteCron)) auth.POST("/batch-delete/cron", commonHandler(batchDeleteCron))
auth.GET("/ddns", commonHandler(listDDNS)) auth.GET("/ddns", listHandler(listDDNS))
auth.GET("/ddns/providers", commonHandler(listProviders)) auth.GET("/ddns/providers", commonHandler(listProviders))
auth.POST("/ddns", commonHandler(createDDNS)) auth.POST("/ddns", commonHandler(createDDNS))
auth.PATCH("/ddns/:id", commonHandler(updateDDNS)) auth.PATCH("/ddns/:id", commonHandler(updateDDNS))
auth.POST("/batch-delete/ddns", commonHandler(batchDeleteDDNS)) auth.POST("/batch-delete/ddns", commonHandler(batchDeleteDDNS))
auth.GET("/nat", commonHandler(listNAT)) auth.GET("/nat", listHandler(listNAT))
auth.POST("/nat", commonHandler(createNAT)) auth.POST("/nat", commonHandler(createNAT))
auth.PATCH("/nat/:id", commonHandler(updateNAT)) auth.PATCH("/nat/:id", commonHandler(updateNAT))
auth.POST("/batch-delete/nat", commonHandler(batchDeleteNAT)) auth.POST("/batch-delete/nat", commonHandler(batchDeleteNAT))
@ -212,6 +213,29 @@ 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 getUid(c *gin.Context) uint64 {
user, _ := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
return user.ID
}
func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) { func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string) bool { checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string) bool {
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {

View File

@ -1,7 +1,6 @@
package controller package controller
import ( import (
"fmt"
"strconv" "strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -50,6 +49,7 @@ func createCron(c *gin.Context) (uint64, error) {
return 0, err return 0, err
} }
cr.UserID = getUid(c)
cr.TaskType = cf.TaskType cr.TaskType = cf.TaskType
cr.Name = cf.Name cr.Name = cf.Name
cr.Scheduler = cf.Scheduler cr.Scheduler = cf.Scheduler
@ -106,7 +106,11 @@ func updateCron(c *gin.Context) (any, error) {
var cr model.Cron var cr model.Cron
if err := singleton.DB.First(&cr, id).Error; err != nil { if err := singleton.DB.First(&cr, id).Error; err != nil {
return nil, fmt.Errorf("task id %d does not exist", id) return nil, singleton.Localizer.ErrorT("task id %d does not exist", id)
}
if !cr.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
} }
cr.TaskType = cf.TaskType cr.TaskType = cf.TaskType
@ -156,12 +160,15 @@ func manualTriggerCron(c *gin.Context) (any, error) {
return nil, err return nil, err
} }
var cr model.Cron singleton.CronLock.RLock()
if err := singleton.DB.First(&cr, id).Error; err != nil { cr, ok := singleton.Crons[id]
if !ok {
singleton.CronLock.RUnlock()
return nil, singleton.Localizer.ErrorT("task id %d does not exist", id) return nil, singleton.Localizer.ErrorT("task id %d does not exist", id)
} }
singleton.CronLock.RUnlock()
singleton.ManualTrigger(&cr) singleton.ManualTrigger(cr)
return nil, nil return nil, nil
} }
@ -177,12 +184,24 @@ func manualTriggerCron(c *gin.Context) (any, error) {
// @Success 200 {object} model.CommonResponse[any] // @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/cron [post] // @Router /batch-delete/cron [post]
func batchDeleteCron(c *gin.Context) (any, error) { func batchDeleteCron(c *gin.Context) (any, error) {
var cr []uint64 var crr []uint64
if err := c.ShouldBindJSON(&crr); err != nil {
if err := c.ShouldBindJSON(&cr); err != nil {
return nil, err return nil, err
} }
var cr []uint64
singleton.CronLock.RLock()
for _, crID := range crr {
if crn, ok := singleton.Crons[crID]; ok {
if !crn.HasPermission(c) {
singleton.CronLock.RUnlock()
return nil, singleton.Localizer.ErrorT("permission denied")
}
cr = append(cr, crn.ID)
}
}
singleton.CronLock.RUnlock()
if err := singleton.DB.Unscoped().Delete(&model.Cron{}, "id in (?)", cr).Error; err != nil { if err := singleton.DB.Unscoped().Delete(&model.Cron{}, "id in (?)", cr).Error; err != nil {
return nil, newGormError("%v", err) return nil, newGormError("%v", err)
} }

View File

@ -56,6 +56,7 @@ func createDDNS(c *gin.Context) (uint64, error) {
return 0, singleton.Localizer.ErrorT("the retry count must be an integer between 1 and 10") return 0, singleton.Localizer.ErrorT("the retry count must be an integer between 1 and 10")
} }
p.UserID = getUid(c)
p.Name = df.Name p.Name = df.Name
enableIPv4 := df.EnableIPv4 enableIPv4 := df.EnableIPv4
enableIPv6 := df.EnableIPv6 enableIPv6 := df.EnableIPv6
@ -125,6 +126,10 @@ func updateDDNS(c *gin.Context) (any, error) {
return nil, singleton.Localizer.ErrorT("profile id %d does not exist", id) return nil, singleton.Localizer.ErrorT("profile id %d does not exist", id)
} }
if !p.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
p.Name = df.Name p.Name = df.Name
enableIPv4 := df.EnableIPv4 enableIPv4 := df.EnableIPv4
enableIPv6 := df.EnableIPv6 enableIPv6 := df.EnableIPv6
@ -172,12 +177,25 @@ func updateDDNS(c *gin.Context) (any, error) {
// @Success 200 {object} model.CommonResponse[any] // @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/ddns [post] // @Router /batch-delete/ddns [post]
func batchDeleteDDNS(c *gin.Context) (any, error) { func batchDeleteDDNS(c *gin.Context) (any, error) {
var ddnsConfigs []uint64 var ddnsConfigsr []uint64
if err := c.ShouldBindJSON(&ddnsConfigs); err != nil { if err := c.ShouldBindJSON(&ddnsConfigsr); err != nil {
return nil, err return nil, err
} }
var ddnsConfigs []uint64
singleton.DDNSCacheLock.RLock()
for _, pid := range ddnsConfigsr {
if p, ok := singleton.DDNSCache[pid]; ok {
if !p.HasPermission(c) {
singleton.DDNSCacheLock.RUnlock()
return nil, singleton.Localizer.ErrorT("permission denied")
}
ddnsConfigs = append(ddnsConfigs, p.ID)
}
}
singleton.DDNSCacheLock.RUnlock()
if err := singleton.DB.Unscoped().Delete(&model.DDNSProfile{}, "id in (?)", ddnsConfigs).Error; err != nil { if err := singleton.DB.Unscoped().Delete(&model.DDNSProfile{}, "id in (?)", ddnsConfigs).Error; err != nil {
return nil, newGormError("%v", err) return nil, newGormError("%v", err)
} }

View File

@ -51,6 +51,9 @@ func createNAT(c *gin.Context) (uint64, error) {
return 0, err return 0, err
} }
uid := getUid(c)
n.UserID = uid
n.Name = nf.Name n.Name = nf.Name
n.Domain = nf.Domain n.Domain = nf.Domain
n.Host = nf.Host n.Host = nf.Host
@ -95,6 +98,10 @@ func updateNAT(c *gin.Context) (any, error) {
return nil, singleton.Localizer.ErrorT("profile id %d does not exist", id) return nil, singleton.Localizer.ErrorT("profile id %d does not exist", id)
} }
if !n.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
n.Name = nf.Name n.Name = nf.Name
n.Domain = nf.Domain n.Domain = nf.Domain
n.Host = nf.Host n.Host = nf.Host
@ -121,12 +128,24 @@ func updateNAT(c *gin.Context) (any, error) {
// @Success 200 {object} model.CommonResponse[any] // @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/nat [post] // @Router /batch-delete/nat [post]
func batchDeleteNAT(c *gin.Context) (any, error) { func batchDeleteNAT(c *gin.Context) (any, error) {
var n []uint64 var nr []uint64
if err := c.ShouldBindJSON(&nr); err != nil {
if err := c.ShouldBindJSON(&n); err != nil {
return nil, err return nil, err
} }
var n []uint64
singleton.NATCacheRwLock.RLock()
for _, id := range nr {
if p, ok := singleton.NATCache[singleton.NATIDToDomain[id]]; ok {
if !p.HasPermission(c) {
singleton.NATCacheRwLock.RUnlock()
return nil, singleton.Localizer.ErrorT("permission denied")
}
n = append(n, p.ID)
}
}
singleton.NATCacheRwLock.RUnlock()
if err := singleton.DB.Unscoped().Delete(&model.NAT{}, "id in (?)", n).Error; err != nil { if err := singleton.DB.Unscoped().Delete(&model.NAT{}, "id in (?)", n).Error; err != nil {
return nil, newGormError("%v", err) return nil, newGormError("%v", err)
} }

View File

@ -48,6 +48,7 @@ func createNotification(c *gin.Context) (uint64, error) {
} }
var n model.Notification var n model.Notification
n.UserID = getUid(c)
n.Name = nf.Name n.Name = nf.Name
n.RequestMethod = nf.RequestMethod n.RequestMethod = nf.RequestMethod
n.RequestType = nf.RequestType n.RequestType = nf.RequestType
@ -106,6 +107,10 @@ func updateNotification(c *gin.Context) (any, error) {
return nil, singleton.Localizer.ErrorT("notification id %d does not exist", id) return nil, singleton.Localizer.ErrorT("notification id %d does not exist", id)
} }
if !n.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
n.Name = nf.Name n.Name = nf.Name
n.RequestMethod = nf.RequestMethod n.RequestMethod = nf.RequestMethod
n.RequestType = nf.RequestType n.RequestType = nf.RequestType
@ -148,12 +153,23 @@ func updateNotification(c *gin.Context) (any, error) {
// @Success 200 {object} model.CommonResponse[any] // @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/notification [post] // @Router /batch-delete/notification [post]
func batchDeleteNotification(c *gin.Context) (any, error) { func batchDeleteNotification(c *gin.Context) (any, error) {
var n []uint64 var nr []uint64
if err := c.ShouldBindJSON(&nr); err != nil {
if err := c.ShouldBindJSON(&n); err != nil {
return nil, err return nil, err
} }
var n []uint64
singleton.NotificationsLock.RLock()
for _, nid := range nr {
if ns, ok := singleton.NotificationMap[nid]; ok {
if !ns.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
n = append(n, ns.ID)
}
}
singleton.NotificationsLock.RUnlock()
err := singleton.DB.Transaction(func(tx *gorm.DB) error { err := singleton.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Unscoped().Delete(&model.Notification{}, "id in (?)", n).Error; err != nil { if err := tx.Unscoped().Delete(&model.Notification{}, "id in (?)", n).Error; err != nil {
return err return err

View File

@ -20,7 +20,7 @@ import (
// @Produce json // @Produce json
// @Success 200 {object} model.CommonResponse[[]model.NotificationGroupResponseItem] // @Success 200 {object} model.CommonResponse[[]model.NotificationGroupResponseItem]
// @Router /notification-group [get] // @Router /notification-group [get]
func listNotificationGroup(c *gin.Context) ([]model.NotificationGroupResponseItem, error) { func listNotificationGroup(c *gin.Context) ([]*model.NotificationGroupResponseItem, error) {
var ng []model.NotificationGroup var ng []model.NotificationGroup
if err := singleton.DB.Find(&ng).Error; err != nil { if err := singleton.DB.Find(&ng).Error; err != nil {
return nil, err return nil, err
@ -39,9 +39,9 @@ func listNotificationGroup(c *gin.Context) ([]model.NotificationGroupResponseIte
groupNotifications[n.NotificationGroupID] = append(groupNotifications[n.NotificationGroupID], n.NotificationID) groupNotifications[n.NotificationGroupID] = append(groupNotifications[n.NotificationGroupID], n.NotificationID)
} }
ngRes := make([]model.NotificationGroupResponseItem, 0, len(ng)) ngRes := make([]*model.NotificationGroupResponseItem, 0, len(ng))
for _, n := range ng { for _, n := range ng {
ngRes = append(ngRes, model.NotificationGroupResponseItem{ ngRes = append(ngRes, &model.NotificationGroupResponseItem{
Group: n, Group: n,
Notifications: groupNotifications[n.ID], Notifications: groupNotifications[n.ID],
}) })
@ -68,8 +68,11 @@ func createNotificationGroup(c *gin.Context) (uint64, error) {
} }
ngf.Notifications = slices.Compact(ngf.Notifications) ngf.Notifications = slices.Compact(ngf.Notifications)
uid := getUid(c)
var ng model.NotificationGroup var ng model.NotificationGroup
ng.Name = ngf.Name ng.Name = ngf.Name
ng.UserID = uid
var count int64 var count int64
if err := singleton.DB.Model(&model.Notification{}).Where("id in (?)", ngf.Notifications).Count(&count).Error; err != nil { if err := singleton.DB.Model(&model.Notification{}).Where("id in (?)", ngf.Notifications).Count(&count).Error; err != nil {
@ -86,6 +89,9 @@ func createNotificationGroup(c *gin.Context) (uint64, error) {
} }
for _, n := range ngf.Notifications { for _, n := range ngf.Notifications {
if err := tx.Create(&model.NotificationGroupNotification{ if err := tx.Create(&model.NotificationGroupNotification{
Common: model.Common{
UserID: uid,
},
NotificationGroupID: ng.ID, NotificationGroupID: ng.ID,
NotificationID: n, NotificationID: n,
}).Error; err != nil { }).Error; err != nil {
@ -131,6 +137,10 @@ func updateNotificationGroup(c *gin.Context) (any, error) {
return nil, singleton.Localizer.ErrorT("group id %d does not exist", id) return nil, singleton.Localizer.ErrorT("group id %d does not exist", id)
} }
if !ngDB.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
ngDB.Name = ngf.Name ngDB.Name = ngf.Name
ngf.Notifications = slices.Compact(ngf.Notifications) ngf.Notifications = slices.Compact(ngf.Notifications)
@ -142,6 +152,8 @@ func updateNotificationGroup(c *gin.Context) (any, error) {
return nil, singleton.Localizer.ErrorT("have invalid notification id") return nil, singleton.Localizer.ErrorT("have invalid notification id")
} }
uid := getUid(c)
err = singleton.DB.Transaction(func(tx *gorm.DB) error { err = singleton.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Save(&ngDB).Error; err != nil { if err := tx.Save(&ngDB).Error; err != nil {
return err return err
@ -152,6 +164,9 @@ func updateNotificationGroup(c *gin.Context) (any, error) {
for _, n := range ngf.Notifications { for _, n := range ngf.Notifications {
if err := tx.Create(&model.NotificationGroupNotification{ if err := tx.Create(&model.NotificationGroupNotification{
Common: model.Common{
UserID: uid,
},
NotificationGroupID: ngDB.ID, NotificationGroupID: ngDB.ID,
NotificationID: n, NotificationID: n,
}).Error; err != nil { }).Error; err != nil {
@ -180,11 +195,24 @@ func updateNotificationGroup(c *gin.Context) (any, error) {
// @Success 200 {object} model.CommonResponse[any] // @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/notification-group [post] // @Router /batch-delete/notification-group [post]
func batchDeleteNotificationGroup(c *gin.Context) (any, error) { func batchDeleteNotificationGroup(c *gin.Context) (any, error) {
var ngn []uint64 var ngnr []uint64
if err := c.ShouldBindJSON(&ngn); err != nil { if err := c.ShouldBindJSON(&ngnr); err != nil {
return nil, err return nil, err
} }
var ng []model.NotificationGroup
if err := singleton.DB.Where("id in (?)", ng).Find(&ng).Error; err != nil {
return nil, err
}
var ngn []uint64
for _, n := range ng {
if !n.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
ngn = append(ngn, n.ID)
}
err := singleton.DB.Transaction(func(tx *gorm.DB) error { err := singleton.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Unscoped().Delete(&model.NotificationGroup{}, "id in (?)", ngn).Error; err != nil { if err := tx.Unscoped().Delete(&model.NotificationGroup{}, "id in (?)", ngn).Error; err != nil {
return err return err

View File

@ -61,6 +61,10 @@ func updateServer(c *gin.Context) (any, error) {
return nil, singleton.Localizer.ErrorT("server id %d does not exist", id) return nil, singleton.Localizer.ErrorT("server id %d does not exist", id)
} }
if !s.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
s.Name = sf.Name s.Name = sf.Name
s.DisplayIndex = sf.DisplayIndex s.DisplayIndex = sf.DisplayIndex
s.Note = sf.Note s.Note = sf.Note
@ -99,11 +103,24 @@ func updateServer(c *gin.Context) (any, error) {
// @Success 200 {object} model.CommonResponse[any] // @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/server [post] // @Router /batch-delete/server [post]
func batchDeleteServer(c *gin.Context) (any, error) { func batchDeleteServer(c *gin.Context) (any, error) {
var servers []uint64 var serversRaw []uint64
if err := c.ShouldBindJSON(&servers); err != nil { if err := c.ShouldBindJSON(&serversRaw); err != nil {
return nil, err return nil, err
} }
var servers []uint64
singleton.ServerLock.RLock()
for _, sid := range serversRaw {
if s, ok := singleton.ServerList[sid]; ok {
if !s.HasPermission(c) {
singleton.ServerLock.RUnlock()
return nil, singleton.Localizer.ErrorT("permission denied")
}
servers = append(servers, s.ID)
}
}
singleton.ServerLock.RUnlock()
err := singleton.DB.Transaction(func(tx *gorm.DB) error { err := singleton.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Unscoped().Delete(&model.Server{}, "id in (?)", servers).Error; err != nil { if err := tx.Unscoped().Delete(&model.Server{}, "id in (?)", servers).Error; err != nil {
return err return err

View File

@ -20,7 +20,7 @@ import (
// @Produce json // @Produce json
// @Success 200 {object} model.CommonResponse[[]model.ServerGroupResponseItem] // @Success 200 {object} model.CommonResponse[[]model.ServerGroupResponseItem]
// @Router /server-group [get] // @Router /server-group [get]
func listServerGroup(c *gin.Context) ([]model.ServerGroupResponseItem, error) { func listServerGroup(c *gin.Context) ([]*model.ServerGroupResponseItem, error) {
var sg []model.ServerGroup var sg []model.ServerGroup
if err := singleton.DB.Find(&sg).Error; err != nil { if err := singleton.DB.Find(&sg).Error; err != nil {
return nil, err return nil, err
@ -38,9 +38,9 @@ func listServerGroup(c *gin.Context) ([]model.ServerGroupResponseItem, error) {
groupServers[s.ServerGroupId] = append(groupServers[s.ServerGroupId], s.ServerId) groupServers[s.ServerGroupId] = append(groupServers[s.ServerGroupId], s.ServerId)
} }
var sgRes []model.ServerGroupResponseItem var sgRes []*model.ServerGroupResponseItem
for _, s := range sg { for _, s := range sg {
sgRes = append(sgRes, model.ServerGroupResponseItem{ sgRes = append(sgRes, &model.ServerGroupResponseItem{
Group: s, Group: s,
Servers: groupServers[s.ID], Servers: groupServers[s.ID],
}) })
@ -67,8 +67,11 @@ func createServerGroup(c *gin.Context) (uint64, error) {
} }
sgf.Servers = slices.Compact(sgf.Servers) sgf.Servers = slices.Compact(sgf.Servers)
uid := getUid(c)
var sg model.ServerGroup var sg model.ServerGroup
sg.Name = sgf.Name sg.Name = sgf.Name
sg.UserID = uid
var count int64 var count int64
if err := singleton.DB.Model(&model.Server{}).Where("id in (?)", sgf.Servers).Count(&count).Error; err != nil { if err := singleton.DB.Model(&model.Server{}).Where("id in (?)", sgf.Servers).Count(&count).Error; err != nil {
@ -84,6 +87,9 @@ func createServerGroup(c *gin.Context) (uint64, error) {
} }
for _, s := range sgf.Servers { for _, s := range sgf.Servers {
if err := tx.Create(&model.ServerGroupServer{ if err := tx.Create(&model.ServerGroupServer{
Common: model.Common{
UserID: uid,
},
ServerGroupId: sg.ID, ServerGroupId: sg.ID,
ServerId: s, ServerId: s,
}).Error; err != nil { }).Error; err != nil {
@ -129,6 +135,11 @@ func updateServerGroup(c *gin.Context) (any, error) {
if err := singleton.DB.First(&sgDB, id).Error; err != nil { if err := singleton.DB.First(&sgDB, id).Error; err != nil {
return nil, singleton.Localizer.ErrorT("group id %d does not exist", id) return nil, singleton.Localizer.ErrorT("group id %d does not exist", id)
} }
if !sgDB.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("unauthorized")
}
sgDB.Name = sg.Name sgDB.Name = sg.Name
var count int64 var count int64
@ -139,6 +150,8 @@ func updateServerGroup(c *gin.Context) (any, error) {
return nil, singleton.Localizer.ErrorT("have invalid server id") return nil, singleton.Localizer.ErrorT("have invalid server id")
} }
uid := getUid(c)
err = singleton.DB.Transaction(func(tx *gorm.DB) error { err = singleton.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Save(&sgDB).Error; err != nil { if err := tx.Save(&sgDB).Error; err != nil {
return err return err
@ -149,6 +162,9 @@ func updateServerGroup(c *gin.Context) (any, error) {
for _, s := range sg.Servers { for _, s := range sg.Servers {
if err := tx.Create(&model.ServerGroupServer{ if err := tx.Create(&model.ServerGroupServer{
Common: model.Common{
UserID: uid,
},
ServerGroupId: sgDB.ID, ServerGroupId: sgDB.ID,
ServerId: s, ServerId: s,
}).Error; err != nil { }).Error; err != nil {
@ -176,11 +192,24 @@ func updateServerGroup(c *gin.Context) (any, error) {
// @Success 200 {object} model.CommonResponse[any] // @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/server-group [post] // @Router /batch-delete/server-group [post]
func batchDeleteServerGroup(c *gin.Context) (any, error) { func batchDeleteServerGroup(c *gin.Context) (any, error) {
var sgs []uint64 var sgsr []uint64
if err := c.ShouldBindJSON(&sgs); err != nil { if err := c.ShouldBindJSON(&sgsr); err != nil {
return nil, err return nil, err
} }
var sg []model.ServerGroup
if err := singleton.DB.Where("id in (?)", sgsr).Find(&sg).Error; err != nil {
return nil, err
}
var sgs []uint64
for _, s := range sg {
if !s.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
sgs = append(sgs, s.ID)
}
err := singleton.DB.Transaction(func(tx *gorm.DB) error { err := singleton.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Unscoped().Delete(&model.ServerGroup{}, "id in (?)", sgs).Error; err != nil { if err := tx.Unscoped().Delete(&model.ServerGroup{}, "id in (?)", sgs).Error; err != nil {
return err return err

View File

@ -190,7 +190,10 @@ func createService(c *gin.Context) (uint64, error) {
return 0, err return 0, err
} }
uid := getUid(c)
var m model.Service var m model.Service
m.UserID = uid
m.Name = mf.Name m.Name = mf.Name
m.Target = strings.TrimSpace(mf.Target) m.Target = strings.TrimSpace(mf.Target)
m.Type = mf.Type m.Type = mf.Type
@ -260,6 +263,11 @@ func updateService(c *gin.Context) (any, error) {
if err := singleton.DB.First(&m, id).Error; err != nil { if err := singleton.DB.First(&m, id).Error; err != nil {
return nil, singleton.Localizer.ErrorT("service id %d does not exist", id) return nil, singleton.Localizer.ErrorT("service id %d does not exist", id)
} }
if !m.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
m.Name = mf.Name m.Name = mf.Name
m.Target = strings.TrimSpace(mf.Target) m.Target = strings.TrimSpace(mf.Target)
m.Type = mf.Type m.Type = mf.Type
@ -314,10 +322,24 @@ func updateService(c *gin.Context) (any, error) {
// @Success 200 {object} model.CommonResponse[any] // @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/service [post] // @Router /batch-delete/service [post]
func batchDeleteService(c *gin.Context) (any, error) { func batchDeleteService(c *gin.Context) (any, error) {
var ids []uint64 var idsr []uint64
if err := c.ShouldBindJSON(&ids); err != nil { if err := c.ShouldBindJSON(&idsr); err != nil {
return nil, err return nil, err
} }
var ids []uint64
singleton.ServiceSentinelShared.ServicesLock.RLock()
for _, id := range idsr {
if ss, ok := singleton.ServiceSentinelShared.Services[id]; ok {
if !ss.HasPermission(c) {
singleton.ServiceSentinelShared.ServicesLock.RUnlock()
return nil, singleton.Localizer.ErrorT("permission denied")
}
ids = append(ids, ss.ID)
}
}
singleton.ServiceSentinelShared.ServicesLock.RUnlock()
err := singleton.DB.Transaction(func(tx *gorm.DB) error { err := singleton.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Unscoped().Delete(&model.Service{}, "id in (?)", ids).Error; err != nil { if err := tx.Unscoped().Delete(&model.Service{}, "id in (?)", ids).Error; err != nil {
return err return err

View File

@ -2,6 +2,8 @@ package model
import ( import (
"time" "time"
"github.com/gin-gonic/gin"
) )
const ( const (
@ -17,6 +19,31 @@ type Common struct {
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at,omitempty"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at,omitempty"`
// Do not use soft deletion // Do not use soft deletion
// DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"` // DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
UserID uint64 `json:"user_id,omitempty"`
}
func (c *Common) GetID() uint64 {
return c.ID
}
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 {
GetID() uint64
HasPermission(*gin.Context) bool
} }
type Response struct { type Response struct {

View File

@ -1,9 +1,15 @@
package model package model
const (
RoleAdmin uint8 = iota
RoleMember
)
type User struct { type User struct {
Common Common
Username string `json:"username,omitempty" gorm:"uniqueIndex"` Username string `json:"username,omitempty" gorm:"uniqueIndex"`
Password string `json:"password,omitempty" gorm:"type:char(72)"` Password string `json:"password,omitempty" gorm:"type:char(72)"`
Role uint8 `json:"role,omitempty"`
} }
type Profile struct { type Profile struct {