Unit test for a sub-method which called from main method and include http-request authoraztion - javascript

I faced to a really complicated scenario, hope you guys give me a hint.
So I have a main method, which is a api endpoint, this method call another method to check if the user is authorized to use this endpoint or not.
The sub-endpoint which I called it apiAuthorazation send a get request to a thirdparty url, and this third-party return a response which says this user is authorized, or not!
So I already have a unit test for the main method, but now I want add this authorization part to it. I know I can use muck libs like Nock or other similar libraries, but my problem is how can I add this sub-method to my uit test.
This is my api endpoint method :
module.exports.api = (event, context, callback) => {
// Authorization
let getBearertoken = event.headers.Authorization.replace("Bearer ", '');
let isAuhtorized = utilities.apiAuthorazation(getBearertoken);
//Some other Codes
}
As you can see I passed a bearer token to my sub-method, and apiAuthorazation method will going to send this token to a third-party api, and the method is like this :
module.exports.apiAuthorazation = function (token){
let url = process.env.authApiUrl
requestLib(`${url}/${token}`, function (error, response, body) {
if (error) console.log('Error while checking token :', error);
if(response.isValidUser){
return true;
}
else{
return false;
}
});
}
Now my question is how can I include this sub-method to my main method unit test. I use mocha and chai for unit testing, bceause the berear token will expire soon, so when I run the test, I send a sample event which have the berear token in it, but it's already expired, so its kind of useless.

When you unit test Api, you can mock apiAuthorization for the two scenarios (true or false) and test if Api behaves as expected. Dont worry about what happens inside the sub method at all for the Api tests as you are testing Api here and the focus is not on what is happening inside the sub method, apiAuthorization.

Related

How to verify PayPal Webhooks in node.js?

I found some old answers dealing with PHP and this code example, but I am not sure whether this is outdated now since the repo is archived and I know that generally PayPal moved to an approach that just uses the REST API.
I would love if somebody could give an update here on whats the latest recommendation is and whether the code here from 2015 is outdated now.
/* Copyright 2015-2016 PayPal, Inc. */
"use strict";
var paypal = require('../../../');
require('../../configure');
// Sends the webhook event data to PayPal to verify the webhook event signature is correct and
// the event data came from PayPal.
// Note this sample is only for illustrative purposes. You must have a valid webhook configured with your
// client ID and secret. This sample may not work due to other tests deleting and creating webhooks.
// Normally, you would pass all the HTTP request headers sent in the Webhook Event, but creating a
// JSON object here for the sample.
var certURL = "https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a594-a5cafa77";
var transmissionId = "103e3700-8b0c-11e6-8695-6b62a8a99ac4";
var transmissionSignature = "t8hlRk64rpEImZMKqgtp5dlWaT1W8ed/mf8Msos341QInVn3BMQubjAhM/cKiSJtW07VwJvSX7X4+YUmHBrm5BQ+CEkClke4Yf4ouhCK6GWsfs0J8cKkmjI0XxfJpPLgjROEWY3MXorwCtbvrEo5vrRI2+TyLkquBKAlM95LbNWG43lxMu0LHzsSRUBDdt5IP1b2CKqbcEJKGrC78iw+fJEQGagkJAiv3Qvpw8F/8q7FCQAZ3c81mzTvP4ZH3Xk2/nNznEA7eMi3u1EjSpTmLfAb423ytX37Ts0QpmPNgxJe8wnMB/+fvt4xjYH6KNe+bIcYU30hUIe9O8c9UFwKuQ==";
var transmissionTimestamp = "2016-10-05T14:57:40Z";
var headers = {
'paypal-auth-algo': 'SHA256withRSA',
'paypal-cert-url': certURL,
'paypal-transmission-id': transmissionId,
'paypal-transmission-sig': transmissionSignature,
'paypal-transmission-time': transmissionTimestamp
};
// The eventBody parameter is the entire webhook event body.
var eventBody = '{"id":"WH-82L71649W50323023-5WC64761VS637831A","event_version":"1.0","create_time":"2016-10-05T14:57:40Z","resource_type":"sale","event_type":"PAYMENT.SALE.COMPLETED","summary":"Payment completed for $ 6.01 USD","resource":{"id":"8RS6210148826604N","state":"completed","amount":{"total":"6.01","currency":"USD","details":{"subtotal":"3.00","tax":"0.01","shipping":"1.00","handling_fee":"2.00","shipping_discount":"3.00"}},"payment_mode":"INSTANT_TRANSFER","protection_eligibility":"ELIGIBLE","protection_eligibility_type":"ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE","transaction_fee":{"value":"0.47","currency":"USD"},"invoice_number":"","custom":"Hello World!","parent_payment":"PAY-11X29866PC6848407K72RIQA","create_time":"2016-10-05T14:57:18Z","update_time":"2016-10-05T14:57:26Z","links":[{"href":"https://api.sandbox.paypal.com/v1/payments/sale/8RS6210148826604N","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/sale/8RS6210148826604N/refund","rel":"refund","method":"POST"},{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-11X29866PC6848407K72RIQA","rel":"parent_payment","method":"GET"}]},"links":[{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-82L71649W50323023-5WC64761VS637831A","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-82L71649W50323023-5WC64761VS637831A/resend","rel":"resend","method":"POST"}]}';
// The webhookId is the ID of the configured webhook (can find this in the PayPal Developer Dashboard or
// by doing a paypal.webhook.list()
var webhookId = "3TR748995U920805P";
paypal.notification.webhookEvent.verify(headers, eventBody, webhookId, function (error, response) {
if (error) {
console.log(error);
throw error;
} else {
console.log(response);
// Verification status must be SUCCESS
if (response.verification_status === "SUCCESS") {
console.log("It was a success.");
} else {
console.log("It was a failed verification");
}
}
});
Those SDKs are abstractions for the REST API but are no longer being maintained, so it is best not to use them.
There are two possible ways to verify Webhooks
Posting the message back to PayPal with the verify webhook sygnature REST API call. You'll need to use a client_id and secret get an access token first, same as all other REST API calls.
Verifying the cryptographic signature yourself (Java pseudocode here).
For either method, the "webhookId" -- as opposed to each webhook event id -- is 17 alphadigits and for security (anti-spoof) reasons not part of the Webhook message itself (you get it when registering for webhooks or reviewing existing subscribed hooks in the REST app config)
As it can sometimes be a point of confusion, it's worth mentioning that verifying webhooks is for your own information -- to confirm the message did in fact originate from PayPal, and not some other (malicious) actor.
But for PayPal itself to consider the webhook message successfully delivered (and not keep retrying), all that needs to happen is for the listener URL it's posted to to respond with an HTTP 200 OK status. That concludes the webhook message delivery.

Custom error message in Google Apps Script Error object [duplicate]

This is a doPost function inside a Google App that returns a Hello World message.
function doPost(e){
return ContentService.createTextOutput('Hello World');
}
Now suppose I want to only accept valid JSON to be posted to this Google App endpoint and I want to send a respones with Bad Request status. How can I do that. Here's the pseudo code:
function doPost(e){
try{
const data = JSON.parse(e.postData.contents);
return ContentService.createTextOutput('Hello World');
}catch(err){
// Send Bad Request
}
}
Issue and workaround:
Unfortunately, in the current stage, ContentService cannot modify the status code. When I saw the official document of Class ContentService, such method cannot be found. Ref It seems that this is the current specification.
So in your situation, as the current workaround, how about returning the value as JSON data? By this, you can check the value using the key of JSON data. For example, how about the following sample script?
When the correct value without the error is returned,
return ContentService.createTextOutput(JSON.stringify({value: 'value'}));
When the value with the error is returned,
return ContentService.createTextOutput(JSON.stringify({error: 'Error message'}));
When you need .setMimeType(ContentService.MimeType.JSON), please add this.
Note:
When I searched about this at the Google issue tracker, I couldn't find it. So how about reporting this as the future request? Ref
Reference:
Class ContentService
Here's another workaround that allows raising errors on the client side for errors on the web app side. For example, a client might need to catch errors such as bad url args sent to the web app (i.e. the OP's question), or catch errors thrown by a method that is called from doGet() or doPost().
As far as I know, when an error is thrown downstream of doGet() or doPost(), a text error message is returned in the response, but the web app request itself succeeds, so there is no error thrown on the client side. As #Tanaike said, there still seems no way for a Google web app dev to throw an HTTP error from the app (like 400 Bad Request or 500 Internal Server Error).
The idea involves returning a function body from the web app, which the client can use to create and run a dynamic function via the Function() constructor (this assumes Javascript is available on the client).
So the web app can be written to:
return a function body that will throw an error for bad args, server method errors, etc.
return a function body that will return intended JSON when there is no error
This is a bit of a hack, but it unifies error handling on the client side. The client makes the http request, constructs a function using the function body returned in the response, and then runs this function, all in one try{} block. Then both Google-raised http errors and web app downstream errors can be caught in the catch{} block.
Example setup for a Google Apps Script client making a request to a Google web app:
(1) In the web app doGet() or doPost() function:
// this string will be returned by the webapp
var fnBody;
// for bad url args, return a fnBody that will throw an error with an indicative message
if(!urlArgsOk()) {
fnBody = "'use strict'; throw new Error('POST args error');";
}
// if url args are ok, call server method
else {
try {
// if the method call succeeds, return a fnBody that will return the intended JSON
var returnObj = myServerMethod(methodArgs);
fnBody = "'use strict'; return JSON.stringify(" + JSON.stringify(returnObj) + ");";
}
catch(serverErr) {
// if the method call fails, return a fnBody that will throw an error ...
// ... simple example shown here, but info from serverErr can be included in fnBody
fnBody = "'use strict'; throw new Error('server error');";
}
}
// return fnBody, which can be run via Function() on the client
return ContentService.createTextOutput(fnBody).setMimeType(ContentService.MimeType.TEXT);
(2) On the client side (Google apps script client making a POST request)
// Set the url, payload, and fetch options
var url = "https://script.google.com/_______/exec?arg1=val1&arg2=val2";
var payload = getPayloadString(); // whatever POST payload needs to be sent
var options = {
'method' : 'POST',
'contentType': 'application/json',
'muteHttpExceptions': false, // let Google http exceptions come through
'payload' : payload,
'headers': {authorization: "Bearer " + ScriptApp.getOAuthToken()}
};
// Send a request to the web app
try {
// make the POST request - this throws Google-generated HTTP errors if any
var response = UrlFetchApp.fetch(url, options);
// create the dynamic function from the fnBody returned
var responseFn = new Function(response.getContentText());
// run the function - this returns intended JSON content
// or throws web app downstream errors if any
var responseJson = responseFn();
}
catch(err) {
// handle either source of error
console.log(err.message);
}
There are potential security risks associated with dynamic code, so I'm not sure how widely applicable this might be. I might use this in an aplication that lives entirely in a private GCP domain, i.e. with the web app restricted to same-domain users and the client app also in the same domain. Some security is also added by the 'use strict' directive, which boxes the dynamic function in by setting its this to undefined (ref). But it's still a good idea to think through the dynamic code implications (ref1, ref2).

adminuserglobalsignout not returning any data or not awaiting promise

When awaiting on a promise as a result of adminuserglobalsignout the promise seems to return but the data contains nothing.
The next call after signout is to authenticate the user. correct accessToken is returned but it's already revoked which makes me think the promise is not awaiting correctly and the new credentials are getting signed out by the previous call which is still running.
We are using globalsignout to prevent users from having multiple sessions so the workflow is along lines of
authenticate -> success -> signout (to kill any other sessions) -> authenticate -> success -> return token
I have updated my lambda package to include the latest SDK version 2.469.0 and no improvement.
Sometimes the timing must be OK as the returned credentials are still valid and the token can be used.
In BOTH cases there appears to be zero data returned from the AWS call
section of lambda code that calls the signout method in the User library
try {
signOutResult = await User.globalSignOut(userId, process.env.COGNITO_POOL);
} catch (err) {
log.error("AWS Global Signout Error: " + JSON.stringify(err));
responseBody = Helper.buildCORSResponse(502, JSON.stringify({ message: err }));
return callback(null, responseBody);
}
globalsignout code in User library:
return new Promise((resolve, reject) => {
log.info(`globalSignOut: Signing ${Username} out from all devices in pool ${UserPoolId}`);
const signOutRequest = new AWS.CognitoIdentityServiceProvider({ apiVersion: "2016-04-18" }).adminUserGlobalSignOut({ Username, UserPoolId });
const signOutPromise = signOutRequest.promise();
signOutPromise.
then((data) => {
log.debug("globalSignOut: Cognito SignOut Success: " + JSON.stringify(data));
resolve(data);
}).catch((err) => {
log.error("globalSignOut: Cognito SignOut Error: " + err);
reject(err);
});
});
}
In every call, we reach the resolve with no issue and then we carry on to authenticate the user again.
log.debug("globalSignOut: Cognito SignOut Success: " + JSON.stringify(data));
resolve(data);
Does anyone see any issues that could be causing this? I've tried a few ways to specify the promise and using the same format that works fine for other services and waits for the promise of the result before code execution continues.
All advice greatly appreciated
Update from AWS Support on this behavior in case anyone else finds this issue. I can confirm that adding a small delay before re-authenticating the user after global signout works fine.
Thank you for getting back to us.
In order to troubleshoot this issue, I tried to replicate it on my end by testing the below mentioned flow (as provided by you in the ) :
Authenticate user —> Global Sign Out —> Authenticate again —-> Check the validity of the new token
I wrote a python code to implement the above flow. In the flow, after calling the globalSignOut method, I authenticated the user again and checked the validity of the token by making getUser API call. But, the getUser API call returned the following response : “An error occurred (NotAuthorizedException) when calling the GetUser operation: Access Token has been revoked”
Now, I added sleep function after the GlobalSignOut for 1 second and the flow worked correctly. I did a few tests with the sleep time and noticed that if we add a sleep period of 0.6 seconds or greater, the API works correctly. So, it seems that the GlobalSignOut API call returns the response immediately but, the global logging out process (revoking of tokens) still runs in the backend for approximately 0.6 seconds.
For this, I reached out to the Cognito development team to confirm this behavior of GlobalSignOut API call. The team has confirmed that this is an expected behavior of GlobalSignOut API call. When GlobalSignOut is called all the tokens that were issued before that time is considered invalid. If the gap between signout and authentication is very small ( from my tests, this is approximately 0.6 seconds ), the token issue after authentication can be treated to be issued before signout call and, for better security, is considered invalid.
I hope that the above information helps. If there is anything else I can do to help, please let me know. I will be more than happy to assist you.
Have a great day ahead.
Best regards,
Amazon Web Services

How to export a variable from inside a Mocha describe block

I am currently using Chakram API Testing framework to test some REST API endpoints.
The first API gets a CSRF token which is used in the rest of the endpoints in the headers.
The CSRF API returns a JSON object - something like this
{
csrf_token : Aajkndaknsda99/adjaj29adja
}
This is how I'm doing it right now
describe('Hits the CSRF API to get the token',()=>{
let csrf_tok;
before(()=>{
return chakram.wait(response = chakram.get(API_ENDPOINT,headers));
});
it('gets the csrf token from the response',()=>{
return response.then(function(resp){
csrf_tok = response.body.csrf_token;
console.log(csrf_tok) //works fine and I can see the value correctly
exports.csrf = csrf_tok;
});
});
});
In my other file, where I need to use the CSRF token, I'm doing something like this
var token = require('../test/csrf_token');
var options ={
headers :{
//other headers
CSRF-TOKEN : token.csrf;
}
}
However, this is not working and the rest of the API endpoint tests are failing due to the token being passed as undefined. I hard coded the value of token and then the tests starts working. However, I do not want to do this every time (I plan on deploying this as a part of pipelines).
This issue seems to be that the variable cannot be accessed outside of Mocha's describe context. Is that right? If so, how can I overcome it?
You can declare the variable outside describe and then export it from outside 'describe'.
Other thing I have noticed regarding line:
csrf_tok = response.body.csrf_token;
It should be :
csrf_tok = resp.response.body.csrf_token;
This doesnt answer your specific question, but I needed something similar - where I needed to get an auth token that could then be passed to other tests.
I did this with a before hook in a shared.js file
before ( function getToken (done) {
chai.request(host)
.post(tokenURL)
.send({my params})
.end(function(err, response){
... getToken expectations
this.myToken = response.token;
done();
});
});
Then in test.js file you can just use 'myToken', as long as your shared.js file is in the root test dir
See https://gist.github.com/icirellik/b9968abcecbb9e88dfb2

How should AngularJS handle 403 error in $http.post due to outdated XSRF token?

An AngularJS version 1.4.8 app is getting an unhandled 403 error when its login form sends data to a backend REST authentication service after the user's browser has been left open for many (16 in this case) hours. Upon deeper analysis, the root cause is that the client AngularJS app has outdated cookies for XSRF-TOKEN and JSESSIONID, which causes the backend Spring Security to reject the request to the public /login1 service because Spring thinks the request is cross site request forgery.
The problem can be resolved manually if the user closes all browser windows and then re-opens a new browser window before making the request again. But this is not an acceptable user experience. I have read the AngularJS documentation at this link, and I see that I can add an errorCallback function, but how specifically should i re-write the function to handle the 403 error?
Here is the original this.logForm() method in the authorization service, which you can see does not handle 403 errors:
this.logForm = function(isValid) {
if (isValid) {
var usercredentials = {type:"resultmessage", name: this.credentials.username, encpwd: this.credentials.password };
$http.post('/login1', usercredentials)
.then(
function(response, $cookies) {
if(response.data.content=='login1success'){// do some stuff
} else {// do other stuff
}
}
);
}
};
Here is my very rough attempt at a revised version of the this.logForm() method attempting to handle a 403 error following the example in the AngularJS documentation:
this.logForm = function(isValid) {
if (isValid) {
var usercredentials = {type:"resultmessage", name: this.credentials.username, encpwd: this.credentials.password };
$http({ method: 'POST', url: '/login1', usercredentials })
.then(
function successCallback(response, $cookies) {
// this callback will be called asynchronously when the response is available
if(response.data.content=='login1success'){// do some stuff
} else {// do other stuff
}
},
function errorCallback(response, status) {// is status a valid parameter to place here to get the error code?
// called asynchronously if an error occurs or server returns response with an error status.
if(status == 403){
this.clearCookies();
// try to call this POST method again, but how? And how avoid infinite loop?
}
}
);
}
};
What specific changes need to be made to the code above to handle the 403 error due to server-perceived XSRF-TOKEN and JSESSIONID issues? And how can the post be called a second time after deleting the cookies without leading to an infinite loop in the case where deleting the cookies does not resolve the 403 error?
I am also looking into global approaches to error handling, but there is a combination of public and secure backend REST services, which would need to be handled separately, leading to complexity. This login form is the first point of user entry, and I want to handle it separately before looking at global approaches which would retain a separate handling of the login form using methods developed in reply to this OP.
You could restructure your http calls to auto retry, and use promises in your controllers (or whatever)
var httpPostRetry = function(url, usercredentials) {
var promise = new Promise(function(resolve, reject) {
var retries = 0;
var postRetry = function(url, usercredentials) {
if (retries < 3) {
$http({ method: 'POST', url: '/login1', usercredentials })
.then(function(result) {
resolve(result);
}).catch(function(result) {
retries ++;
postRetry(url, usercredentials);
});
} else {
reject(result);
}
};
}.bind(this));
return promise;
}
and then you would call
httpPostRetry(bla, bla).then(function(result) {
// one of the 3 tries must of succeeded
}).catch(function(result) {
// tried 3 times and failed each time
});
To handle specific http errors you can broadcast that specific error and handle that case in a specific controller. Or use a service to encapsulate the status and have some other part of your code handle the UI flow for that error.
$rootScope.$broadcast('unauthorized http error', { somedata: {} });
Does this help?
Have a look at the angular-http-auth module and how things are done there. I think one key element you would want to use is a http interceptor.
For purposes of global error handling, authentication, or any kind of
synchronous or asynchronous pre-processing of request or
postprocessing of responses, it is desirable to be able to intercept
requests before they are handed to the server and responses before
they are handed over to the application code that initiated these
requests. The interceptors leverage the promise APIs to fulfill this
need for both synchronous and asynchronous pre-processing.
After playing around with interceptors you can look at the angular-http-auth http buffer and the way they handle rejected requests there. If their interceptor receives a responseError, they add the config object - which basically stores all information about your request - to a buffer, and then any time they want they can manipulate elements in that buffer. You could easily adept their code to manipulate the config's xsrfHeaderName, xsrfCookieName, or parameters on your behalf when you receive a 403.
I hope that helps a little.

Categories