Gottwik/Enduro

View on GitHub
libs/admin_utilities/admin_security.js

Summary

Maintainability
A
2 hrs
Test Coverage
// * ———————————————————————————————————————————————————————— * //
// *     Enduro Admin Security
// * ———————————————————————————————————————————————————————— * //
const admin_security = function () {}

// * vendor dependencies
const Promise = require('bluebird')
const crypto = require('crypto')

// * enduro dependencies
const logger = require(enduro.enduro_path + '/libs/logger')
const flat = require(enduro.enduro_path + '/libs/flat_db/flat')

// * ———————————————————————————————————————————————————————— * //
// *     get user by username
// *    @param {string} username - username of user to be returned
// *    @return {object} - User as defiend in the user file
// * ———————————————————————————————————————————————————————— * //
admin_security.prototype.get_user_by_username = function (username) {
    return new Promise(function (resolve, reject) {
        // load up all admins
        return flat.load(enduro.config.admin_secure_file)
            .then((raw_userlist) => {

                // if there are no users
                if (!raw_userlist.users) {
                    return reject('no users found')
                }

                // find user with specified username
                const selected_user = raw_userlist.users.filter((user) => {
                    if (user.username == username) {
                        return user
                    }
                })

                // resolve/reject based on if user was found
                selected_user.length
                    ? resolve(selected_user[0])
                    : reject('user not found')

            })
    })
}

// * ———————————————————————————————————————————————————————— * //
// *     get all users
// *    @return {list} - list of all user names
// * ———————————————————————————————————————————————————————— * //
admin_security.prototype.get_all_users = function () {

    // load up the user file
    return flat.load(enduro.config.admin_secure_file)
        .then((raw_userlist) => {

            // return empty array if no users found
            if (!raw_userlist.users) {
                return []
            }

            // return just usernames
            return raw_userlist.users.map(function (user) {
                return user.username
            })
        })
}

// * ———————————————————————————————————————————————————————— * //
// *     login by password
// *    @param {string} username
// *    @param {string} plaintext password
// *    @return {promise} - resolves if login successful and returns user
// * ———————————————————————————————————————————————————————— * //
admin_security.prototype.login_by_password = function (username, password) {
    const self = this

    return new Promise(function (resolve, reject) {

        // if username or password is missing
        if (!username || !password) {
            return reject({success: false, message: 'username or password not provided'})
        }

        // gets user with specified username
        self.get_user_by_username(username)
            .then((user) => {

                // hashes password
                const hashed_input_password = hash(password, user.salt)

                // compares hashed password with stored hash
                if (hashed_input_password == user.hash) {
                    resolve(user)
                } else {

                    // reject if provided password does not match the stored one
                    reject({success: false, message: 'wrong password'})
                }
            }, () => {

                // reject if user does not exist
                reject({success: false, message: 'wrong username'})
            })
    })
}

// * ———————————————————————————————————————————————————————— * //
// *     add addmin
// *    @param {string} username
// *    @param {string} plaintext password
// *    @return {promise} - resolves/rejects based on if the creation was successful
// * ———————————————————————————————————————————————————————— * //
admin_security.prototype.add_admin = function (username, password, tags) {
    const self = this

    return new Promise(function (resolve, reject) {

        // sets username to 'root' if no username is provided
        if (!username || typeof username == 'object') {
            username = 'root'
        }

        // generate random password if no password is provided
        password = password || Math.random().toString(10).substring(10)

        // put empty tag if no tags are provided
        tags = tags
            ? tags.split(',')
            : []

        const logincontext = {
            username: username,
            password: password,
            tags: tags
        }

        self.get_user_by_username(logincontext.username)
            .then(() => {
                logger.err_block('User \'' + username + '\' already exists')
                reject()
            }, () => {
                salt_and_hash(logincontext)
                timestamp(logincontext)

                return flat.upsert(enduro.config.admin_secure_file, {users: [logincontext]})
            })
            .then(() => {
                // Let the user know the project was created successfully
                logger.init('ENDURO - Creating admin user')
                logger.log('Username:', false)
                logger.tablog(username, true)
                logger.log('Password:', false)
                logger.tablog(password, true)
                logger.end()
                resolve()
            })
    })
}

admin_security.prototype.remove_all_users = function () {
    return flat.save('.users', {})
}

// private functions

// * ———————————————————————————————————————————————————————— * //
// *     hash
// *    @param {string} plaintext password
// *    @param {string} salt
// *    @return {string} - hashed password
// * ———————————————————————————————————————————————————————— * //
function hash (password, salt) {
    return require('crypto').createHash('sha256').update(password + salt, 'utf8').digest('hex')
}

// * ———————————————————————————————————————————————————————— * //
// *     salt and hash
// *    @param {object} logincontext
// *    @return {} - nothing, just adds salt and hash to logincontext
// * ———————————————————————————————————————————————————————— * //
function salt_and_hash (logincontext) {
    if (!logincontext || !logincontext.username || !logincontext.password) {
        return
    }

    // adds salt
    logincontext.salt = crypto.randomBytes(16).toString('hex')

    // adds hash
    logincontext.hash = hash(logincontext.password, logincontext.salt)

    // deletes plain password
    delete logincontext.password

}

// * ———————————————————————————————————————————————————————— * //
// *     timestamp
// *    @param {object} logincontext
// *    @return {} - nothing, just adds timestamp to logincontext
// * ———————————————————————————————————————————————————————— * //
function timestamp (logincontext) {
    logincontext.user_created_timestamp = Date.now()

    return logincontext
}

module.exports = new admin_security()