-
Notifications
You must be signed in to change notification settings - Fork 29
fix(webhook): validate signature and body for DELETE webhook message API #2913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
b2d7a6c
cbbd8be
a7f5042
098ff5f
3d74556
dd42c14
e63c352
b04d7c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -255,6 +255,17 @@ func (h *Handlers) DeleteWebhookMessage(c echo.Context) error { | |
| botUserID := w.GetBotUserID() | ||
| messageUserID := m.GetUserID() | ||
|
|
||
| // Webhookシークレット確認 | ||
| if len(w.GetSecret()) > 0 { | ||
| sig, _ := hex.DecodeString(c.Request().Header.Get(consts.HeaderSignature)) | ||
| if len(sig) == 0 { | ||
| return herror.BadRequest("missing X-TRAQ-Signature header") | ||
| } | ||
| if subtle.ConstantTimeCompare(hmac.SHA1([]byte{}, w.GetSecret()), sig) != 1 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return herror.BadRequest("X-TRAQ-Signature is wrong") | ||
| } | ||
| } | ||
|
|
||
| if botUserID == messageUserID { | ||
| if err := h.Repo.DeleteMessage(messageID); err != nil { | ||
| return herror.InternalServerError(err) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -551,21 +551,36 @@ func TestHandlers_DeleteWebhookMessage(t *testing.T) { | |
| wh := env.CreateWebhook(t, rand, user.GetID(), ch.ID) | ||
| wh2 := env.CreateWebhook(t, rand, user.GetID(), ch.ID) | ||
| message := env.CreateMessage(t, wh.GetBotUserID(), ch.ID, "test") | ||
| s := env.S(t, user.GetID()) | ||
|
|
||
| t.Run("not logged in", func(t *testing.T) { | ||
| calcHMACSHA1 := func(t *testing.T, message, secret string) string { | ||
| t.Helper() | ||
| mac := hmac.New(sha1.New, []byte(secret)) | ||
| _, _ = mac.Write([]byte(message)) | ||
| return hex.EncodeToString(mac.Sum(nil)) | ||
| } | ||
|
|
||
| t.Run("bad request (no signature)", func(t *testing.T) { | ||
| t.Parallel() | ||
| e := env.R(t) | ||
| e.DELETE(path, wh.GetID(), message.GetID()). | ||
| Expect(). | ||
| Status(http.StatusUnauthorized) | ||
| Status(http.StatusBadRequest) | ||
|
Comment on lines
559
to
+567
|
||
| }) | ||
|
|
||
| t.Run("bad request (bad signature)", func(t *testing.T) { | ||
| t.Parallel() | ||
| e := env.R(t) | ||
| e.DELETE(path, wh.GetID(), message.GetID()). | ||
| WithHeader("X-TRAQ-Signature", calcHMACSHA1(t, "test", wh.GetSecret())). | ||
| Expect(). | ||
| Status(http.StatusBadRequest) | ||
|
Comment on lines
+573
to
+576
|
||
| }) | ||
|
Comment on lines
+570
to
577
|
||
|
|
||
| t.Run("not found webhook", func(t *testing.T) { | ||
| t.Parallel() | ||
| e := env.R(t) | ||
| e.DELETE(path, uuid.Must(uuid.NewV4()), message.GetID()). | ||
| WithCookie(session.CookieName, s). | ||
| WithHeader("X-TRAQ-Signature", calcHMACSHA1(t, "", wh.GetSecret())). | ||
| Expect(). | ||
| Status(http.StatusNotFound) | ||
|
Comment on lines
582
to
585
|
||
| }) | ||
|
|
@@ -574,7 +589,7 @@ func TestHandlers_DeleteWebhookMessage(t *testing.T) { | |
| t.Parallel() | ||
| e := env.R(t) | ||
| e.DELETE(path, wh.GetID(), uuid.Must(uuid.NewV4())). | ||
| WithCookie(session.CookieName, s). | ||
| WithHeader("X-TRAQ-Signature", calcHMACSHA1(t, "", wh.GetSecret())). | ||
| Expect(). | ||
| Status(http.StatusNotFound) | ||
|
Comment on lines
591
to
594
|
||
| }) | ||
|
|
@@ -583,20 +598,21 @@ func TestHandlers_DeleteWebhookMessage(t *testing.T) { | |
| t.Parallel() | ||
| e := env.R(t) | ||
| e.DELETE(path, wh2.GetID(), message.GetID()). | ||
| WithCookie(session.CookieName, s). | ||
| WithHeader("X-TRAQ-Signature", calcHMACSHA1(t, "", wh2.GetSecret())). | ||
| Expect(). | ||
| Status(http.StatusForbidden) | ||
| }) | ||
|
|
||
| t.Run("success", func(t *testing.T) { | ||
| t.Parallel() | ||
| e := env.R(t) | ||
| e.DELETE(path, wh.GetID(), message.GetID()). | ||
| WithCookie(session.CookieName, s). | ||
| message2 := env.CreateMessage(t, wh.GetBotUserID(), ch.ID, "test") | ||
| e.DELETE(path, wh.GetID(), message2.GetID()). | ||
| WithHeader("X-TRAQ-Signature", calcHMACSHA1(t, "", wh.GetSecret())). | ||
| Expect(). | ||
|
Comment on lines
+610
to
612
|
||
| Status(http.StatusNoContent) | ||
|
|
||
| _, err := env.Repository.GetMessageByID(message.GetID()) | ||
| _, err := env.Repository.GetMessageByID(message2.GetID()) | ||
| assert.ErrorIs(t, err, repository.ErrNotFound) | ||
| }) | ||
| } | ||
|
Comment on lines
606
to
618
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The endpoint path has changed from "/webhooks/:webhookID/messages/:messageID" (under the authenticated API) to "/webhook/:webhookID/messages/:messageID" (under the no-auth API). This is a breaking API change that changes both the authentication mechanism (from cookie-based to signature-based) and the URL structure (webhooks → webhook, plural to singular). This breaking change should be carefully documented and versioned appropriately, as existing clients using the old authenticated endpoint will break.