configure knexfile.js using aws secret manager - javascript

I need to configure the knexfile.js using the secrets retrieve from the secret manager.
I retrieve the secrets from secret manager and stores it in secret variable and use it in configuration.
var AWS = require('aws-sdk'),
endpoint = "abcd",
region = "us-east-1",
secretName = "abcd",
secret,
binarySecretData;
var client = new AWS.SecretsManager({
endpoint: endpoint,
region: region
});
client.getSecretValue({
SecretId: secretName
}, function (err, data) {
if (err) {
if (err.code === 'ResourceNotFoundException')
console.log("The requested secret " + secretName + " was not found");
else if (err.code === 'InvalidRequestException')
console.log("The request was invalid due to: " + err.message);
else if (err.code === 'InvalidParameterException')
console.log("The request had invalid params: " + err.message);
} else {
if (data.SecretString !== "") {
secret = data.SecretString;
} else {
binarySecretData = data.SecretBinary;
}
}
});
module.exports = {
development: {
client: secret.localClient,
connection: {
host: secret.localHost,
user: secret.localUser,
password: secret.localPassword,
database: secret.localDatabase,
charset: "utf8"
}
},
};
But it shows an error
TypeError: Cannot read property 'localClient' of undefined

This is now possible in Knex. You can pass an async function to the configuration.
async function getConfig() {
return new Promise((resolve, reject) => {
client.getSecretValue({ SecretId: 'SECRETID' }, function(
err,
data
) {
if (err) {
console.log('secretsErr', err);
reject(err);
} else {
console.log('Secrets Manager call successful');
if ('SecretString' in data) {
let secret = data.SecretString;
secret = JSON.parse(secret);
const config = {
user: secret.DbUser,
password: secret.DbPassword,
server: secret.DbServer,
database: secret.DbDatabase,
expirationChecker: () => false,
options: {
encrypt: true,
enableArithAbort: true
}
};
resolve(config);
} else {
console.log('no secret found');
reject();
}
}
});
}
let knex = require('knex')({
client: 'mssql',
connection: async function() {
return await getConfig();
}
});

Getting secret is asynchronous operation, so your variable secret it doesn't exist yet when you are trying to export it from knexfile.js.
You probably should first fetch secret to be stored somewhere locally, when starting up virtual machine and then in knexfile.js read it synchronously for example from local file.

Related

How to mock jsonwebtoken + jwsks-rsa for custom lambda Authroizer

I am using lambda custom authoriser in front of api endpoint.
so whenever a request comes on that endpoint api gateway calls authoriser to validate jwt token and based on validation it generates a policy.
Also jwt tokens are generated by auth0 api, i know there is a library https://www.npmjs.com/package/mock-jwks which mocks auth0 jwt token but it a http request to endpoint which is not possible in my case
the code is pretty self explanatory howver i stuck with mocking jsonwebtoken and jwks-rsa library? what could be the ideal way to test such kind of function?
my authorizer.js
require('dotenv').config();
const jwks = require('jwks-rsa');
const jwt = require('jsonwebtoken');
const createPolicyDocument = (effect) => {
const policy = {
Version: '2012-10-17',
Statement: [
{
Effect: effect,
Action: 'execute-api:Invoke',
Resource:
'xxxxxxxxxxxxxxxxx',
},
],
};
return policy;
};
// Extract the Bearer token from the event sent by lambda and return it to the authorizer
const extractToken = (event) => {
const tokenWithBearer = event.authorizationToken;
if (!tokenWithBearer) {
throw new Error(' "event.authorization" paramters is missing');
}
const bearer = tokenWithBearer.split(' ');
if (!bearer) {
throw new Error('Invalid token');
}
return bearer[1];
};
const jwtVerifyOptions = {
audience: process.env.AUDIENCE,
issuer: process.env.TOKEN_ISSUER,
};
const client = jwks({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 10,
jwksUri: process.env.JWKS_URI,
});
function verifyToken(token) {
return new Promise((resolve, reject) => {
const tempDecodedToken = jwt.decode(token, { complete: true });
console.log(token);
console.log(tempDecodedToken);
const { kid } = tempDecodedToken.header;
client.getSigningKey(kid, (err, key) => {
if (err) {
console.log('signinin key get error', err);
reject('Deny');
}
const signingKey = key.publicKey || key.rsaPublicKey;
console.log(signingKey);
jwt.verify(token, signingKey, jwtVerifyOptions, (error, decoded) => {
if (error) {
console.log('jwt verify error', error);
reject('Deny');
}
console.log(decoded);
resolve({ response: 'Allow', decoded });
});
});
});
}
module.exports.auth = async (event, context, callback) => {
try {
const token = extractToken(event);
const tokenResponse = await verifyToken(token);
console.log(tokenResponse);
if (tokenResponse.response === 'Allow') {
return {
principalId: tokenResponse.decoded.sub,
policyDocument: createPolicyDocument('Allow'),
context: { scope: tokenResponse.decoded.scope },
};
}
return {
policyDocument: createPolicyDocument('Deny'),
};
} catch (err) {
return {
policyDocument: createPolicyDocument('Deny'),
};
}
};

Async Function working in Express but not NestJs

I initially created a little express server to run a report and file write function.
var ssrs = require('mssql-ssrs');
var fs = require('fs');
const express = require('express')
const app = express()
const port = 3001
app.get('/', (req, res) => {
reportCreation();
res.send('File Created');
})
app.get('/api', (req, res) => {
reportCreation();
res.json({'File Created': true});
})
app.listen(port, () => {
console.log(`Report Api listening at http://localhost:${port}`)
})
The function reportCreation() is an async function which gets a report from a SSRS. This works fine
async function reportCreation() {
var serverUrl = 'http://reportServerName/ReportServer/ReportExecution2005.asmx';
ssrs.setServerUrl(serverUrl);
var reportPath = '/ApplicationPortalReports/TestReportNew';
var fileType = 'word';
var parameters = { ApplicationId: 3, TrainingCardId: 267, PortalPersonId: 52 }
var auth = {
username: 'USERNAME',
password: 'PASSWORD',
domain: 'dmz'
};
try {
var report = await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth)
} catch (error) {
console.log(error);
}
console.log(report);
try {
fs.writeFile('ReportApiTest.doc', report, (err) => {
if (!err) console.log('Data written');
});
} catch (error) {
console.log(error);
}
I have been working a lot with NestJs recently and wanted to use the same function but within a NestJs service.
#Injectable()
export class AppService {
async getReport(): Promise<string> {
const serverUrl = 'http://reportServerName/ReportServer/ReportExecution2005.asmx';
ssrs.setServerUrl(serverUrl);
const reportPath = '/ApplicationPortalReports/TestReportNew';
const fileType = 'word';
// var parameters = {appId: 3, ReportInstanceId: 1 }
const parameters = {ApplicationId: 3, TrainingCardId: 267, PortalPersonId: 52 };
const auth = {
username: 'USERNAME',
password: 'PASSWORD',
domain: 'dmz'
};
try {
var report = await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth)
} catch (error) {
console.log(error);
}
console.log(report);
// excel = xlsx
// word = doc
// pdf = pdf
try {
fs.writeFile('ReportApiTest.doc', report, (err) => {
if (!err) { console.log('Data written');
return 'File Written Succesfully'}
});
} catch (error) {
console.log(error);
return 'File Write Error'
}
}
}
As you can see the files are almost identical, but when I run it through NestJs I get an error which looks like a problem with the line
var report = await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth)
not awaiting. Why does this work with Express and not NestJS? Below is the error from NestJs
buffer.js:219
throw new ERR_INVALID_ARG_TYPE(
^
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of type string, Buffer, ArrayBuffer,
Array, or Array-like Object. Received type undefined
at Function.from (buffer.js:219:9)
at new Buffer (buffer.js:179:17)
at Object.createType3Message (C:\Projects\SSRS-report-api\ssrs-report-api\node_modules\httpntlm\ntlm.js:172:19)
at sendType3Message (C:\Projects\SSRS-report-api\ssrs-report-api\node_modules\httpntlm\httpntlm.js:77:23)
at Immediate._onImmediate (C:\Projects\SSRS-report-api\ssrs-report-api\node_modules\httpntlm\httpntlm.js:101:4)
within the mssql-ssrs node package the getReportByURL looks like this
async function getReportByUrl(reportPath, fileType, params, auth) {
try {
var config = {
binary: true, // very important
username: auth.userName,
password: auth.password,
workstation: auth.workstation,
domain: auth.domain,
url: soap.getServerUrl()
+ "?" + (testReportPath(reportPath).replace(/\s/g, '+'))
+ "&rs:Command=Render&rs:Format=" + reportFormat(fileType)
+ formatParamsToUrl(params)
};
} catch (err) { report.errorHandler(err) }
return new Promise((resolve, reject) => {
config.url = encodeURI(config.url);
httpntlm.post(config, function (err, res) {
if (res.statusCode === 500) { reject(res) }
if (err || res.statusCode !== 200) { reject(err) }
else { resolve(res.body) }
})
})
}
Here is the app.controller.ts
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getHello(): Promise<string> {
return this.appService.getReport();
}
}
This is not an answer for the question. But after I see your code, I can see an error you will face in future if await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth) failed. Actually you see above error because of this.
The way you used the try catch is really bad.
Here's the way I code it.
#Injectable()
export class AppService {
async getReport(): Promise<string> {
const serverUrl = 'http://reportServerName/ReportServer/ReportExecution2005.asmx';
ssrs.setServerUrl(serverUrl);
const reportPath = '/ApplicationPortalReports/TestReportNew';
const fileType = 'word';
// var parameters = {appId: 3, ReportInstanceId: 1 }
const parameters = {ApplicationId: 3, TrainingCardId: 267, PortalPersonId: 52 };
const auth = {
username: 'USERNAME',
password: 'PASSWORD',
domain: 'dmz'
};
const report = await ssrs.reportExecution.getReportByUrl(reportPath, fileType, parameters, auth)
return new Promise(function(resolve, reject) {
fs.writeFile('ReportApiTest.doc', report, , function(err) {
if (err) reject(err);
resolve("File Created");
});
});
}
And in my controller
#POST
async writeFile() {
try {
const res = await this.appService.getReport();
return res;
} catch(err) {
// handle your error
}
}
I had fudged the code in the node_module changing the userName variable to username and had not done the same in the NestJS version. I forgot I had done that so now it is working.

Wait for child functions to execute before returning

So I have this script that renames files on Google Drive.
It reads two columns from Google Spreadsheet, containing fileID and filename.
Finds the files with fileId on Google Drive, and renames it with the filename from the sheet.
Everything is working fine. To make it easier and available as a route in an API, I exported it.
The problem is, the main module.exports.rename() function has all these child functions that are being executed. It doesn't wait for the execution and renaming and just returns a response.
It just returns the message "Renamed 1 Files Successfully !", since the counter ctr is initially 1.
I tried to use a boolean variable, set as false. Then set it true in the getSheetandBatchRename(), and added an if statement above return for the main function. But that doesn't work of course.
I think I am missing something fundamental here.
What is the right way to achieve this?
module.exports.rename = async function(req, res) {
const fs = require("fs");
const readline = require("readline");
const { google } = require("googleapis");
// If modifying these scopes, delete token.json.
const SCOPES = [
"https://www.googleapis.com/auth/drive.metadata",
"https://www.googleapis.com/auth/spreadsheets.readonly"
];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const PATH = "./server/components/scripts/renamingTool/";
const TOKEN_PATH = PATH + "token.json";
let taskDone = false;
// Load client secrets from a local file.
fs.readFile(PATH + "credentials.json", (err, content) => {
if (err) return console.log("Error loading client secret file:", err);
// Authorize a client with credentials, then call the Google Drive API.
authorize(JSON.parse(content), getSheetandBatchRename);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0]
);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: SCOPES
});
console.log("Authorize this app by visiting this url:", authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Enter the code from that page here: ", code => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error("Error retrieving access token", err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), err => {
if (err) return console.error(err);
console.log("Token stored to", TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
async function getSheetandBatchRename(auth) {
try {
const data = await getSheet(auth);
console.log("Number of inventories : " + data.length);
taskDone = true;
} catch (err) {
console.log("getSheetandBatchRename");
console.log(err);
}
}
function updateExt(ext) {
// to solve if ther were some problems
if (ext === "1" || ext === "0" || ext === "2" || ext === " Aula 2")
ext = "jpg";
return ext;
}
function getSheet(auth) {
const sheets = google.sheets({ version: "v4", auth });
return new Promise((resolve, reject) => {
sheets.spreadsheets.values.get(
{
spreadsheetId: "1QV3ZshFNVrPCSvMP1O0KjXTcx5pfQMNuNeFbku-TKm4",
range: "Final!A2:B"
},
async (err, res) => {
if (err) reject(err);
let data = res.data.values;
try {
for (const [i, el] of data.entries()) {
let filename = await getafile(auth, el[0], i);
if (filename !== -1) {
let ext = filename.split(".");
ext = ext[ext.length - 1];
// ext = updateExt(ext);
let newName = el[1] + "." + ext;
await renameafile(auth, el[0], i, newName);
}
}
} catch (err) {
console.log("getSheet");
console.log(err);
reject(err);
}
resolve(data);
}
);
});
}
let ctr = 1;
function getafile(auth, fileId, i) {
if (fileId === -1) return -1;
const drive = google.drive({ version: "v3", auth });
return new Promise((resolve, reject) => {
drive.files.get(
{
fileId: fileId
},
(err, res) => {
if (err) {
reject(err);
}
if (res.status !== 200) {
reject("Status Code : " + res.status);
}
let filename = res.data.name;
// console.log(ctr + "\t" + fileId + "\t" + filename);
ctr++;
resolve(filename);
}
);
});
}
function renameafile(auth, fileId, i, newName) {
const drive = google.drive({ version: "v3", auth });
return new Promise((resolve, reject) => {
drive.files.update(
{
fileId: fileId,
resource: {
name: newName
}
},
(err, res) => {
if (err) {
reject(err);
}
if (res.status !== 200) {
reject("Status Code : " + res.status);
}
let filename = res.data.name;
console.log(i + 1 + "\t" + fileId + "\t" + filename);
resolve(filename);
}
);
});
}
return res.status(200).json({
message: "Renamed " + ctr + " Files Successfully !",
status: 200
});
};
You are using fs.readFile, which is asynchronous, as noted by M1K1O in the comments.
If you're using node 10 or more, You can use fs.promises.readFile and await its result, so it will wait for its return before continuing the execution or your program.

Node js - How to use connection variable

I'm trying to create a database connection structure that allow multi database connections but I'm having problems about saving the connection so I can use in mssql.request().
I login form the user has to choose which database wants to use, so I'm thinking in trigger the select box database with this code that works fine.
LoginController.js
setDB: (req, res) =>
{
var connection = db.setDB(req.params.dbname);
connection.then(result => {
res.send(result);
});
},
db.setDB
const mssql = require('mssql');
module.exports =
{
setDB: (req, res) =>
{
console.log('Prepare to connect to ' + req);
return new Promise(function (resolve, reject){
var result = makeConnection(req);
result.then(value => {
console.log('result: ' + value);
if(value == 0)
{
console.log('Unknown database!');
resolve('Unknown database!');
}
else if(value == 1)
{
console.log('Error trying to connect! Maybe wrong connection data.');
resolve('Error trying to connect! Maybe wrong connection data.');
}
else if(value == 2)
{
console.log('Connection Done!');
resolve('Connection Done!');
}
}).catch(err => {
console.log(err);
console.log('Error handle setDB() results.');
reject('Error handle setDB() results! Call tech guy.');
});
})
//return connection;
}
}
function makeConnection(dbname)
{
return new Promise(function (resolve, reject){
console.log('Start connection....');
const configs = {
Emp1: {
user: "us",
password: "pass",
server: "ip",
database: "Emp1",
pool: {
max: 20,
min: 0,
idleTimeoutMillis: 900000
}
},
Emp2: {
user: "us",
password: "pass",
server: "ip",
database: "Emp2",
pool: {
max: 20,
min: 0,
idleTimeoutMillis: 900000
}
}
};
var config = configs[dbname];
if(config == undefined)
{
resolve(0);
}
global.conn = new mssql.Connection(config);
conn.connect(function(err)
{
if (err) {
console.log(err);
resolve(1);
} else {
console.log('Database Connected!');
resolve(2);
}
});
});
}
I do this global.conn = new mssql.Connection(config); to try to save the connection.
Now if I try to access a link(index) that do a query I have error because conn is not defined ...
var db = require('../config/db');
var mssql = require('mssql');
var squel = require("squel");
var request = new mssql.Request(conn);
module.exports =
{
index: (req, res) => {
console.log("User Index");
request.query("SELECT * from us",(err, records) => {
console.log(err);
//console.log(records);
res.send(records[0].username);
});
}
}
I appreciate a lot your opinion about the better way to do this, I don't see much information about multi database connection.
The user has to choose database in login form but after I have to save the database name but how? I already think in cache... I don't know, I need really some advices.
Any doubt about that ask me please.
Thank you

Using Node.js to connect to a REST API

Is it sensible to use Node.js to write a stand alone app that will connect two REST API's?
One end will be a POS - Point of sale - system
The other will be a hosted eCommerce platform
There will be a minimal interface for configuration of the service. nothing more.
Yes, Node.js is perfectly suited to making calls to external APIs. Just like everything in Node, however, the functions for making these calls are based around events, which means doing things like buffering response data as opposed to receiving a single completed response.
For example:
// get walking directions from central park to the empire state building
var http = require("http");
url = "http://maps.googleapis.com/maps/api/directions/json?origin=Central Park&destination=Empire State Building&sensor=false&mode=walking";
// get is a simple wrapper for request()
// which sets the http method to GET
var request = http.get(url, function (response) {
// data is streamed in chunks from the server
// so we have to handle the "data" event
var buffer = "",
data,
route;
response.on("data", function (chunk) {
buffer += chunk;
});
response.on("end", function (err) {
// finished transferring data
// dump the raw data
console.log(buffer);
console.log("\n");
data = JSON.parse(buffer);
route = data.routes[0];
// extract the distance and time
console.log("Walking Distance: " + route.legs[0].distance.text);
console.log("Time: " + route.legs[0].duration.text);
});
});
It may make sense to find a simple wrapper library (or write your own) if you are going to be making a lot of these calls.
Sure. The node.js API contains methods to make HTTP requests:
http.request
http.get
I assume the app you're writing is a web app. You might want to use a framework like Express to remove some of the grunt work (see also this question on node.js web frameworks).
/*Below logics covered in below sample GET API
-DB connection created in class
-common function to execute the query
-logging through bunyan library*/
const { APIResponse} = require('./../commonFun/utils');
const createlog = require('./../lib/createlog');
var obj = new DB();
//Test API
routes.get('/testapi', (req, res) => {
res.status(201).json({ message: 'API microservices test' });
});
dbObj = new DB();
routes.get('/getStore', (req, res) => {
try {
//create DB instance
const store_id = req.body.storeID;
const promiseReturnwithResult = selectQueryData('tablename', whereField, dbObj.conn);
(promiseReturnwithResult).then((result) => {
APIResponse(200, 'Data fetched successfully', result).then((result) => {
res.send(result);
});
}).catch((err) => { console.log(err); throw err; })
} catch (err) {
console.log('Exception caught in getuser API', err);
const e = new Error();
if (err.errors && err.errors.length > 0) {
e.Error = 'Exception caught in getuser API';
e.message = err.errors[0].message;
e.code = 500;
res.status(404).send(APIResponse(e.code, e.message, e.Error));
createlog.writeErrorInLog(err);
}
}
});
//create connection
"use strict"
const mysql = require("mysql");
class DB {
constructor() {
this.conn = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'pass',
database: 'db_name'
});
}
connect() {
this.conn.connect(function (err) {
if (err) {
console.error("error connecting: " + err.stack);
return;
}
console.log("connected to DBB");
});
}
//End class
}
module.exports = DB
//queryTransaction.js File
selectQueryData= (table,where,db_conn)=>{
return new Promise(function(resolve,reject){
try{
db_conn.query(`SELECT * FROM ${table} WHERE id = ${where}`,function(err,result){
if(err){
reject(err);
}else{
resolve(result);
}
});
}catch(err){
console.log(err);
}
});
}
module.exports= {selectQueryData};
//utils.js file
APIResponse = async (status, msg, data = '',error=null) => {
try {
if (status) {
return { statusCode: status, message: msg, PayLoad: data,error:error }
}
} catch (err) {
console.log('Exception caught in getuser API', err);
}
}
module.exports={
logsSetting: {
name: "USER-API",
streams: [
{
level: 'error',
path: '' // log ERROR and above to a file
}
],
},APIResponse
}
//createlogs.js File
var bunyan = require('bunyan');
const dateFormat = require('dateformat');
const {logsSetting} = require('./../commonFun/utils');
module.exports.writeErrorInLog = (customError) => {
let logConfig = {...logsSetting};
console.log('reached in writeErrorInLog',customError)
const currentDate = dateFormat(new Date(), 'yyyy-mm-dd');
const path = logConfig.streams[0].path = `${__dirname}/../log/${currentDate}error.log`;
const log = bunyan.createLogger(logConfig);
log.error(customError);
}
A more easy and useful tool is just using an API like Unirest; URest is a package in NPM that is just too easy to use jus like
app.get('/any-route', function(req, res){
unirest.get("https://rest.url.to.consume/param1/paramN")
.header("Any-Key", "XXXXXXXXXXXXXXXXXX")
.header("Accept", "text/plain")
.end(function (result) {
res.render('name-of-the-page-according-to-your-engine', {
layout: 'some-layout-if-you-want',
markup: result.body.any-property,
});
});

Categories

Resources