FIX (webhook): Do not encypt webhook URL, keep encyption for headers only

This commit is contained in:
Rostislav Dugin
2026-01-14 09:09:00 +03:00
parent 5d851d73bd
commit 2c0a294027
3 changed files with 53 additions and 13 deletions

View File

@@ -21,6 +21,10 @@ type WebhookHeader struct {
Value string `json:"value"`
}
// Before both WebhookURL, BodyTemplate and HeadersJSON were considered
// as sensetive data and it was causing issues. Now only headers values
// considered as sensetive data, but we try to decrypt webhook URL and
// body template for backward combability
type WebhookNotifier struct {
NotifierID uuid.UUID `json:"notifierId" gorm:"primaryKey;column:notifier_id"`
WebhookURL string `json:"webhookUrl" gorm:"not null;column:webhook_url"`
@@ -58,6 +62,20 @@ func (t *WebhookNotifier) AfterFind(_ *gorm.DB) error {
}
}
encryptor := encryption.GetFieldEncryptor()
if t.WebhookURL != "" {
if decrypted, err := encryptor.Decrypt(t.NotifierID, t.WebhookURL); err == nil {
t.WebhookURL = decrypted
}
}
if t.BodyTemplate != nil && *t.BodyTemplate != "" {
if decrypted, err := encryptor.Decrypt(t.NotifierID, *t.BodyTemplate); err == nil {
t.BodyTemplate = &decrypted
}
}
return nil
}
@@ -79,22 +97,24 @@ func (t *WebhookNotifier) Send(
heading string,
message string,
) error {
webhookURL, err := encryptor.Decrypt(t.NotifierID, t.WebhookURL)
if err != nil {
return fmt.Errorf("failed to decrypt webhook URL: %w", err)
if err := t.decryptHeadersForSending(encryptor); err != nil {
return err
}
switch t.WebhookMethod {
case WebhookMethodGET:
return t.sendGET(webhookURL, heading, message, logger)
return t.sendGET(t.WebhookURL, heading, message, logger)
case WebhookMethodPOST:
return t.sendPOST(webhookURL, heading, message, logger)
return t.sendPOST(t.WebhookURL, heading, message, logger)
default:
return fmt.Errorf("unsupported webhook method: %s", t.WebhookMethod)
}
}
func (t *WebhookNotifier) HideSensitiveData() {
for i := range t.Headers {
t.Headers[i].Value = ""
}
}
func (t *WebhookNotifier) Update(incoming *WebhookNotifier) {
@@ -105,14 +125,15 @@ func (t *WebhookNotifier) Update(incoming *WebhookNotifier) {
}
func (t *WebhookNotifier) EncryptSensitiveData(encryptor encryption.FieldEncryptor) error {
if t.WebhookURL != "" {
encrypted, err := encryptor.Encrypt(t.NotifierID, t.WebhookURL)
for i := range t.Headers {
if t.Headers[i].Value != "" {
encrypted, err := encryptor.Encrypt(t.NotifierID, t.Headers[i].Value)
if err != nil {
return fmt.Errorf("failed to encrypt header value: %w", err)
}
if err != nil {
return fmt.Errorf("failed to encrypt webhook URL: %w", err)
t.Headers[i].Value = encrypted
}
t.WebhookURL = encrypted
}
return nil
@@ -241,3 +262,15 @@ func escapeJSONString(s string) string {
return string(b[1 : len(b)-1])
}
func (t *WebhookNotifier) decryptHeadersForSending(encryptor encryption.FieldEncryptor) error {
for i := range t.Headers {
if t.Headers[i].Value != "" {
if decrypted, err := encryptor.Decrypt(t.NotifierID, t.Headers[i].Value); err == nil {
t.Headers[i].Value = decrypted
}
}
}
return nil
}

View File

@@ -112,6 +112,12 @@ export function EditWebhookNotifierComponent({ notifier, setNotifier, setUnsaved
</span>
</div>
{notifier.id && (
<div className="mb-1 text-xs text-orange-700">
*Saved headers hidden for security reasons
</div>
)}
<div className="w-full max-w-[500px]">
{headers.map((header: WebhookHeader, index: number) => (
<div key={index} className="mb-1 flex items-center gap-2">
@@ -204,11 +210,12 @@ export function EditWebhookNotifierComponent({ notifier, setNotifier, setUnsaved
<div className="text-xs font-semibold text-gray-500 dark:text-gray-400">
Headers:
</div>
{headers
.filter((h) => h.key)
.map((h, i) => (
<div key={i} className="text-xs">
{h.key}: {h.value || '(empty)'}
{h.key}: {h.value || '(hidden)'}
</div>
))}
</div>

View File

@@ -29,7 +29,7 @@ export function ShowWebhookNotifierComponent({ notifier }: Props) {
.filter((h: WebhookHeader) => h.key)
.map((h: WebhookHeader, i: number) => (
<div key={i} className="text-gray-600">
<span className="font-medium">{h.key}:</span> {h.value || '(empty)'}
<span className="font-medium">{h.key}:</span> {h.value || '(hidden)'}
</div>
))}
</div>