Repository: go-gitea/gitea
File: services/webhook/feishu.go (Go)

Code before:
	return newFeishuTextPayload(text), nil
}

func (fc feishuConvertor) Status(p *api.CommitStatusPayload) (FeishuPayload, error) {
	text, _ := getStatusPayloadInfo(p, noneLinkFormatter, true)

	return newFeishuTextPayload(text), nil
}

func (feishuConvertor) WorkflowRun(p *api.WorkflowRunPayload) (FeishuPayload, error) {
	text, _ := getWorkflowRunPayloadInfo(p, noneLinkFormatter, true)

	return newFeishuTextPayload(text), nil
}

func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, error) {
	text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)

	return newFeishuTextPayload(text), nil
}

// GenSign generates a signature for Feishu webhook
// https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
func GenSign(secret string, timestamp int64) (string, error) {
	// timestamp + key do sha256, then base64 encode
	stringToSign := strconv.FormatInt(timestamp, 10) + "\n" + secret

	h := hmac.New(sha256.New, []byte(stringToSign))
	_, err := h.Write([]byte{})
	if err != nil {
		return "", err
	}

	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
	return signature, nil
}

func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
	var pc payloadConvertor[FeishuPayload] = feishuConvertor{}

	// Get the payload first
	payload, err := newPayload(pc, []byte(t.PayloadContent), t.EventType)
	if err != nil {
		return nil, nil, err
	}

	// Add timestamp and signature if secret is provided
	if w.Secret != "" {
		timestamp := time.Now().Unix()
		payload.Timestamp = timestamp


Review comment (security):
```suggestion
	stringToSign := fmt.Sprintf("%d\n%s", timestamp, secret)
```

Code after:
func (fc feishuConvertor) Status(p *api.CommitStatusPayload) (FeishuPayload, error) {
	text, _ := getStatusPayloadInfo(p, noneLinkFormatter, true)

	return newFeishuTextPayload(text), nil
}

func (feishuConvertor) WorkflowRun(p *api.WorkflowRunPayload) (FeishuPayload, error) {
	text, _ := getWorkflowRunPayloadInfo(p, noneLinkFormatter, true)

	return newFeishuTextPayload(text), nil
}

func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, error) {
	text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)

	return newFeishuTextPayload(text), nil
}

// feishuGenSign generates a signature for Feishu webhook
// https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
func feishuGenSign(secret string, timestamp int64) string {
	// key="{timestamp}\n{secret}", then hmac-sha256, then base64 encode
	stringToSign := fmt.Sprintf("%d\n%s", timestamp, secret)
	h := hmac.New(sha256.New, []byte(stringToSign))
	return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
	payload, err := newPayload(feishuConvertor{}, []byte(t.PayloadContent), t.EventType)
	if err != nil {
		return nil, nil, err
	}

	// Add timestamp and signature if secret is provided
	if w.Secret != "" {
		timestamp := time.Now().Unix()
		payload.Timestamp = timestamp
		payload.Sign = feishuGenSign(w.Secret, timestamp)
	}

	return prepareJSONRequest(payload, w, t, false /* no default headers */)
}

func init() {
	RegisterWebhookRequester(webhook_module.FEISHU, newFeishuRequest)
}