openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
360 lines (279 loc) • 12.3 kB
text/coffeescript
User = require('../model/users').User
Q = require 'q'
logger = require 'winston'
authorisation = require './authorisation'
moment = require 'moment'
randtoken = require 'rand-token'
contact = require '../contact'
config = require "../config/config"
config.newUserExpiry = config.get('newUserExpiry')
config.userPasswordResetExpiry = config.get('userPasswordResetExpiry')
config.alerts = config.get('alerts')
utils = require "../utils"
atna = require 'atna-audit'
os = require 'os'
auditing = require '../auditing'
himSourceID = config.get('auditing').auditEvents.auditSourceID
###
# Get authentication details
###
exports.authenticate = (email) ->
email = unescape email
try
user = yield User.findOne(email: email).exec()
if not user
utils.logAndSetResponse this, 404, "Could not find user by email #{email}", 'info'
# Audit unknown user requested
audit = atna.userLoginAudit atna.OUTCOME_SERIOUS_FAILURE, himSourceID, os.hostname(), email
audit = atna.wrapInSyslog audit
auditing.sendAuditEvent audit, -> logger.debug 'Processed internal audit'
else
this.body =
salt: user.passwordSalt
ts: new Date()
catch e
utils.logAndSetResponse this, 500, "Error during authentication #{e}", 'error'
#################################
### Reset password Functions ###
#################################
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.alerts.himInstance}
Follow the below link to reset your password and log into OpenHIM Console
#{setPasswordLink}
<---------- Existing User - Reset Password ---------->
"""
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.alerts.himInstance}</p>
<p>Follow the below link to set your password and log into OpenHIM Console</p>
<p>#{setPasswordLink}</p>
"""
exports.generateRandomToken = () ->
return randtoken.generate 32
###
# update user token/expiry and send new password email
###
exports.userPasswordResetRequest = (email) ->
email = unescape email
if email is 'root@openhim.org'
this.body = "Cannot request password reset for 'root@openhim.org'"
this.status = 403
return
# Generate the new user token here
# set expiry date = true
token = exports.generateRandomToken()
duration = config.userPasswordResetExpiry.duration
durationType = config.userPasswordResetExpiry.durationType
expiry = moment().add(duration, durationType).utc().format()
updateUserTokenExpiry =
token: token
tokenType: 'existingUser'
expiry: expiry
try
user = yield User.findOneAndUpdate(email: email, updateUserTokenExpiry).exec()
if not user
this.body = "Tried to request password reset for invalid email address: #{email}"
this.status = 404
logger.info "Tried to request password reset for invalid email address: #{email}"
return
consoleURL = config.alerts.consoleURL
setPasswordLink = "#{consoleURL}/#/set-password/#{token}"
# Send email to user to reset password
plainMessage = passwordResetPlainMessageTemplate user.firstname, setPasswordLink
htmlMessage = passwordResetHtmlMessageTemplate user.firstname, setPasswordLink
sendEmail = Q.denodeify contact.contactUser
sendEmailError = yield sendEmail 'email', email, 'OpenHIM Console Password Reset', plainMessage, htmlMessage
if sendEmailError
utils.logAndSetResponse this, 500, "Could not send email to user via the API #{e}", 'error'
logger.info 'The email has been sent to the user'
this.body = "Successfully set user token/expiry for password reset."
this.status = 201
logger.info "User updated token/expiry for password reset #{email}"
catch e
utils.logAndSetResponse this, 500, "Could not update user with email #{email} via the API #{e}", 'error'
#######################################
### New User Set Password Functions ###
#######################################
# get the new user details
exports.getUserByToken = (token) ->
token = unescape token
try
projectionRestriction = "email": 1, "firstname": 1, "surname": 1, "msisdn": 1, "token": 1, "tokenType": 1, "locked": 1, "expiry": 1, "_id": 0
result = yield User.findOne(token: token, projectionRestriction).exec()
if not result
this.body = "User with token #{token} could not be found."
this.status = 404
else
# if expiry date has past
if moment(result.expiry).isBefore(moment())
# user- set password - expired
this.body = "Token #{token} has expired"
this.status = 410
else
this.body = result
catch e
utils.logAndSetResponse this, 500, "Could not find user with token #{token} via the API #{e}", 'error'
# update the password/details for the new user
exports.updateUserByToken = (token) ->
token = unescape token
userData = this.request.body
try
# first try get new user details to check expiry date
userDataExpiry = yield User.findOne(token: token).exec()
if not userDataExpiry
this.body = "User with token #{token} could not be found."
this.status = 404
return
else
# if expiry date has past
if moment(userDataExpiry.expiry).isBefore(moment())
# new user- set password - expired
this.body = "User with token #{token} has expired to set their password."
this.status = 410
return
catch e
utils.logAndSetResponse this, 500, "Could not find user with token #{token} via the API #{e}", 'error'
return
# check to make sure 'msisdn' isnt 'undefined' when saving
if userData.msisdn then msisdn = userData.msisdn else msisdn = null
# construct user object to prevent other properties from being updated
userUpdateObj =
token: null
tokenType: null
expiry: null
passwordAlgorithm: userData.passwordAlgorithm
passwordSalt: userData.passwordSalt
passwordHash: userData.passwordHash
if userDataExpiry.tokenType is 'newUser'
userUpdateObj.firstname = userData.firstname
userUpdateObj.surname = userData.surname
userUpdateObj.locked = false
userUpdateObj.msisdn = msisdn
try
yield User.findOneAndUpdate(token: token, userUpdateObj).exec()
this.body = "Successfully set new user password."
logger.info "User updated by token #{token}"
catch e
utils.logAndSetResponse this, 500, "Could not update user with token #{token} via the API #{e}", 'error'
#######################################
### New User Set Password Functions ###
#######################################
plainMessageTemplate = (firstname, setPasswordLink) -> """
<---------- New User - Set Password ---------->
Hi #{firstname},
A profile has been created for you on the OpenHIM instance running on #{config.alerts.himInstance}
Follow the below link to set your password and log into OpenHIM Console
#{setPasswordLink}
<---------- New User - Set Password ---------->
"""
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.alerts.himInstance}</p>
<p>Follow the below link to set your password and log into OpenHIM Console</p>
<p>#{setPasswordLink}</p>
"""
###
# Adds a user
###
exports.addUser = ->
# Test if the user is authorised
if not authorisation.inGroup 'admin', this.authenticated
utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to addUser denied.", 'info'
return
userData = this.request.body
# Generate the new user token here
# set locked = true
# set expiry date = true
token = randtoken.generate 32
userData.token = token
userData.tokenType = 'newUser'
userData.locked = true
duration = config.newUserExpiry.duration
durationType = config.newUserExpiry.durationType
userData.expiry = moment().add(duration, durationType).utc().format()
consoleURL = config.alerts.consoleURL
setPasswordLink = "#{consoleURL}/#/set-password/#{token}"
try
user = new User userData
result = yield Q.ninvoke user, 'save'
# Send email to new user to set password
plainMessage = plainMessageTemplate userData.firstname, setPasswordLink
htmlMessage = htmlMessageTemplate userData.firstname, setPasswordLink
contact.contactUser 'email', userData.email, 'OpenHIM Console Profile', plainMessage, htmlMessage, (err) ->
if err
logger.error "The email could not be sent to the user via the API #{err}"
else
logger.info 'The email has been sent to the new user'
this.body = 'User successfully created'
this.status = 201
logger.info "User #{this.authenticated.email} created user #{userData.email}"
catch e
utils.logAndSetResponse this, 500, "Could not add user via the API #{e}", 'error'
###
# Retrieves the details of a specific user
###
exports.getUser = (email) ->
email = unescape email
# Test if the user is authorised, allow a user to fetch their own details
if not authorisation.inGroup('admin', this.authenticated) and this.authenticated.email isnt email
utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to getUser denied.", 'info'
return
try
result = yield User.findOne(email: email).exec()
if not result
this.body = "User with email #{email} could not be found."
this.status = 404
else
this.body = result
catch e
utils.logAndSetResponse this, 500, "Could not get user via the API #{e}", 'error'
exports.updateUser = (email) ->
email = unescape email
# Test if the user is authorised, allow a user to update their own details
if not authorisation.inGroup('admin', this.authenticated) and this.authenticated.email isnt email
utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to updateUser denied.", 'info'
return
userData = this.request.body
# reset token/locked/expiry when user is updated and password supplied
if userData.passwordAlgorithm and userData.passwordHash and 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 this.authenticated.email is email and not authorisation.inGroup 'admin', this.authenticated then delete userData.groups
#Ignore _id if it exists (update is by email)
if userData._id then delete userData._id
try
yield User.findOneAndUpdate(email: email, userData).exec()
this.body = "Successfully updated user."
logger.info "User #{this.authenticated.email} updated user #{userData.email}"
catch e
utils.logAndSetResponse this, 500, "Could not update user #{email} via the API #{e}", 'error'
exports.removeUser = (email) ->
# Test if the user is authorised
if not authorisation.inGroup 'admin', this.authenticated
utils.logAndSetResponse this, 403, "User #{this.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 is 'root@openhim.org'
utils.logAndSetResponse this, 403, "User root@openhim.org is OpenHIM root, User cannot be deleted through the API", 'info'
return
try
yield User.findOneAndRemove(email: email).exec()
this.body = "Successfully removed user with email #{email}"
logger.info "User #{this.authenticated.email} removed user #{email}"
catch e
utils.logAndSetResponse this, 500, "Could not remove user #{email} via the API #{e}", 'error'
exports.getUsers = ->
# Test if the user is authorised
if not authorisation.inGroup 'admin', this.authenticated
utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to getUsers denied.", 'info'
return
try
this.body = yield User.find().exec()
catch e
utils.logAndSetResponse this, 500, "Could not fetch all users via the API #{e}", 'error'