openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
442 lines (358 loc) • 14.8 kB
JavaScript
;
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