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
360 | 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
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'
|