/**
## /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')
}