All files / src/hooks hooks.users.js

19.67% Statements 12/61
5.13% Branches 2/39
14.29% Functions 1/7
20.34% Lines 12/59
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 1011x 1x 1x 1x 1x   1x                                                                                                                                                       1x     1x 1x   1x   1x 1x                  
import _ from 'lodash'
import generateRandomPassword from 'password-generator'
import makeDebug from 'debug'
import { getItems, replaceItems } from 'feathers-hooks-common'
import { BadRequest } from '@feathersjs/errors'
 
const debug = makeDebug('kalisio:kCore:users:hooks')
 
export function enforcePasswordPolicy (options = {}) {
  return async function (hook) {
    if (hook.type !== 'before') {
      throw new Error(`The 'enforePasswordPolicy' hook should only be used as a 'before' hook.`)
    }
    // By pass check ?
    if (hook.params.force) return hook
    let app = hook.app
    let item = getItems(hook)
    let user = options.userAsItem ? item : hook.params.user
    // Get both password(s) since some rules target one and some the other one(s)
    let clearPassword = _.get(item, options.passwordField || 'clearPassword')
    let hashedPasswords = _.get(user, options.previousPasswordsField || 'previousPasswords', [])
    if (clearPassword && hashedPasswords && app.getPasswordPolicy) {
      debug('Enforcing password policy on user', user)
      const validator = app.getPasswordPolicy()
      // First check the clear password
      let result = validator.validate(clearPassword, { list: true })
      // Then check for the last used passwords using password policy verifier
      for (let i = 0; i < hashedPasswords.length; i++) {
        try {
          await validator.comparePassword({ password: hashedPasswords[i] }, clearPassword)
          // If we have found a similar password stop
          result.push('previous')
          break
        } catch (error) {
          // Check next one
        }
      }
 
      if (!_.isEmpty(result)) {
        throw new BadRequest('The provided password does not comply to the password policy', {
          translation: {
            key: 'WEAK_PASSWORD',
            keys: result.map(rule => 'WEAK_PASSWORD_' + rule.toUpperCase()),
            params: Object.assign({ failedRules: result }, _.omit(validator.options, ['prohibited']))
          }
        })
      }
    }
    return hook
  }
}
 
export function storePreviousPassword (options = {}) {
  return function (hook) {
    if (hook.type !== 'before') {
      throw new Error(`The 'storePreviousPassword' hook should only be used as a 'before' hook.`)
    }
    let app = hook.app
    let data = getItems(hook)
    if (app.getPasswordPolicy && hook.params.previousItem) {
      const validator = app.getPasswordPolicy()
      // Based on previous password value
      let user = hook.params.previousItem
      const passwordField = options.passwordField || 'password'
      let password = _.get(user, passwordField)
      if (password) {
        const previousPasswordsField = options.previousPasswordsField || 'previousPasswords'
        let previousPasswords = _.get(user, previousPasswordsField, [])
        debug(`Moving previous password from field ${passwordField} in field ${previousPasswords} on user`, user)
        previousPasswords.push(password)
        // Pop oldest password when required
        const max = _.get(validator, 'options.history', 5)
        if (previousPasswords.length > max) previousPasswords.shift()
        Object.assign(data, { [previousPasswordsField]: previousPasswords })
        replaceItems(hook, data)
      }
    }
    return hook
  }
}
 
export function generatePassword (hook) {
  Iif (hook.type !== 'before') {
    throw new Error(`The 'generatePassword' hook should only be used as a 'before' hook.`)
  }
  let app = hook.app
  let data = hook.data
  // Generate a password
  let passwordRule = new RegExp('[\\w\\d\\?\\-]')
  // If we have a password policy ensure we match it
  Eif (app.getPasswordPolicy) {
    const validator = app.getPasswordPolicy()
    do {
      data.password = generateRandomPassword(validator.options.minLength || 12, false, passwordRule)
    } while (!validator.validate(data.password))
  } else {
    data.password = generateRandomPassword(12, false, passwordRule)
  }
  return hook
}