4
4
package webhook
5
5
6
6
import (
7
+ "bytes"
7
8
"context"
9
+ "crypto/hmac"
10
+ "crypto/sha256"
11
+ "encoding/base64"
12
+ "encoding/json"
8
13
"fmt"
9
14
"net/http"
10
15
"strings"
16
+ "time"
11
17
12
18
webhook_model "code.gitea.io/gitea/models/webhook"
13
19
"code.gitea.io/gitea/modules/git"
@@ -16,10 +22,12 @@ import (
16
22
)
17
23
18
24
type (
19
- // FeishuPayload represents
25
+ // FeishuPayload represents the payload for Feishu webhook
20
26
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 {
23
31
Text string `json:"text"`
24
32
} `json:"content"`
25
33
}
@@ -178,9 +186,64 @@ func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, er
178
186
return newFeishuTextPayload (text ), nil
179
187
}
180
188
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
+
181
205
func newFeishuRequest (_ context.Context , w * webhook_model.Webhook , t * webhook_model.HookTask ) (* http.Request , []byte , error ) {
182
206
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 )
184
247
}
185
248
186
249
func init () {
0 commit comments