UNPKG

openhim-core

Version:

The OpenHIM core application that provides logging and routing of http requests

442 lines (358 loc) 14.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.authenticate = authenticate; exports.userPasswordResetRequest = userPasswordResetRequest; exports.getUserByToken = getUserByToken; exports.updateUserByToken = updateUserByToken; exports.addUser = addUser; exports.getUser = getUser; exports.updateUser = updateUser; exports.removeUser = removeUser; exports.getUsers = getUsers; var _winston = require('winston'); var _winston2 = _interopRequireDefault(_winston); var _moment = require('moment'); var _moment2 = _interopRequireDefault(_moment); var _atnaAudit = require('atna-audit'); var _atnaAudit2 = _interopRequireDefault(_atnaAudit); var _os = require('os'); var _os2 = _interopRequireDefault(_os); var _users = require('../model/users'); var _authorisation = require('./authorisation'); var authorisation = _interopRequireWildcard(_authorisation); var _contact = require('../contact'); var contact = _interopRequireWildcard(_contact); var _config = require('../config'); var _utils = require('../utils'); var utils = _interopRequireWildcard(_utils); var _auditing = require('../auditing'); var auditing = _interopRequireWildcard(_auditing); var _crypto = require('crypto'); var _crypto2 = _interopRequireDefault(_crypto); var _util = require('util'); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } _config.config.newUserExpiry = _config.config.get('newUserExpiry'); _config.config.userPasswordResetExpiry = _config.config.get('userPasswordResetExpiry'); _config.config.alerts = _config.config.get('alerts'); const himSourceID = _config.config.get('auditing').auditEvents.auditSourceID; /* * Get authentication details */ async function authenticate(ctx, email) { email = unescape(email); try { const user = await _users.UserModelAPI.findOne({ email: utils.caseInsensitiveRegex(email) }); if (!user) { utils.logAndSetResponse(ctx, 404, `Could not find user by email ${email}`, 'info'); // Audit unknown user requested let audit = _atnaAudit2.default.construct.userLoginAudit(_atnaAudit2.default.constants.OUTCOME_SERIOUS_FAILURE, himSourceID, _os2.default.hostname(), email); audit = _atnaAudit2.default.construct.wrapInSyslog(audit); return auditing.sendAuditEvent(audit, () => _winston2.default.debug('Processed internal audit')); } else { ctx.body = { salt: user.passwordSalt, ts: new Date() }; } } catch (e) { return utils.logAndSetResponse(ctx, 500, `Error during authentication ${e}`, 'error'); } } /** * Reset password Functions */ const passwordResetPlainMessageTemplate = (firstname, setPasswordLink) => `\ <---------- Existing User - Reset Password ----------> Hi ${firstname}, A request has been made to reset your password on the OpenHIM instance running on ${_config.config.alerts.himInstance} Follow the below link to reset your password and log into OpenHIM Console ${setPasswordLink} <---------- Existing User - Reset Password ---------->\ `; const passwordResetHtmlMessageTemplate = (firstname, setPasswordLink) => `\ <h1>Reset OpenHIM Password</h1> <p>Hi ${firstname},<br/><br/>A request has been made to reset your password on the OpenHIM instance running on ${_config.config.alerts.himInstance}</p> <p>Follow the below link to set your password and log into OpenHIM Console</p> <p>${setPasswordLink}</p>\ `; function generateRandomToken() { return _crypto2.default.randomBytes(16).toString('hex'); } /* * update user token/expiry and send new password email */ async function userPasswordResetRequest(ctx, email) { email = unescape(email); if (email === 'root@openhim.org') { ctx.body = 'Cannot request password reset for \'root@openhim.org\''; ctx.status = 403; return; } // Generate the new user token here // set expiry date = true const token = generateRandomToken(); const { duration, durationType } = _config.config.userPasswordResetExpiry; const expiry = (0, _moment2.default)().add(duration, durationType).utc().format(); const updateUserTokenExpiry = { token, tokenType: 'existingUser', expiry }; try { const user = await _users.UserModelAPI.findOneAndUpdate({ email: utils.caseInsensitiveRegex(email) }, updateUserTokenExpiry); if (!user) { ctx.body = `Tried to request password reset for invalid email address: ${email}`; ctx.status = 404; _winston2.default.info(`Tried to request password reset for invalid email address: ${email}`); return; } const { consoleURL } = _config.config.alerts; const setPasswordLink = `${consoleURL}/#!/set-password/${token}`; // Send email to user to reset password const plainMessage = passwordResetPlainMessageTemplate(user.firstname, setPasswordLink); const htmlMessage = passwordResetHtmlMessageTemplate(user.firstname, setPasswordLink); const sendEmail = (0, _util.promisify)(contact.contactUser); const sendEmailError = await sendEmail('email', email, 'OpenHIM Console Password Reset', plainMessage, htmlMessage); if (sendEmailError) { utils.logAndSetResponse(ctx, 500, `Could not send email to user via the API ${sendEmailError}`, 'error'); return; } _winston2.default.info('The email has been sent to the user'); ctx.body = 'Successfully set user token/expiry for password reset.'; ctx.status = 201; return _winston2.default.info(`User updated token/expiry for password reset ${email}`); } catch (error) { utils.logAndSetResponse(ctx, 500, `Could not update user with email ${email} via the API ${error}`, 'error'); } } /** *New User Set Password Functions */ // get the new user details async function getUserByToken(ctx, token) { token = unescape(token); try { const projectionRestriction = { email: 1, firstname: 1, surname: 1, msisdn: 1, token: 1, tokenType: 1, locked: 1, expiry: 1, _id: 0 }; const result = await _users.UserModelAPI.findOne({ token }, projectionRestriction); if (!result) { ctx.body = `User with token ${token} could not be found.`; ctx.status = 404; } else if ((0, _moment2.default)(result.expiry).isBefore((0, _moment2.default)())) { // user- set password - expired ctx.body = `Token ${token} has expired`; ctx.status = 410; } else { ctx.body = result; } } catch (e) { utils.logAndSetResponse(ctx, 500, `Could not find user with token ${token} via the API ${e}`, 'error'); } } // update the password/details for the new user async function updateUserByToken(ctx, token) { let userDataExpiry; token = unescape(token); const userData = ctx.request.body; try { // first try get new user details to check expiry date userDataExpiry = await _users.UserModelAPI.findOne({ token }); if (!userDataExpiry) { ctx.body = `User with token ${token} could not be found.`; ctx.status = 404; return; } else if ((0, _moment2.default)(userDataExpiry.expiry).isBefore((0, _moment2.default)())) { // new user- set password - expired ctx.body = `User with token ${token} has expired to set their password.`; ctx.status = 410; return; } } catch (error) { utils.logAndSetResponse(ctx, 500, `Could not find user with token ${token} via the API ${error}`, 'error'); return; } // check to make sure 'msisdn' isnt 'undefined' when saving let msisdn = null; if (userData.msisdn) { msisdn = userData.msisdn; } // construct user object to prevent other properties from being updated const userUpdateObj = { token: null, tokenType: null, expiry: null, passwordAlgorithm: userData.passwordAlgorithm, passwordSalt: userData.passwordSalt, passwordHash: userData.passwordHash }; if (userDataExpiry.tokenType === 'newUser') { userUpdateObj.firstname = userData.firstname; userUpdateObj.surname = userData.surname; userUpdateObj.locked = false; userUpdateObj.msisdn = msisdn; } try { await _users.UserModelAPI.findOneAndUpdate({ token }, userUpdateObj); ctx.body = 'Successfully set new user password.'; return _winston2.default.info(`User updated by token ${token}`); } catch (error) { return utils.logAndSetResponse(ctx, 500, `Could not update user with token ${token} via the API ${error}`, 'error'); } } /** New User Set Password Functions */ const plainMessageTemplate = (firstname, setPasswordLink) => `\ <---------- New User - Set Password ----------> Hi ${firstname}, A profile has been created for you on the OpenHIM instance running on ${_config.config.alerts.himInstance} Follow the below link to set your password and log into OpenHIM Console ${setPasswordLink} <---------- New User - Set Password ---------->\ `; const htmlMessageTemplate = (firstname, setPasswordLink) => `\ <h1>New OpenHIM Profile</h1> <p>Hi ${firstname},<br/><br/>A profile has been created for you on the OpenHIM instance running on ${_config.config.alerts.himInstance}</p> <p>Follow the below link to set your password and log into OpenHIM Console</p> <p>${setPasswordLink}</p>\ `; /* * Adds a user */ async function addUser(ctx) { // Test if the user is authorised if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to addUser denied.`, 'info'); return; } const userData = ctx.request.body; // Generate the new user token here // set locked = true // set expiry date = true const token = generateRandomToken(); userData.token = token; userData.tokenType = 'newUser'; userData.locked = true; userData.email = userData.email.toLowerCase(); const { duration, durationType } = _config.config.newUserExpiry; userData.expiry = (0, _moment2.default)().add(duration, durationType).utc().format(); const consoleURL = _config.config.alerts.consoleURL; const setPasswordLink = `${consoleURL}/#!/set-password/${token}`; try { const user = new _users.UserModelAPI(userData); await user.save(); // Send email to new user to set password const plainMessage = plainMessageTemplate(userData.firstname, setPasswordLink); const htmlMessage = htmlMessageTemplate(userData.firstname, setPasswordLink); contact.contactUser('email', userData.email, 'OpenHIM Console Profile', plainMessage, htmlMessage, err => { if (err) { return _winston2.default.error(`The email could not be sent to the user via the API ${err}`); } else { return _winston2.default.info('The email has been sent to the new user'); } }); ctx.body = 'User successfully created'; ctx.status = 201; _winston2.default.info(`User ${ctx.authenticated.email} created user ${userData.email}`); } catch (e) { utils.logAndSetResponse(ctx, 500, `Could not add user via the API ${e}`, 'error'); } } /* * Retrieves the details of a specific user */ async function getUser(ctx, email) { email = unescape(email); // Test if the user is authorised, allow a user to fetch their own details if (!authorisation.inGroup('admin', ctx.authenticated) && ctx.authenticated.email !== email) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getUser denied.`, 'info'); return; } try { const result = await _users.UserModelAPI.findOne({ email: utils.caseInsensitiveRegex(email) }); if (!result) { ctx.body = `User with email ${email} could not be found.`; ctx.status = 404; } else { ctx.body = result; } } catch (e) { utils.logAndSetResponse(ctx, 500, `Could not get user via the API ${e}`, 'error'); } } async function updateUser(ctx, email) { email = unescape(email); // Test if the user is authorised, allow a user to update their own details if (!authorisation.inGroup('admin', ctx.authenticated) && ctx.authenticated.email !== email) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to updateUser denied.`, 'info'); return; } const userData = ctx.request.body; // reset token/locked/expiry when user is updated and password supplied if (userData.passwordAlgorithm && userData.passwordHash && userData.passwordSalt) { userData.token = null; userData.tokenType = null; userData.locked = false; userData.expiry = null; } // Don't allow a non-admin user to change their groups if (ctx.authenticated.email === email && !authorisation.inGroup('admin', ctx.authenticated)) { delete userData.groups; } // Ignore _id if it exists (update is by email) if (userData._id) { delete userData._id; } try { await _users.UserModelAPI.findOneAndUpdate({ email: utils.caseInsensitiveRegex(email) }, userData); ctx.body = 'Successfully updated user.'; _winston2.default.info(`User ${ctx.authenticated.email} updated user ${userData.email}`); } catch (e) { utils.logAndSetResponse(ctx, 500, `Could not update user ${email} via the API ${e}`, 'error'); } } async function removeUser(ctx, email) { // Test if the user is authorised if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to removeUser denied.`, 'info'); return; } email = unescape(email); // Test if the user is root@openhim.org if (email === 'root@openhim.org') { utils.logAndSetResponse(ctx, 403, 'User root@openhim.org is OpenHIM root, User cannot be deleted through the API', 'info'); return; } try { await _users.UserModelAPI.findOneAndRemove({ email: utils.caseInsensitiveRegex(email) }); ctx.body = `Successfully removed user with email ${email}`; _winston2.default.info(`User ${ctx.authenticated.email} removed user ${email}`); } catch (e) { utils.logAndSetResponse(ctx, 500, `Could not remove user ${email} via the API ${e}`, 'error'); } } async function getUsers(ctx) { // Test if the user is authorised if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getUsers denied.`, 'info'); return; } try { ctx.body = await _users.UserModelAPI.find(); } catch (e) { utils.logAndSetResponse(ctx, 500, `Could not fetch all users via the API ${e}`, 'error'); } } //# sourceMappingURL=users.js.map