NodeJS joining promise with request functions - node.js

I have the following code below for clover payment api and although the code works perfectly, I do not get a response back when I call the api from postman. I know this is because of the multiple requests in the service and I tried to find a cleaner way to do it but keep failing to get it to work. I am trying to send back the final response which is the response of the request in the postPayment() function. Any help would be appreciated.
my service code is:
const db = require('../_helpers/db');
const crypto = require('crypto');
const request = require("request-promise");
module.exports = {
getAll
};
var targetEnv = 'https://sandbox.dev.clover.com/v2/merchant/';
var cardNumber = '6011361000006668';
async function getAll(data) {
var url = targetEnv + data.merchant_id + '/pay/key';
var options = {
url: url,
method: 'GET',
headers: {
Authorization: 'Bearer ' + data.api_token
}
};
request(options, (error, response, body) => {
if (!error && response.statusCode === 200) {
console.log('getAll ' +data);
processEncryption(JSON.parse(body), JSON.stringify(data));
}
});
}
// Process the encryption information received by the pay endpoint.
function processEncryption(jsonResponse, data) {
console.log('processEncryption ' +data);
var prefix = jsonResponse['prefix'];
var pem = jsonResponse['pem'];
// create a cipher from the RSA key and use it to encrypt the card number, prepended with the prefix from GET /v2/merchant/{mId}/pay/key
var encrypted = crypto.publicEncrypt(pem, Buffer(prefix + cardNumber));
// Base64 encode the resulting encrypted data into a string to Clover as the 'cardEncrypted' property.
var cardEncrypted = new Buffer(encrypted).toString('base64');
return postPayment(cardEncrypted, data);
}
// Post the payment to the pay endpoint with the encrypted card information.
async function postPayment(cardEncrypted, body) {
// POST to /v2/merchant/{mId}/pay
console.log('mid ' +JSON.parse(body));
var posturl = targetEnv + '9ZQTAJSQKZ391/pay';
var postData = {
"orderId": "4N3RBF33EBEGT",
"currency": "usd",
"amount": 2,
"tipAmount": 0,
"taxAmount": 0,
"expMonth": 12,
"cvv": 123,
"expYear": 2018,
"cardEncrypted": cardEncrypted,
"last4": 6668,
"first6": 601136,
"streetAddress": "123 Fake street",
"zip": "94080",
"merchant_id": "9ZQTAJSQKZ391",
"order_id": "4N3RBF33EBEGT",
"api_token": "4792a281-38a9-868d-b33d-e36ecbad66f5"
}
var options = {
url: posturl,
method: 'POST',
headers: {
'Authorization': 'Bearer ' + "4792a281-38a9-868d-b33d-e36ecbad66f5",
},
json: postData
};
request(options, (error, response, body) => {
if (!error && response.statusCode === 200) {
//console.log(response);
return response; <---- this response is what i need to show in postman
}
});
console.log(response);
}
my controller is:
const express = require('express');
const router = express.Router();
const tableOrderService = require('./cloverPayment.service');
// routes
router.post('/getAll', getAll);
module.exports = router;
function getAll(req, res, next) {
tableOrderService.getAll(req.body)
.then(users => res.json(users))
.catch(err => next(err));
}

Your asynchronous functions getAll() and postPayment() are not properly returning an asynchronous value (either via callback or promise).
I'd suggest converting everything to promises and returning a promise from getAll() and from postPayment(). And, since converting to promises, I'd remove the deprecated request-promise library in favor of the got() library. Then, you can call getAll(), get a promise back and use either the resolved value or the rejection to send your response from the actual request handler:
const db = require('../_helpers/db');
const crypto = require('crypto');
const got = require('got');
module.exports = {
getAll
};
var targetEnv = 'https://sandbox.dev.clover.com/v2/merchant/';
var cardNumber = '6011361000006668';
async function getAll(data) {
console.log('getAll ', data);
const url = targetEnv + data.merchant_id + '/pay/key';
const options = {
url: url,
method: 'GET',
headers: {
Authorization: 'Bearer ' + data.api_token
}
};
const response = await got(options);
return processEncryption(response, data);
}
// Process the encryption information received by the pay endpoint.
function processEncryption(jsonResponse, data) {
console.log('processEncryption ' + data);
const prefix = jsonResponse.prefix;
const pem = jsonResponse.pem;
// create a cipher from the RSA key and use it to encrypt the card number, prepended with the prefix from GET /v2/merchant/{mId}/pay/key
const encrypted = crypto.publicEncrypt(pem, Buffer(prefix + cardNumber));
// Base64 encode the resulting encrypted data into a string to Clover as the 'cardEncrypted' property.
const cardEncrypted = Buffer.from(encrypted).toString('base64');
return postPayment(cardEncrypted, data);
}
// Post the payment to the pay endpoint with the encrypted card information.
function postPayment(cardEncrypted, body) {
// POST to /v2/merchant/{mId}/pay
console.log('mid ', body);
const posturl = targetEnv + '9ZQTAJSQKZ391/pay';
const postData = {
"orderId": "4N3RBF33EBEGT",
"currency": "usd",
"amount": 2,
"tipAmount": 0,
"taxAmount": 0,
"expMonth": 12,
"cvv": 123,
"expYear": 2018,
"cardEncrypted": cardEncrypted,
"last4": 6668,
"first6": 601136,
"streetAddress": "123 Fake street",
"zip": "94080",
"merchant_id": "9ZQTAJSQKZ391",
"order_id": "4N3RBF33EBEGT",
"api_token": "4792a281-38a9-868d-b33d-e36ecbad66f5"
}
const options = {
url: posturl,
method: 'POST',
headers: {
'Authorization': 'Bearer ' + "4792a281-38a9-868d-b33d-e36ecbad66f5",
},
json: postData
};
return got(options);
}
And, then your controller:
const express = require('express');
const router = express.Router();
const tableOrderService = require('./cloverPayment.service');
// routes
router.post('/getAll', (req, res) => {
tableOrderService.getAll(req.body)
.then(users => res.json(users))
.catch(err => next(err));
});
module.exports = router;

Related

Save Response as variable and send it as Header - NodeJS

I'm currently working on sending a GET request to my own private Domain, alongside
various Headers that would be populated with various values such as 'Token' etc. - that are base64 encoded. This is running perfectly fine.
My main goal here is to send the Response of another request i'm sending to a different endpoint.
This is the modified code (I've removed various fields so please ignore any best practices for now).
const fs = require('fs');
const http = require('http');
const net = require('net');
const os = require("os");
const dns = require("dns");
const https = require("https");
var token = process.env.HOME+'/token.txt';
let base64data1 = '';
try {
if (fs.existsSync(token)) {
var data1 = fs.readFileSync(token,'utf8');
let buff1 = Buffer.from(data1);
base64data1 = buff1.toString('base64');
}} catch(error) {
console.log('')
}
var options = {
hostname: "myprivatedomain.com",
port: 443,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Token": base64data1
},
};
var req = https.request(options, (res) => {
res.on("data", (d) => {
process.stdout.write(d);
});
});
req.on("error", (e) => {
// console.error(e);
});
req.write(postData);
req.end();
My goal, as mentioned, is to add additional Header (In addition to the "Token" header) to my private domain, which will be populated by the Response for the following domain - www.seconddomain.com
I was thinking about creating a simple function that would retrieve the response, save it as variable and use it as my 2nd Header. Something similar to this -
function 2ndresponse(url) {
let data = '';
http.get(url, (resp) => {resp.on('data', (chunk) => {
data += chunk;
});
});
let responsevalue = Buffer.from(data);
base64data = responsevalue.toString('base64');
return http.get(url).then((resp) => resp.json());
}
var = 2ndresponse("http://www.seconddomain.com");
Hopefully this is clear enough (:
Update
I figured it out -
The workaround is to set both request in one function like so -
function req2() {
http.get({
hostname: 'seconddomain.com',
port: 80,
path: '/blahblah',
agent: false}, (res) => {
res.setEncoding('utf8');
let data = '';
res.on("data", (d) => {
var x;
x = d;
let buff5 = Buffer.from(x);
seconddomainvalue = buff5.toString('base64');
var options = {
hostname: "myprivatedomain.com",
port: 443,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": postData.length,
"token": tokenvalue,
"seconddomain": seconddomainvalue
},
};
var req = https.request(options, (res) => {
res.on("data", (d) => {
process.stdout.write(d);
});
});
req.on("error", (e) => {
// console.error(e);
});
req.write(postData);
req.end();
});
});
}
req2();
Thanks
The same can be achieved using the HTTP REQUEST also, But AXIOS allows us to make HTTP requests from both the browser and Node. js applications. It allows us to make both GET and POST requests which are the most used HTTP methods.
const axios = require('axios'); // Axios import
const controllerFunction = async () => {
const firstResponse = await axios.get('https://seconddomain.com'); // Here the request will wait, as it is synchronous
const bufferValue = Buffer.from(firstResponse.data);
const base64data = bufferValue.toString('base64');
const secondResponse = await axios.post('https://myprivatedomain.com', {"body": data}, {
headers: {
"Content-Type": "application/json",
"Token": base64data
}
}); // Here the second request can use the first request response data, as this code is executed synchronously
};
Also adding the AXIOS documentation link: https://www.npmjs.com/package/axios

Basic HTTP Authentication for CloudFront with Lambda#Edge in NodeJS

I am working on protecting a static website with a username and password. I created a basic HTTP Authentication for CloudFront with Lambda#Edge in NodeJS.
I am completely new to NodeJS. Initially, I had the user and the password hardcoded, and this worked properly.
'use strict';
exports.handler = (event, context, callback) => {
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
// Configure authentication
const authUser = 'user';
const authPass = 'pass';
// Construct the Basic Auth string
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
I stored my secrets in SSM and I want to retrieve them through the function. I tested this piece of code separately in Lambda and it returns the credentials as espected.
'use strict';
exports.handler = async (event, context, callback) => {
const ssm = new (require('aws-sdk/clients/ssm'))();
let userData = await ssm.getParameters({Names: ['website-user']}).promise();
let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
let user = userData.Parameters[0].Value;
let pass = userPass.Parameters[0].Value;
return {user, pass};
};
But when I stitch the two, I get 503 ERROR The request could not be satisfied.
Does anyone know what I might be doing wrong? Thank you for your help!
The complete code:
'use strict';
exports.handler = async (event, context, callback) => {
const ssm = new (require('aws-sdk/clients/ssm'))();
let userData = await ssm.getParameters({Names: ['website-user']}).promise();
let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
let user = userData.Parameters[0].Value;
let pass = userPass.Parameters[0].Value;
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
// Construct the Basic Auth string
let authString = 'Basic ' + new Buffer(user + ':' + pass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
After reading about promises I was able to resolve the error. Here's the solution that worked for me:
'use strict';
var AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
var ssm = new AWS.SSM();
function getParameter(param) {
return new Promise(function (success, reject) {
ssm.getParameter(param, function (err, data) {
if (err) {
reject(err);
} else {
success(data);
}
});
});
};
exports.handler = (event, context, callback) => {
let request = event.Records[0].cf.request;
let headers = request.headers;
let user = {Name: 'user-path', WithDecryption: false};
let pass = {Name: 'password-path', WithDecryption: false};
let authUser;
let authPass;
var promises = [];
promises.push(getParameter(user), getParameter(pass));
Promise.all(promises)
.then(function (result) {
authUser = result[0].Parameter.Value;
authPass = result[1].Parameter.Value;
console.log(authUser);
console.log(authPass);
let authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
})
.catch(function (err) {
console.log(err);
});
};
Thank you for sharing the solution. And let me do my humble contribution.
To successfully authenticate additionally need the choice right version of Node.js(14.x)
Grant AWS Lambda Access to SSM Parameter Store to retrieve AWS Parameter Store:
Go to Lambda function -> Configuration -> Permissions -> Role name -> Whatever-Your-Role-Name-> Add Permissions -> Create inline Policy -> JSON -> And input the policy as shown below:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath",
"ssm:PutParameter"
],
"Resource": [
"arn:aws:ssm:us-east-1:YOUR_ACCOUNT_NUMBER:PARAMETER_NAME_WITHOUT_LEADING_SLASH",
"arn:aws:ssm:us-east-1:252522211181:parameter/web-pass"
]
}
]
}
You could set "ssm:*" for the Action element in the policy to grant full parameter store access to the lambda function.
You could also set the Resource element to be *, which means the function can access all SSM parameters in the account.

Request doesn't go through the middleware in express

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.

How to send back the data got from response.on('end') to the client-side

I'm new to NodeJs and I'm having the problem with response.on('end') I still can't find out the method to send the data I got from the response to the client side.
exports.getCheckoutSession = catchAsync(async (req, res, next) => {
const uuidv1 = require('uuid/v1');
const https = require('https');
const tour = await Tour.findById(req.params.tourId);
console.log(tour);
//parameters send to MoMo get get payUrl
var endpoint = 'https://test-payment.momo.vn/gw_payment/transactionProcessor';
var hostname = 'https://test-payment.momo.vn';
var path = '/gw_payment/transactionProcessor';
var partnerCode = 'MOMO';
var accessKey = 'accessKey';
var serectkey = 'secretKey';
var orderInfo = 'pay with MoMo';
var returnUrl = 'https://momo.vn/return';
var notifyurl = 'https://callback.url/notify';
var amount = (tour.price * 23000).toString();
console.log(amount);
var orderId = req.params.tourId;
var requestId = req.params.tourId;
var requestType = 'captureMoMoWallet';
var extraData = 'merchantName=;merchantId='; //pass empty value if your merchant does not have stores else merchantName=[storeName]; merchantId=[storeId] to identify a transaction map with a physical store
//before sign HMAC SHA256 with format
//partnerCode=$partnerCode&accessKey=$accessKey&requestId=$requestId&amount=$amount&orderId=$oderId&orderInfo=$orderInfo&returnUrl=$returnUrl&notifyUrl=$notifyUrl&extraData=$extraData
var rawSignature =
'partnerCode=' +
partnerCode +
'&accessKey=' +
accessKey +
'&requestId=' +
requestId +
'&amount=' +
amount +
'&orderId=' +
orderId +
'&orderInfo=' +
orderInfo +
'&returnUrl=' +
returnUrl +
'&notifyUrl=' +
notifyurl +
'&extraData=' +
extraData;
//puts raw signature
console.log('--------------------RAW SIGNATURE----------------');
console.log(rawSignature);
//signature
const crypto = require('crypto');
var signature = crypto
.createHmac('sha256', serectkey)
.update(rawSignature)
.digest('hex');
console.log('--------------------SIGNATURE----------------');
console.log(signature);
//json object send to MoMo endpoint
var body = JSON.stringify({
partnerCode: partnerCode,
accessKey: accessKey,
requestId: requestId,
amount: amount,
orderId: orderId,
orderInfo: orderInfo,
returnUrl: returnUrl,
notifyUrl: notifyurl,
extraData: extraData,
requestType: requestType,
signature: signature
});
//Create the HTTPS objects
var options = {
hostname: 'test-payment.momo.vn',
port: 443,
path: '/gw_payment/transactionProcessor',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
}
};
//Send the request and get the response
console.log('Sending....');
var req = https.request(options, res => {
console.log(`Status: ${res.statusCode}`);
console.log(`Headers: ${JSON.stringify(res.headers)}`);
console.log('Type of body', JSON.stringify(res.body));
res.setEncoding('utf8');
let fullBody = '';
res.on('data', body => {
fullBody += body;
console.log(' Real Body');
console.log(fullBody);
//console.log('Type of body', body.payUrl);
// console.log(JSON.parse(body).payUrl);
// res.redirect(JSON.parse(body).payUrl);
});
res.on('end', () => {
const payURL = JSON.parse(fullBody).payUrl;
console.log('payUrl', payURL);
console.log('No more data in response.');
});
});
req.on('error', e => {
console.log(`problem with request: ${e.message}`);
});
// write data to request body
req.write(body);
req.end();
});
This is the url I got from response
payUrl https://test-payment.momo.vn/gw_payment/payment/qr?partnerCode=MOMO&accessKey=F8BBA842ECF85&requestId=5f38cc86954a6206211e2842&amount=23000&orderId=5f38cc86954a6206211e2842&signature=37ae247d56efd9ed6630b7d7d1435b88ffb8895956da5711a62ebbab8118aa7b&requestType=captureMoMoWallet
Can you please tell how could i send the data from res.on('end'), the "payURL" in the picture above, to client-side. I have tried some methods like res.writeHead, res.send, res.json( ) .... But they all returned error: res.send, res.writeHead, res.json... is not a function
This is my client-side, . If you guys don't mind , please also show me how to automatically redirect the payURL site above when the client click my button. Should I keep using window.location.replace like above ?
export const bookTour = async tourId => {
try {
const res = await fetch(
`http://localhost:3000/api/v1/bookings/checkout-session/${tourId}`,
{
method: 'POST',
body: 'a=1'
}
).then(res => window.location.replace(res.redirectURL));
console.log('The res', res);
} catch (err) {
showAlert('error', err);
}
};
This is my index.js
if (bookBtn) {
bookBtn.addEventListener('click', e => {
e.target.textContent = 'Processing...';
const tourId = e.target.dataset.tourId;
bookTour(tourId);
});
}
You're shadowing the req/res-variables from your getCheckoutSession-handler by using the same names for your http-request. If you change it to:
const request = https.request(options, response => {
// ...
let fullBody = '';
response.on('data', body => {
fullBody += body;
});
response.on('end', () => {
const payURL = JSON.parse(fullBody).payUrl;
// access the handler "res" object here
res.send(payURL);
// alternatively use res.json({payURL}) to send a json response
});
});
it should work fine.
Note: Nowadays you should definitely use const/let instead of var (see this for more information)
Simple,
res.on('end', () => {
const payURL = JSON.parse(fullBody).payUrl;
res.json({
payURL: payURL
})
});
or other way
res.on('end', () => {
const payURL = JSON.parse(fullBody).payUrl;
res.status(200).send({
payURL: payURL
});
});

I want to make test cases for request module call in Sinon (TDD) but when i made stubs for that it stopped on request call part

This code contains a login function in which it takes a encrypted key as a parameter and and after that it decrypted it and then make a request call using request node module. I want to make a test cases for that.
var request = require('request');
var Check = require('../libs/core/Check');
var util = require('./../libs/core/utility');
var crypto = require('crypto');
var decrypted_key;
var vendor = {};
vendor.login = function (encrypted, callback) {
util.decryption(encrypted, function (err, decrypted) {
decrypted_key = decrypted;
});
/*input data for request call*/
var requestVar = {
url: 'some URL',
method: 'GET',
headers: {
'Authorization': 'Basic ' + decrypted_key,
'Content-Type': 'application/json'
}
};
//request call
request(requestVar, function (err, data) {
if (err)
callback(err, null);
else {
if (data == undefined || data == "") {
var temp = JSON.stringify({ status_code: 401 });
temp = JSON.parse(temp);
callback(null, temp);
}
else {
data = JSON.stringify({ auth_key: decrypted_key });
callback(null, data);
}
}
});
};
module.exports = vendor;

Resources