Source: idService.js

const config = require('config');
const bitcoinMessage = require('bitcoinjs-message');
const qs = require('qs');
const os = require('os');

const userconfig = require('../../../config/userconfig');
const log = require('../lib/log');
const serviceHelper = require('./serviceHelper');
const messageHelper = require('./messageHelper');
const dbHelper = require('./dbHelper');
const verificationHelper = require('./verificationHelper');
const generalService = require('./generalService');
const dockerService = require('./dockerService');
const syncthingService = require('./syncthingService');
const fluxNetworkHelper = require('./fluxNetworkHelper');

const goodchars = /^[1-9a-km-zA-HJ-NP-Z]+$/;

/**
 * To check if the hardware specification requirements of the node tier are being met by the node (RAM and CPU threads).
 * @returns {boolean} True or an error is thrown.
 */
async function confirmNodeTierHardware() {
  try {
    const tier = await generalService.nodeTier().catch((error) => {
      log.error(error);
    });

    const collateral = await generalService.nodeCollateral().catch((error) => {
      log.error(error);
    });
    const nodeRam = os.totalmem() / 1024 / 1024 / 1024;
    const nodeCpuThreads = os.cpus().length;
    log.info(`Node Tier: ${tier}`);
    log.info(`Node Collateral: ${collateral}`);
    log.info(`Node Total Ram: ${nodeRam}`);
    log.info(`Node Cpu Threads: ${nodeCpuThreads}`);
    if (tier === 'bamf' && collateral === 100000) {
      if (nodeRam < 30) {
        throw new Error(`Node Total Ram (${nodeRam}) below Stratus requirements`);
      }
      if (nodeCpuThreads < 8) {
        throw new Error(`Node Cpu Threads (${nodeCpuThreads}) below Stratus requirements`);
      }
    } else if (tier === 'super' && collateral === 25000) {
      if (nodeRam < 7) {
        throw new Error(`Node Total Ram (${nodeRam}) below Nimbus requirements`);
      }
      if (nodeCpuThreads < 4) {
        throw new Error(`Node Cpu Threads (${nodeCpuThreads}) below Nimbus requirements`);
      }
    } else if (tier === 'basic' && collateral === 10000) {
      if (nodeRam < 3) {
        throw new Error(`Node Total Ram (${nodeRam}) below Cumulus requirements`);
      }
      if (nodeCpuThreads < 2) {
        throw new Error(`Node Cpu Threads (${nodeCpuThreads}) below Cumulus requirements`);
      }
    } else if (tier === 'bamf' && collateral === 40000) {
      if (nodeRam < 61) {
        throw new Error(`Node Total Ram (${nodeRam}) below new Stratus requirements`);
      }
      if (nodeCpuThreads < 16) {
        throw new Error(`Node Cpu Threads (${nodeCpuThreads}) below new Stratus requirements`);
      }
    } else if (tier === 'super' && collateral === 12500) {
      if (nodeRam < 30) {
        throw new Error(`Node Total Ram (${nodeRam}) below new Nimbus requirements`);
      }
      if (nodeCpuThreads < 8) {
        throw new Error(`Node Cpu Threads (${nodeCpuThreads}) below new Nimbus requirements`);
      }
    } else if (tier === 'basic' && collateral === 1000) {
      if (nodeRam < 3) {
        throw new Error(`Node Total Ram (${nodeRam}) below new Cumulus requirements`);
      }
      if (nodeCpuThreads < 4) {
        throw new Error(`Node Cpu Threads (${nodeCpuThreads}) below new Cumulus requirements`);
      }
    }
    return true;
  } catch (error) {
    log.error(error);
    return false;
  }
}

/**
 * To return a JSON response with the user's login phrase.
 * @param {object} req Request.
 * @param {object} res Response.
 * @returns {void} Return statement is only used here to interrupt the function and nothing is returned.
 */
async function loginPhrase(req, res) {
  try {
    // check docker availablility
    await dockerService.dockerListImages();
    // check synthing availability
    const syncthingDeviceID = await syncthingService.getDeviceID();
    if (syncthingDeviceID.status === 'error') {
      // throw new Error('Syncthing is not running properly');
      log.error('Syncthing is not running properly');
    }
    // check Node Hardware Requirements are ok.
    const hwPassed = await confirmNodeTierHardware();
    if (hwPassed === false) {
      throw new Error('Node hardware requirements not met');
    }
    // check DOS state (contains daemon checks)
    const dosState = await fluxNetworkHelper.getDOSState();
    if (dosState.status === 'error') {
      const errorMessage = 'Unable to check DOS state';
      const errMessage = messageHelper.createErrorMessage(errorMessage);
      res.json(errMessage);
      return;
    }
    if (dosState.status === 'success') {
      if (dosState.data.dosState > 10 || dosState.data.dosMessage !== null || dosState.data.nodeHardwareSpecsGood === false) {
        let errMessage = messageHelper.createErrorMessage(dosState.data.dosMessage, 'DOS', dosState.data.dosState);
        if (dosState.data.dosMessage !== 'Flux IP detection failed' && dosState.data.dosMessage !== 'Flux collision detection') {
          errMessage = messageHelper.createErrorMessage(dosState.data.dosMessage, 'CONNERROR', dosState.data.dosState);
        }
        if (dosState.data.nodeHardwareSpecsGood === false) {
          errMessage = messageHelper.createErrorMessage('Minimum hardware required for FluxNode tier not met', 'DOS', 100);
        }
        res.json(errMessage);
        return;
      }
    }

    const timestamp = new Date().getTime();
    const validTill = timestamp + (15 * 60 * 1000); // 15 minutes
    const phrase = timestamp + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);

    /* const activeLoginPhrases = [
       {
         loginPhrase: 1565356121335e9obp7h17bykbbvub0ts488wnnmd12fe1pq88mq0v,
         createdAt: 2019-08-09T13:08:41.335Z,
         expireAt: 2019-08-09T13:23:41.335Z
       }
    ] */
    const db = dbHelper.databaseConnection();
    const database = db.db(config.database.local.database);
    const collection = config.database.local.collections.activeLoginPhrases;
    const newLoginPhrase = {
      loginPhrase: phrase,
      createdAt: new Date(timestamp),
      expireAt: new Date(validTill),
    };
    const value = newLoginPhrase;
    await dbHelper.insertOneToDatabase(database, collection, value);
    // all is ok
    const phraseResponse = messageHelper.createDataMessage(phrase);
    res.json(phraseResponse);
  } catch (error) {
    log.error(error);
    const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
    res.json(errMessage);
  }
}

/**
 * To return a JSON response with the user's emergency login phrase.
 * @param {object} req Request.
 * @param {object} res Response.
 */
// loginPhrase without status checks
async function emergencyPhrase(req, res) {
  try {
    const timestamp = new Date().getTime();
    const validTill = timestamp + (15 * 60 * 1000); // 15 minutes
    const phrase = timestamp + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);

    const db = dbHelper.databaseConnection();
    const database = db.db(config.database.local.database);
    const collection = config.database.local.collections.activeLoginPhrases;
    const newLoginPhrase = {
      loginPhrase: phrase,
      createdAt: new Date(timestamp),
      expireAt: new Date(validTill),
    };
    const value = newLoginPhrase;
    await dbHelper.insertOneToDatabase(database, collection, value);
    const phraseResponse = messageHelper.createDataMessage(phrase);
    res.json(phraseResponse);
  } catch (error) {
    log.error(error);
    const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
    res.json(errMessage);
  }
}

/**
 * To return a JSON response to show the user if they have logged in successfully or not. In order to successfully log in, a series of checks are performed on the ZelID and signature.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function verifyLogin(req, res) {
  // Phase 2 - check that request is valid
  let body = '';
  req.on('data', (data) => {
    body += data;
  });
  req.on('end', async () => {
    try {
      const processedBody = serviceHelper.ensureObject(body);
      const address = processedBody.zelid || processedBody.address;
      const { signature } = processedBody;
      const message = processedBody.loginPhrase || processedBody.message;
      const timestamp = new Date().getTime();

      // First check that this message is valid - for example, it does not have an old timestamp, it is at least 40 chars and was generated by us (as in it is stored in our db)
      if (address === undefined || address === '') {
        throw new Error('No ZelID is specified');
      }

      if (!goodchars.test(address)) {
        throw new Error('ZelID is not valid');
      }

      if (address[0] !== '1') {
        throw new Error('ZelID is not valid');
      }

      if (address.length > 34 || address.length < 25) {
        throw new Error('ZelID is not valid');
      }

      if (message === undefined || message === '') {
        throw new Error('No message is specified');
      }

      if (message.length < 40) {
        throw new Error('Signed message is not valid');
      }

      if (+message.substring(0, 13) < (timestamp - 900000) || +message.substring(0, 13) > timestamp) {
        throw new Error('Signed message is not valid');
      }

      if (signature === undefined || signature === '') {
        throw new Error('No signature is specified');
      }
      // Basic checks passed. First check if message is in our activeLoginPhrases collection

      const db = dbHelper.databaseConnection();
      const database = db.db(config.database.local.database);
      const collection = config.database.local.collections.activeLoginPhrases;
      const query = { loginPhrase: message };
      const projection = {};
      const result = await dbHelper.findOneInDatabase(database, collection, query, projection);

      if (result) {
        // It is present in our database
        if (+result.loginPhrase.substring(0, 13) < timestamp) {
          // Second verify that this address signed this message
          let valid = false;
          try {
            valid = bitcoinMessage.verify(message, address, signature);
          } catch (error) {
            throw new Error('Invalid signature');
          }
          if (valid) {
            // Third associate address with message in our database
            const createdAt = new Date(+result.loginPhrase.substring(0, 13));
            const validTill = +result.loginPhrase.substring(0, 13) + (14 * 24 * 60 * 60 * 1000); // valid for 14 days
            const expireAt = new Date(validTill);
            const newLogin = {
              zelid: address,
              loginPhrase: message,
              signature,
              createdAt,
              expireAt,
            };
            let privilage = 'user';
            if (address === config.fluxTeamZelId) {
              privilage = 'fluxteam';
            } else if (address === userconfig.initial.zelid) {
              privilage = 'admin';
            }
            const loggedUsersCollection = config.database.local.collections.loggedUsers;
            const value = newLogin;
            await dbHelper.insertOneToDatabase(database, loggedUsersCollection, value);
            const resData = {
              message: 'Successfully logged in',
              zelid: address,
              loginPhrase: message,
              signature,
              privilage,
              createdAt,
              expireAt,
            };
            const resMessage = messageHelper.createDataMessage(resData);
            res.json(resMessage);
            serviceHelper.deleteLoginPhrase(message); // delete so it cannot be used again
            setTimeout(async () => {
              // after 1 minute remove signature from database
              const updatedDocument = {
                signature: '',
              };
              const update = { $unset: updatedDocument };
              const options = {
                upsert: false,
              };
              await dbHelper.updateOneInDatabase(database, loggedUsersCollection, query, update, options);
            }, 60000);
          } else {
            throw new Error('Invalid signature');
          }
        } else {
          throw new Error('Signed message is no longer valid. Please request a new one.');
        }
      } else {
        throw new Error('Signed message is no longer valid. Please request a new one.');
      }
    } catch (error) {
      log.error(error);
      const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
      res.json(errMessage);
    }
  });
}

/**
 * To return a JSON response with a new signature for the user. A series of checks are performed on the ZelID and signature.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function provideSign(req, res) {
  let body = '';
  req.on('data', (data) => {
    body += data;
  });
  req.on('end', async () => {
    try {
      const processedBody = serviceHelper.ensureObject(body);
      const address = processedBody.zelid || processedBody.address;
      const { signature } = processedBody;
      const message = processedBody.loginPhrase || processedBody.message;

      if (address === undefined || address === '') {
        throw new Error('No ZelID is specified');
      }

      if (!goodchars.test(address)) {
        throw new Error('ZelID is not valid');
      }

      if (address[0] !== '1') {
        throw new Error('ZelID is not valid');
      }

      if (address.length > 34 || address.length < 25) {
        throw new Error('ZelID is not valid');
      }

      if (message === undefined || message === '') {
        throw new Error('No message is specified');
      }

      if (message.length < 40) {
        throw new Error('Signed message is not valid');
      }

      if (signature === undefined || signature === '') {
        throw new Error('No signature is specified');
      }
      const timestamp = new Date().getTime();
      const validTill = timestamp + (15 * 60 * 1000); // 15 minutes
      const identifier = address + message.slice(-13);

      const db = dbHelper.databaseConnection();
      const database = db.db(config.database.local.database);
      const collection = config.database.local.collections.activeSignatures;
      const newSignature = {
        signature,
        identifier,
        createdAt: new Date(timestamp),
        expireAt: new Date(validTill),
      };
      const value = newSignature;
      await dbHelper.insertOneToDatabase(database, collection, value);
      // all is ok
      const phraseResponse = messageHelper.createDataMessage(newSignature);
      res.json(phraseResponse);
    } catch (error) {
      log.error(error);
      const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
      res.json(errMessage);
    }
  });
}

/**
 * To return a JSON response with a list of active login phrases. Only accessible by admins.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function activeLoginPhrases(req, res) {
  try {
    const authorized = await verificationHelper.verifyPrivilege('admin', req);
    if (authorized === true) {
      const db = dbHelper.databaseConnection();

      const database = db.db(config.database.local.database);
      const collection = config.database.local.collections.activeLoginPhrases;
      const query = {};
      const projection = {
        projection: {
          _id: 0, loginPhrase: 1, createdAt: 1, expireAt: 1,
        },
      };
      const results = await dbHelper.findInDatabase(database, collection, query, projection);
      const resultsResponse = messageHelper.createDataMessage(results);
      res.json(resultsResponse);
    } else {
      const errMessage = messageHelper.errUnauthorizedMessage();
      res.json(errMessage);
    }
  } catch (error) {
    log.error(error);
    const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
    res.json(errMessage);
  }
}

/**
 * To return a JSON response with a list of logged in users. Only accessible by admins.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function loggedUsers(req, res) {
  try {
    const authorized = await verificationHelper.verifyPrivilege('admin', req);
    if (authorized === true) {
      const db = dbHelper.databaseConnection();
      const database = db.db(config.database.local.database);
      const collection = config.database.local.collections.loggedUsers;
      const query = {};
      const projection = {
        projection: {
          _id: 0, zelid: 1, loginPhrase: 1, createdAt: 1, expireAt: 1,
        },
      };
      const results = await dbHelper.findInDatabase(database, collection, query, projection);
      const resultsResponse = messageHelper.createDataMessage(results);
      res.json(resultsResponse);
    } else {
      const errMessage = messageHelper.errUnauthorizedMessage();
      res.json(errMessage);
    }
  } catch (error) {
    log.error(error);
    const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
    res.json(errMessage);
  }
}

/**
 * To return a JSON response with a list of logged sessions. Only accessible by the ZelID owner.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function loggedSessions(req, res) {
  try {
    const authorized = await verificationHelper.verifyPrivilege('user', req);
    if (authorized === true) {
      const db = dbHelper.databaseConnection();

      const auth = serviceHelper.ensureObject(req.headers.zelidauth);
      const queryZelID = auth.zelid;
      const database = db.db(config.database.local.database);
      const collection = config.database.local.collections.loggedUsers;
      const query = { zelid: queryZelID };
      const projection = {
        projection: {
          _id: 0, zelid: 1, loginPhrase: 1, createdAt: 1, expireAt: 1,
        },
      };
      const results = await dbHelper.findInDatabase(database, collection, query, projection);
      const resultsResponse = messageHelper.createDataMessage(results);
      res.json(resultsResponse);
    } else {
      const errMessage = messageHelper.errUnauthorizedMessage();
      res.json(errMessage);
    }
  } catch (error) {
    log.error(error);
    const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
    res.json(errMessage);
  }
}

/**
 * To return a JSON response to show the user if they have logged out of the current session successfully or not. Only accessible by the ZelID owner.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function logoutCurrentSession(req, res) {
  try {
    const authorized = await verificationHelper.verifyPrivilege('user', req);
    if (authorized === true) {
      const auth = serviceHelper.ensureObject(req.headers.zelidauth);
      const db = dbHelper.databaseConnection();
      const database = db.db(config.database.local.database);
      const collection = config.database.local.collections.loggedUsers;
      const query = { $and: [{ loginPhrase: auth.loginPhrase }, { zelid: auth.zelid }] };
      const projection = {};
      await dbHelper.findOneAndDeleteInDatabase(database, collection, query, projection);
      // console.log(results)
      const message = messageHelper.createSuccessMessage('Successfully logged out');
      res.json(message);
    } else {
      const errMessage = messageHelper.errUnauthorizedMessage();
      res.json(errMessage);
    }
  } catch (error) {
    log.error(error);
    const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
    res.json(errMessage);
  }
}

/**
 * To return a JSON response to show the user if they have logged out of a specific session successfully or not. Only accessible by the ZelID owner.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function logoutSpecificSession(req, res) {
  let body = '';
  req.on('data', (data) => {
    body += data;
  });
  req.on('end', async () => {
    try {
      const authorized = await verificationHelper.verifyPrivilege('user', req);
      if (authorized === true) {
        const processedBody = serviceHelper.ensureObject(body);
        const obtainedLoginPhrase = processedBody.loginPhrase;
        const db = dbHelper.databaseConnection();
        const database = db.db(config.database.local.database);
        const collection = config.database.local.collections.loggedUsers;
        const query = { loginPhrase: obtainedLoginPhrase };
        const projection = {};
        const result = await dbHelper.findOneAndDeleteInDatabase(database, collection, query, projection);
        if (result.value === null) {
          const message = messageHelper.createWarningMessage('Specified user was already logged out');
          res.json(message);
        }
        const message = messageHelper.createSuccessMessage('Session successfully logged out');
        res.json(message);
      } else {
        const errMessage = messageHelper.errUnauthorizedMessage();
        res.json(errMessage);
      }
    } catch (error) {
      log.error(error);
      const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
      res.json(errMessage);
    }
  });
}

/**
 * To return a JSON response to show the user if they have logged out from all sessions successfully or not. Only accessible by the ZelID owner.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function logoutAllSessions(req, res) {
  try {
    const authorized = await verificationHelper.verifyPrivilege('user', req);
    if (authorized === true) {
      const auth = serviceHelper.ensureObject(req.headers.zelidauth);
      const db = dbHelper.databaseConnection();
      const database = db.db(config.database.local.database);
      const collection = config.database.local.collections.loggedUsers;
      const query = { zelid: auth.zelid };
      await dbHelper.removeDocumentsFromCollection(database, collection, query);
      // console.log(result)
      const message = messageHelper.createSuccessMessage('Successfully logged out all sessions');
      res.json(message);
    } else {
      const errMessage = messageHelper.errUnauthorizedMessage();
      res.json(errMessage);
    }
  } catch (error) {
    log.error(error);
    const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
    res.json(errMessage);
  }
}

/**
 * To return a JSON response to show the admin user if they have logged out all users successfully or not. Only accessible by admins.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function logoutAllUsers(req, res) {
  try {
    const authorized = await verificationHelper.verifyPrivilege('admin', req);
    if (authorized === true) {
      const db = dbHelper.databaseConnection();
      const database = db.db(config.database.local.database);
      const collection = config.database.local.collections.loggedUsers;
      const query = {};
      await dbHelper.removeDocumentsFromCollection(database, collection, query);
      const message = messageHelper.createSuccessMessage('Successfully logged out all users');
      res.json(message);
    } else {
      const errMessage = messageHelper.errUnauthorizedMessage();
      res.json(errMessage);
    }
  } catch (error) {
    log.error(error);
    const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
    res.json(errMessage);
  }
}

/**
 * To check if a login phrase is currently active. The user's ZelID, login phrase, signature and privilege level are returned if the login phrase is active.
 * @param {object} ws Web socket.
 * @param {object} req Request.
 */
async function wsRespondLoginPhrase(ws, req) {
  const { loginphrase } = req.params;
  // console.log(loginphrase)
  // respond with object containing address and signature to received message
  let connclosed = false;
  // eslint-disable-next-line no-param-reassign
  ws.onclose = (evt) => {
    console.log(evt.code);
    connclosed = true;
  };
  // eslint-disable-next-line no-param-reassign
  ws.onerror = (evt) => {
    log.error(evt.code);
    connclosed = true;
  };
  const db = dbHelper.databaseConnection();

  const database = db.db(config.database.local.database);
  const collection = config.database.local.collections.loggedUsers;
  const query = { loginPhrase: loginphrase };
  const projection = {};
  // eslint-disable-next-line no-inner-declarations
  async function searchDatabase() {
    try {
      const result = await dbHelper.findOneInDatabase(database, collection, query, projection).catch((error) => {
        const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
        ws.send(qs.stringify(errMessage));
        ws.close(1011);
        throw error;
      });
      if (result) {
        // user is logged, all ok
        let privilage = 'user';
        if (result.zelid === config.fluxTeamZelId) {
          privilage = 'fluxteam';
        } else if (result.zelid === userconfig.initial.zelid) {
          privilage = 'admin';
        }
        const resData = {
          message: 'Successfully logged in',
          zelid: result.zelid,
          loginPhrase: result.loginPhrase,
          signature: result.signature,
          privilage,
          createdAt: result.createdAt,
          expireAt: result.expireAt,
        };
        const message = messageHelper.createDataMessage(resData);
        if (!connclosed) {
          try {
            ws.send(qs.stringify(message));
            ws.close(1000);
          } catch (e) {
            log.error(e);
          }
        }
      } else {
        // check if this loginPhrase is still active. If so rerun this searching process
        const activeLoginPhrasesCollection = config.database.local.collections.activeLoginPhrases;
        const resultB = await dbHelper.findOneInDatabase(database, activeLoginPhrasesCollection, query, projection).catch((error) => {
          const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
          ws.send(qs.stringify(errMessage));
          ws.close(1011);
          throw error;
        });
        if (resultB) {
          setTimeout(() => {
            if (!connclosed) {
              searchDatabase();
            }
          }, 500);
        } else {
          const errMessage = messageHelper.createErrorMessage('Signed message is no longer valid. Please request a new one.');
          if (!connclosed) {
            try {
              ws.send(qs.stringify(errMessage));
              ws.close();
            } catch (e) {
              log.error(e);
            }
          }
        }
      }
    } catch (error) {
      log.error(error);
    }
  }
  searchDatabase();
}

/**
 * To check if a signature exists.
 * @param {object} ws Web socket.
 * @param {object} req Request.
 */
async function wsRespondSignature(ws, req) {
  const { message } = req.params;
  console.log(message);

  let connclosed = false;
  // eslint-disable-next-line no-param-reassign
  ws.onclose = (evt) => {
    console.log(evt.code);
    connclosed = true;
  };
  // eslint-disable-next-line no-param-reassign
  ws.onerror = (evt) => {
    log.error(evt.code);
    connclosed = true;
  };

  const db = dbHelper.databaseConnection();

  const database = db.db(config.database.local.database);
  const collection = config.database.local.collections.activeSignatures;
  const query = { identifier: message };
  const projection = {};
  async function searchDatabase() {
    try {
      const result = await dbHelper.findOneInDatabase(database, collection, query, projection).catch((error) => {
        const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
        ws.send(qs.stringify(errMessage));
        ws.close(1011);
        throw error;
      });

      if (result) {
        // signature exists
        const response = messageHelper.createDataMessage(result);
        if (!connclosed) {
          try {
            ws.send(qs.stringify(response));
            ws.close(1000);
          } catch (e) {
            log.error(e);
          }
        }
      } else {
        setTimeout(() => {
          if (!connclosed) {
            searchDatabase();
          }
        }, 500);
      }
    } catch (error) {
      log.error(error);
    }
  }
  searchDatabase();
}

/**
 * To check the privilege level a user (ZelID). The privilege level is either admin, flux team or user.
 * @param {object} req Request.
 * @param {object} res Response.
 */
async function checkLoggedUser(req, res) {
  let body = '';
  req.on('data', (data) => {
    body += data;
  });
  req.on('end', async () => {
    try {
      const processedBody = serviceHelper.ensureObject(body);
      const { zelid, signature } = processedBody;
      const loggedPhrase = processedBody.loginPhrase;
      if (!zelid) {
        throw new Error('No user ZelID specificed');
      }
      if (!loggedPhrase) {
        throw new Error('No user loginPhrase specificed');
      }
      if (!signature) {
        throw new Error('No user ZelID signature specificed');
      }
      const request = {
        headers: {
          zelidauth: {
            zelid,
            loginPhrase: loggedPhrase,
            signature,
          },
        },
      };
      const isAdmin = await verificationHelper.verifyPrivilege('admin', request);
      if (isAdmin) {
        const message = messageHelper.createSuccessMessage('admin');
        res.json(message);
        return;
      }
      const isFluxTeam = await verificationHelper.verifyPrivilege('fluxteam', request);
      if (isFluxTeam) {
        const message = messageHelper.createSuccessMessage('fluxteam');
        res.json(message);
        return;
      }
      const isUser = await verificationHelper.verifyPrivilege('user', request);
      if (isUser) {
        const message = messageHelper.createSuccessMessage('user');
        res.json(message);
        return;
      }
      const message = messageHelper.createErrorMessage('none');
      res.json(message);
    } catch (error) {
      log.error(error);
      const errMessage = messageHelper.createErrorMessage(error.message, error.name, error.code);
      res.json(errMessage);
    }
  });
}

module.exports = {
  loginPhrase,
  emergencyPhrase,
  verifyLogin,
  provideSign,
  activeLoginPhrases,
  loggedUsers,
  loggedSessions,
  logoutCurrentSession,
  logoutSpecificSession,
  logoutAllSessions,
  logoutAllUsers,
  wsRespondLoginPhrase,
  wsRespondSignature,
  checkLoggedUser,

  // exports for testing purposes
  confirmNodeTierHardware,
};