Feat/webhook auth#3454
Conversation
ajhollid
left a comment
There was a problem hiding this comment.
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.
| const defaults = buildDefaults(data); | ||
| return { schema: notificationSchema, defaults }; | ||
| return { | ||
| schema: notificationSchema as unknown as typeof baseNotificationSchema, |
There was a problem hiding this comment.
This removes all type safety, please revert this
| authPassword: { type: String }, | ||
| authToken: { type: String }, |
There was a problem hiding this comment.
These two fields should stripped before being returned to the front end, credentials should not be sent from the API to an unknown consumer.
There was a problem hiding this comment.
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.
| accessToken: { type: String }, | ||
| authType: { | ||
| type: String, | ||
| enum: ["none", "basic", "bearer"] as WebhookAuthType[], |
There was a problem hiding this comment.
No casting please, use the correct type here
864f403 to
2496576
Compare
ajhollid
left a comment
There was a problem hiding this comment.
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!
| private sanitizeNotification = (notification: any) => { | ||
| if (!notification) return notification; | ||
| const sanitized = { ...notification }; | ||
| delete sanitized.authPassword; | ||
| delete sanitized.authToken; | ||
| delete sanitized.accessToken; | ||
| return sanitized; | ||
| }; | ||
|
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Done.
Thanks for the suggestion.
|
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. |
@ajhollid I have simplified the helper method. |
0feb663 to
012c068
Compare
|
@ajhollid Just pushed an update to clear out the branch rot, sync up with develop. What i changed:
Could you approve the CI workflow when you have a sec and give it another look? |
cf36d2f to
681feac
Compare
|
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
left a comment
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
Nit - type this as Notification instead of any.
There was a problem hiding this comment.
Done!
Typed it to Notification.
| import got from "got"; | ||
|
|
||
| export class WebhookProvider extends NotificationProvider { | ||
| private buildAuthHeaders(notification: Partial<Notification>): Record<string, string> { |
There was a problem hiding this comment.
There's already a webhookProvider.test.ts but nothing covers this. buildAuthHeaders is pure and security-sensitive, please add cases.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Ah I didn't realize that.
I've removed accessToken from the destructuring list so it's no longer stripped
… 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
…typing and test coverage Ref bluewave-labs#2369
681feac to
3319894
Compare
|
|
||
| private sanitizeNotification = (notification: Notification | null | undefined): Notification | null | undefined => { | ||
| if (!notification) return notification; | ||
| const { authPassword, authToken, ...sanitized } = notification; |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@harsh-aghara have your pushed the changes? I am not seeing any latest commits in the PR.
There was a problem hiding this comment.
@Br0wnHammer Apologies for the confusion. I had to force-push to clean up an accidental commit that included unrelated files.
9137344 to
3319894
Compare
…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
left a comment
There was a problem hiding this comment.
LGMT! Great work on this @harsh-aghara 🚀
|
Hi @harsh-aghara, Given the scope of the code changes, it would be best to open a new PR and incorporate the following feedback there:
Once these concerns are addressed in a new PR, we can take another look. |
Describe your changes
Add optional Basic Auth and Bearer Token authentication support for Webhook notifications.
Server:
WebhookAuthTypetype definitions (none|basic|bearer)authType,authUsername,authPassword,authTokenfieldsWebhookProvider.buildAuthHeaders()to injectAuthorizationheader into outgoing HTTP requestsClient:
superRefinefor conditional requirementsWrite 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.
<div>Add</div>, use):npm run formatin server and client directories, which automatically formats your code.None

Basic Auth

Bearer Token
