UNPKG

openhim-core

Version:

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

451 lines (360 loc) 15.3 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 = _interopRequireDefault(require("winston")); var _moment = _interopRequireDefault(require("moment")); var _atnaAudit = _interopRequireDefault(require("atna-audit")); var _os = _interopRequireDefault(require("os")); var _users = require("../model/users"); var authorisation = _interopRequireWildcard(require("./authorisation")); var contact = _interopRequireWildcard(require("../contact")); var _config = require("../config"); var utils = _interopRequireWildcard(require("../utils")); var auditing = _interopRequireWildcard(require("../auditing")); var _crypto = _interopRequireDefault(require("crypto")); var _util = require("util"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } 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 = _atnaAudit.default.construct.userLoginAudit(_atnaAudit.default.constants.OUTCOME_SERIOUS_FAILURE, himSourceID, _os.default.hostname(), email); audit = _atnaAudit.default.construct.wrapInSyslog(audit); return auditing.sendAuditEvent(audit, () => _winston.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 _crypto.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, _moment.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; _winston.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; } _winston.default.info('The email has been sent to the user'); ctx.body = 'Successfully set user token/expiry for password reset.'; ctx.status = 201; return _winston.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, _moment.default)(result.expiry).isBefore((0, _moment.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, _moment.default)(userDataExpiry.expiry).isBefore((0, _moment.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 _winston.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, _moment.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 _winston.default.error(`The email could not be sent to the user via the API ${err}`); } else { return _winston.default.info('The email has been sent to the new user'); } }); ctx.body = 'User successfully created'; ctx.status = 201; _winston.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.'; _winston.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}`; _winston.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