From e1d09aa58f55a59ec1aad6648d5afa27ce4dd8be Mon Sep 17 00:00:00 2001 From: JustSong Date: Fri, 11 Nov 2022 17:24:03 +0800 Subject: [PATCH] feat: channel email is done --- channel/ding-talk.go | 1 + channel/email.go | 18 ++++++ channel/lark.go | 1 + channel/main.go | 39 ++++++++++++ channel/telegram.go | 1 + channel/wechat-corp-account.go | 1 + channel/wechat-test-account.go | 1 + common/constants.go | 5 +- common/email.go | 4 +- controller/message.go | 109 +++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + middleware/auth.go | 67 ++------------------ model/user.go | 1 + router/api-router.go | 12 +++- 15 files changed, 196 insertions(+), 67 deletions(-) create mode 100644 channel/ding-talk.go create mode 100644 channel/email.go create mode 100644 channel/lark.go create mode 100644 channel/main.go create mode 100644 channel/telegram.go create mode 100644 channel/wechat-corp-account.go create mode 100644 channel/wechat-test-account.go create mode 100644 controller/message.go diff --git a/channel/ding-talk.go b/channel/ding-talk.go new file mode 100644 index 0000000..9c86d4a --- /dev/null +++ b/channel/ding-talk.go @@ -0,0 +1 @@ +package channel diff --git a/channel/email.go b/channel/email.go new file mode 100644 index 0000000..21f375d --- /dev/null +++ b/channel/email.go @@ -0,0 +1,18 @@ +package channel + +import ( + "errors" + "message-pusher/common" + "message-pusher/model" +) + +func SendEmailMessage(message *Message, user *model.User) error { + if user.Email == "" { + return errors.New("未配置邮箱地址") + } + subject := message.Description + if subject == "" { + subject = message.Title + } + return common.SendEmail(subject, user.Email, message.Content) +} diff --git a/channel/lark.go b/channel/lark.go new file mode 100644 index 0000000..9c86d4a --- /dev/null +++ b/channel/lark.go @@ -0,0 +1 @@ +package channel diff --git a/channel/main.go b/channel/main.go new file mode 100644 index 0000000..d3d59d1 --- /dev/null +++ b/channel/main.go @@ -0,0 +1,39 @@ +package channel + +import ( + "errors" + "message-pusher/model" +) + +const ( + TypeEmail = "email" + TypeWeChatTestAccount = "test" + TypeWeChatCorpAccount = "corp" + TypeLark = "lark" + TypeDingTalk = "ding" + TypeTelegram = "telegram" +) + +type Message struct { + Title string `json:"title"` + Description string `json:"description"` + Content string `json:"content"` + URL string `json:"url"` + Channel string `json:"channel"` + Token string `json:"token"` +} + +func (message *Message) Send(user *model.User) error { + switch message.Channel { + case TypeEmail: + return SendEmailMessage(message, user) + case TypeWeChatTestAccount: + case TypeWeChatCorpAccount: + case TypeLark: + case TypeDingTalk: + case TypeTelegram: + default: + return errors.New("不支持的消息通道:" + message.Channel) + } + return nil +} diff --git a/channel/telegram.go b/channel/telegram.go new file mode 100644 index 0000000..9c86d4a --- /dev/null +++ b/channel/telegram.go @@ -0,0 +1 @@ +package channel diff --git a/channel/wechat-corp-account.go b/channel/wechat-corp-account.go new file mode 100644 index 0000000..9c86d4a --- /dev/null +++ b/channel/wechat-corp-account.go @@ -0,0 +1 @@ +package channel diff --git a/channel/wechat-test-account.go b/channel/wechat-test-account.go new file mode 100644 index 0000000..9c86d4a --- /dev/null +++ b/channel/wechat-test-account.go @@ -0,0 +1 @@ +package channel diff --git a/common/constants.go b/common/constants.go index d0941d7..4520b91 100644 --- a/common/constants.go +++ b/common/constants.go @@ -75,6 +75,7 @@ var ( var RateLimitKeyExpirationDuration = 20 * time.Minute const ( - UserStatusEnabled = 1 // don't use 0, 0 is the default value! - UserStatusDisabled = 2 // also don't use 0 + UserStatusNonExisted = 0 + UserStatusEnabled = 1 // don't use 0, 0 is the default value! + UserStatusDisabled = 2 // also don't use 0 ) diff --git a/common/email.go b/common/email.go index 8e293be..4b65eb9 100644 --- a/common/email.go +++ b/common/email.go @@ -1,6 +1,8 @@ package common -import "gopkg.in/gomail.v2" +import ( + "gopkg.in/gomail.v2" +) func SendEmail(subject string, receiver string, content string) error { m := gomail.NewMessage() diff --git a/controller/message.go b/controller/message.go new file mode 100644 index 0000000..d8bb3ca --- /dev/null +++ b/controller/message.go @@ -0,0 +1,109 @@ +package controller + +import ( + "bytes" + "encoding/json" + "github.com/gin-gonic/gin" + "github.com/yuin/goldmark" + "message-pusher/channel" + "message-pusher/common" + "message-pusher/model" + "net/http" +) + +func GetPushMessage(c *gin.Context) { + message := channel.Message{ + Title: c.Query("title"), + Description: c.Query("description"), + Content: c.Query("content"), + URL: c.Query("url"), + Channel: c.Query("channel"), + Token: c.Query("token"), + } + if message.Description == "" { + // Keep compatible with ServerChan + message.Description = c.Query("desp") + } + pushMessageHelper(c, &message) +} + +func PostPushMessage(c *gin.Context) { + message := channel.Message{} + err := json.NewDecoder(c.Request.Body).Decode(&message) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "无法解析请求体,请检查其是否为合法 JSON", + }) + return + } + pushMessageHelper(c, &message) +} + +func pushMessageHelper(c *gin.Context, message *channel.Message) { + user := model.User{Username: c.Param("username")} + user.FillUserByUsername() + if user.Status == common.UserStatusNonExisted { + c.JSON(http.StatusForbidden, gin.H{ + "success": false, + "message": "用户不存在", + }) + return + } + if user.Status == common.UserStatusDisabled { + c.JSON(http.StatusForbidden, gin.H{ + "success": false, + "message": "用户已被封禁", + }) + return + } + if user.Token != "" { + if message.Token == "" { + message.Token = c.Request.Header.Get("Authorization") + } + if user.Token != message.Token { + c.JSON(http.StatusForbidden, gin.H{ + "success": false, + "message": "无效的 token", + }) + return + } + } + if message.Title == "" { + message.Title = common.SystemName + } + if message.Content != "" { + var buf bytes.Buffer + err := goldmark.Convert([]byte(message.Content), &buf) + if err != nil { + common.SysLog(err.Error()) + } else { + message.Content = buf.String() + } + } else { + if message.Description != "" { + message.Content = message.Description + } else { + message.Content = "无内容" + } + } + if message.Channel == "" { + message.Channel = user.Channel + if message.Channel == "" { + message.Channel = channel.TypeEmail + } + } + err := message.Send(&user) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "ok", + }) + return +} diff --git a/go.mod b/go.mod index 265e667..b2e6552 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/stretchr/testify v1.8.0 // indirect github.com/ugorji/go/codec v1.2.7 // indirect + github.com/yuin/goldmark v1.5.2 // indirect golang.org/x/net v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.4.0 // indirect diff --git a/go.sum b/go.sum index 699d7f1..74862fc 100644 --- a/go.sum +++ b/go.sum @@ -94,6 +94,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= diff --git a/middleware/auth.go b/middleware/auth.go index a5278d6..e938a2c 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -4,7 +4,6 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "message-pusher/common" - "message-pusher/model" "net/http" ) @@ -14,34 +13,13 @@ func authHelper(c *gin.Context, minRole int) { role := session.Get("role") id := session.Get("id") status := session.Get("status") - authByToken := false if username == nil { - // Check token - token := c.Request.Header.Get("Authorization") - if token == "" { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无权进行此操作,未登录或 token 无效", - }) - c.Abort() - return - } - user := model.ValidateUserToken(token) - if user != nil && user.Username != "" { - // Token is valid - username = user.Username - role = user.Role - id = user.Id - status = user.Status - } else { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无权进行此操作,未登录或 token 无效", - }) - c.Abort() - return - } - authByToken = true + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "无权进行此操作,未登录或 token 无效", + }) + c.Abort() + return } if status.(int) == common.UserStatusDisabled { c.JSON(http.StatusOK, gin.H{ @@ -62,7 +40,6 @@ func authHelper(c *gin.Context, minRole int) { c.Set("username", username) c.Set("role", role) c.Set("id", id) - c.Set("authByToken", authByToken) c.Next() } @@ -83,35 +60,3 @@ func RootAuth() func(c *gin.Context) { authHelper(c, common.RoleRootUser) } } - -// NoTokenAuth You should always use this after normal auth middlewares. -func NoTokenAuth() func(c *gin.Context) { - return func(c *gin.Context) { - authByToken := c.GetBool("authByToken") - if authByToken { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "本接口不支持使用 token 进行验证", - }) - c.Abort() - return - } - c.Next() - } -} - -// TokenOnlyAuth You should always use this after normal auth middlewares. -func TokenOnlyAuth() func(c *gin.Context) { - return func(c *gin.Context) { - authByToken := c.GetBool("authByToken") - if !authByToken { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "本接口仅支持使用 token 进行验证", - }) - c.Abort() - return - } - c.Next() - } -} diff --git a/model/user.go b/model/user.go index d632da0..b7df409 100644 --- a/model/user.go +++ b/model/user.go @@ -17,6 +17,7 @@ type User struct { Email string `json:"email" gorm:"index" validate:"max=50"` GitHubId string `json:"github_id" gorm:"column:github_id;index"` WeChatId string `json:"wechat_id" gorm:"column:wechat_id;index"` + Channel string `json:"channel"` VerificationCode string `json:"verification_code" gorm:"-:all"` } diff --git a/router/api-router.go b/router/api-router.go index 17048cb..774159d 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -28,7 +28,7 @@ func SetApiRouter(router *gin.Engine) { userRoute.GET("/logout", controller.Logout) selfRoute := userRoute.Group("/") - selfRoute.Use(middleware.UserAuth(), middleware.NoTokenAuth()) + selfRoute.Use(middleware.UserAuth()) { selfRoute.GET("/self", controller.GetSelf) selfRoute.PUT("/self", controller.UpdateSelf) @@ -37,7 +37,7 @@ func SetApiRouter(router *gin.Engine) { } adminRoute := userRoute.Group("/") - adminRoute.Use(middleware.AdminAuth(), middleware.NoTokenAuth()) + adminRoute.Use(middleware.AdminAuth()) { adminRoute.GET("/", controller.GetAllUsers) adminRoute.GET("/search", controller.SearchUsers) @@ -49,7 +49,7 @@ func SetApiRouter(router *gin.Engine) { } } optionRoute := apiRouter.Group("/option") - optionRoute.Use(middleware.RootAuth(), middleware.NoTokenAuth()) + optionRoute.Use(middleware.RootAuth()) { optionRoute.GET("/", controller.GetOptions) optionRoute.PUT("/", controller.UpdateOption) @@ -61,4 +61,10 @@ func SetApiRouter(router *gin.Engine) { fileRoute.DELETE("/:id", middleware.UserAuth(), controller.DeleteFile) } } + pushRouter := router.Group("/push") + pushRouter.Use(middleware.GlobalAPIRateLimit()) + { + pushRouter.GET("/:username", controller.GetPushMessage) + pushRouter.POST("/:username", controller.PostPushMessage) + } }