visit
function _getHash(params) {
const paramString = JSON.stringify(params);
const hash = crypto
.createHash('sha256')
.update(paramString)
.digest('hex');
return hash;
}
function _sign(hash, secret) {
const payload = {
iss: 'server',
sha256: hash,
};
const token = jwt.sign(payload, secret);
return token;
}
Here,
_getHash
creates a SHA256 hash of a message body, and _sign
, well, signs it using a secret. Let's use it in our client.const axios = require('axios');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const client = axios.create({
baseURL: '//our.reporting.server.url',
});
async function sendSuccess(app) {
const params = {
success: true,
app,
}
const secret = process.env.SECRET;
const hash = _getHash(params);
const token = _sign(hash, secret);
await client.post('/cd/server', params, {
headers: {
'X-Signature': token,
},
});
}
Here, we get our secret from
.env
file, use it to sign a request body, and then send it to our reporting server. Few things to note:our.reporting.server.url
with yours./cd/server
as I have other sources like Netlify to receive updates from, but you can use anything, including /
.X-Signature
header: again, it can be almost anything, but I would suggest sticking to something similar as this is kinda standard.function checkSignature(data, signature, secret, issuer) {
if (signature == undefined) {
return false;
}
let decoded;
try {
decoded = jwt.verify(signature, secret);
} catch(e) {
return false;
}
const dataString = JSON.stringify(data);
const hash = crypto
.createHash('sha256')
.update(dataString)
.digest('hex');
const hashMatches = decoded.sha256 == hash;
const issuerMatches = decoded.iss == issuer;
if (!hashMatches || !issuerMatches) {
return false;
}
return true;
}
Similar to the one in the article on the CD server, this
checkSignature
function validates that the signature is authentic.Here's the rest of the server code.const crypto = require('crypto');
const jwt = require('jsonwebtoken');
app.post('/cd/server', async (req, res) {
const data = req.body;
const signature = req.header('X-Signature');
const secret = process.env.SERVER_SECRET;
const issuer = 'server';
if (!checkSignature(data, signature, secret, issuer)) {
res.status(403).end();
}
const success = data.success;
const app = data.app;
const error = data.error;
bot.cd('Server', app, success);
res.send('Hello server!');
});
What we do here is check the signature and send a message. A message is sent via the provider of your choice. Here, it's Telegram bot (
bot.cd('Server', app, success);
).app.post('/cd/netlify', async (req, res) {
const data = req.body;
const signature = req.header('X-Webhook-Signature');
const secret = process.env.NETLIFY_SECRET;
const issuer = 'netlify';
if (!checkSignature(data, signature, secret, issuer)) {
res.status(403).end();
}
const success = data.state == 'ready';
const app = data.name;
bot.cd('Netlify', app, success);
res.send('Hello Netlify!');
});
Note:
NETLIFY_SECRET
and SERVER_SECRET
don't have to be different, but I highly recommend making them so. Otherwise, if one key is leaked (say, by a hacker attack on Netlify), another one will be compromised as well, making your stack less secure.To add webhooks on Netlify, open a project, then click
Settings -> Build & Deploy -> Deploy notifications
, then press Add notification -> Outgoing webhook
. You can add webhooks for successful or failed builds among other events.function errorWatcher(err, req, res, next) {
if (process.env.ENV == 'dev') {
console.log(err);
}
if (process.env.ENV == 'prod') {
_sendRuntimeFailure(err.toString());
}
next(err);
}
async function _sendRuntimeFailure(error) {
const app = 'my-app';
const params = {
app,
error,
};
const hash = _getHash(params);
const secret = process.env.SECRET;
const token = _sign(hash, secret);
await client.post('/runtime', params, {
headers: {
'X-Signature': token,
},
});
}
Functions
_getHash
and _sign
are the same as we used above. We also use .env
to set the ENV variable to dev
or prod
. That way, only production errors will be sent to you.The only thing left is to tell express about our handler.app.use(errorWatcher);
app.get('/endpoint', wrapAsync(router.endpoint));
// Helper function to pass error down the middleware chain
function wrapAsync(fn) {
return function(req, res, next) {
fn(req, res, next).catch(next);
};
}