-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Http - verify hmac signature #14717
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Http - verify hmac signature #14717
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,68 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import crypto from "crypto"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: "Verify HMAC Signature", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| version: "0.0.1", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key: "http-verify-hmac-signature", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: "Validate HMAC signature for incoming HTTP webhook requests. Make sure to configure the HTTP trigger to \"Return a custom response from your workflow\".", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "action", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| props: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| http: "$.interface.http", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secret: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "string", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label: "Secret", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: "Your secret key used for validation", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secret: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| signature: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "string", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label: "Signature", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: "The HMAC signature received from the incoming webhook, typically provided in a specific HTTP header", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bodyRaw: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "string", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label: "Raw Body", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: "The raw request body received from the webhook caller, provided as a string without any parsing or modifications", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| customResponse: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "boolean", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label: "Return Error to Webhook Caller", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: "If `True`, returns a `401: Invalid credentials` error in the case of invalid authorization. **Make sure to configure the HTTP trigger to \"Return a custom response from your workflow\"**. If `False`, does not return a custom response in the case of invalid auth.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| methods: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _checkHmac(secret, signature, bodyRaw) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const expectedSignature = crypto | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .createHmac("sha256", secret) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .update(bodyRaw, "utf8") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .digest(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const signatureBuffer = Buffer.from(signature, "hex"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (signatureBuffer.length !== expectedSignature.length) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return crypto.timingSafeEqual(signatureBuffer, expectedSignature); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance input validation and add JSDoc documentation While the core HMAC validation is secure, there are some improvements needed:
+ /**
+ * Validates an HMAC signature against a request body
+ * @param {string} secret - The secret key (UTF-8 encoded)
+ * @param {string} signature - Hexadecimal signature string
+ * @param {string} bodyRaw - Raw request body
+ * @returns {boolean} True if signature is valid
+ * @throws {Error} If inputs are null/empty or improperly formatted
+ */
_checkHmac(secret, signature, bodyRaw) {
+ if (!secret || !signature || !bodyRaw) {
+ throw new Error("Missing required parameters");
+ }
+
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(bodyRaw, "utf8")
.digest();
const signatureBuffer = Buffer.from(signature, "hex");
if (signatureBuffer.length !== expectedSignature.length) {
return false;
}
return crypto.timingSafeEqual(signatureBuffer, expectedSignature);
},📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async run({ $ }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const valid = this._checkHmac( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.secret, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.signature, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.bodyRaw, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!valid) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this.customResponse) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await $.respond({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: 401, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: {}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: "Invalid credentials", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $.flow.exit("Invalid credentials"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+55
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add security logging and secure headers For security monitoring and hardening:
if (!valid) {
+ console.error("HMAC validation failed", {
+ timestamp: new Date().toISOString(),
+ ip: $.event.client_ip,
+ });
+
if (this.customResponse) {
await $.respond({
status: 401,
- headers: {},
+ headers: {
+ "Content-Type": "text/plain",
+ "X-Content-Type-Options": "nosniff",
+ "Cache-Control": "no-store",
+ },
body: "Invalid credentials",
});
}
return $.flow.exit("Invalid credentials");
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $.export("$summary", "HTTP request successfully authenticated"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add input validation for signature and bodyRaw props
For security-critical components, it's important to validate inputs before processing. Consider adding:
signature: { type: "string", label: "Signature", description: "The HMAC signature received from the incoming webhook, typically provided in a specific HTTP header", + validate: (value) => { + if (!/^[0-9a-f]{64}$/i.test(value)) { + throw new Error("Signature must be a 64-character hexadecimal string"); + } + }, }, bodyRaw: { type: "string", label: "Raw Body", description: "The raw request body received from the webhook caller, provided as a string without any parsing or modifications", + validate: (value) => { + if (value.length > 1048576) { // 1MB limit + throw new Error("Request body exceeds maximum size of 1MB"); + } + }, },📝 Committable suggestion