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
This commit is contained in:
rhclayto
2026-01-30 07:07:31 -07:00
committed by GitHub
parent a89b1bed85
commit cf029cef0c
9 changed files with 106 additions and 5 deletions

View File

@@ -1328,4 +1328,4 @@
"years": "Jahr|Jahre"
}
}
}
}

View File

@@ -1328,4 +1328,4 @@
"years": "Jahr|Jahre"
}
}
}
}

View File

@@ -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."
},

View File

@@ -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

View File

@@ -6,6 +6,8 @@ export default class WebhookModel extends AbstractModel<IWebhook> implements IWe
id = 0
projectId = 0
secret = ''
basicauthuser = ''
basicauthpassword = ''
targetUrl = ''
events = []
createdBy = null
@@ -16,7 +18,7 @@ export default class WebhookModel extends AbstractModel<IWebhook> implements IWe
constructor(data: Partial<IWebhook> = {}) {
super()
this.assignData(data)
this.createdBy = new UserModel(this.createdBy)
this.created = new Date(this.created)

View File

@@ -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<IProject>()
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() {
</BaseButton>
</p>
</div>
<BaseButton
class="mbe-2 has-text-primary"
@click="showBasicAuth = !showBasicAuth"
>
{{ $t('project.webhooks.basicauthlink') }}
</BaseButton>
<Expandable
:open="showBasicAuth"
class="content"
>
<div class="field">
<label
class="label"
for="basicauthuser"
>
{{ $t('project.webhooks.basicauthuser') }}
</label>
<div class="control">
<input
id="basicauthuser"
v-model="newWebhook.basicauthuser"
class="input"
>
</div>
</div>
<div class="field">
<label
class="label"
for="basicauthpassword"
>
{{ $t('project.webhooks.basicauthpassword') }}
</label>
<div class="control">
<input
id="basicauthpassword"
v-model="newWebhook.basicauthpassword"
class="input"
>
</div>
</div>
</Expandable>
<div class="field">
<label
class="label"

View File

@@ -0,0 +1,44 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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
},
})
}

View File

@@ -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")

View File

@@ -9782,4 +9782,4 @@
"in": "header"
}
}
}
}