mod_user_update.js

/**
## /user/update

Exports the [user] update method for the /api/user/cookie route.

@requires module:/user/acl
@requires module:/utils/mailer

@module /user/update
*/

const acl = require('./acl');

const mailer = require('../utils/mailer');

/**
@function update

@description
The update method will send a request to the ACL to update a user record in the ACL.

The user object to update can be provided as request body.

Property values to be updated must be provided as a substitutes array to prevent SQL injection.

The update_user keys must be validated to contain white listed characters only to prevent SQL injection.

@param {req} req HTTP request.
@param {req} res HTTP response.

@property {Object} [req.body] 
HTTP Post request body containing the update information.
@property {Object} req.params 
HTTP request parameter.
@property {string} params.email 
User to update
@property {string} params.field 
User record field to update
@property {string} params.value 
Update value for user record field.
@property {Object} params.user 
Requesting user.
@property {boolean} user.admin 
Requesting user is admin.
*/
module.exports = async function update(req, res) {
  // acl module will export an empty require object without the ACL being configured.
  if (acl === null) {
    return res.status(500).send('ACL unavailable.');
  }

  if (!req.params.user?.admin) {
    // The update request can only be made by an administrator.
    return new Error('admin_user_login_required');
  }

  // Create update_user from request body or create Object with email from params.
  const update_user =
    Object.keys(req.body || {}).length === 0 || req.body === undefined
      ? { email: req.params.email }
      : req.body;

  //If the client has provided a request with a body/params that does not have an email we will return a 400
  if (!update_user.email) {
    return res.status(400).send('Email address required.');
  }

  if (req.params.field) {
    if (req.params.field === 'roles') {
      // The value string must be split into an array for the roles field params.
      req.params.value = req.params.value?.split(',') || [];
    }

    // Assign field property from request params.
    update_user[req.params.field] = req.params.value;
  }

  // Create ISODate for administrator request log.
  const ISODate = new Date().toISOString().replace(/\..*/, '');

  let password_reset = '';

  if (update_user.verified) {
    // Verifying a user will also approve the user, reset password, and failed login attempts.
    Object.assign(update_user, {
      password_reset: null,
      failedattempts: 0,
      verificationtoken: null,
      approved: true,
      approved_by: `${req.params.user.email}|${ISODate}`,
    });

    password_reset = `password = password_reset,`;
  }

  if (update_user.approved) {
    // Log who and when approved a user.
    update_user.approved_by = `${req.params.user.email}|${ISODate}`;
  }

  // Validate update_user keys.
  if (
    Object.keys(update_user).some((key) => !/^[A-Za-z0-9.,_-\s]*$/.test(key))
  ) {
    // Return bad request 400 if an update_user key contains not white listed characters.
    return res.status(400).send('Invalid key in user object for SQL update.');
  }

  const properties = [];
  const substitutes = [update_user.email];

  // Populate properties, substitutes array for update_query.
  Object.keys(update_user)
    .filter((key) => key !== 'email')
    .forEach((key) => {
      substitutes.push(update_user[key]);
      properties.push(`${key} = $${substitutes.length}`);
    });

  const update_query = `
    UPDATE acl_schema.acl_table
    SET 
      ${password_reset}
      ${properties.join(',')}
    WHERE lower(email) = lower($1);`;

  const rows = await acl(update_query, substitutes);

  if (rows instanceof Error) {
    return res.status(500).send('Failed to access ACL.');
  }

  // Send email to the user account if an account has been approved.
  if (update_user.approved) {
    const approval_mail = {
      template: 'approved_account',
      language: update_user.language,
      to: update_user.email,
      host: req.params.host,
    };

    await mailer(approval_mail);
  }

  return res.send('Update success');
};