How to use async-await function when using connect-mongo with AWS Secrets Manager

Viewed 61

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')
    });
});
0 Answers
Related