Browse Source

feat: channel email is done

JustSong 2 years ago
parent
commit
e1d09aa58f

+ 1 - 0
channel/ding-talk.go

@@ -0,0 +1 @@
+package channel

+ 18 - 0
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)
+}

+ 1 - 0
channel/lark.go

@@ -0,0 +1 @@
+package channel

+ 39 - 0
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
+}

+ 1 - 0
channel/telegram.go

@@ -0,0 +1 @@
+package channel

+ 1 - 0
channel/wechat-corp-account.go

@@ -0,0 +1 @@
+package channel

+ 1 - 0
channel/wechat-test-account.go

@@ -0,0 +1 @@
+package channel

+ 3 - 2
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
 )

+ 3 - 1
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()

+ 109 - 0
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
+}

+ 1 - 0
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

+ 2 - 0
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=

+ 6 - 61
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()
-	}
-}

+ 1 - 0
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"`
 }
 

+ 9 - 3
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)
+	}
 }