mirror of
https://github.com/nezhahq/nezha.git
synced 2025-02-02 09:38:13 -05:00
Compare commits
1 Commits
f127ff8141
...
59b5f4598d
Author | SHA1 | Date | |
---|---|---|---|
|
59b5f4598d |
@ -79,7 +79,7 @@ func routers(r *gin.Engine, frontendDist fs.FS) {
|
|||||||
|
|
||||||
auth.GET("/profile", commonHandler(getProfile))
|
auth.GET("/profile", commonHandler(getProfile))
|
||||||
auth.POST("/profile", commonHandler(updateProfile))
|
auth.POST("/profile", commonHandler(updateProfile))
|
||||||
auth.GET("/user", adminHandler(listUser))
|
auth.GET("/user", commonHandler(listUser))
|
||||||
auth.POST("/user", adminHandler(createUser))
|
auth.POST("/user", adminHandler(createUser))
|
||||||
auth.POST("/batch-delete/user", adminHandler(batchDeleteUser))
|
auth.POST("/batch-delete/user", adminHandler(batchDeleteUser))
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ 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))
|
||||||
@ -243,13 +243,13 @@ func listHandler[S ~[]E, E model.CommonInterface](handler handlerFunc[S]) func(*
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, model.CommonResponse[S]{Success: true, Data: filter(c, data)})
|
c.JSON(http.StatusOK, filter(c, data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func filter[S ~[]E, E model.CommonInterface](ctx *gin.Context, s S) S {
|
func filter[S ~[]E, E model.CommonInterface](ctx *gin.Context, s S) S {
|
||||||
return slices.DeleteFunc(s, func(e E) bool {
|
return slices.DeleteFunc(s, func(e E) bool {
|
||||||
return !e.HasPermission(ctx)
|
return e.HasPermission(ctx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,10 +168,6 @@ func manualTriggerCron(c *gin.Context) (any, error) {
|
|||||||
}
|
}
|
||||||
singleton.CronLock.RUnlock()
|
singleton.CronLock.RUnlock()
|
||||||
|
|
||||||
if !cr.HasPermission(c) {
|
|
||||||
return nil, singleton.Localizer.ErrorT("permission denied")
|
|
||||||
}
|
|
||||||
|
|
||||||
singleton.ManualTrigger(cr)
|
singleton.ManualTrigger(cr)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ func batchDeleteNotificationGroup(c *gin.Context) (any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ng []model.NotificationGroup
|
var ng []model.NotificationGroup
|
||||||
if err := singleton.DB.Where("id in (?)", ngnr).Find(&ng).Error; err != nil {
|
if err := singleton.DB.Where("id in (?)", ng).Find(&ng).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,9 +178,6 @@ func forceUpdateServer(c *gin.Context) (*model.ForceUpdateResponse, error) {
|
|||||||
server := singleton.ServerList[sid]
|
server := singleton.ServerList[sid]
|
||||||
singleton.ServerLock.RUnlock()
|
singleton.ServerLock.RUnlock()
|
||||||
if server != nil && server.TaskStream != nil {
|
if server != nil && server.TaskStream != nil {
|
||||||
if !server.HasPermission(c) {
|
|
||||||
return nil, singleton.Localizer.ErrorT("permission denied")
|
|
||||||
}
|
|
||||||
if err := server.TaskStream.Send(&pb.Task{
|
if err := server.TaskStream.Send(&pb.Task{
|
||||||
Type: model.TaskTypeUpgrade,
|
Type: model.TaskTypeUpgrade,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -126,7 +126,6 @@ func createUser(c *gin.Context) (uint64, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
singleton.OnUserUpdate(&u)
|
|
||||||
return u.ID, nil
|
return u.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +150,5 @@ func batchDeleteUser(c *gin.Context) (any, error) {
|
|||||||
return nil, singleton.Localizer.ErrorT("can't delete yourself")
|
return nil, singleton.Localizer.ErrorT("can't delete yourself")
|
||||||
}
|
}
|
||||||
|
|
||||||
singleton.OnUserDelete(ids)
|
|
||||||
return nil, singleton.DB.Where("id IN (?)", ids).Delete(&model.User{}).Error
|
return nil, singleton.DB.Where("id IN (?)", ids).Delete(&model.User{}).Error
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ type Common struct {
|
|||||||
// 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:"-"`
|
UserID uint64 `json:"user_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) GetID() uint64 {
|
func (c *Common) GetID() uint64 {
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nezhahq/nezha/pkg/utils"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RoleAdmin uint8 = iota
|
RoleAdmin uint8 = iota
|
||||||
RoleMember
|
RoleMember
|
||||||
@ -12,20 +7,9 @@ const (
|
|||||||
|
|
||||||
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"`
|
Role uint8 `json:"role,omitempty"`
|
||||||
AgentSecret string `json:"agent_secret,omitempty" gorm:"type:char(32)"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) BeforeSave(tx *gorm.DB) error {
|
|
||||||
key, err := utils.GenerateRandomString(32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
u.AgentSecret = key
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
|
6
model/user_group.go
Normal file
6
model/user_group.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type UserGroup struct {
|
||||||
|
Common
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
7
model/user_group_user.go
Normal file
7
model/user_group_user.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type UserGroupUser struct {
|
||||||
|
Common
|
||||||
|
UserGroupId uint64 `json:"user_group_id"`
|
||||||
|
UserId uint64 `json:"user_id"`
|
||||||
|
}
|
@ -2,7 +2,6 @@ package rpc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/subtle"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
petname "github.com/dustinkirkland/golang-petname"
|
petname "github.com/dustinkirkland/golang-petname"
|
||||||
@ -37,14 +36,10 @@ func (a *authHandler) Check(ctx context.Context) (uint64, error) {
|
|||||||
|
|
||||||
ip, _ := ctx.Value(model.CtxKeyRealIP{}).(string)
|
ip, _ := ctx.Value(model.CtxKeyRealIP{}).(string)
|
||||||
|
|
||||||
singleton.UserLock.RLock()
|
if clientSecret != singleton.Conf.AgentSecretKey {
|
||||||
userId, ok := singleton.AgentSecretToUserId[clientSecret]
|
|
||||||
if !ok && subtle.ConstantTimeCompare([]byte(clientSecret), []byte(singleton.Conf.AgentSecretKey)) != 1 {
|
|
||||||
singleton.UserLock.RUnlock()
|
|
||||||
model.BlockIP(singleton.DB, ip, model.WAFBlockReasonTypeAgentAuthFail)
|
model.BlockIP(singleton.DB, ip, model.WAFBlockReasonTypeAgentAuthFail)
|
||||||
return 0, status.Error(codes.Unauthenticated, "客户端认证失败")
|
return 0, status.Error(codes.Unauthenticated, "客户端认证失败")
|
||||||
}
|
}
|
||||||
singleton.UserLock.RUnlock()
|
|
||||||
|
|
||||||
model.ClearIP(singleton.DB, ip)
|
model.ClearIP(singleton.DB, ip)
|
||||||
|
|
||||||
@ -58,26 +53,21 @@ func (a *authHandler) Check(ctx context.Context) (uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
singleton.ServerLock.RLock()
|
singleton.ServerLock.RLock()
|
||||||
clientID, hasID := singleton.ServerUUIDToID[clientUUID]
|
defer singleton.ServerLock.RUnlock()
|
||||||
singleton.ServerLock.RUnlock()
|
|
||||||
|
|
||||||
|
clientID, hasID := singleton.ServerUUIDToID[clientUUID]
|
||||||
if !hasID {
|
if !hasID {
|
||||||
s := model.Server{UUID: clientUUID, Name: petname.Generate(2, "-"), Common: model.Common{
|
s := model.Server{UUID: clientUUID, Name: petname.Generate(2, "-")}
|
||||||
UserID: userId,
|
|
||||||
}}
|
|
||||||
if err := singleton.DB.Create(&s).Error; err != nil {
|
if err := singleton.DB.Create(&s).Error; err != nil {
|
||||||
return 0, status.Error(codes.Unauthenticated, err.Error())
|
return 0, status.Error(codes.Unauthenticated, err.Error())
|
||||||
}
|
}
|
||||||
s.Host = &model.Host{}
|
s.Host = &model.Host{}
|
||||||
s.State = &model.HostState{}
|
s.State = &model.HostState{}
|
||||||
s.GeoIP = &model.GeoIP{}
|
s.GeoIP = &model.GeoIP{}
|
||||||
|
// generate a random silly server name
|
||||||
singleton.ServerLock.Lock()
|
|
||||||
singleton.ServerList[s.ID] = &s
|
singleton.ServerList[s.ID] = &s
|
||||||
singleton.ServerUUIDToID[clientUUID] = s.ID
|
singleton.ServerUUIDToID[clientUUID] = s.ID
|
||||||
singleton.ServerLock.Unlock()
|
|
||||||
singleton.ReSortServer()
|
singleton.ReSortServer()
|
||||||
|
|
||||||
clientID = s.ID
|
clientID = s.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,10 +24,12 @@ var (
|
|||||||
|
|
||||||
func initDDNS() {
|
func initDDNS() {
|
||||||
DB.Find(&DDNSList)
|
DB.Find(&DDNSList)
|
||||||
|
DDNSCacheLock.Lock()
|
||||||
DDNSCache = make(map[uint64]*model.DDNSProfile)
|
DDNSCache = make(map[uint64]*model.DDNSProfile)
|
||||||
for i := 0; i < len(DDNSList); i++ {
|
for i := 0; i < len(DDNSList); i++ {
|
||||||
DDNSCache[DDNSList[i].ID] = DDNSList[i]
|
DDNSCache[DDNSList[i].ID] = DDNSList[i]
|
||||||
}
|
}
|
||||||
|
DDNSCacheLock.Unlock()
|
||||||
|
|
||||||
OnNameserverUpdate()
|
OnNameserverUpdate()
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ var (
|
|||||||
|
|
||||||
func initNAT() {
|
func initNAT() {
|
||||||
DB.Find(&NATList)
|
DB.Find(&NATList)
|
||||||
|
NATCacheRwLock.Lock()
|
||||||
|
defer NATCacheRwLock.Unlock()
|
||||||
NATCache = make(map[string]*model.NAT)
|
NATCache = make(map[string]*model.NAT)
|
||||||
for i := 0; i < len(NATList); i++ {
|
for i := 0; i < len(NATList); i++ {
|
||||||
NATCache[NATList[i].Domain] = NATList[i]
|
NATCache[NATList[i].Domain] = NATList[i]
|
||||||
|
@ -30,7 +30,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// InitNotification 初始化 GroupID <-> ID <-> Notification 的映射
|
// InitNotification 初始化 GroupID <-> ID <-> Notification 的映射
|
||||||
func initNotification() {
|
func InitNotification() {
|
||||||
NotificationList = make(map[uint64]map[uint64]*model.Notification)
|
NotificationList = make(map[uint64]map[uint64]*model.Notification)
|
||||||
NotificationIDToGroups = make(map[uint64]map[uint64]struct{})
|
NotificationIDToGroups = make(map[uint64]map[uint64]struct{})
|
||||||
NotificationGroup = make(map[uint64]string)
|
NotificationGroup = make(map[uint64]string)
|
||||||
@ -38,7 +38,9 @@ func initNotification() {
|
|||||||
|
|
||||||
// loadNotifications 从 DB 初始化通知方式相关参数
|
// loadNotifications 从 DB 初始化通知方式相关参数
|
||||||
func loadNotifications() {
|
func loadNotifications() {
|
||||||
initNotification()
|
InitNotification()
|
||||||
|
NotificationsLock.Lock()
|
||||||
|
|
||||||
groupNotifications := make(map[uint64][]uint64)
|
groupNotifications := make(map[uint64][]uint64)
|
||||||
var ngn []model.NotificationGroupNotification
|
var ngn []model.NotificationGroupNotification
|
||||||
if err := DB.Find(&ngn).Error; err != nil {
|
if err := DB.Find(&ngn).Error; err != nil {
|
||||||
@ -72,6 +74,8 @@ func loadNotifications() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotificationsLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateNotificationList() {
|
func UpdateNotificationList() {
|
||||||
|
@ -192,6 +192,13 @@ func (ss *ServiceSentinel) loadServiceHistory() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ss.serviceResponseDataStoreLock.Lock()
|
||||||
|
defer ss.serviceResponseDataStoreLock.Unlock()
|
||||||
|
ss.monthlyStatusLock.Lock()
|
||||||
|
defer ss.monthlyStatusLock.Unlock()
|
||||||
|
ss.ServicesLock.Lock()
|
||||||
|
defer ss.ServicesLock.Unlock()
|
||||||
|
|
||||||
for i := 0; i < len(services); i++ {
|
for i := 0; i < len(services); i++ {
|
||||||
task := *services[i]
|
task := *services[i]
|
||||||
// 通过cron定时将服务监控任务传递给任务调度管道
|
// 通过cron定时将服务监控任务传递给任务调度管道
|
||||||
|
@ -40,7 +40,6 @@ func InitTimezoneAndCache() {
|
|||||||
|
|
||||||
// LoadSingleton 加载子服务并执行
|
// LoadSingleton 加载子服务并执行
|
||||||
func LoadSingleton() {
|
func LoadSingleton() {
|
||||||
initUser() // 加载用户ID绑定表
|
|
||||||
initI18n() // 加载本地化服务
|
initI18n() // 加载本地化服务
|
||||||
loadNotifications() // 加载通知服务
|
loadNotifications() // 加载通知服务
|
||||||
loadServers() // 加载服务器列表
|
loadServers() // 加载服务器列表
|
||||||
@ -80,8 +79,8 @@ func InitDBFromPath(path string) {
|
|||||||
}
|
}
|
||||||
err = DB.AutoMigrate(model.Server{}, model.User{}, model.ServerGroup{}, model.NotificationGroup{},
|
err = DB.AutoMigrate(model.Server{}, model.User{}, model.ServerGroup{}, model.NotificationGroup{},
|
||||||
model.Notification{}, model.AlertRule{}, model.Service{}, model.NotificationGroupNotification{},
|
model.Notification{}, model.AlertRule{}, model.Service{}, model.NotificationGroupNotification{},
|
||||||
model.ServiceHistory{}, model.Cron{}, model.Transfer{}, model.ServerGroupServer{},
|
model.ServiceHistory{}, model.Cron{}, model.Transfer{}, model.ServerGroupServer{}, model.UserGroup{},
|
||||||
model.NAT{}, model.DDNSProfile{}, model.NotificationGroupNotification{},
|
model.UserGroupUser{}, model.NAT{}, model.DDNSProfile{}, model.NotificationGroupNotification{},
|
||||||
model.WAF{})
|
model.WAF{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
package singleton
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/nezhahq/nezha/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
UserIdToAgentSecret map[uint64]string
|
|
||||||
AgentSecretToUserId map[string]uint64
|
|
||||||
|
|
||||||
UserLock sync.RWMutex
|
|
||||||
)
|
|
||||||
|
|
||||||
func initUser() {
|
|
||||||
UserIdToAgentSecret = make(map[uint64]string)
|
|
||||||
AgentSecretToUserId = make(map[string]uint64)
|
|
||||||
|
|
||||||
var users []model.User
|
|
||||||
DB.Find(&users)
|
|
||||||
|
|
||||||
for _, u := range users {
|
|
||||||
UserIdToAgentSecret[u.ID] = u.AgentSecret
|
|
||||||
AgentSecretToUserId[u.AgentSecret] = u.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func OnUserUpdate(u *model.User) {
|
|
||||||
UserLock.Lock()
|
|
||||||
defer UserLock.Unlock()
|
|
||||||
|
|
||||||
if u == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
UserIdToAgentSecret[u.ID] = u.AgentSecret
|
|
||||||
AgentSecretToUserId[u.AgentSecret] = u.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func OnUserDelete(id []uint64) {
|
|
||||||
UserLock.Lock()
|
|
||||||
defer UserLock.Unlock()
|
|
||||||
|
|
||||||
if len(id) < 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, uid := range id {
|
|
||||||
secret := UserIdToAgentSecret[uid]
|
|
||||||
delete(AgentSecretToUserId, secret)
|
|
||||||
delete(UserIdToAgentSecret, uid)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user