44package webhook
55
66import (
7+ "bytes"
78 "context"
9+ "crypto/hmac"
10+ "crypto/sha256"
11+ "encoding/base64"
12+ "encoding/json"
813 "fmt"
914 "net/http"
1015 "strings"
16+ "time"
1117
1218 webhook_model "code.gitea.io/gitea/models/webhook"
1319 "code.gitea.io/gitea/modules/git"
@@ -16,10 +22,12 @@ import (
1622)
1723
1824type (
19- // FeishuPayload represents
25+ // FeishuPayload represents the payload for Feishu webhook
2026 FeishuPayload struct {
21- MsgType string `json:"msg_type"` // text / post / image / share_chat / interactive / file /audio / media
22- Content struct {
27+ Timestamp int64 `json:"timestamp,omitempty"` // Unix timestamp for signature verification
28+ Sign string `json:"sign,omitempty"` // Signature for verification
29+ MsgType string `json:"msg_type"` // text / post / image / share_chat / interactive / file /audio / media
30+ Content struct {
2331 Text string `json:"text"`
2432 } `json:"content"`
2533 }
@@ -178,9 +186,64 @@ func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, er
178186 return newFeishuTextPayload (text ), nil
179187}
180188
189+ // GenSign generates a signature for Feishu webhook
190+ // https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
191+ func GenSign (secret string , timestamp int64 ) (string , error ) {
192+ // timestamp + key do sha256, then base64 encode
193+ stringToSign := fmt .Sprintf ("%v" , timestamp ) + "\n " + secret
194+
195+ h := hmac .New (sha256 .New , []byte (stringToSign ))
196+ _ , err := h .Write ([]byte {})
197+ if err != nil {
198+ return "" , err
199+ }
200+
201+ signature := base64 .StdEncoding .EncodeToString (h .Sum (nil ))
202+ return signature , nil
203+ }
204+
181205func newFeishuRequest (_ context.Context , w * webhook_model.Webhook , t * webhook_model.HookTask ) (* http.Request , []byte , error ) {
182206 var pc payloadConvertor [FeishuPayload ] = feishuConvertor {}
183- return newJSONRequest (pc , w , t , true )
207+
208+ // Get the payload first
209+ payload , err := newPayload (pc , []byte (t .PayloadContent ), t .EventType )
210+ if err != nil {
211+ return nil , nil , err
212+ }
213+
214+ // Add timestamp and signature if secret is provided
215+ if w .Secret != "" {
216+ timestamp := time .Now ().Unix ()
217+ payload .Timestamp = timestamp
218+
219+ // Generate signature
220+ sign , err := GenSign (w .Secret , timestamp )
221+ if err != nil {
222+ return nil , nil , err
223+ }
224+ payload .Sign = sign
225+ }
226+
227+ // Marshal the payload
228+ body , err := json .MarshalIndent (payload , "" , " " )
229+ if err != nil {
230+ return nil , nil , err
231+ }
232+
233+ // Create the request
234+ method := w .HTTPMethod
235+ if method == "" {
236+ method = http .MethodPost
237+ }
238+
239+ req , err := http .NewRequest (method , w .URL , bytes .NewReader (body ))
240+ if err != nil {
241+ return nil , nil , err
242+ }
243+ req .Header .Set ("Content-Type" , "application/json" )
244+
245+ // Add default headers
246+ return req , body , addDefaultHeaders (req , []byte (w .Secret ), w , t , body )
184247}
185248
186249func init () {
0 commit comments