Skip to content

Commit 480de46

Browse files
Fix Feishu webhook signature verification
1 parent d462ce1 commit 480de46

File tree

1 file changed

+67
-4
lines changed

1 file changed

+67
-4
lines changed

services/webhook/feishu.go

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
package webhook
55

66
import (
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

1824
type (
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+
181205
func 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

186249
func init() {

0 commit comments

Comments
 (0)