All files / src/api users.coffee

20.98% Statements 30/143
0% Branches 0/21
0% Functions 0/16
21.13% Lines 30/142
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 3601x 1x 1x 1x   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x         1x                                                     1x                   1x             1x           1x                                                                                                                 1x                                             1x                                                                                                             1x                   1x                   1x                                                                                                 1x                                       1x                                                               1x                                           1x                    
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'