1

I'm building rest api using Node JS from here. My problem is, when i'm using password regex in my schema it return ValidationError: password: Path password is invalid ($2a$09$oh.2MUqOVSCDdC/ZpjTyXOVCykNg73B8wVDdxsg3ElieOZ5hLmD4K). But when i remove password regex, it's working fine.

Note: It's just occured when i tried to update the data, it working fine when i create or login the user

here is my code :

model.js

import crypto from 'crypto'
import bcrypt from 'bcrypt'
import randtoken from 'rand-token'
import mongoose, { Schema } from 'mongoose'
import mongooseKeywords from 'mongoose-keywords'
import { env } from '../../config'

const roles = ['owner', 'freelancer', 'admin']

const userSchema = new Schema({
  email         : {type: String, match: /^\S+@\S+\.\S+$/, required: true, unique: true, trim: true, lowercase: true},
  password      : {type: String, required: true, match: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{6,}$/},
  username      : {type: String,  unique: true, lowercase: true},
  role          : {type: String, enum: roles, required: true},
  picture       : {type: String, trim: true},
  services      : {facebook: String, google: String},
  status        : {type: Boolean, default: false},
  skills        : [{type: String}],
  address       : {type: String},
  rating        : {type: Number, default: 0},
  project       : {type: Number, default: 0},
  country       : {type: String},
  provinsi      : {type: String},
  wilayah       : {type: String}, //kabupaten atau kota
  kecamatan     : {type: String},
  kelurahan     : {type: String},
  zip_code      : {type: Number}
}, {
  timestamps: true
})

userSchema.path('email').set(function (email) {
  if (!this.picture || this.picture.indexOf('https://gravatar.com') === 0) {
    const hash = crypto.createHash('md5').update(email).digest('hex')
    this.picture = `https://gravatar.com/avatar/${hash}?d=identicon`
  }

  // if (!this.username) {
  //   this.username = email.replace(/^(.+)@.+$/, '$1')
  // }

  return email
})

userSchema.pre('save', function (next) {
  if (!this.isModified('password')) return next()

  /* istanbul ignore next */
  const rounds = env === 'test' ? 1 : 9

  bcrypt.hash(this.password, rounds).then((hash) => {
    this.password = hash
    next()
  }).catch(next)
})

userSchema.methods = {
  view (full) {
    let view = {}
    let fields = ['id', 'username', 'picture', 'rating']

    if (full) {
      fields = [...fields, 'email', 'role', 'status', 'skills', 'address', 'project', 'country', 'provinsi', 'wilayah', 'kecamatan', 'kelurahan', 'zip_code', 'createdAt']
    }

    fields.forEach((field) => { view[field] = this[field] })

    return view
  },

  authenticate (password) {
    return bcrypt.compare(password, this.password).then((valid) => valid ? this : false)
  }
}

userSchema.statics = {
  roles,

  createFromService ({ service, id, email, username, picture }) {
    return this.findOne({ $or: [{ [`services.${service}`]: id }, { email }] }).then((user) => {
      if (user) {
        user.services[service] = id
        user.username = username
        user.picture = picture
        return user.save()
      } else {
        const password = randtoken.generate(16)
        return this.create({ services: { [service]: id }, email, password, username, picture })
      }
    })
  }
}

// userSchema.plugin(mongooseKeywords, { paths: ['email', 'username'] })

const model = mongoose.model('User', userSchema)

export const schema = model.schema
export default model

index.js

import { Router } from 'express'
import { middleware as query } from 'querymen'
import { middleware as body } from 'bodymen'
import { password as passwordAuth, master, token } from '../../services/passport'
import { index, showMe, show, create, update, updatePassword, destroy } from './controller'
import { schema } from './model'
export User, { schema } from './model'

const router = new Router()
const { username, email, password, address, skills, picture, role, rating, project, country, provinsi, wilayah, kecamatan, kelurahan, zip_code  } = schema.tree

/**
 * @api {get} /users Retrieve users
 * @apiName RetrieveUsers
 * @apiGroup User
 * @apiPermission admin
 * @apiParam {String} access_token User access_token.
 * @apiUse listParams
 * @apiSuccess {Object[]} users List of users.
 * @apiError {Object} 400 Some parameters may contain invalid values.
 * @apiError 401 Admin access only.
 */
router.get('/',
  token({ required: true, roles: ['admin'] }),
  query(),
  index)

/**
 * @api {get} /users/me Retrieve current user
 * @apiName RetrieveCurrentUser
 * @apiGroup User
 * @apiPermission user
 * @apiParam {String} access_token User access_token.
 * @apiSuccess {Object} user User's data.
 */
router.get('/me',
  token({ required: true }),
  showMe)

/**
 * @api {get} /users/:id Retrieve user
 * @apiName RetrieveUser
 * @apiGroup User
 * @apiPermission public
 * @apiSuccess {Object} user User's data.
 * @apiError 404 User not found.
 */
router.get('/:id',
  show)

/**
 * @api {post} /users Create user
 * @apiName CreateUser
 * @apiGroup User
 * @apiPermission master
 * @apiParam {String} access_token Master access_token.
 * @apiParam {String} email User's email.
 * @apiParam {String{6..}} password User's password.
 * @apiParam {String} [name] User's name.
 * @apiParam {String} [picture] User's picture.
 * @apiParam {String=user,admin} [role=user] User's picture.
 * @apiSuccess (Sucess 201) {Object} user User's data.
 * @apiError {Object} 400 Some parameters may contain invalid values.
 * @apiError 401 Master access only.
 * @apiError 409 Email already registered.
 */
router.post('/',
  master(),
  body({ email, password, username, picture, role }),
  create)

/**
 * @api {put} /users/:id Update user
 * @apiName UpdateUser
 * @apiGroup User
 * @apiPermission user
 * @apiParam {String} access_token User access_token.
 * @apiParam {String} [name] User's name.
 * @apiParam {String} [picture] User's picture.
 * @apiSuccess {Object} user User's data.
 * @apiError {Object} 400 Some parameters may contain invalid values.
 * @apiError 401 Current user or admin access only.
 * @apiError 404 User not found.
 */
router.put('/:id',
  token({ required: true }),
  body({  address }),
  update)

/**
 * @api {put} /users/:id/password Update password
 * @apiName UpdatePassword
 * @apiGroup User
 * @apiHeader {String} Authorization Basic authorization with email and password.
 * @apiParam {String{6..}} password User's new password.
 * @apiSuccess (Success 201) {Object} user User's data.
 * @apiError {Object} 400 Some parameters may contain invalid values.
 * @apiError 401 Current user access only.
 * @apiError 404 User not found.
 */
router.put('/:id/password',
  passwordAuth(),
  body({ password }),
  updatePassword)

/**
 * @api {delete} /users/:id Delete user
 * @apiName DeleteUser
 * @apiGroup User
 * @apiPermission admin
 * @apiParam {String} access_token User access_token.
 * @apiSuccess (Success 204) 204 No Content.
 * @apiError 401 Admin access only.
 * @apiError 404 User not found.
 */
router.delete('/:id',
  token({ required: true, roles: ['admin'] }),
  destroy)

export default router

controller.js

import _ from 'lodash'
import { success, notFound } from '../../services/response/'
import { User } from '.'

export const index = ({ querymen: { query, select, cursor } }, res, next) =>
  User.find(query, select, cursor)
    .then((users) => users.map((user) => user.view()))
    .then(success(res))
    .catch(next)

export const show = ({ params }, res, next) =>
  User.findById(params.id)
    .then(notFound(res))
    .then((user) => user ? user.view() : null)
    .then(success(res))
    .catch(next)

export const showMe = ({ user }, res) =>
  res.json(user.view(true))

export const create = ({ bodymen: { body } }, res, next) =>
  User.create(body)
    .then((users) => users.view(true))
    .then(success(res, 201))
    .catch((err) => {
      /* istanbul ignore else */
      if (err.name === 'MongoError' && err.code === 11000) {
        res.status(409).json({
          valid: false,
          param: 'username or email',
          message: 'username or email already registered'
        })
      } else {
        next(err)
      }
    })

export const update = ({ bodymen: { body }, params, user }, res, next) =>
  User.findById(params.id === 'me' ? user.id : params.id)
    .then(notFound(res))
    .then((result) => {
      if (!result) return null
      const isAdmin = user.role === 'admin'
      const isSelfUpdate = user.id === result.id
      if (!isSelfUpdate && !isAdmin) {
        res.status(401).json({
          valid: false,
          message: 'You can\'t change other user\'s data'
        })
        return null
      }
      return result
    })
    .then((user) => user ? _.merge(user, body).save() : null)
    .then((user) => user ? user.view(true) : null)
    .then(success(res))
    .catch(next)

export const updatePassword = ({ bodymen: { body }, params, user }, res, next) =>
  User.findById(params.id === 'me' ? user.id : params.id)
    .then(notFound(res))
    .then((result) => {
      if (!result) return null
      const isSelfUpdate = user.id === result.id
      if (!isSelfUpdate) {
        res.status(401).json({
          valid: false,
          param: 'password',
          message: 'You can\'t change other user\'s password'
        })
        return null
      }
      return result
    })
    .then((user) => user ? user.set({ password: body.password }).save() : null)
    .then((user) => user ? user.view(true) : null)
    .then(success(res))
    .catch(next)

export const destroy = ({ params }, res, next) =>
  User.findById(params.id)
    .then(notFound(res))
    .then((user) => user ? user.remove() : null)
    .then(success(res, 204))
    .catch(next)
imam kusuma
  • 61
  • 1
  • 8
  • 1
    This is way too much code and too little in the way of telling us where the actual problem may lie. Please highlight the portions of code which may be causing the problem. – Tim Biegeleisen Feb 26 '18 at 02:07
  • i think my problem is on the update part in controller.js – imam kusuma Feb 26 '18 at 02:10
  • but it's working fine when i remove match: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{6,}$/ in my schema(model.js) – imam kusuma Feb 26 '18 at 02:11
  • Your regex is insisting on a lowercase, uppercase, and digit, as well as 6 or more alphanumeric characters. Is the problem that your code crashes, or that the password seems to be "failing?" You are not being clear on this. – Tim Biegeleisen Feb 26 '18 at 02:13
  • my problem is why when i'm update the data its return ValidationError: password: Path `password` is invalid – imam kusuma Feb 26 '18 at 02:18
  • ugh sorry i don't know how to ask properly – imam kusuma Feb 26 '18 at 02:19
  • 1
    Again, _should_ that password be failing? Does it have a lower, upper, and digit? Is it six or more characters? – Tim Biegeleisen Feb 26 '18 at 02:19
  • no, i intentionally want the password have lower, uper and digit – imam kusuma Feb 26 '18 at 02:22
  • 1
    Per @TimBiegeleisen's comment above: it sounds like the password you're testing with isn't being matched by the regex. I'd suggest simplifying your regex (start with something simple like `/\S+/`) to verify that the plumbing is working as expected and then building it back up bit-by-bit -- ideally driven by tests. – pdoherty926 Feb 26 '18 at 03:52
  • @pdoherty926 it still error – imam kusuma Feb 26 '18 at 05:47
  • @imamkusuma you may benefit from [Reference - Password Validation](https://stackoverflow.com/questions/48345922/reference-password-validation/) – ctwheels Feb 27 '18 at 18:14

0 Answers0