From cf029cef0cf2920735e1533950f84c6dcee6d09e Mon Sep 17 00:00:00 2001
From: rhclayto
Date: Fri, 30 Jan 2026 07:07:31 -0700
Subject: [PATCH] feat: add option to send Basic Auth header with webhook
requests (#2137)
Resolves https://github.com/go-vikunja/vikunja/issues/2136
Docs PR: https://github.com/go-vikunja/website/pull/284
---
frontend/src/i18n/lang/de-DE.json | 2 +-
frontend/src/i18n/lang/de-swiss.json | 2 +-
frontend/src/i18n/lang/en.json | 3 ++
frontend/src/modelTypes/IWebhook.ts | 4 +-
frontend/src/models/webhook.ts | 4 +-
.../settings/ProjectSettingsWebhooks.vue | 43 ++++++++++++++++++
pkg/migration/20260123000717.go | 44 +++++++++++++++++++
pkg/models/webhooks.go | 7 +++
pkg/swagger/swagger.json | 2 +-
9 files changed, 106 insertions(+), 5 deletions(-)
create mode 100644 pkg/migration/20260123000717.go
diff --git a/frontend/src/i18n/lang/de-DE.json b/frontend/src/i18n/lang/de-DE.json
index 9e10ce624..57596074f 100644
--- a/frontend/src/i18n/lang/de-DE.json
+++ b/frontend/src/i18n/lang/de-DE.json
@@ -1328,4 +1328,4 @@
"years": "Jahr|Jahre"
}
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/i18n/lang/de-swiss.json b/frontend/src/i18n/lang/de-swiss.json
index f4fa3062d..7b71476dd 100644
--- a/frontend/src/i18n/lang/de-swiss.json
+++ b/frontend/src/i18n/lang/de-swiss.json
@@ -1328,4 +1328,4 @@
"years": "Jahr|Jahre"
}
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json
index 79ec86fcb..40d9a5de1 100644
--- a/frontend/src/i18n/lang/en.json
+++ b/frontend/src/i18n/lang/en.json
@@ -439,6 +439,9 @@
"deleteSuccess": "The webhook was successfully deleted.",
"create": "Create webhook",
"secret": "Secret",
+ "basicauthuser": "Basic Auth User",
+ "basicauthpassword": "Basic Auth Password",
+ "basicauthlink": "Use Basic Auth?",
"secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.",
"secretDocs": "Check out the docs for more details about how to use secrets."
},
diff --git a/frontend/src/modelTypes/IWebhook.ts b/frontend/src/modelTypes/IWebhook.ts
index e4e317cab..268d7260b 100644
--- a/frontend/src/modelTypes/IWebhook.ts
+++ b/frontend/src/modelTypes/IWebhook.ts
@@ -4,7 +4,9 @@ import type {IUser} from '@/modelTypes/IUser'
export interface IWebhook extends IAbstract {
id: number
projectId: number
- secret: string
+ secret: string
+ basicauthuser: string
+ basicauthpassword: string
targetUrl: string
events: string[]
createdBy: IUser
diff --git a/frontend/src/models/webhook.ts b/frontend/src/models/webhook.ts
index c0825b1b0..a92d4a6f5 100644
--- a/frontend/src/models/webhook.ts
+++ b/frontend/src/models/webhook.ts
@@ -6,6 +6,8 @@ export default class WebhookModel extends AbstractModel implements IWe
id = 0
projectId = 0
secret = ''
+ basicauthuser = ''
+ basicauthpassword = ''
targetUrl = ''
events = []
createdBy = null
@@ -16,7 +18,7 @@ export default class WebhookModel extends AbstractModel implements IWe
constructor(data: Partial = {}) {
super()
this.assignData(data)
-
+
this.createdBy = new UserModel(this.createdBy)
this.created = new Date(this.created)
diff --git a/frontend/src/views/project/settings/ProjectSettingsWebhooks.vue b/frontend/src/views/project/settings/ProjectSettingsWebhooks.vue
index f00c655a0..6e5def93a 100644
--- a/frontend/src/views/project/settings/ProjectSettingsWebhooks.vue
+++ b/frontend/src/views/project/settings/ProjectSettingsWebhooks.vue
@@ -19,6 +19,7 @@ import WebhookModel from '@/models/webhook'
import BaseButton from '@/components/base/BaseButton.vue'
import FancyCheckbox from '@/components/input/FancyCheckbox.vue'
import FormField from '@/components/input/FormField.vue'
+import Expandable from '@/components/base/Expandable.vue'
import {success} from '@/message'
import {isValidHttpUrl} from '@/helpers/isValidHttpUrl'
@@ -30,6 +31,7 @@ const project = ref()
useTitle(t('project.webhooks.title'))
const showNewForm = ref(false)
+const showBasicAuth = ref(false)
async function loadProject(projectId: number) {
const projectService = new ProjectService()
@@ -167,6 +169,47 @@ function validateSelectedEvents() {
+
+ {{ $t('project.webhooks.basicauthlink') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
.
+
+package migration
+
+import (
+ "src.techknowlogick.com/xormigrate"
+ "xorm.io/xorm"
+)
+
+type webhooks20260123000717 struct {
+ BasicAuthUser string `xorm:"null" json:"basicauthuser"`
+ BasicAuthPassword string `xorm:"null" json:"basicauthpassword"`
+}
+
+func (webhooks20260123000717) TableName() string {
+ return "webhooks"
+}
+
+func init() {
+ migrations = append(migrations, &xormigrate.Migration{
+ ID: "20260123000717",
+ Description: "Add basic auth to webhooks",
+ Migrate: func(tx *xorm.Engine) error {
+ return tx.Sync(webhooks20260123000717{})
+ },
+ Rollback: func(tx *xorm.Engine) error {
+ return nil
+ },
+ })
+}
diff --git a/pkg/models/webhooks.go b/pkg/models/webhooks.go
index de8d4a57f..18494860d 100644
--- a/pkg/models/webhooks.go
+++ b/pkg/models/webhooks.go
@@ -55,6 +55,9 @@ type Webhook struct {
ProjectID int64 `xorm:"bigint not null index" json:"project_id" param:"project"`
// If provided, webhook requests will be signed using HMAC. Check out the docs about how to use this: https://vikunja.io/docs/webhooks/#signing
Secret string `xorm:"null" json:"secret"`
+ // If provided, webhook requests will be sent with a Basic Auth header.
+ BasicAuthUser string `xorm:"null" json:"basic_auth_user"`
+ BasicAuthPassword string `xorm:"null" json:"basic_auth_password"`
// The user who initially created the webhook target.
CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"`
@@ -289,6 +292,10 @@ func (w *Webhook) sendWebhookPayload(p *WebhookPayload) (err error) {
req.Header.Add("X-Vikunja-Signature", signature)
}
+ if len(w.BasicAuthUser) > 0 && len(w.BasicAuthPassword) > 0 {
+ req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(w.BasicAuthUser+":"+w.BasicAuthPassword)))
+ }
+
req.Header.Add("User-Agent", "Vikunja/"+version.Version)
req.Header.Add("Content-Type", "application/json")
diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json
index ec364df4d..71ad08f16 100644
--- a/pkg/swagger/swagger.json
+++ b/pkg/swagger/swagger.json
@@ -9782,4 +9782,4 @@
"in": "header"
}
}
-}
\ No newline at end of file
+}