Currently, I'm using AWS Secrets Manager to store my MongoDB (Atlas) ID and password.
It is working fine for connecting to the DB; however, I'm facing challenges when I am trying to use the same ID and password for MongoDB session store using connect-mongo(https://www.npmjs.com/package/connect-mongo).
If I directly set the ID and Password of MongoDB in the URL, it is working fine. Therefore, I'm guessing that I'm doing something wrong in the 6 lines of codes (please see the comment in app.js) where I'm trying to retrieve the ID and Password from the Secret Manager using the async-await function.
Thank you for your help!
[retrieveSecrets.js]
const AWS = require("aws-sdk");
module.exports = () => {
//configure AWS SDK
const region = "[MY_REGION]";
const client = new AWS.SecretsManager({ region });
const SecretId = "[MY_SECRET_ID]";
return new Promise((resolve, reject) => {
//retrieving secrets from secrets manager
client.getSecretValue({ SecretId }, (err, data) => {
if (err) {
reject(err);
} else {
//parsing the fetched data into JSON
const secretsJSON = JSON.parse(data.SecretString);
let secretsString = "";
Object.keys(secretsJSON).forEach((key) => {
secretsString += `${key}=${secretsJSON[key]}\n`;
});
resolve(secretsString);
}
});
});
};
[app.js (extracts)]
const express = require('express');
const session = require('express-session');
const MongoDBStore = require('connect-mongo');
const app = express();
const retrieveSecrets = require("./retrieveSecrets");
const dbAddress = [MY_DB_ADDRESS]
const dbName = [MY_DB_NAME]
const getsecret = (async function () {
try {
const secretsString = await retrieveSecrets();
const secretsArray = await secretsString.split("=");
return secretsArray;
} catch (error) {
//log the error and crash the app
console.log("Error in setting environment variables", error);
process.exit(-1);
}
});
const connectMongoose = (async function () {
const secretsArray = await getsecret();
let url = `mongodb+srv://${secretsArray[0]}:${secretsArray[1]}@${dbAddress}/${dbName}`
mongoose.connect(url);
});
connectMongoose();
const db = mongoose.connection;
db.on("error", console.error.bind(console, "Connection error:"));
db.once("open", () => {
console.log("Database connected");
});
// Codes above this line are working fine (connect to MongoDB using ID and Password from Secret Manager).
// *** These 6 lines of codes look like causing the issue ***
const getUrl = (async function () {
const secretsArray = await getsecret();
let url = `mongodb+srv://${secretsArray[0]}:${secretsArray[1]}@${dbAddress}/${dbName}`
return url;
});
const dbUrl = getUrl();
// Instead of the above 6 lines of codes, if I just directly set the URL as the following, it works fine.
// const dbUrl = 'mongodb+srv://[MY_ID]:[MY_PASS]@[MY_DB_ADDRESS]/[MY_DB_NAME]';
const store = MongoDBStore.create({
mongoUrl: dbUrl,
touchAfter: 24 * 60 * 60,
crypto: {
secret: '[MY_SECRET]'
}
});
store.on("error", function(e) {
console.log("Session Store Error: ", e)
});
const sessionConfig = {
store,
name: 'session',
secret: '[MY_SECRET]',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
expires: Date.now() + 1000 * 60 * 60 * 24 * 7,
maxAge: 1000 * 60 * 60 * 24 * 7
}
}
app.use(session(sessionConfig));
[Run Results & Log]
$ nodemon app.js
[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node app.js`
Serving on port 3000
/home/ec2-user/dev/node_modules/mongodb-connection-string-url/lib/index.js:9
return (connectionString.startsWith('mongodb://') ||
^
TypeError: connectionString.startsWith is not a function
at connectionStringHasValidScheme (/home/ec2-user/dev/node_modules/mongodb-connection-string-url/lib/index.js:9:30)
at new ConnectionString (/home/ec2-user/dev/node_modules/mongodb-connection-string-url/lib/index.js:85:34)
at parseOptions (/home/ec2-user/dev/node_modules/mongodb/lib/connection_string.js:217:17)
at new MongoClient (/home/ec2-user/dev/node_modules/mongodb/lib/mongo_client.js:62:63)
at Function.connect (/home/ec2-user/dev/node_modules/mongodb/lib/mongo_client.js:184:33)
at new MongoStore (/home/ec2-user/dev/node_modules/connect-mongo/build/main/lib/MongoStore.js:110:46)
at Function.create (/home/ec2-user/dev/node_modules/connect-mongo/build/main/lib/MongoStore.js:136:16)
at Object.<anonymous> (/home/ec2-user/dev/app.js:103:28)
at Module._compile (node:internal/modules/cjs/loader:1099:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
Node.js v17.7.2
[nodemon] app crashed - waiting for file changes before starting...
[Edit (9/3/2022, 23:20, PST)]
I continued the trial and error and updated my code as the following:
- after defining the async function getUrl, store the returned value from getUrl to a const url
- call url with .then(dbUrl)
- include rest of all codes in "app.js" inside the ".then()"
After updating the code in this way, it runs without error; however, the problem is, because a large number of codes are included in the ".then()", it looks very ugly and I was feeling this is not the right way ...(please see the following codes below)
I believe, that the fact it is running without error in this way is showing that my original codes were causing errors because of the way I use async-await.
Is there any better way other than including everything inside the ".then()"? (or, is this the right way?)
Following is the updated code.
[Updated app.js (extracts)]
const express = require('express');
const session = require('express-session');
const MongoDBStore = require('connect-mongo');
const app = express();
const retrieveSecrets = require("./retrieveSecrets");
const dbAddress = [MY_DB_ADDRESS]
const dbName = [MY_DB_NAME]
const getsecret = (async function () {
try {
const secretsString = await retrieveSecrets();
const secretsArray = await secretsString.split("=");
return secretsArray;
} catch (error) {
//log the error and crash the app
console.log("Error in setting environment variables", error);
process.exit(-1);
}
});
const connectMongoose = (async function () {
const secretsArray = await getsecret();
let url = `mongodb+srv://${secretsArray[0]}:${secretsArray[1]}@${dbAddress}/${dbName}`
mongoose.connect(url);
});
connectMongoose();
const db = mongoose.connection;
db.on("error", console.error.bind(console, "Connection error:"));
db.once("open", () => {
console.log("Database connected");
});
// Codes above this line are working fine (connect to MongoDB using ID and Password from Secret Manager).
const getUrl = (async function () {
const secretsArray = await getsecret();
let url = `mongodb+srv://${secretsArray[0]}:${secretsArray[1]}@${dbAddress}/${dbName}`
return url;
});
// *** Codes below this line were updated (included inside .then) ***
const url = getUrl();
url.then((dbUrl) => {
const store = MongoDBStore.create({
mongoUrl: dbUrl,
touchAfter: 24 * 60 * 60,
crypto: {
secret: dbSecret
}
});
store.on("error", function(e) {
console.log("Session Store Error: ", e)
});
const sessionConfig = {
store,
name: 'session',
secret: dbSecret,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
expires: Date.now() + 1000 * 60 * 60 * 24 * 7,
maxAge: 1000 * 60 * 60 * 24 * 7
}
}
app.use(session(sessionConfig));
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
app.use((req, res, next) => {
res.locals.currentUser = req.user;
res.locals.success = req.flash('success');
res.locals.error = req.flash('error');
next();
})
app.use('/', usersRoutes);
app.use('/abc', abcRoutes);
app.use('/abc/:id/reviews', reviewRoutes);
app.get('/', (req, res) => {
res.render('home')
});
app.all('*', (req, res, next) => {
next(new ExpressError('Page Not Found', 404))
})
app.use((err, req, res, next) => {
const { statusCode = 500 } = err;
if (!err.message) err.message = 'Something went wrong!';
res.status(statusCode).render('error', { err });
})
app.listen(3000, () => {
console.log('Serving on port 3000')
});
});