瀏覽代碼

feat: support channel level auth

JustSong 11 月之前
父節點
當前提交
08a8688aa7
共有 5 個文件被更改,包括 68 次插入35 次删除
  1. 1 0
      README.md
  2. 2 0
      controller/channel.go
  3. 31 18
      controller/message.go
  4. 14 13
      model/channel.go
  5. 20 4
      web/src/components/PushSetting.js

+ 1 - 0
README.md

@@ -214,6 +214,7 @@ proxy_send_timeout 300s;
       15. `tencent_alarm`:通过腾讯云监控告警进行推送,仅支持 `description` 字段。
       16. `none`:仅保存到数据库,不做推送。
    5. `token`:如果你在后台设置了推送 token,则此项必填。另外可以通过设置 HTTP `Authorization` 头部设置此项。
+      * 注意令牌有两种,一种是全局鉴权令牌,一种是通道维度的令牌,前者可以鉴权任何通道,后者只能鉴权指定通道。
    6. `url`:选填,如果不填则系统自动为消息生成 URL,其内容为消息详情。
    7. `to`:选填,推送给指定用户,如果不填则默认推送给自己,受限于具体的消息推送方式,有些推送方式不支持此项。
       1. `@all`:推送给所有用户。

+ 2 - 0
controller/channel.go

@@ -135,6 +135,7 @@ func AddChannel(c *gin.Context) {
 		URL:         channel_.URL,
 		Other:       channel_.Other,
 		CreatedTime: common.GetTimestamp(),
+		Token:       channel_.Token,
 	}
 	err = cleanChannel.Insert()
 	if err != nil {
@@ -206,6 +207,7 @@ func UpdateChannel(c *gin.Context) {
 		cleanChannel.AccountId = channel_.AccountId
 		cleanChannel.URL = channel_.URL
 		cleanChannel.Other = channel_.Other
+		cleanChannel.Token = channel_.Token
 	}
 	err = cleanChannel.Update()
 	if err != nil {

+ 31 - 18
controller/message.go

@@ -10,6 +10,7 @@ import (
 	"message-pusher/model"
 	"net/http"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -46,7 +47,7 @@ func GetPushMessage(c *gin.Context) {
 
 func PostPushMessage(c *gin.Context) {
 	var message model.Message
-	if c.Request.Header.Get("Content-Type") == "application/json" {
+	if strings.Contains(strings.ToLower(c.Request.Header.Get("Content-Type")), "application/json") {
 		// Looks like the user is using JSON
 		message = model.Message{}
 		err := json.NewDecoder(c.Request.Body).Decode(&message)
@@ -110,26 +111,24 @@ func pushMessageHelper(c *gin.Context, message *model.Message) {
 		})
 		return
 	}
-	if user.Token != "" && user.Token != " " {
-		if message.Token == "" {
-			message.Token = c.Request.Header.Get("Authorization")
-			if message.Token == "" {
-				c.JSON(http.StatusOK, gin.H{
-					"success": false,
-					"message": "token 为空",
-				})
-				return
-			}
+	if message.Token == "" {
+		message.Token = strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer ")
+	}
+	processMessage(c, message, &user)
+}
+
+func authMessage(messageToken string, userToken string, channelToken *string) bool {
+	if userToken != "" {
+		if messageToken == userToken {
+			return true
 		}
-		if user.Token != message.Token {
-			c.JSON(http.StatusOK, gin.H{
-				"success": false,
-				"message": "无效的 token",
-			})
-			return
+	}
+	if channelToken != nil && *channelToken != "" {
+		if messageToken != *channelToken {
+			return false
 		}
 	}
-	processMessage(c, message, &user)
+	return true
 }
 
 func processMessage(c *gin.Context, message *model.Message, user *model.User) {
@@ -150,6 +149,20 @@ func processMessage(c *gin.Context, message *model.Message, user *model.User) {
 		})
 		return
 	}
+	if !authMessage(message.Token, user.Token, channel_.Token) {
+		if message.Token == "" {
+			c.JSON(http.StatusUnauthorized, gin.H{
+				"success": false,
+				"message": "通道维度或用户维度设置了鉴权令牌,需要提供鉴权令牌",
+			})
+			return
+		}
+		c.JSON(http.StatusUnauthorized, gin.H{
+			"success": false,
+			"message": "无效的 token",
+		})
+		return
+	}
 	err = saveAndSendMessage(user, message, channel_)
 	if err != nil {
 		c.JSON(http.StatusOK, gin.H{

+ 14 - 13
model/channel.go

@@ -25,18 +25,19 @@ const (
 )
 
 type Channel struct {
-	Id          int    `json:"id"`
-	Type        string `json:"type" gorm:"type:varchar(32)"`
-	UserId      int    `json:"user_id" gorm:"uniqueIndex:name_user_id;index"`
-	Name        string `json:"name" gorm:"type:varchar(32);uniqueIndex:name_user_id"`
-	Description string `json:"description"`
-	Status      int    `json:"status" gorm:"default:1"` // enabled, disabled
-	Secret      string `json:"secret" gorm:"index"`
-	AppId       string `json:"app_id"`
-	AccountId   string `json:"account_id"`
-	URL         string `json:"url" gorm:"column:url"`
-	Other       string `json:"other"`
-	CreatedTime int64  `json:"created_time" gorm:"bigint"`
+	Id          int     `json:"id"`
+	Type        string  `json:"type" gorm:"type:varchar(32)"`
+	UserId      int     `json:"user_id" gorm:"uniqueIndex:name_user_id;index"`
+	Name        string  `json:"name" gorm:"type:varchar(32);uniqueIndex:name_user_id"`
+	Description string  `json:"description"`
+	Status      int     `json:"status" gorm:"default:1"` // enabled, disabled
+	Secret      string  `json:"secret" gorm:"index"`
+	AppId       string  `json:"app_id"`
+	AccountId   string  `json:"account_id"`
+	URL         string  `json:"url" gorm:"column:url"`
+	Other       string  `json:"other"`
+	CreatedTime int64   `json:"created_time" gorm:"bigint"`
+	Token       *string `json:"token" gorm:"token"`
 }
 
 type BriefChannel struct {
@@ -120,7 +121,7 @@ func (channel *Channel) UpdateStatus(status int) error {
 // Update Make sure your token's fields is completed, because this will update non-zero values
 func (channel *Channel) Update() error {
 	var err error
-	err = DB.Model(channel).Select("type", "name", "description", "secret", "app_id", "account_id", "url", "other", "status").Updates(channel).Error
+	err = DB.Model(channel).Select("type", "name", "description", "secret", "app_id", "account_id", "url", "other", "status", "token").Updates(channel).Error
 	return err
 }
 

+ 20 - 4
web/src/components/PushSetting.js

@@ -1,6 +1,12 @@
 import React, { useEffect, useState } from 'react';
 import { Button, Form, Grid, Header, Message } from 'semantic-ui-react';
-import { API, showError, showSuccess, testChannel } from '../helpers';
+import {
+  API,
+  generateToken,
+  showError,
+  showSuccess,
+  testChannel,
+} from '../helpers';
 import { loadUser, loadUserChannels } from '../helpers/loader';
 
 const PushSetting = () => {
@@ -70,15 +76,25 @@ const PushSetting = () => {
               options={channels}
               value={user.channel}
               onChange={handleInputChange}
-              width={6}
+              width={5}
             />
             <Form.Input
               label='全局鉴权令牌'
-              placeholder='未设置渠道维度令牌时,会检查该令牌,如果该令牌也没有设置,则不检查'
+              placeholder='优先级高于通道维度令牌,但为了安全期间建议使用通道维度的令牌'
               value={user.token}
               name='token'
               onChange={handleInputChange}
-              width={10}
+              width={9}
+              action={{
+                content: '随机生成',
+                onClick: () => {
+                  console.log('generate token');
+                  setUser((inputs) => ({
+                    ...inputs,
+                    token: generateToken(16),
+                  }));
+                },
+              }}
             />
           </Form.Group>
           <Button onClick={() => submit('general')} loading={loading}>