Change Node express Timeout try connection - node.js

i'm trying to change the timeout of a post express method using node-expose-sspi for authentication.
The problem is, none of what i tried worked so far.
My goal is to set the connection timeout 5s instead of the standard 120s.
When i enter the right credentials the code take approximately between 0.3s and 1.5s, and when the credentials are wrong the code takes between 7s to 120s
The "actual" code is:
const app = express();
app.post('myRoute', async (req, res) => {
try {
const domain = sso.getDefaultDomain();
const credentials: UserCredential = {
domain,
user: req.body.login,
password: req.body.password,
};
const ssoObject = await sso.connect(credentials);
if (ssoObject) {
req.session.sso = ssoObject;
return res.json({
sso: req.session.sso,
});
}
} catch (err) {
return res.status(401).json({
error: 'bad login/password.',
});
}
});
What i've tried so far was:
Change globally setTimeout:
var server = app.listen();
server.setTimeout(5000);
Change res inside the method:
res.setTimeout(5000, () => {
console.log('Request has timed out.');
res.send(408);
});
And Promise race:
let promisseConnect = new Promise(async (resolve, reject) => {
try {
const domain = sso.getDefaultDomain();
const credentials: UserCredential = {
domain,
user: req.body.login,
password: req.body.password,
};
const ssoObject = await sso.connect(credentials);
if(ssoObject){
resolve(ssoObject);
}else{
reject(401);
}
} catch (error) {
reject(401);
}
})
let promisseTimer = new Promise((resolve, reject) => {
let wait = setTimeout(() => {
clearTimeout(wait);
resolve(408)
}, 1000)
})
Promise.race([
promisseConnect,
promisseTimer,
])
.then((res) => console.log(res))
.catch((res) => console.log(res))
All of then didn't showed any error messages as far i remember, just the usual message after tried connection for 120s.

Related

How to test requests that require the user to be authenticated in Express

How can you test requests that require the user to be authenticated? I'm using local passport.js in my express app. I'm testing using Jest and Supertest. I've looked up countless posts, I tried supertest-session and that doesn't seem to work either. All of these posts are from almost 10 years ago so I'm not sure if they're still valid.
Here is the last thing that I've tried:
const request = require('supertest');
const app = require('../app');
const { pool } = require('../dbConfig');
let testAccount = { email: 'loginTestUser#gmail.com', password: '123456' }
const loginUrl = '/users/login';
const createRoomUrl = '/rooms/create'
const successfulRoomCreateUrl = '/users/dashboard';
const successfulLoginUrl = '/users/dashboard';
const failedRoomCreateUrl = '/';
afterAll(async () => {
pool.end();
});
describe('Room CRUD: Create | POST /rooms/create', () => {
describe('Given the user is Authenticated', () => {
let token;
beforeEach( async () => {
const response = await
request(app).post(loginUrl).type('form').send(testAccount);
token = { access_token: response.body.token }
});
test(`should get statusCode 200 if user is logged in`, async () => {
const createRoomResponse = await request(app).get(createRoomUrl).query(token);
// 302 since the user doesn't stay logged in
expect(createRoomResponse.statusCode).toEqual(200);
});
});
});
Here is what I tried with supertest-session and it also doesn't work:
const session = require('supertest-session');
const myApp = require('../app');
let testAccount = { email: 'loginTestUser#gmail.com', password: '123456' }
var testSession = null;
beforeEach(function () {
testSession = session(myApp);
});
it('should fail accessing a restricted page', function (done) {
testSession.get('/rooms/create')
.expect(302)
.end(done)
});
it('should sign in', function (done) {
testSession.post('/users/login')
.send(testAccount)
.expect(302) // get redirected to dashboard
.end(done);
});
describe('after authenticating session', function () {
var authenticatedSession;
beforeEach(function (done) {
testSession.post('/users/login')
.send(testAccount)
.expect(302) // get redirected to /users/dashboard
.end(function (err) {
if (err) return done(err);
authenticatedSession = testSession;
return done();
});
});
it('should get a restricted page', function (done) {
authenticatedSession.get('/rooms/create')
.expect(200) // <------ still get a 302 (redirect if user !logged
.end(done)
});
});
Turns out all I needed to do was append .type('form') like so:
beforeEach(function (done) {
testSession.post('/users/login').type('form')
.send(testAccount)
.expect(302) // get redirected to /users/dashboard
.end(function (err) {
if (err) return done(err);
authenticatedSession = testSession;
return done();
});
});

Cannot use Async/Await in a Serverless NodeJS Function

I'm new to Serverless with NodeJS, I'm trying to use an Async function to hash a password and perform some stuff in a database, the problem is that when I declare the functions async I always get this error:
But if I remove the async keyword here:
module.exports.login = async (event, context, callback) => {
the function runs properly, but of course I won't be able to use promises within the function.
This is an endpoint, to make API calls.
Here's my code:
'use strict';
require('dotenv').config({ path: './.env' });
const dataBase = require('./utils/db');
const bcrypt = require('bcrypt');
const encryptPassword = async (plainPassword) => {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
return hashedPassword;
};
module.exports.login = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const parsedBody = JSON.parse(event.body);
const connect = dataBase.connectToDatabase();
const newPassword = await encryptPassword('test');
console.log(newPassword);
connect.query('SELECT * FROM users', (error, results, fields) => {
if (error) {
console.error(error);
callback(null, {
statusCode: 500,
body: JSON.stringify({
error: JSON.stringify(error),
message: 'Internal Server Error Booh!',
}),
});
}
if (results) {
callback(null, {
statusCode: 200,
body: JSON.stringify({
error: results,
message: 'No Error',
}),
});
}
});
};
Here my db connection and config:
db.js
const mysql = require('mysql');
const isDev = true;
// Create mySQL Connection
const connectToDatabase = () => {
const pool = mysql.createPool({
host: isDev ? process.env.DATABASE_HOST_DEV : process.env.DATABASE_HOST_PROD,
user: isDev ? process.env.DATABASE_USER_DEV : DATABASE_USER_PROD,
password: isDev ? process.env.DATABASE_PASSWORD_DEV : DATABASE_PASSWORD_PROD,
database: isDev ? process.env.DATABASE_DATABASE_DEV : DATABASE_DATABASE_PROD,
multipleStatements: true,
});
return pool;
};
exports.connectToDatabase = connectToDatabase;
exports.mysql = mysql;
What am I missing?
EDIT:
The purpose of all this, it's because I'm learning serverless.
All this will end up in an AWS Lambda with an endpoint using API Gateway.
So, when you call this endpoint, you will be sending some params to register/login into an app.
I need to use async/await because if you register an account, the password needs to be hashed and then store into a database or if you login, the password will need to be compared, both of these actions are asynchronous ones.
That's why I need the endpoint to be an async function.
EDIT 2:
Reading this post: https://github.com/netlify/netlify-dev-plugin/issues/160
As Phil mention, async and callback shouldn't be used togehter, so I modified my code like this, and same error:
'use strict';
require('dotenv').config({ path: './.env' });
const dataBase = require('./utils/db');
const bcrypt = require('bcrypt');
const encryptPassword = async (plainPassword) => {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
return hashedPassword;
};
module.exports.login = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const parsedBody = JSON.parse(event.body);
const connect = dataBase.connectToDatabase();
const newPassword = await encryptPassword('test');
console.log(newPassword);
connect.query('SELECT * FROM users', (error, results, fields) => {
if (error) {
console.error(error);
return {
error: 'some error to test',
};
}
if (results) {
return {
body: 'someBody',
};
}
});
};
FINAL FUNCTIONAL CODE:
In case any wonder how to make these queries as promises, here's my approach, hope it helps anyone out there struggling with the same.
'use strict';
require('dotenv').config({ path: './.env' });
const dataBase = require('./utils/db');
const bcrypt = require('bcrypt');
const encryptPassword = async (plainPassword) => {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
return hashedPassword;
};
module.exports.login = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const parsedBody = JSON.parse(event.body);
const connect = dataBase.connectToDatabase();
const hashedPassword = await encryptPassword('test');
console.log(hashedPassword);
return new Promise((resolve, reject) => {
connect.query('SELECT * FROM users', (error, result) => {
if (error) {
reject(
callback(null, {
statusCode: 500,
body: JSON.stringify({
error: JSON.stringify(error),
message: 'Internal Server Error Booh!',
}),
})
);
}
if (result) {
resolve(
callback(null, {
statusCode: 200,
body: JSON.stringify({
data: result,
message: 'No Error',
}),
})
);
}
});
});
};
Regards.
Taking a quick look at the docs, it doesn't look like you should be mixing async with the callback arg. Just use one or the other, not both.
You could migrate to the mysql2 library so you can use promises or stick with the one you've got and use encryptPassword("test").then() instead of await
// no async
module.exports.login = (event, context, callback) => {
// remove this, you don't want it
// context.callbackWaitsForEmptyEventLoop = false;
const parsedBody = JSON.parse(event.body);
const connect = dataBase.connectToDatabase();
encryptPassword("test").then(newPassword => { // use .then()
console.log(newPassword);
// callback APIs are a pain, recommend migrating to mysql2 and use promises
connect.query('SELECT * FROM users', (error, results, fields) => {
if (error) {
console.error(error);
return callback(null, {
statusCode: 500,
body: JSON.stringify({
error: JSON.stringify(error),
message: 'Internal Server Error Booh!',
}),
});
}
callback(null, {
statusCode: 200,
body: JSON.stringify({
error: results, // error?
message: 'No Error',
}),
});
}
});
}

How to mock function using node-tap on fastify inject

I want to make 100% coverage on this function with node-tap but I can't mock the error part, it always throw
Cannot find module 'create' Require stack: - /home/mptr8/Code/Projects/me/fastify-example/fastify-postgres/test/integration.js
But I have create function on my query.js file, what do I do wrong here? Why it doesn't invoke the method?
t.mock("../query.js", {
create: () => {
throw new Error();
},
});
I also try this combination, because query.js are dependent on db.js. Now the mock error gone but still I'm not getting the error throw from my fastify.inject.
t.mock("../db.js", {
"../query.js": {
create: () => { throw new Error() },
},
});
app.post("/", async (request, reply) => {
try {
const { body } = request;
const book = create(body.title);
reply.send(book);
} catch (error) {
// this part are not covered
reply.code(500).send({ message: "internal server error" });
}
});
here are my complete code. You can see the full code on this github repository.
// server.js
const fastify = require("fastify");
const {
migration,
create,
} = require("./query");
const db = require("./db");
function build(opts = {}) {
const app = fastify(opts);
migration();
app.post("/", async (request, reply) => {
try {
const { body } = request;
const book = create(body.title);
reply.send(book);
} catch (error) {
reply.code(500).send({ message: "internal server error" });
}
});
app.addHook("onClose", (_instance, done) => {
db.close();
done();
});
}
module.exports = build;
// db.js
const { Pool } = require("pg");
const pool = new Pool({
connectionString:
"postgresql://postgres:postgres#localhost:5432/fastify_postgres?schema=public",
});
module.exports = {
query: (text, params) => pool.query(text, params),
close: () => pool.end(),
};
// query.js
const db = require("./db");
async function migration() {
await db.query(`
CREATE TABLE IF NOT EXISTS books (
id serial PRIMARY KEY,
title varchar (255) NOT NULL
)
`);
}
async function create(title) {
return await db.query("INSERT INTO books (title) VALUES ($1)", [title]);
}
module.exports = { migration, create };
// test.js
const tap = require("tap");
const fastify = require("../server");
tap.test("coba", async (t) => {
const app = await fastify();
t.test("should success create books", async (t) => {
const response = await app.inject({
method: "POST",
url: "/",
payload: {
title: "Hello,World!",
},
});
t.equal(response.statusCode, 200);
});
t.test("should throw error", async (t) => {
const app = await fastify();
// it doesn't throw the error :((
t.mock("../query.js", {
create: () => {
throw new Error();
},
});
const response = await app.inject({
method: "POST",
url: "/",
payload: {
title: "Hello,World!",
},
});
t.equal(response.statusCode, 500);
// call app close on last test child to close app and db properly
app.close();
});
});
You should use the returned value by the t.mock function:
const build = t.mock({
"../server": {
"./query.js": {
create: () => { throw new Error() },
}
}
})
const app = await build({})

How can I make my Hapi route wait for data before returning a value?

I am using Hapi.js and have a route that I want to use to fetch data and then return a result.
I have tried to use async/await, but I must be doing something wrong because while the function I am calling eventually prints a result to the console, the route is returning without waiting for that function to return a value.
'use strict';
const Hapi = require('#hapi/hapi');
const HandyStorage = require('handy-storage');
var ethBalance ='';
// Connection to public blockchain via Infura.io
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/cf44bc52af3743bcad5f0b66813f8740"));
// Initialize Handy Storage
const storage = new HandyStorage({
beautify: true
});
//Get ETH address from Handy Storage
storage.connect('./preferences.json');
var walletAddress = storage.state.wallet;
// Get wallet balance
const getWalletBalance = async () => {
web3.eth.getBalance(`${walletAddress}`, async function(err, result) {
if (err) {
console.log('There was an error: ' + err);
return ({ error: 'The wallet balance call failed.' });
} else {
ethBalance = await web3.utils.fromWei(result, "ether");
console.log("This should be first: The wallet balance via API call is " + ethBalance + " ETH.");
return ethBalance; // I expect the walletbalance route to wait for this to be returned
}
});
};
// API Server
const init = async () => {
// Connection settings
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
// Get wallet balance
server.route({
method: 'GET',
path: '/walletbalance/',
handler: async (request, h) => {
let result = null;
try {
result = await getWalletBalance();
console.log('This should be second, after the getWalletBalance function has printed to the console.'); // this prints first, so await isn't working as expected
return ({ ethBalance: result });
} catch (err) {
console.log('Error in walletbalance route');
}
}
});
// 404 error handling
server.route({
method: '*',
path: '/{any*}',
handler: function (request, h) {
return ({
message: 'Error!'
});
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
Any idea where I have gone wrong here? This is the first time I have used async/await.
ETA: My console looks like this:
[nodemon] starting `node index.js`
Server running on http://localhost:3000
This should be second, after the getWalletBalance function has printed to the console.
This should be first: The wallet balance via API call is 4061.894069996147660079 ETH.
And this is the JSON I get back when I use the wallet balance route:
{}
Based on the answer I was given, I was able to get the results I wanted with this:
'use strict';
const Hapi = require('#hapi/hapi');
const HandyStorage = require('handy-storage');
var ethBalance ='';
// Connection to public blockchain via Infura.io
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/cf44bc52af3743bcad5f0b66813f8740"));
// Initialize Handy Storage
const storage = new HandyStorage({
beautify: true
});
//Get ETH address from Handy Storage
storage.connect('./preferences.json');
var walletAddress = storage.state.wallet;
// Get wallet balance
async function getWalletBalance(){
let ethBalance = await web3.eth.getBalance(`${walletAddress}`);
if (ethBalance.err) {
console.log('error in the called function');
} else {
return ethBalance;
}
}
// API Server
const init = async () => {
// Connection settings
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
// Get wallet balance
server.route({
method: 'GET',
path: '/walletbalance/',
handler: async (request, h) => {
try {
const result = await getWalletBalance();
const ethBalanceInWei = web3.utils.fromWei(result, "ether");
return ({ balance: ethBalanceInWei });
} catch (err) {
console.log('Error in walletbalance route');
}
}
});
// 404 error handling
server.route({
method: '*',
path: '/{any*}',
handler: function (request, h) {
return ({
message: 'Error!'
});
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
Thank you for the help! That got me going in the right direction.
Basically your getWalletBalance function is using multiple concepts. callback style functions and inside that you are using await. I have restructured your code a little bit. Hopefully that should fix the issue which you are facing.
'use strict';
const Hapi = require('#hapi/hapi');
const HandyStorage = require('handy-storage');
var ethBalance ='';
// Connection to public blockchain via Infura.io
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/cf44bc52af3743bcad5f0b66813f8740"));
// Initialize Handy Storage
const storage = new HandyStorage({
beautify: true
});
//Get ETH address from Handy Storage
storage.connect('./preferences.json');
var walletAddress = storage.state.wallet;
function getWalletBalance() {
return Promise((resolve, reject) => {
web3.eth.getBalance(`${walletAddress}`, (err, result) => {
if (err) {
console.log('There was an error: ' + err);
reject({ error: 'The wallet balance call failed.' });
} else {
resolve(result);
}
});
});
}
// API Server
const init = async () => {
// Connection settings
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
// Get wallet balance
server.route({
method: 'GET',
path: '/walletbalance/',
handler: async (request, h) => {
try {
const result = await getWalletBalance();
ethBalance = await web3.utils.fromWei(result, "ether");
return ethBalance;
} catch (err) {
console.log('Error in walletbalance route');
}
}
});
// 404 error handling
server.route({
method: '*',
path: '/{any*}',
handler: function (request, h) {
return ({
message: 'Error!'
});
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();

UnhandledPromiseRejectionWarning when adding a new record to DB

I am trying to add new records to my MongoDB database. The records come in the form of an array, and then, for each record, I add it as a new item to MongoDB.
The problem is that every time I try to add some records (30-50 records) I get this message:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
followed by this one:
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
This is the snippet I use for adding records:
// update the blacklist with a new entry
router.post("/updateBlacklist", async (req, res) => {
req.body.product_id.forEach(async element => {
const blacklistItem = new Blacklist({ product_id: element });
try {
const savedItem = await blacklistItem.save();
res.json(savedItem);
} catch (err) {
console.log(err);
res.json({ message: err });
}
});
});
The same warning appears also when I try to download some photos using this function:
router.post("/product_ids", async (req, res) => {
await downloadPhotos(req.body.items_id);
});
the function I call inside this route:
const axios = require("axios");
const download = require("./utils/download");
const downloadPhotos = async itemsIDs => {
try {
console.log(itemsIDs.length);
await itemsIDs.forEach(ID => {
axios
.get(
`https://${process.env.SHOPIFY_API_KEY}:${
process.env.SHOPIFY_PASSWORD
}#blablabla.myshopify.com/admin/products/${ID}/images.json?fields=id, src`
)
.then((req, res) => {
console.log(req.data.images[0].src);
download(req.data.images[0].src, req.data.images[0].id, 3000);
});
});
} catch (err) {
console.log(err);
}
};
and the download function:
const Fs = require("fs");
const Path = require("path");
const Axios = require("axios");
async function downloadImage(url, filename, timeout) {
const appDir = Path.dirname(require.main.filename);
const path = Path.resolve(appDir, "images", `${filename}.jpg`);
const writer = Fs.createWriteStream(path);
const response = await Axios({
url,
method: "GET",
responseType: "stream",
timeout: timeout
});
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on("finish", resolve);
writer.on("error", reject);
});
}
module.exports = downloadImage;
What am I doing wrong in this approach?
This error:
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
It's because of these lines:
res.json(savedItem);
res.json({ message: err });
You're running these lines several times, because they are inside the loop, you can't respond a request more than once because the connection it's closed after the first call.
Try with this:
router.post("/updateBlacklist", async (req, res) => {
const savedList = [];
req.body.product_id.forEach(async element => {
const blacklistItem = new Blacklist({ product_id: element });
try {
const savedItem = await blacklistItem.save();
savedList.push(savedItem)
} catch (err) {
console.log(err);
savedList.push({ message: err });
}
});
res.json(savedItem);
});
UPDATE (suggestion):
router.post("/updateBlacklist", async (req, res) => {
const items = req.body.product_id.map(element =>
new Blacklist({ product_id: element })
});
const result = await Blacklist.insertMany(items); // not sure about the syntax
res.json(result);
});

Resources