I'm writing a script in Node.js that integrates Todoist, Notion and Discord. I'm using Heroku for hosting because I want to use webhooks (from Todoist). The idea is that every time I add or complete a task, a notification is fired by Todoist to my app and after verifying that the request is legitimate, I update my Notion database and send messages through a discord bot.
The problem I encounter is when I have to check the HMAC SHA256. I'm still quite new to JavaScript and even newer to hashing and secret keys, so please bear with me.
The Todoist documentation says:
To verify each webhook request was indeed sent by Todoist, an
X-Todoist-Hmac-SHA256header is included; it is a SHA256 Hmac generated using yourclient_secretas the encryption key and the whole request payload as the message to be encrypted. The resulting Hmac would be encoded in a base64 string.
After a lot of research, I tried to use a function I found as the verified answer in another question, but this happens: throw new Error('Malformed UTF-8 data');
This is what I have so far:
const express = require('express');
const Discord = require('discord.js');
const client = new Discord.Client();
var CryptoJS = require('crypto-js');
require('dotenv').config();
const PORT = process.env.PORT || 3000;
app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
process.on('unhandledRejection', error => {
// Will print "unhandledRejection err is not defined"
console.log('unhandledRejection', error.message);
});
function sign_string(message, key){
var secret_key = CryptoJS.enc.Base64.parse(key).toString(CryptoJS.enc.Utf8);
var hash = CryptoJS.HmacSHA256(message, secret_key);
return CryptoJS.enc.Base64.stringify(hash);
}
client.on('ready', () => {
client.users.fetch(process.env.MY_USER_ID).then(user => user.send('Hey, The bot is up!'));
});
app.get('', (req, res) => {
res.send('Hello World');
});
app.post('', (req, res) => {
if(req.get('User-Agent') === 'Todoist-Webhooks') {
var delivered_hmac = req.get('X-Todoist-Hmac-SHA256');
var computed_hmac = sign_string(JSON.stringify(req.body), process.env.TODOIST_CLIENT_SECRET);
if(delivered_hmac === computed_hmac) {
if(req.body.event_name === 'item:added' && req.body.event_data.description === '') {
// add task to notion
// idea: ask user for data
} else {
if(req.body.event_name === 'item:completed' && req.body.event_data.description !== '') {
// complete task on notion
}
}
client.users.fetch(process.env.MY_USER_ID).then(user => user.send('You can update your tasklist if you want'));
res.status(200).send('Event handled');
} else {
client.users.fetch(process.env.MY_USER_ID).then(user => user.send('A 403 (Unauthorized) status code has been sent to a request'));
res.status(403).send('Unauthorized');
console.log(`delivered_hmac: ${delivered_hmac}\ncomputed_hmac: ${computed_hmac}\n`)
console.log(req.body);
}
} else {
client.users.fetch(process.env.MY_USER_ID).then(user => user.send('A 400 (Bad request) status code has been sent to a request'));
res.status(400).send('Bad request');
}
//handle notion
})
client.login(process.env.BOT_TOKEN);
app.listen(PORT, () => {
console.log(`App up at port ${PORT}`);
});