Validating Kentico Cloud webhooks signatures in Express.js - javascript

How to validate webbooks signature using express.js?
In docs, there is a section about notification signatures but I don't know how to combine it with Express.js
This question is a migrated from official Kentico Cloud Forum, that would be deleted.

In the API reference, there is a sample describing webhook validation in various languages including node.js.
If you want to use express.js you could start with this template code:
const express = require('express');
const crypto = require('crypto');
// Create a new instance of express
const app = express();
// Set up a raw bodyparser to read the webhook post
const bodyParserRaw = require('body-parser').raw({
type: '*/*',
});
function webhookValidator (req, res, next) {
// get the header signature from the webhook request
const givenSignature = req.headers['x-kc-signature'];
// throw error if it's missing
if (!givenSignature) {
console.log('Missing signature');
return res.status(409).json({
error: 'Missing signature'
});
}
// create HMAC from the raw request body
let hmac = crypto.createHmac('sha256', [your-webhook-secret-key]);
hmac.write(req.body);
hmac.end();
// get a base64 hash from HMAC
let hash = hmac.read().toString('base64');
// check validity with timingSafeEqual
let webhookValid = false;
try {
webhookValid = crypto.timingSafeEqual(Buffer.from(givenSignature, 'base64'), Buffer.from(hash, 'base64'));
} catch (e) {
webhookValid = false
}
// return validity
if (webhookValid) {
return next();
} else {
console.log('Invalid signature');
return res.status(409).json({
error: 'Invalid signature'
});
}
}
// create a route and pass through the bodyparser and validator
app.post('/webhook', bodyParserRaw, webhookValidator, ( req, res, next ) => {
// If execution gets here, the HMAC is valid
console.log('webhook is valid');
});
EDIT
You can use the Kontent webhook helper library to quickly verify the webhook notifications and their signatures. The library is available as #kentico/kontent-webhook-helper npm package and helps you avoid common problems when calculating the hash.

Related

Axios 404 when requesting from routes folder

I have a server.js file in Express and Node.js that contains the majority of my back-end code, outside of my database config file.
The file is quite long and to improve maintainability, I would like to make it modular by splitting various components into their own files that can be imported.
I have attempted to move a database endpoint as a starting point into its own file called auth.js, located in the routes folder. The file is set up as follows:
const express = require('express');
const router = express.Router();
const db = require('../config/db');
const crypto = require("crypto"); // set up crypto middleware for hashing password and checking password hashes
const salt = crypto.randomBytes(256).toString("hex"); // create a hash salt/pepper
const iterations = 1000; // number of iterations to jumble the hash
const hashSize = 64; //set up char length of hash
const hashAlgorithm = "sha256"; // which hashing algorithm will be used
//This function returns a hash of the password, combined with the pepper and the salt.
function PasswordHash(password, salt) {
//PEPPER MUST BE MOVED TO ENV FILE WHEN READY
const pepper = "ec3fd71d14f7cab570fc94df34e60e4d8c80cdff4d1dde66c74b10ae576b88239315295adabaced63b05104bbf8d2f2f92a24aeebe8444861ba1b7efc73dafdeda6fe2bf6e7288f959832d5db953a7eab0b37ef8ad126f94616b0c1e7b3b0ce7418dff91afaa78401dacce6aee72649840e26a01d75bfac69acf8d0dd50aaddebb9397150bb0f88795cde94ea3d03fec2992fc3b5c3c7bbd8de3f8f7d693cdcca879d9aefd6e02d4457217928091a731c08f83f9927f9b19ca34ab589dd02ecc40e336e067a1f2e072ec2b3a93617ded73028ed5bc5d55f011ba5a53099312f06d649fa06fdbf49e81c8f9a81f113f95cd416d230c2cb6056189c77f889dc83d";
return crypto.pbkdf2Sync (
password,
salt + pepper,
iterations,
hashSize,
hashAlgorithm
).toString("hex");
};
// login route
router.post('/signin', (req, res) => {
const { email, password } = req.body;
// Check all emails against input
db.query(selectEmail, [email], (err, rows) => {
if (err) throw err;
// If email exists
if (rows.length > 0) {
// If password with salt and compares to database
if (PasswordHash(password, rows[0].salt) == rows[0].password) {
// Create session
req.session.firstName = rows[0].first_name;
req.session.lastName = rows[0].last_name;
req.session.username = rows[0].user_name;
req.session.ProfilePicture = rows[0].profile_picture;
console.log('Session created:', req.session); // Print session
res.send('Login successful');
}
// If password is incorrect
else {
res.send('Email or password are incorrect');
}
}
// If email does not exist
else {
res.send('Email or password are incorrect');
};
});
});
module.exports = router;
This is then used in the main server.js file by requiring the auth.js file and using it with the route '/signin':
const authRoutes = require('./routes/auth');
app.use('/signin', authRoutes);
Finally, I make a request on my React front-end application to the /signin route.
const validateRow = () => {
// Validate login
axios.post('http://localhost:8080/signin', {
email: emailInput,
password: passwordInput
})
.then((res) => {
setMessage(res.data);
//If validation passed
if (res.data === 'Login successful') {
navigate('/directory')
};
});
};
To add some context this is for a login form and validates login data inputted into the form against rows found in the database. This worked as intended until I moved the endpoint into a separate file I now receive:
AxiosError {message: 'Request failed with status code 404', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}
on my front end. I would like to know how to resolve this issue.
The issue is that app.use('/signin', authRoutes) makes an endpoint be "/signin/signin" not just "/signin" which you are trying to request. The simplest solution would be to change link you pass in axios.post function to "http://localhost:8080/signin/signin".
Try using relative path as suggested in this (possible duplicate):
I don't understand why my axios post request isn't working - 404 not found
You need to listen to the port 8080
const app = express()
const port = 8080
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

Stripe Webhooks StripeSignatureVerificationError

I'm currently testing the Stripe Webhook Endpoints for the Checkout Process.
I'm confused because Stripe shows two different snippets how to set up the Webhook endpoint in their docs.
In the Checkout Doc they show this code snippet:
const stripe = require('stripe')('sk_test_...');
// Find your endpoint's secret in your Dashboard's webhook settings
const endpointSecret = 'whsec_...';
// Using Express
const app = require('express')();
// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require('body-parser');
// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the checkout.session.completed event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// Fulfill the purchase...
handleCheckoutSession(session);
}
// Return a response to acknowledge receipt of the event
response.json({received: true});
});
app.listen(8000, () => console.log('Running on port 8000'));
And in their Webhook Docs they are showing this snippet:
const app = require('express')();
// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require('body-parser');
// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
let event;
try {
event = JSON.parse(request.body);
} catch (err) {
response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
// Then define and call a method to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
case 'payment_method.attached':
const paymentMethod = event.data.object;
// Then define and call a method to handle the successful attachment of a PaymentMethod.
// handlePaymentMethodAttached(paymentMethod);
break;
// ... handle other event types
default:
// Unexpected event type
return response.status(400).end();
}
// Return a response to acknowledge receipt of the event
response.json({received: true});
});
app.listen(8000, () => console.log('Running on port 8000'));
I tried both snippets but nothing seems to work for me.
The first one gives me the StripeSignatureVerificationError when I try to constructEvent(...)
and the second one is telling me that the Object event is undefined.
Does someone know why both of these endpoints are not working for me ?
Before using JSON Body parser configure to receive RAW body
app.use(bodyParser.raw({type: "*/*"})) <-- This line need to be added
app.use(bodyParser.json())
More discussion https://github.com/stripe/stripe-node/issues/331

.put method for updating password in mocha chai requests not working

My code:
const model = require('../db/models/user');
const describe = require('mocha').describe;
const assert = require('chai').assert;
const chaiHttp = require('chai-http');
let chai = require('chai');
let server = require('../server');
chai.use(chaiHttp);
describe('Test user registration, login, update password', () => {
beforeEach((done) => {
// Reset user mode before each test
model.User.remove({}, (err) => {
console.log(err);
done();
})
});
Now, I get the error
UnhandledPromiseRejectionWarning: TypeError: Cannot read property
'_id' of null
in the route itself, specifically:
router.put('/me/update-password', async (req, res, next) => {
const {body} = req;
const auth = req;
const userId = auth._id; // problem on this line!
// rest of code...
});
So, after registration and logging in (which works fine, as it should!), I am having a lot of problems to update the password. In the params I am sending generated token and in the body is the password field with new password. On live example (for example Postman) it works as it should, but in tests it simply does not.
I really have no idea and have lost a lot of my time over this already (3 days).
Can someone please take a look suggest solution?
Much appreciated.
Updated with auth.js:
const jwt = require('jsonwebtoken');
const isAu = function(req) {
return jwt.verify(req.headers.authorization.split(' ')[1], 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};
module.exports = isAu;
EDIT:
Since OP changed the original question after it has been answered here is the link to original: https://stackoverflow.com/revisions/55064109/1
=======================================
JWT verify method accepts Authorization token - you are fetching that correctly by splitting Authorization header string in order to fetch token.
HTTP Authorization header string hold Authentication scheme type (Bearer, Basic, Digest, etc) and the token value
Authorization: Bearer eyJhbGciOiJIUzI1NiIXVCJ9...TJVA95OrM7E20RMHrHDcEfxjoYZgeFONFh7HgQ
but your Authorization header in the Chai request only holds the value of the token and not the Authentication scheme type.
Assumin your Authentication scheme is Bearer you need to set that in your Chai request Authorization header:
...
chai.request(server)
.put('/api/me/update-password')
.set('Authorization', `Bearer ${token}`)
.send(`${updatedPassword}`)
.end((error, response) => {
assert.equal(response.status, 200);
done();
});
...
On the other hand, in case you do not specify Authentication type in the request authorization header than you should send it like that to JWT to veirfy:
const isAuthenticated = function(req) {
return jwt.verify(req.headers.authorization, 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};

Hapi.js - adding mechanism to check every route

I am trying to implement a mechanism that will be run before any route is hit. In that mechanism I want to take a value from the header and check for authentication.
I have come up with this:
server.js:
// Create a server with a host and port
'use strict';
var Hapi = require('hapi');
var mongojs = require('mongojs');
var plugins = [
require('./routes/entities')
];
var server = new Hapi.Server();
server.connection({
port: 3000
});
//Connect to db
server.app.db = mongojs('hapi-rest-mongo', ['entities']);
server.app.checkHeader = function (request) {
var header = request.headers['x-authorization'];
if(header === "letmein"){
return true
}
return false
};
//Load plugins and start server
server.register(plugins, function (err) {
if (err) {
throw err;
}
// Start the server
server.start(function (err) {
console.log('Server running at:', server.info.uri);
});
});
and in routes.entities:
'use strict';
var Boom = require('boom');
var uuid = require('node-uuid');
var Joi = require('joi');
exports.register = function (server, options, next) {
var db = server.app.db;
server.route({
method: 'GET',
path: '/entities',
handler: function handler(request, reply) {
if(!server.app.checkHeader(request))
{
return reply(Boom.unauthorized());
};
//request.server.myFunc();
db.entities.find(function (err, docs) {
if (err) {
return reply(Boom.wrap(err, 'Internal MongoDB error'));
}
reply(docs);
});
}
});
So in short while starting the server I have registered my function server.app.checkHeader
And in the routes I am calling it and sending a request object to it. Request object contains information about the headers.
While this works, I am having a feeling I am not following the best practices with the Hapi.
How could I do it more elegantly?
There are a few options.
You can, of course, tap into the request lifecycle - note the events that occur in the pipeline prior to the route handler.
Although, I'd urge you to consider implementing an auth strategy that can be set as the default for all routes or selectively on appropriate routes.
The best way to require authentication for all or selected route is to use hapi’s integrated functionality.
You should set a default authentication strategy that is applied to each route handler. The sample below uses basic auth. You’d want to create a custom authentication strategy for hapi to check your x-authentication header.
const Hapi = require('hapi')
const BasicAuth = require('hapi-auth-basic')
const server = new Hapi.Server()
server.register(BasicAuth, function (err) {
if (err) {
console.log('error', 'failed to install plugins')
throw err
}
// TODO: add authentication strategy & set as default
server.auth.strategy('simple', 'basic', true, { validateFunc: basicValidationFn })
// or set strategy separately as default auth strategy
server.auth.strategy('simple', 'basic', { validateFunc: basicValidationFn })
server.auth.default('simple')
// TODO: add routes
server.start(function (err) {
})
})
You can also inject hapi’s request lifecycle and extend it at given points. Extending the request lifecycle should be done by using plugins:
register: function (server, options, next) {
// do some processing before 'onPreAuth'
// or pick another extension point
server.ext('onPreAuth', (request, reply) => {
// your functionality
})
}
Hope that helps!

Send JWT from AngularJS to Node.js

An AngularJS app needs to exchange a JWT with the Node.js instance that serves it. The Node.js instance has a /user route which returns a JWT to the Angular client. What specific changes need to be made to the code below so that 1.) The AngularJS app can send the JWT back to the Node.js instance's /user route, and 2.) the Node.js code can isolate the JWT as a variable for processing?
The current AngularJS code for calling the backend /user route is:
$http.get('user').then(function(response) {
console.log('response is: ');
console.log(response);
if (response.data.token === 'anonymous') {
$rootScope.authenticated = false;
} else {
$rootScope.userJWT = response.data.token;
var payload = $rootScope.userJWT.split('.')[1];
payload = $window.atob(payload);
payload = JSON.parse(payload);
self.name = payload.name;
self.authorities = payload.authorities;
$rootScope.authenticated = true;
}
}, function() {
$rootScope.authenticated = false;
});
And the Node.js code for the backend /user route is:
app.get('/user**', function(req, res) {
console.log("You Hit The User Route TOP");
//How do we get the JWT from req?
var user = getUserName(theJwt);
var token = getToken(user);
var jwtJSON = getUser(token);
if( (jwtJSON["token"] == 'error') || jwtJSON["token"] == 'anonymous' ) {
res.sendStatus(500); // Return back that an error occurred
} else {
res.json(jwtJSON);
}
console.log("You Hit The User Route BOTTOM");
});
Note, the Node.js instance includes var jwt = require('jsonwebtoken');, and one of the processing methods will decode the JWT using var decoded = jwt.decode(token, {complete: true});, as per the jsonwebtoken API.
When using JWT there is no required way to communicate the token.
The most common way is to place the token into an HTTP Header.
On the AngularJS side you would make an HTTP request with an extra header (e.g. X-Auth-Token) which contains the JWT.
Example of AngularJS side:
var config = {
headers: {
"X-Auth-Token": $rootScope.userJWT
}
}
$http.get('routeThatNeedsJWT', config).then(function(response) { ... });
On the Node.js side you would get the contents of the header and process it using the jsonwebtoken library.
Example of Node.js side:
app.get('/routeThatNeedsJWT', function(req, res) {
var rawTokenFromHeader = req.get('X-Auth-Token'); // Get JWT from header
try {
var jwtJSON = jwt.verify(token, 'secret'); // Verify and decode JWT
res.json(jwtJSON);
} catch (err) {
res.sendStatus(500); // Return back that an error occurred
}
});
Helpful links:
Express 4.x getting header value
jsonwebtoken library verify token

Categories