I cannot able to validate the webhook response from the shopify by using the "shopify-node-api". and i am using the following code to validate the signature.
Below code is on app.js
app.use(bodyParser.json({
type:'application/json',
limit: '50mb',
verify: function(req, res, buf, encoding) {
if (req.url.startsWith('/webhook')){
req.rawbody = buf;
}
}
})
);
app.use("/webhook", webhookRouter);
Below on webhook.router.js
router.post('/orders/create', verifyWebhook, async (req, res) => {
console.log('🎉 We got an order')
res.sendStatus(200)
});
Below for the verification function
function verifyWebhook(req, res, next) {
let hmac;
let data;
try {
hmac = req.get("X-Shopify-Hmac-SHA256");
data = req.rawbody;
} catch (e) {
console.log(`Webhook request failed from: ${req.get("X-Shopify-Shop-Domain")}`);
res.sendStatus(200);
}
if (verifyHmac(JSON.stringify(data), hmac)) { // Problem Starting from Here
req.topic = req.get("X-Shopify-Topic");
req.shop = req.get("X-Shopify-Shop-Domain");
return next();
}
return res.sendStatus(200);
}
Verify signature function
function verifyHmac(data, hmac) {
if (!hmac) {
return false;
} else if (!data || typeof data.data !== "object") {
// I am Getting Error HERE
console.log('Error in data', data);
return false;
}
const sharedSecret = config.shopify_shared_secret;
const calculatedSignature = crypto
.createHmac("sha256", sharedSecret)
.update(Buffer.from(data), "utf8")
.digest("base64");
console.log('calculatedsecret', calculatedSignature);
return calculatedSignature === hmac;
};
and the body I am getting it as undefined. suggest me how to fix this problem in shopify webhook API
Instead of using the bodyparser.json() use bodyparser.raw to fetch the all the payload to process the shopify webhook verification.
router.use(bodyparser.raw({ type: "application/json" }));
// Webhooks
router.post("/", async (req, res) => {
console.log("Webhook heard!");
// Verify
const hmac = req.header("X-Shopify-Hmac-Sha256");
const topic = req.header("X-Shopify-Topic");
const shop = req.header("X-Shopify-Shop-Domain");
const verified = verifyWebhook(req.body, hmac);
if (!verified) {
console.log("Failed to verify the incoming request.");
res.status(401).send("Could not verify request.");
return;
}
const data = req.body.toString();
const payload = JSON.parse(data);
console.log(
`Verified webhook request. Shop: ${shop} Topic: ${topic} \n Payload: \n ${data}`
);
res.status(200).send("OK");
});
// Verify incoming webhook.
function verifyWebhook(payload, hmac) {
const message = payload.toString();
const genHash = crypto
.createHmac("sha256", process.env.API_SECRET)
.update(message)
.digest("base64");
console.log(genHash);
return genHash === hmac;
}
Related
Hey stack overflow so I am using webooks with stripe. For some reason I am getting the error "Stripe webook signature verification failed". Here is my source code below. Any ideas on how I can get it working? I know it has something to do with the bodyparser. But I am not able to figure out a workaround to get the stripe webhooks working.
Error Message:
Webhook signature verification failed. No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing
require("dotenv").config();
const express = require("express");
var cors = require('cors')
const axios = require("axios");
const bodyParser = require("body-parser");
const Stripe = require('stripe');
const stripe = Stripe('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
const invoiceDescription = require('./constants');
const endpointSecret = "whsec_w5Qipi3Wz0fm8sSCHcJIHwWfobS0kfYe";
const { TOKEN, SERVER_URL, BOTTOKEN } = process.env;
const TELEGRAM_API = `https://api.telegram.org/bot${TOKEN}`;
const URI = `/webhook/${TOKEN}`;
const WEBHOOK_URL = SERVER_URL + URI;
const app = express();
app.use(cors());
app.use(bodyParser.json());
const init = async () => {
const res = await axios.get(`${TELEGRAM_API}/setWebhook?url=${WEBHOOK_URL}`);
console.log(res.data);
};
app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {
let event = request.body;
// Only verify the event if you have an endpoint secret defined.
// Otherwise use the basic event deserialized with JSON.parse
if (endpointSecret) {
// Get the signature sent by Stripe
console.log(request.headers)
const signature = request.headers['stripe-signature'];
try {
event = stripe.webhooks.constructEvent(
request.body,
signature,
endpointSecret
);
} catch (err) {
console.log(`⚠️ Webhook signature verification failed.`, err.message);
return response.sendStatus(400);
}
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`);
// Then define and call a method to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
console.log('payment intent is', 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);
console.log('payment method is', paymentMethod)
break;
default:
// Unexpected event type
console.log(`Unhandled event type ${event.type}.`);
}
// Return a 200 response to acknowledge receipt of the event
response.send();
});
app.post(URI, async (req, res) => {
let text = "", chatId = "", userObjectForTable = {};
if(req.body.message?.chat?.id && req.body.message?.text && req.body.message?.text === "Start"){
chatId = req.body.message.chat.id;
text = invoiceDescription;
const message = await axios.post(`${TELEGRAM_API}/sendMessage`, {
chat_id: chatId,
text: text,
reply_markup: {
inline_keyboard: [[{
text: 'Pay $65.00',
web_app: {url: 'https://buy.stripe.com/test_14kbKj3Gd0AGeRi7ss' }
}]]
}
});
}
return res.send();
});
app.listen(process.env.PORT || 5050, async () => {
console.log("🚀 app running on port", process.env.PORT || 5050);
await init();
});
I am trying to activate Magento2, version 2.4.4, integration with expressjs backend.
The callback url is being hit and the data is being stored in the db. Then upon hitting the identity url, the pop up of login for app to be integrated is opened and user logs in.
Following the oauth process as defined at https://devdocs.magento.com/guides/v2.4/get-started/authentication/gs-authentication-oauth.html#pre-auth-token on making the POST request to /oauth/token/request I'm getting the following error -
oauth_problem=An+error+occurred+validating+the+nonce
I cannot figure out the source of this error, please help me fix this as I've been stuck at it since many days.
Following are one of the values calculated for the header Authorization and the post body -
Authorization: 'OAuth oauth_consumer_key=kxw5v6vwr4rm77cn2pxmqxdzdhhkor58, oauth_nonce=Fi9KRqgAmSX7sf32YpCTdPQ15FIY-LyY, oauth_signature=OTUzNWU4ZDViMzljZmM1NTM2MDNiMGQxOTUyMmRmMGRiMjdkZDZmNzY5ZTIxZTZkNGM1MzMzMmRkN2U5ZjcxNQ%3D%3D, oauth_signature_method=HMAC-SHA256, oauth_timestamp=1652694701394, oauth_version=1.0'
POST BODY -
{
oauth_consumer_key: 'kxw5v6vwr4rm77cn2pxmqxdzdhhkor58',
oauth_nonce: 'Fi9KRqgAmSX7sf32YpCTdPQ15FIY-LyY',
oauth_signature: 'OTUzNWU4ZDViMzljZmM1NTM2MDNiMGQxOTUyMmRmMGRiMjdkZDZmNzY5ZTIxZTZkNGM1MzMzMmRkN2U5ZjcxNQ%3D%3D',
oauth_signature_method: 'HMAC-SHA256',
oauth_timestamp: '1652694701394',
oauth_version: '1.0'
}
Following is callback url route code -
router.post('/magento-integration/callback', callbackHandler);
async function callbackHandler(req, res) {
const [{store_base_url, oauth_verifier, oauth_consumer_key, oauth_consumer_secret}] = [req.body];
try {
await saveOAuthCredentials({
store_base_url,
oauth_verifier,
oauth_consumer_key,
oauth_consumer_secret
});
return ApiResponse(res, 200);
} catch (err) {
// TODO: check err and set precise value of response status code and err msg
console.error(err.message)
return ApiResponse(res, 500, {message: err});
}
}
Following is the code for the controller of identity url route -
async function appLogin(req, res) {
// code to validate user
// ......
// Magento2 OAuth token exchange initiation
// Magento2 initiates the token exchange process by requesting the /login endpoint and sends
// url encoded query string params oauth_consumer_key and success_call_back which the front end sends in
// the body, against key queryParams, of the request it makes to /appLogin endpoint of sx-sellerapi.
const {oauth_consumer_key, success_call_back} = req.body.queryParams req.body.queryParams : [{}];
if(oauth_consumer_key && success_call_back){
try{
await runMagentoOAuthKeyX(sellerInfo.id, oauth_consumer_key);
res.redirect(success_call_back);
return;
} catch(err) {
return ApiResponse(res, 400, {message: err})
}
}
// rest of the code for usual login
}
Code for runMagentoOAuthKeyX
async function runMagentoOAuthKeyX(sellerId, oauthConsumerKey) {
try {
const oauthCred = await magentoModel.checkOAuthConsumerKeyExists(oauthConsumerKey, sellerId);
// isNonEmptyObject checks if arg passed is of type Object and has keys
if (isNonEmptyObject(oauthCred)) {
oauthCred.oauth_consumer_key = oauthConsumerKey;
oauthCred.url = `${oauthCred.store_base_url}${OAUTH_TOKEN_ENDPOINTS.request}`;
let requestTokenData;
try{
requestTokenData = await getToken(oauthCred, OAUTH_TOKEN_TYPE.requestToken);
} catch(err){
throw err
}
return Promise.all([
magentoModel.updateOAuthCred(oauthConsumerKey, requestTokenData, OAUTH_TOKEN_TYPE.requestToken),
getToken({...oauthCred, ...requestTokenData,
...{url: `${oauthCred.store_base_url}${OAUTH_TOKEN_ENDPOINTS.access}`}}, OAUTH_TOKEN_TYPE.accessToken)
])
.then(async ([_, accessTokenData]) =>
magentoModel.updateOAuthCred(oauthConsumerKey, accessTokenData, OAUTH_TOKEN_TYPE.accessToken)
)
.catch(err => {
throw err;
});
} else {
throw new Error(`OAuthConsumer key passed is unknown ${oauthConsumerKey}`);
}
} catch (err) {
// TODO: add logging
throw err;
}
Code for getToken()
async function getToken(tokenData, tokenType) {
const {url} = tokenData
const [authHeader, body] = await getAuthHeaderAndBody(tokenData, tokenType);
return axios.post(
url,
body,
{
headers: {
Authorization: authHeader
}
})
.catch(err => {
console.error(err.response.data);
throw err;
});
}
Code for getAuthHeaderAndBody
async function getAuthHeaderAndBody(tokenData, tokenType) {
const oauth_nonce = await genOAuthNonce();
const oauth_timestamp = Date.now();
const {
oauth_consumer_key,
oauth_consumer_secret,
oauth_signature_method,
url,
oauth_token,
oauth_token_secret,
oauth_verifier
} = tokenData;
const tokenList = ['access', 'webAPI'];
const oauthSignature = genOAuthSignature(url, {
oauth_consumer_key,
oauth_consumer_secret,
oauth_signature_method,
oauth_nonce,
oauth_timestamp,
oauth_version: OAUTH_VERSION,
oauth_token: tokenList.includes(tokenType) ? oauth_token : null,
oauth_token_secret: tokenList.includes(tokenType) ? oauth_token_secret : null,
oauth_verifier: OAUTH_TOKEN_TYPE.accessToken === tokenType ? oauth_verifier : null
});
const validParams = Object.entries({
oauth_consumer_key,
oauth_signature_method,
oauth_signature: oauthSignature,
oauth_nonce,
oauth_timestamp,
oauth_version: OAUTH_VERSION,
oauth_token: tokenList.includes(tokenType) ? oauth_token : null,
oauth_verifier: OAUTH_TOKEN_TYPE.accessToken == tokenType ? oauth_verifier : null
})
.filter(([_, val]) => val !== null)
.sort((a, b) => a[0] < b[0] ? -1 : 0);
const authHeaderValue = validParams
.map(([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`)
.join(', ');
const authHeaderStart = [OAUTH_TOKEN_TYPE.requestToken, OAUTH_TOKEN_TYPE.accessToken].includes(tokenType) ? 'OAuth' : 'Bearer';
const authHeader = `${authHeaderStart} ${authHeaderValue}`;
return [authHeader, Object.fromEntries(validParams)];
}
Code for genOAuthNonce -
async function genOAuthNonce() {
const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._~';
const buff = Buffer.alloc(32);
const result = [];
return new Promise((resolve, reject) => crypto.randomFill(buff, (err, buff) => {
if(err){
reject(err);
}
buff.forEach(c => result.push(charset[c % charset.length]));
resolve(result.join(''));
}));
}
Code for genOAuthSignature
function genOAuthSignature(baseUrl, params, method = 'POST') {
const keysNotInSignature = ['oauth_consumer_secret', 'oauth_token_secret'];
const signatureString = Object.entries(params)
.filter(([key, val]) => val
!= null && !keysNotInSignature.includes(key))
.sort((item1, item2) => item1[0] < item2[0 ] ? -1 : 0)
.map(([key, val]) => `${key}=${val}`)
.join(AUTH_HEADER_DELIMITER);
const baseString = [
encodeURIComponent(method.toUpperCase()),
encodeURIComponent(baseUrl),
encodeURIComponent(signatureString)
].join(AUTH_HEADER_DELIMITER);
const {oauth_consumer_secret, oauth_token_secret} = params;
let signKey = `${encodeURIComponent(oauth_consumer_secret)}${AUTH_HEADER_DELIMITER}`
signKey += oauth_token_secret ? `${encodeURIComponent(oauth_token_secret)}` : '';
const hmac = createHmac('sha256', signKey);
return Buffer.from(hmac.update(baseString).digest('hex')).toString('base64');
}
Found the bugs in the code for invalid Nonce. The issue was with timestamp as I was using Date.now() which returns UTC timestamp in ms whereas magento2 oauth requires it to be in seconds. Also found and fixed the bug in evaluating the signature for oauth token exchange.
In function getAuthHeaderAndBody -
async function getAuthHeaderAndBody(tokenData, tokenType) {
const oauth_nonce = await genOAuthNonce();
// changed below from Date.now() as timestamp must be in seconds.
const oauth_timestamp = parseInt(Date.now() / 1000);
// ... rest of the code
}
In genOAuthSignature
function genOAuthSignature(baseUrl, params, method = 'POST') {
// preceding code
// last line is changed by looking at Magento2 code for validating the signature
return createHmac('sha256', signKey)
.update(baseString, 'binary')
.digest()
.toString('base64');
}
I am integrating a Twitch API for subs, and having issue with getting the webhook callback response to the middleware function that it should check the header and verify the signature.
I am receive the right response! however, it stops right there!
I check the order of the routes, and I am not sure what I am missing
I am following https://dev.twitch.tv/docs/eventsub
app.post('/createWebhook/:broadcasterId', (req, res) => {
const createWebHookParams = {
host: "api.twitch.tv",
path: "helix/eventsub/subscriptions",
method: 'POST',
headers: {
"Content-Type": "application/json",
"Client-ID": clientId,
"Authorization": "Bearer " + authToken
}
}
const createWebHookBody = {
"type": "channel.follow",
"version": "1",
"condition": {
"broadcaster_user_id": req.params.broadcasterId
},
"transport": {
"method": "webhook",
"callback": "ngrokURL/notification",
"secret": webhookSecret //
}
}
let responseData = ""
const webhookReq = https.request(createWebHookParams, (result) => {
result.setEncoding('utf8')
result.on('data', (d) => {
responseData = responseData + d
})
.on('end', (result) => {
const responseBody = JSON.parse(responseData) // json
console.log(responseBody)
res.send(responseBody)
})
})
webhookReq.on('error', (e) => {
console.log("Error")
})
webhookReq.write(JSON.stringify(createWebHookBody))
webhookReq.end()
});
// middlewsre ---> // not triggered!!!
app.use(express.json({
verify: verifyTwitchSignature
}));
// making post to receeive the notification.
app.post('/notification', (req, res) => {
console.log("incoming notificatin", req.body)
res.status(200).end();
})
// the middleware verifing the signature
const crypto = require("crypto");
const twitchSigningSecret = process.env.SECRET;
const verifyTwitchSignature = (req, res, buf, encoding)=>{
const messageId = req.header("Twitch-Eventsub-Message-Id");
const timeStamp = req.header("Twitch-Eventsub-Message-Timestamp")
const messageSignature = req.header("Twitch-Eventsub-Message-Signature")
console.log(`Message ${messageId} Signature: `,messageSignature)
if (!twitchSigningSecret){
console.log(`Twitch signing secret is empty`);
throw new Error ("Twitch signing secret is empty.");
}
const computedSignature = "sha256=" + crypto.createHmac("sha256", twitchSigningSecret).update(messageId + timeStamp + buf).digist("hex");
console.log(`Message ${messageId} Computed Signature: `, computedSignature)
if (messageSignature !== computedSignature) {
throw new Error("Invalid Signature.");
}else {
console.log("Verification Successful");
}
}
module.exports = verifyTwitchSignature
I believe in your verifyTwitchSignature function you need to pass next as one of the parameters and in the else-statement when it passes call next();.
That is my observation.
If you are working with middleware you always have to pass next together with req and res. next is what calls the next middleware or function in the queue.
*Edited for clarity and to reflect changes:
Hello. I'm struggling to understand why my async functions are working just fine individually, and when chained together, but not when chained together and fired off in an event listener....
worth noting: when i try to run this without an even listener, data passes from my app, through my post route, and to my endpoint the way it should, it is then returned to my app through the correct route, etc. However, i do get an error in the console that says :
error SyntaxError: Unexpected token < in JSON at position 0
however, when i try to run my chain of async functions on the click of a dom element i don't get the above error, data is passed to my post route, i can log the object that is posted:
Object: null prototype] { zipcode: '97206', feel: 'confused' }
but then my server doesn't save any data, and my get route is never triggered, and nothing gets sent back to my app.......
i'm fairly lost.....
full server and app code below:
server:
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.use(express.static('public'));
//test values
let projectData = {
date: "today",
temp: "38",
feelings: "confused"
};
app.get("/", (req, res) => {
});
app.post("/postData", (req, res) => {
console.log(req.body)
projectData = {
date: req.body.date,
temp: req.body.temp,
feelings: req.body.feelings
}
console.log("post route was hit, saved:", projectData);
res.redirect("/")
});
app.get("/getData", (req, res) => {
console.log("getData route was hit, sent: ", projectData)
res.send(projectData);
})
app.listen(3000, (req, res) => {
console.log("Listening on port 3000");
});
app
let newDate = getDate();
const apiKey = "5fd7b3a253e67a551e88ff34a92b9e02";
const baseURL = "http://api.openweathermap.org/data/2.5/weather?";
// zip=${}&APPID=${}&units=metric;
const userData = {};
// returns the date //
function getDate() {
let d = new Date();
let newDate = d.getMonth() + "." + d.getDate() + "." + d.getFullYear();
return newDate;
}
//constructs string for api call, adds temp to userData object
const getData = async (apiUrl, zip, key) => {
const url = `${apiUrl}zip=${zip}&APPID=${key}&units=metric`;
const result = await fetch(url);
try {
let newData = await result.json()
// console.log(newData.main.temp)
userData.temp = newData.main.temp
}catch(error) {
console.log("error", error);
}
}
// getData(baseURL, 97206, apiKey)
// .then(result => {
// console.log(userData)
// })
//saves contents of userData Object to server
const postData = async (url, data) => {
const result = await fetch(url, {
method: "POST",
credentials: "same-origin",
headers: {
"Content-type": "application/json"
},
body: JSON.stringify(data)
});
try {
const newData = await result.json();
// console.log(newData);
return newData;
} catch (error) {
console.log("error", error);
}
};
// postData('/postData', userData);
//updates interface with projectData values from server
const updateUI = async url => {
const result = await fetch(url);
try {
const newData = await result.json();
document.getElementById("date").innerHTML = newData.date;
document.getElementById("temp").innerHTML = newData.temp;
document.getElementById("feelings").innerHTML = newData.feelings;
} catch (error) {
console.log("error", error);
}
};
// updateUI("/getData")
// THIS WORKS
userData.date = newDate;
userData.feelings = document.getElementById("feel").value;
const zipCode = document.getElementById("zipcode").value;
getData(baseURL, 97206, apiKey).then(result => {
postData("/postData", userData).then(result => {
updateUI("/getData");
});
});
// THIS DOESNT WORK
// document.getElementById("btn").addEventListener("click", e => {
// userData.date = newDate;
// userData.feelings = document.getElementById("feel").value;
// const zipCode = document.getElementById("zipcode").value;
// getData(baseURL, zipCode, apiKey).then(result => {
// postData("/postData", userData).then(result => {
// updateUI("/getData");
// });
// });
// });
EDIT:
I realized that the information that was being passed through the post route when my async functions are fired off by an event listener was actually just the form input element values, rather than the contents of the fetch/post request. after i removed the name attributes from the form inputs, i'm getting no data at all hitting my post route......yet the corosponding function works fine with not in the event listener.
I'm stumped.
well, the syntax error was solved by using .text() instead of .json() on the results of my post request.
adding e.preventDefault() as the first line of my event listener callback fixed the event listener, and now everything is working as it should.
I tried to implement jwt token generation in node js.I got jwt token but how to validate token using node js crud operation.but I got token jwt verfiy code using callback function.without call back function used to implement async/awit function implement.
index.js
router.post('/', async (req, res) => {
(async function() {
try {
await client.connect();
console.log("Connected correctly to server");
const db = client.db('olc_prod_db');
//Validation
const { error } = validate.validate(req.body);
if (error)
{
return res.status(400).send(error.details[0].message);
}
else
{
const check_login = req.body
const r = await db.collection('UserRegistration').find().toArray();
r.forEach(element => {
if(element['username'] == check_login['username'])
{
const token = get_token.validate(req.body)
res.send({"token ":token})
}
else
{
return res.send(401,"Un Authorized");
}
});
}
client.close();
} catch(err) {
console.log(err.stack);
}
})();
});
authtoken.js
var jwt = require('jsonwebtoken')
function get_token(userdata)
{
var accessToken = jwt.sign(userdata, 'secretkey', {
//Set the expiration
expiresIn: 3600 //we are setting the expiration time of 1 hr.
});
//send the response to the caller with the accesstoken and data
console.log('Authentication is done successfully.....');
return accessToken
}
exports.validate = get_token;
const jwt = require('jsonwebtoken')
const config = require('../../config/default')
function verifyjwt(req,res,next){
const token = req.headers['authorization']
if(!token) return res.status(401).json('Unauthorize user')
try{
const decoded = jwt.verify(token,config.secret);
req.user = decoded
next()
}catch(e){
res.status(400).json('Token not valid')
}
}
module.exports = verifyjwt
const CONST = require('../../config')
exports.validJWTNeeded = (req, res, next) => {
if (req.headers['authorization']) {
try {
let authorization = req.headers['authorization'].split(' ');
if (authorization[0] !== 'Bearer') {
return res.status(401).send('invalid request'); //invalid request
} else {
req.jwt = jwt.verify(authorization[1], CONST.SECRET);
return next();
}
} catch (err) {
return res.status(403).send(); //invalid token
}
} else {
return res.status(401).send('invalid request');
}
}