Skip to content

Feat/webhook auth#3454

Closed
harsh-aghara wants to merge 13 commits into
bluewave-labs:developfrom
harsh-aghara:feat/webhook-auth
Closed

Feat/webhook auth#3454
harsh-aghara wants to merge 13 commits into
bluewave-labs:developfrom
harsh-aghara:feat/webhook-auth

Conversation

@harsh-aghara

@harsh-aghara harsh-aghara commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Describe your changes

Add optional Basic Auth and Bearer Token authentication support for Webhook notifications.

Server:

  • Added WebhookAuthType type definitions (none | basic | bearer)
  • Extended Mongoose Notification schema with authType, authUsername, authPassword, authToken fields
  • Added Zod validation with conditional requirements (username/password for Basic, token for Bearer)
  • Updated WebhookProvider.buildAuthHeaders() to inject Authorization header into outgoing HTTP requests

Client:

  • Added auth type selector dropdown (None / Basic Auth / Bearer Token) in webhook notification form
  • Conditional form fields: username + password for Basic Auth, token for Bearer
  • Client-side Zod validation with superRefine for conditional requirements
  • Added all i18n translation keys, no hardcoded strings

Write your issue number after "Fixes "

Fixes #2369

Please ensure all items are checked off before requesting a review. "Checked off" means you need to add an "x" character between brackets so they turn into checkmarks.

  • (Do not skip this or your PR will be closed) I deployed the application locally.
  • (Do not skip this or your PR will be closed) I have performed a self-review and testing of my code.
  • I have included the issue # in the PR.
  • I have added i18n support to visible strings (instead of <div>Add</div>, use):
const { t } = useTranslation();
<div>{t('add')}</div>
  • I have not included any files that are not related to my pull request, including package-lock and package-json if dependencies have not changed
  • I didn't use any hardcoded values (otherwise it will not scale, and will make it difficult to maintain consistency across the application).
  • I made sure font sizes, color choices etc are all referenced from the theme. I don't have any hardcoded dimensions.
  • My PR is granular and targeted to one specific feature.
  • I ran npm run format in server and client directories, which automatically formats your code.
  • I took a screenshot or a video and attached to this PR if there is a UI change.

None
image

Basic Auth
image

Bearer Token
image

@harsh-aghara harsh-aghara deleted the feat/webhook-auth branch April 1, 2026 10:39
@harsh-aghara harsh-aghara restored the feat/webhook-auth branch April 1, 2026 10:44
@harsh-aghara harsh-aghara reopened this Apr 1, 2026

@ajhollid ajhollid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, it looks decent. There are some type casting issues to resolve, and credentials should be stripped so they are not returned from the API server.

Comment thread client/src/Hooks/useNotificationForm.ts Outdated
const defaults = buildDefaults(data);
return { schema: notificationSchema, defaults };
return {
schema: notificationSchema as unknown as typeof baseNotificationSchema,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes all type safety, please revert this

Comment on lines +46 to +47
authPassword: { type: String },
authToken: { type: String },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two fields should stripped before being returned to the front end, credentials should not be sent from the API to an unknown consumer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted the type casts in the client hook and server schema.
For security I've implemented sanitization at the Controller layer this ensures the WebhookProvider still has access to credentials for outgoing requests while stripping them from all API responses sent to the client.

Comment thread server/src/db/models/Notification.ts Outdated
accessToken: { type: String },
authType: {
type: String,
enum: ["none", "basic", "bearer"] as WebhookAuthType[],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No casting please, use the correct type here

@harsh-aghara harsh-aghara force-pushed the feat/webhook-auth branch 2 times, most recently from 864f403 to 2496576 Compare April 2, 2026 17:49
@harsh-aghara harsh-aghara requested a review from ajhollid April 2, 2026 18:25

@ajhollid ajhollid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the requested changes, looking pretty good! We can simplify that helper method to be more performant though, so let's do that. Should be good to go after that!

Comment on lines +54 to +62
private sanitizeNotification = (notification: any) => {
if (!notification) return notification;
const sanitized = { ...notification };
delete sanitized.authPassword;
delete sanitized.authToken;
delete sanitized.accessToken;
return sanitized;
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid using delete, we can just spread the properties and discard the unused ones

const {authPassword, authToken, accessToken, ...sanitized} = notification
return sanitized

That should be sufficient

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.
Thanks for the suggestion.

@harsh-aghara harsh-aghara requested a review from ajhollid April 3, 2026 19:17
@pauln17 pauln17 mentioned this pull request Apr 5, 2026
10 tasks
@pauln17

pauln17 commented Apr 5, 2026

Copy link
Copy Markdown

Hey @harsh-aghara, I am working on an issue with slightly similar tasks, do you have a discord or something I could reach you on if you wouldn't mind?

@harsh-aghara

Copy link
Copy Markdown
Contributor Author

Hey @harsh-aghara, I am working on an issue with slightly similar tasks, do you have a discord or something I could reach you on if you wouldn't mind?

Sure.
You can mail me!

@harsh-aghara

harsh-aghara commented Apr 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for making the requested changes, looking pretty good! We can simplify that helper method to be more performant though, so let's do that. Should be good to go after that!

@ajhollid I have simplified the helper method.
Please review it.

@harsh-aghara

Copy link
Copy Markdown
Contributor Author

@ajhollid Just pushed an update to clear out the branch rot, sync up with develop.

What i changed:

  • Rebase & feat: add Pushover notification channel #3530: Got everything rebased and made sure the new Pushover channel plays nicely with the shared validation schemas and UI.
  • Build fix: Caught a TS type mismatch in the client that broke the build after recent merges. Fixed it and verified with a local npm run build.
  • Review feedback: Swapped in the lighter helper method you suggested and added credential sanitization to the controller.
  • All unit and integration tests are passing locally again.

Could you approve the CI workflow when you have a sec and give it another look?

@harsh-aghara

Copy link
Copy Markdown
Contributor Author

Hey @ajhollid, I just completed another rebase to resolve the fresh conflicts from the recently merged Twilio(#3534) and Pushover(#3530) PRs.

To make your review easier, here is what I've ensured in this update:

Conflict Resolution: Integrated the new Twilio/Pushover fields while keeping my Webhook Auth logic strictly isolated to avoid regressions.

Maintainer Feedback: Applied the performance-optimized sanitization (object destructuring) we discussed and ensured all credentials (including Webhook tokens) are stripped from API responses.

Validation: Verified both Client and Server builds locally, and all 995 tests are passing.

Since we were good to go after the last review, I'd love to get this merged before any more notification channels cause new conflicts. Let me know if there's anything else you need!

@Br0wnHammer Br0wnHammer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, a nice and clean implementation. The PR was easy to follow through. However, I have some questions and nitpicks. Please look at them.

}
};

private sanitizeNotification = (notification: any) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - type this as Notification instead of any.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!
Typed it to Notification.

import got from "got";

export class WebhookProvider extends NotificationProvider {
private buildAuthHeaders(notification: Partial<Notification>): Record<string, string> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's already a webhookProvider.test.ts but nothing covers this. buildAuthHeaders is pure and security-sensitive, please add cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I have added test cases covering all the auth types and edge cases for missing credentials and empty strings.


private sanitizeNotification = (notification: any) => {
if (!notification) return notification;
const { authPassword, authToken, accessToken, ...sanitized } = notification;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strips accessToken from every notification, but that's the live credential for Telegram/Twilio/Pushover/Matrix too, won't this leave their token fields empty and block re-saving on edit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I didn't realize that.
I've removed accessToken from the destructuring list so it's no longer stripped

@Br0wnHammer Br0wnHammer removed the request for review from Owaiseimdad May 25, 2026 16:40
@Br0wnHammer Br0wnHammer added the enhancement New feature or request label May 25, 2026
… authentication

Add WebhookAuthType union type ('none' | 'basic' | 'bearer') and
WebhookAuthTypes const array to both client and server type modules.
Extend the Notification interface with optional authType, authUsername,
authPassword, and authToken fields.

Ref bluewave-labs#2369
…epository

Add authType (enum: none/basic/bearer, default: none), authUsername,
authPassword, and authToken fields to the Notification Mongoose schema.
Map the new fields in MongoNotificationsRepository document-to-domain
conversion.

Ref bluewave-labs#2369
Add authType, authUsername, authPassword, and authToken to the webhook
notification Zod validation schema. Use superRefine to conditionally
require username/password for Basic Auth and token for Bearer Auth.

Ref bluewave-labs#2369
Add buildAuthHeaders method to WebhookProvider that constructs
Basic (base64-encoded username:password) or Bearer token Authorization
headers based on the notification's authType. Apply to both sendMessage
and sendTestMessage flows.

Ref bluewave-labs#2369
Extend webhookSchema with authType, authUsername, authPassword, and
authToken fields. Extract baseNotificationSchema and add superRefine
to conditionally validate credentials based on selected auth type.

Ref bluewave-labs#2369
…orm fields

Add Authentication ConfigBox to webhook notification create/edit page
with auth type selector (None/Basic/Bearer), conditional username/password
fields for Basic Auth, and token field for Bearer Auth. Update
useNotificationForm hook with auth field defaults. Add i18n translation
keys for all new webhook auth UI strings.

Ref bluewave-labs#2369
…fety

- Strip authPassword, authToken, and accessToken from notification API responses in the controller to ensure security at the API boundary.
- Remove redundant type casting from authType enum in the Mongoose schema.
- Revert unnecessary type casting in useNotificationForm hook to maintain proper type safety.

Ref bluewave-labs#2369
…place the delete operator with object destructuring in sanitizeNotification to improve performance and avoid object de-optimization. Ref bluewave-labs#2369
@Br0wnHammer Br0wnHammer self-requested a review May 25, 2026 18:16

private sanitizeNotification = (notification: Notification | null | undefined): Notification | null | undefined => {
if (!notification) return notification;
const { authPassword, authToken, ...sanitized } = notification;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since sanitizeNotification strips authPassword/authToken from responses, the edit form loads them empty, but superRefine still requires them, so editing a Basic/Bearer webhook seems to force re-entering credentials every time (and an empty submit would $set over the stored secret).

@harsh-aghara harsh-aghara May 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was focused on the sanitization that's why I didn't think through the "Edit" UX. It’s definitely a pain if a user has to re-enter their token just to rename a webhook.
I can fix this by updating the Zod schema to allow empty strings on update and then tweak the service logic so it only $sets those fields if the user actually types something new. That way, leaving them blank would just keep the existing secret in the DB. Does that sound like the right path to you?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ajhollid what are your thoughts on this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harsh-aghara, I was going through your proposed solution and I agree, leave blank to keep the secret is the right call. Just heads up that create and edit share the same validation, so loosening it would also let someone create a Basic auth webhook with no password. Probably want a separate lenient edit schema, and when authType changes, clear the old secret fields instead of skipping them so they don't linger in the DB.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Br0wnHammer Thanks for the heads up.

I've just pushed a fix for this. I split out a lenient edit schema on both the client and server so users can safely leave secrets blank to keep their existing ones.

I also updated the service layer to explicitly $unset stale credentials if the authType (or the main channel type) changes, so we never leave lingering data in the DB.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harsh-aghara have your pushed the changes? I am not seeing any latest commits in the PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Br0wnHammer Apologies for the confusion. I had to force-push to clean up an accidental commit that included unrelated files.

…or webhook auth

- Split create/edit validation schemas (client + server) so secrets
  are optional on update, strict on create
- Service layer skips empty secret fields on edit to preserve DB values
- Explicit $unset of stale credentials when authType or channel changes
- Repository supports $unset alongside $set for clean field removal
- Edit UX: 'Leave blank to keep existing' placeholder on secret fields
- Test button validates against strict schema with inline error feedback
- Added i18n key for keepExisting placeholder

Ref bluewave-labs#2369

@Br0wnHammer Br0wnHammer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGMT! Great work on this @harsh-aghara 🚀

@Br0wnHammer

Copy link
Copy Markdown
Member

Hi @harsh-aghara,
We discussed your PR with the team, and we've decided that we can't merge it in its current form due to a number of related changes that still need to be addressed.

Given the scope of the code changes, it would be best to open a new PR and incorporate the following feedback there:

  • The edit schema is largely a duplicate of the create schema (roughly 90% overlap), so we'd like to avoid maintaining two nearly identical implementations.
  • There are a few type casts that undermine type safety and should be refactored.
  • It looks like notification testing may no longer work as expected, since some fields are being stripped before processing.

Once these concerns are addressed in a new PR, we can take another look.
Thankyou again for all your hardwork! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add bearer/basic auth option to webhooks

4 participants