Completed
Pull Request — master (#40)
by
unknown
02:00
created

src/cli/users/utils.js   F

Complexity

Total Complexity 84
Complexity/F 2.9

Size

Lines of Code 308
Function Count 29

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 0
wmc 84
nc 1
mnd 4
bc 74
fnc 29
dl 0
loc 308
rs 3.12
bpm 2.5517
cpm 2.8965
noi 5
c 2
b 0
f 0

18 Functions

Rating   Name   Duplication   Size   Complexity  
A utils.js ➔ findSync 0 10 3
A utils.js ➔ findByEmail 0 10 3
A utils.js ➔ findByUsername 0 10 3
B utils.js ➔ decodeUser 0 12 5
A utils.js ➔ getAll 0 3 1
A utils.js ➔ getRole 0 10 1
B utils.js ➔ checkSameEmail 0 21 5
A utils.js ➔ findByResetPasswordToken 0 10 3
B utils.js ➔ getUserWorkflow 0 37 2
A utils.js ➔ isValid 0 8 3
B utils.js ➔ isAbeRestrictedUrl 0 10 5
C utils.js ➔ commonPassword 0 45 8
A utils.js ➔ encryptPassword 0 4 1
A utils.js ➔ getTokenFromCookies 0 6 1
A utils.js ➔ getUserRoutes 0 11 1
C utils.js ➔ isUserAllowedOnRoute 0 25 8
A utils.js ➔ find 0 10 3
B utils.js ➔ loginLimitTry 0 35 1

How to fix   Complexity   

Complexity

Complex classes like src/cli/users/utils.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import redis from 'redis'
2
import Limiter from 'ratelimiter'
3
import owasp from 'owasp-password-strength-test'
4
import bcrypt from 'bcrypt-nodejs'
5
import Cookies from 'cookies'
6
import jwt from 'jwt-simple'
7
8
import {
9
  User
10
  ,config
11
  ,coreUtils
12
} from '../../cli'
13
14
export function checkSameEmail(data) {
15
  var emailAlreadyUsed = false
16
  var bdd = User.manager.instance.get()
17
  for (var i = 0, len = bdd.length; i < len; i++) {
18
    var user = bdd[i]
19
    if (user.email === data.email && parseInt(user.id) !== parseInt(data.id)) {
20
      emailAlreadyUsed = true
21
    }
22
  }
23
24
  if (emailAlreadyUsed === true) {
25
    return {
26
      success:0,
27
      message: 'Email adress already exist'
28
    }
29
  }else {
30
    return {
31
      success:1
32
    }
33
  }
34
}
35
36
export function getRole(data) {
37
  var roles = config.users.roles
38
  Array.prototype.forEach.call(roles, (role) => {
39
    if(role.name === data.role) {
40
      data.role = role
41
    }
42
  })
43
44
  return data
45
}
46
47
export function commonPassword(data) {
48
  var owaspConfig = config.users.owasp
49
  owasp.config(owaspConfig)
50
  var sameAsUser = (typeof owaspConfig.sameAsUser !== 'undefined' && owaspConfig.sameAsUser !== null) ? owaspConfig.sameAsUser : true
51
  var mostCommon = (typeof owaspConfig.mostCommon !== 'undefined' && owaspConfig.mostCommon !== null) ? owaspConfig.mostCommon : true
52
  var mostCommonPassword = config.users.mostCommonPassword
53
  owasp.tests.required.push(function(password) {
54
    if (mostCommon && coreUtils.array.contains(mostCommonPassword, password.toLowerCase())) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if mostCommon && coreUtils....password.toLowerCase()) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
55
      return 'the password used is too common.'
56
    }
57
  })
58
59
  var currentUserName = data.username
60
  owasp.tests.required.push(function(password) {
61
    var username = currentUserName
62
    var shouldTest = sameAsUser
63
64
    if(shouldTest) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if shouldTest is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
65
      if (password.toLowerCase() === username.toLowerCase()) {
66
        return 'username and password must be different.'
67
      }
68
      if (password.toLowerCase() === username.toLowerCase().split('').reverse().join('')) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if password.toLowerCase() =...("").reverse().join("") is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
69
        return 'username and password must be different, not just inverted.'
70
      }
71
    }
72
  })
73
74
  var res = owasp.test(data.password)
75
76
  if(typeof res.errors !== 'undefined' && res.errors !== null
77
      && res.errors.length > 0) {
78
    var message = ''
79
    Array.prototype.forEach.call(res.errors, (error) => {
80
      message += error + '<br />'
81
    })
82
    return {
83
      success:0,
84
      message: message
85
    }
86
  }else {
87
    return {
88
      success:1
89
    }
90
  }
91
}
92
93
export function encryptPassword(numb, password) {
94
  var salt = bcrypt.genSaltSync(numb)
95
  return bcrypt.hashSync(password, salt)
96
}
97
98
export function getUserRoutes(workflow) {
99
  var routes = config.users.routes
100
  var userRoles = []
101
  Array.prototype.forEach.call(Object.keys(routes), (role) => {
102
    if(role === workflow) {
103
      userRoles = routes[role]
104
    }
105
  })
106
107
  return userRoles
108
}
109
110
111
export function findSync(id) {
112
  var bdd = User.manager.instance.get()
113
  for (var i = 0, len = bdd.length; i < len; i++) {
114
    var user = bdd[i]
115
    if (parseInt(user.id) === parseInt(id)) {
116
      return user
117
    }
118
  }
119
  return null
120
}
121
122
export function find(id, done) {
123
  var bdd = User.manager.instance.get()
124
  for (var i = 0, len = bdd.length; i < len; i++) {
125
    var user = bdd[i]
126
    if (parseInt(user.id) === parseInt(id)) {
127
      return done(null, user)
128
    }
129
  }
130
  return done(null, null)
131
}
132
133
export function findByUsername(username, done) {
134
  var bdd = User.manager.instance.get()
135
  for (var i = 0, len = bdd.length; i < len; i++) {
136
    var user = bdd[i]
137
    if (user.username === username) {
138
      return done(null, user)
139
    }
140
  }
141
  return done(null, null)
142
}
143
144
export function findByEmail(email, done) {
145
  var bdd = User.manager.instance.get()
146
  for (var i = 0, len = bdd.length; i < len; i++) {
147
    var user = bdd[i]
148
    if (user.email === email) {
149
      return done(null, user)
150
    }
151
  }
152
  return done(null, null)
153
}
154
155
export function findByResetPasswordToken(resetPasswordToken, done) {
156
  var bdd = User.manager.instance.get()
157
  for (var i = 0, len = bdd.length; i < len; i++) {
158
    var user = bdd[i]
159
    if (user.resetPasswordToken === resetPasswordToken) {
160
      return done(null, user)
161
    }
162
  }
163
  return done(null, null)
164
}
165
166
export function isValid(user, password) {
167
  if(user.actif === 1) {
168
    if(bcrypt.compareSync(password, user.password)) {
169
      return true
170
    }
171
  }
172
  return false
173
}
174
175
export function decodeUser(req, res) {
176
  var decoded = {}
177
  var token = User.utils.getTokenFromCookies(req, res)
178
  if(typeof token !== 'undefined' && token !== null && token !== '') {
179
    try {
180
      var secret = config.users.secret
181
      decoded = jwt.decode(token, secret)
182
    } catch (err) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
183
  }
184
185
  return decoded
186
}
187
188
export function getTokenFromCookies(req, res) {
189
  var cookies = new Cookies(req, res, {
190
    secure: config.cookie.secure
191
  })
192
  return cookies.get('x-access-token')
193
}
194
195
export function isAbeRestrictedUrl(currentRoute) {
196
  if( currentRoute.indexOf('/abe/users/forgot') > -1
197
    || currentRoute.indexOf('/abe/users/login') > -1
198
    || currentRoute.indexOf('/abe/users/reset') > -1
199
    || !/^\/abe/.test(currentRoute)) {
200
    return false
201
  }
202
203
  return true
204
}
205
206
export function isUserAllowedOnRoute(workflow, currentRoute) {
207
  if( currentRoute.indexOf('/abe/users/forgot') > -1 || currentRoute.indexOf('/abe/users/login') > -1 || !/^\/abe/.test(currentRoute)) {
208
    return true
209
  }
210
211
  var isAllowed = false
212
213
  if (currentRoute.indexOf('abe/') === -1) {
214
    isAllowed = true
215
  }
216
217
  if (workflow != null) {
218
    var routes = config.users.routes
219
    if(typeof routes[workflow] !== 'undefined' && routes[workflow] !== null) {
220
      Array.prototype.forEach.call(routes[workflow], (route) => {
221
        var reg = new RegExp(route)
222
        if(reg.test(currentRoute)) {
223
          isAllowed = true
224
        }
225
      })
226
    }
227
  }
228
  
229
  return isAllowed
230
}
231
232
export function getAll() {
233
  return User.manager.instance.get()
234
}
235
236
export function getUserWorkflow(status, role) {
0 ignored issues
show
Unused Code introduced by
The parameter role is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
237
  var flows = []
238
239
  function addFlow (flow, type, action) {
240
    type = (type != null) ? type : flow
241
    return {
242
      status: flow,
243
      url: `/abe/save/${type}/${action}`
244
    }
245
  }
246
247
  if (config.users.enable) {
248
    var before = null
249
    var found = false
250
    Array.prototype.forEach.call(config.users.workflow, (flow) => {
251
      if (found) {
252
        flows.push(addFlow(flow, flow, "submit"))
253
        found = false
254
      }
255
      if (status == flow) {
256
        found = true
257
        if (before != null) {
258
          if (flow == "publish") {
259
            flows.push(addFlow("edit", "draft", "submit"))
260
          }else {
261
            flows.push(addFlow("reject", before, "reject"))
262
          }
263
        }
264
        flows.push(addFlow(flow, flow, "edit"))
265
      }
266
      before = flow
267
    })
268
  }else {
269
    flows = [addFlow("draft", "draft", "submit"), addFlow("publish", "publish", "submit")]
270
  }
271
  return flows
272
}
273
274
export function loginLimitTry(username) {
275
  var p = new Promise((resolve) => {
276
    var isNexted = false
277
    try {
278
      var limiterConfig = config.users.limiter
279
280
      var client = redis.createClient()
281
      client.on('error', function() {
282
        if (!isNexted) {
283
          isNexted = true
284
          resolve()
285
        }
286
      })
287
288
      var limit = new Limiter({
289
        id: username,
290
        db: client,
291
        duration: limiterConfig.duration,
292
        max: limiterConfig.max
293
      })
294
295
      limit.get(function(err, limit) {
296
        if (err) {
297
          resolve()
298
        }else {
299
          resolve(limit)
300
        }
301
      })
302
    }catch(e) {
303
      resolve()
304
    }
305
  })
306
307
  return p
308
}