Skip to content

Commit

Permalink
Merge pull request #356 from Secreto31126/signature-verification
Browse files Browse the repository at this point in the history
Modularized request validation
  • Loading branch information
Secreto31126 authored Aug 1, 2024
2 parents a35606f + 02b7b5e commit b9556de
Showing 1 changed file with 47 additions and 29 deletions.
76 changes: 47 additions & 29 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,10 +742,10 @@ export class WhatsAppAPI<EmittersReturnType = void> {
* @param raw_body - The raw body of the POST request
* @param signature - The x-hub-signature-256 (all lowercase) header signature sent by Whatsapp
* @returns The emitter's return value, undefined if the corresponding emitter isn't set
* @throws 500 if secure and the appSecret isn't specified
* @throws 501 if secure and crypto.subtle or ponyfill isn't available
* @throws 400 if secure and the raw body is missing
* @throws 401 if secure and the signature is missing
* @throws 500 if secure and the appSecret isn't defined
* @throws 501 if secure and crypto.subtle or ponyfill isn't available
* @throws 401 if secure and the signature doesn't match the hash
* @throws 400 if the POSTed data is not a valid Whatsapp API request
* @throws 500 if the user's callback throws an error
Expand All @@ -758,35 +758,11 @@ export class WhatsAppAPI<EmittersReturnType = void> {
): Promise<EmittersReturnType | undefined> {
//Validating the payload
if (this.secure) {
if (!this.appSecret) throw 500;
if (!this.subtle) throw 501;

if (!raw_body) throw 400;

signature = signature?.split("sha256=")[1];
if (!signature) throw 401;

const encoder = new TextEncoder();
const keyBuffer = encoder.encode(this.appSecret);

const key = await this.subtle.importKey(
"raw",
keyBuffer,
{ name: "HMAC", hash: "SHA-256" },
true,
["sign", "verify"]
);

const data = encoder.encode(escapeUnicode(raw_body));
const result = await this.subtle.sign("HMAC", key, data.buffer);
const result_array = Array.from(new Uint8Array(result));

// Convert an array of bytes to a hex string
const check = result_array
.map((b) => b.toString(16).padStart(2, "0"))
.join("");

if (signature !== check) throw 401;
if (!(await this.verifyRequestSignature(raw_body, signature))) {
throw 401;
}
}

// Throw "400 Bad Request" if data is not a valid WhatsApp API request
Expand Down Expand Up @@ -960,6 +936,48 @@ export class WhatsAppAPI<EmittersReturnType = void> {
});
}

/**
* Verify the signature of a request
*
* @param raw_body - The raw body of the request
* @param signature - The signature to validate
* @returns If the signature is valid
* @throws 500 if the appSecret isn't defined
* @throws 501 if crypto.subtle or ponyfill isn't available
*/
async verifyRequestSignature(
raw_body: string,
signature: string
): Promise<boolean> {
if (!this.appSecret) throw 500;
if (!this.subtle) throw 501;

signature = signature.split("sha256=")[1];
if (!signature) return false;

const encoder = new TextEncoder();
const keyBuffer = encoder.encode(this.appSecret);

const key = await this.subtle.importKey(
"raw",
keyBuffer,
{ name: "HMAC", hash: "SHA-256" },
true,
["sign", "verify"]
);

const data = encoder.encode(escapeUnicode(raw_body));
const result = await this.subtle.sign("HMAC", key, data.buffer);
const result_array = Array.from(new Uint8Array(result));

// Convert an array of bytes to a hex string
const check = result_array
.map((b) => b.toString(16).padStart(2, "0"))
.join("");

return signature !== check;
}

/**
* Get the body of a fetch response
*
Expand Down

0 comments on commit b9556de

Please sign in to comment.