openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
451 lines (360 loc) • 15.3 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 = _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