1
1
/*
2
2
* Copyright (C) 2014 TopCoder Inc., All Rights Reserved.
3
3
*
4
- * @version 1.0
5
- * @author LazyChild
4
+ * @version 1.1
5
+ * @author LazyChild, isv
6
+ *
7
+ * changes in 1.1
8
+ * - implemented generateResetToken function
6
9
*/
7
10
"use strict" ;
8
11
9
12
var async = require ( 'async' ) ;
13
+ var stringUtils = require ( "../common/stringUtils.js" ) ;
14
+ var moment = require ( 'moment-timezone' ) ;
15
+
16
+ var NotFoundError = require ( '../errors/NotFoundError' ) ;
10
17
var BadRequestError = require ( '../errors/BadRequestError' ) ;
11
18
var UnauthorizedError = require ( '../errors/UnauthorizedError' ) ;
12
19
var ForbiddenError = require ( '../errors/ForbiddenError' ) ;
20
+ var TOKEN_ALPHABET = stringUtils . ALPHABET_ALPHA_EN + stringUtils . ALPHABET_DIGITS_EN ;
21
+
22
+ /**
23
+ * Looks up for the user account matching specified handle (either TopCoder handle or social login username) or email.
24
+ * If user account is not found then NotFoundError is returned to callback; otherwise ID for found user account is
25
+ * passed to callback.
26
+ *
27
+ * @param {String } handle - the handle to check.
28
+ * @param {String } email - the email to check.
29
+ * @param {Object } api - the action hero api object.
30
+ * @param {Object } dbConnectionMap - the database connection map.
31
+ * @param {Function<err, row> } callback - the callback function.
32
+ */
33
+ var resolveUserByHandleOrEmail = function ( handle , email , api , dbConnectionMap , callback ) {
34
+ api . dataAccess . executeQuery ( "find_user_by_handle_or_email" , { handle : handle , email : email } , dbConnectionMap ,
35
+ function ( err , result ) {
36
+ if ( err ) {
37
+ callback ( err ) ;
38
+ return ;
39
+ }
40
+ if ( result && result [ 0 ] ) {
41
+ callback ( null , result [ 0 ] ) ;
42
+ } else {
43
+ callback ( new NotFoundError ( "User does not exist" ) ) ;
44
+ }
45
+ } ) ;
46
+ } ;
13
47
14
48
/**
15
49
* This is the function that stub reset password
@@ -21,24 +55,21 @@ var ForbiddenError = require('../errors/ForbiddenError');
21
55
function resetPassword ( api , connection , next ) {
22
56
var result , helper = api . helper ;
23
57
async . waterfall ( [
24
- function ( cb ) {
25
- if ( connection . params . handle == "nonValid" ) {
58
+ function ( cb ) {
59
+ if ( connection . params . handle === "nonValid" ) {
26
60
cb ( new BadRequestError ( "The handle you entered is not valid" ) ) ;
27
- return ;
28
- } else if ( connection . params . handle == "badLuck" ) {
61
+ } else if ( connection . params . handle === "badLuck" ) {
29
62
cb ( new Error ( "Unknown server error. Please contact support." ) ) ;
30
- return ;
31
- } else if ( connection . params . token == "unauthorized_token" ) {
63
+ } else if ( connection . params . token === "unauthorized_token" ) {
32
64
cb ( new UnauthorizedError ( "Authentication credentials were missing or incorrect." ) ) ;
33
- return ;
34
- } else if ( connection . params . token == "forbidden_token" ) {
65
+ } else if ( connection . params . token === "forbidden_token" ) {
35
66
cb ( new ForbiddenError ( "The request is understood, but it has been refused or access is not allowed." ) ) ;
36
- return ;
67
+ } else {
68
+ result = {
69
+ "description" : "Your password has been reset!"
70
+ } ;
71
+ cb ( ) ;
37
72
}
38
- result = {
39
- "description" : "Your password has been reset!"
40
- } ;
41
- cb ( ) ;
42
73
}
43
74
] , function ( err ) {
44
75
if ( err ) {
@@ -50,45 +81,55 @@ function resetPassword(api, connection, next) {
50
81
} ) ;
51
82
}
52
83
84
+
53
85
/**
54
- * This is the function that stub reset token
86
+ * Generates the token for resetting the password for specified user account. First checks if non-expired token already
87
+ * exists for the user. If so then BadRequestError is passed to callback. Otherwise a new token is generated and saved
88
+ * to cache and returned to callback.
55
89
*
90
+ * @param {Number } userHandle - handle of user to generate token for.
91
+ * @param {String } userEmailAddress - email address of user to email generated token to.
56
92
* @param {Object } api - The api object that is used to access the global infrastructure
57
- * @param {Object } connection - The connection object for the current request
58
- * @param {Function<connection, render> } next - The callback to be called after this function is done
93
+ * @param {Function<err> } callback - the callback function.
59
94
*/
60
- function generateResetToken ( api , connection , next ) {
61
- var result , helper = api . helper ;
62
- async . waterfall ( [
63
- function ( cb ) {
64
- if ( connection . params . handle == "nonValid" || connection . params . email == "nonValid@test.com" ) {
65
- cb ( new BadRequestError ( "The handle you entered is not valid" ) ) ;
66
- return ;
67
- } else if ( connection . params . handle == "badLuck" || connection . params . email == "badLuck@test.com" ) {
68
- cb ( new Error ( "Unknown server error. Please contact support." ) ) ;
69
- return ;
70
- }
95
+ var generateResetToken = function ( userHandle , userEmailAddress , api , callback ) {
96
+ var tokenCacheKey = 'tokens-' + userHandle + '-reset-token' ,
97
+ current ,
98
+ expireDate ,
99
+ expireDateString ,
100
+ emailParams ;
71
101
72
- if ( connection . params . handle == "googleSocial" || connection . params . email == "googleSocial@test.com" ) {
73
- result = {
74
- "socialLogin" : "Google"
75
- } ;
76
- } else {
77
- result = {
78
- "token" : "a3cbG"
79
- } ;
80
- }
81
- cb ( ) ;
82
- }
83
- ] , function ( err ) {
102
+ api . helper . getCachedValue ( tokenCacheKey , function ( err , token ) {
84
103
if ( err ) {
85
- helper . handleError ( api , connection , err ) ;
104
+ callback ( err ) ;
105
+ } else if ( token ) {
106
+ // Non-expired token already exists for this user - raise an error
107
+ callback ( new BadRequestError ( "You have already requested the reset token, please find it in your email inbox."
108
+ + " If it's not there. Please contact support@topcoder.com." ) ) ;
86
109
} else {
87
- connection . response = result ;
110
+ // There is no token - generate new one
111
+ var newToken = stringUtils . generateRandomString ( TOKEN_ALPHABET , 6 ) ,
112
+ lifetime = api . config . general . defaultResetPasswordTokenCacheLifetime ;
113
+ api . cache . save ( tokenCacheKey , newToken , lifetime ) ;
114
+
115
+ // Send email with token to user
116
+ current = new Date ( ) ;
117
+ expireDate = current . setSeconds ( current . getSeconds ( ) + lifetime / 1000 ) ;
118
+ expireDateString = moment ( expireDate ) . tz ( 'America/New_York' ) . format ( 'YYYY-MM-DD HH:mm:ss z' ) ;
119
+ emailParams = {
120
+ handle : userHandle ,
121
+ token : newToken ,
122
+ expiry : expireDateString ,
123
+ template : 'reset_token_email' ,
124
+ subject : api . config . general . resetPasswordTokenEmailSubject ,
125
+ toAddress : userEmailAddress
126
+ } ;
127
+ api . tasks . enqueue ( "sendEmail" , emailParams , 'default' ) ;
128
+
129
+ callback ( null , newToken ) ;
88
130
}
89
- next ( connection , true ) ;
90
131
} ) ;
91
- }
132
+ } ;
92
133
93
134
/**
94
135
* Reset password API.
@@ -113,17 +154,58 @@ exports.resetPassword = {
113
154
* Generate reset token API.
114
155
*/
115
156
exports . generateResetToken = {
116
- " name" : "generateResetToken" ,
117
- " description" : "generateResetToken" ,
157
+ name : "generateResetToken" ,
158
+ description : "generateResetToken" ,
118
159
inputs : {
119
160
required : [ ] ,
120
161
optional : [ "handle" , "email" ]
121
162
} ,
122
163
blockedConnectionTypes : [ ] ,
123
164
outputExample : { } ,
124
165
version : 'v2' ,
166
+ cacheEnabled : false ,
167
+ transaction : 'read' ,
168
+ databases : [ "common_oltp" ] ,
125
169
run : function ( api , connection , next ) {
126
170
api . log ( "Execute generateResetToken#run" , 'debug' ) ;
127
- generateResetToken ( api , connection , next ) ;
171
+ if ( connection . dbConnectionMap ) {
172
+ async . waterfall ( [
173
+ function ( cb ) { // Find the user either by handle or by email
174
+ // Get handle, email from request parameters
175
+ var handle = ( connection . params . handle || '' ) . trim ( ) ,
176
+ email = ( connection . params . email || '' ) . trim ( ) ,
177
+ byHandle = ( handle !== '' ) ,
178
+ byEmail = ( email !== '' ) ;
179
+
180
+ // Validate the input parameters, either handle or email but not both must be provided
181
+ if ( byHandle && byEmail ) {
182
+ cb ( new BadRequestError ( "Both handle and email are specified" ) ) ;
183
+ } else if ( ! byHandle && ! byEmail ) {
184
+ cb ( new BadRequestError ( "Either handle or email must be specified" ) ) ;
185
+ } else {
186
+ resolveUserByHandleOrEmail ( handle , email , api , connection . dbConnectionMap , cb ) ;
187
+ }
188
+ } , function ( result , cb ) {
189
+ if ( result . social_login_provider_name !== '' ) {
190
+ // For social login accounts return the provider name
191
+ cb ( null , null , result . social_login_provider_name ) ;
192
+ } else {
193
+ // Generate reset password token for user
194
+ generateResetToken ( result . handle , result . email_address , api , cb ) ;
195
+ }
196
+ }
197
+ ] , function ( err , newToken , socialProviderName ) {
198
+ if ( err ) {
199
+ api . helper . handleError ( api , connection , err ) ;
200
+ } else if ( newToken ) {
201
+ connection . response = { successful : true } ;
202
+ } else if ( socialProviderName ) {
203
+ connection . response = { socialProvider : socialProviderName } ;
204
+ }
205
+ next ( connection , true ) ;
206
+ } ) ;
207
+ } else {
208
+ api . helper . handleNoConnection ( api , connection , next ) ;
209
+ }
128
210
}
129
211
} ;
0 commit comments