TripleParity/docks-api

View on GitHub
lib/user_manager.js

Summary

Maintainability
A
0 mins
Test Coverage
const Sequelize = require('sequelize');
const Promise = require('promise');
const bcrypt = require('bcrypt');
const userModel = require('../models/user');

/**
 * Provides functions for managing users in the given database.
 *
 * Based on the flip of a coin, this module will be async.
 * You can synchronize outside this module if required.
 */
class UserManager {
  /**
   * Create a new UserManager object
   * @param {Sequelize} db
   */
  constructor(db) {
    this.db = db;
    this.ready = false;

    // TODO(egeldenhuys): Move to dedicated models file/dir?
    this.User = userModel(db, Sequelize.DataTypes);
  }

  /**
   * Returns the state of the database initialization.
   *
   * If not true, the database operations MAY fail
   *
   * @return {boolean}
   */
  getDatabaseState() {
    return this.ready;
  }

  /**
   * Initialize the User table and create default admin
   * @return {Promise<boolean>}
   */
  initDatabase() {
    return new Promise((resolve, reject) => {
      this.User.findById(1).then((possibleUser) => {
        if (possibleUser == null) {
          this.createUser('admin', 'admin').then((newUser) => {
            if (newUser != null) {
              this.ready = true;
              resolve(true);
            } else {
              this.ready = false;
              resolve(false);
            }
          });
        } else {
          this.ready = true;
          resolve(true);
        }
      });
    });
  }

  /**
   * Return an array of all users in the database.
   * TODO(egeldenhuys): Use pages/offsets/ranges
   * @return {Promise<Array<User>>}
   */
  getAllUsers() {
    return new Promise((resolve, reject) => {
      this.User.findAll()
        .then((users) => {
          resolve(users);
        })
        .catch((err) => {
          reject(err);
        });
    });
  }

  /**
   * Find a user in the database given their PK id
   * @param {number} id
   * @return {Promise<User>}
   */
  getUserById(id) {
    return this.User.findById(id);
  }

  /**
   * Finds a user in the database given the username
   * Returns null if the user does not exist
   * @param {string} username
   * @return {Promise<User>}
   */
  getUserByUsername(username) {
    return this.User.findOne({where: {username: username}});
  }

  /**
   * Check if the username/password pair is valid
   *
   * True if valid, else false
   *
   * @param {string} username
   * @param {string} password
   * @return {Promise<boolean>}
   */
  verifyCredentials(username, password) {
    return new Promise((resolve, reject) => {
      this.getUserByUsername(username)
        .then((user) => {
          if (user != null) {
            bcrypt.compare(password, user.hash).then((res) => {
              resolve(res);
            });
          } else {
            resolve(false);
          }
        })
        .catch((err) => {
          reject(err);
        });
    });
  }

  /**
   * Create a new user and add it to the database.
   * If the username already exist, the old user is returned
   * @param {string} username
   * @param {string} password
   * @return {Promise<User>}
   */
  createUser(username, password) {
    const saltRounds = 10;

    return new Promise((resolve, reject) => {
      this.getUserByUsername(username)
        .then((possibleUser) => {
          if (possibleUser == null) {
            bcrypt.hash(password, saltRounds).then((hash) => {
              this.User.create({
                username: username,
                hash: hash,
              }).then((newUser) => {
                resolve(newUser);
              });
            });
          } else {
            resolve(possibleUser);
          }
        })
        .catch((err) => {
          reject(err);
        });
    });
  }

  /**
   * Change the password hash for the given username
   *
   * Returns true if the user exists and the password was changed.
   * Returns false if the user does not exist
   * @param {string} username
   * @param {string} password
   * @return {Promise<boolean>}
   */
  changePassword(username, password) {
    const saltRounds = 10;

    return new Promise((resolve, reject) => {
      this.getUserByUsername(username)
        .then((user) => {
          if (user !== null) {
            bcrypt.hash(password, saltRounds).then((hash) => {
              user.hash = hash;
              user.save().then(() => {
                resolve(true);
              });
            });
          } else {
            resolve(false);
          }
        })
        .catch((err) => {
          reject(err);
        });
    });
  }

  /**
   * Remove a User from the database
   * @param {string} username
   * @return {Promise<boolean>} true if we could delete, otherwise false
   */
  removeUser(username) {
    return new Promise((resolve, reject) => {
      this.getUserByUsername(username)
        .then((userToDelete) => {
          if (userToDelete != null) {
            this.User.destroy({where: {username: username}}).then(
              (rows) => {
                if (rows > 0) {
                  resolve(true);
                } else {
                  resolve(false);
                }
              }
            );
          } else {
            resolve(false);
          }
        })
        .catch((err) => {
          reject(err);
        });
    });
  }

  /**
   * Update the user's 2FA status (enabled or disabled)
   * @param {string} username
   * @param {boolean} twofactorenabled
   * @return {Promise<boolean>} // Did it successfully change?
   */
  updateTwoFactorStatus(username, twofactorenabled) {
    return new Promise((resolve, reject) => {
      this.getUserByUsername(username)
        .then((userToUpdate) => {
          if (userToUpdate === null) {
            resolve(false);
            return;
          }
          userToUpdate.twofactorenabled = twofactorenabled;
          userToUpdate.save().then(() => {
            resolve(true);
          });
        })
        .catch((err) => {
          reject(err);
        });
    });
  }

  /**
   * Test if the UserManager has a connection to the database
   * @return {Promise}
   */
  testConnection() {
    return this.db.authenticate();
  }
}

module.exports = UserManager;