Skip to content

Commit

Permalink
Update authenticate method to return a Promise
Browse files Browse the repository at this point in the history
  • Loading branch information
venables committed Feb 13, 2017
1 parent 2b40935 commit 27bdadb
Show file tree
Hide file tree
Showing 6 changed files with 785 additions and 64 deletions.
51 changes: 43 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

A Bookshelf.js plugin for handling secure passwords.

Adds a method to set and authenticate against a BCrypt password.
Adds a method to securely set and authenticate a password.

Similar to [has_secure_password](http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) in Ruby on Rails.

Expand All @@ -27,10 +27,10 @@ npm install bookshelf-secure-password --save
1. Initialize the plugin

```javascript
const bookshelf = require('bookshelf')(knex);
const securePassword = require('bookshelf-secure-password');
const bookshelf = require('bookshelf')(knex)
const securePassword = require('bookshelf-secure-password')

bookshelf.plugin(securePassword);
bookshelf.plugin(securePassword)
```

2. Add `hasSecurePassword` to the model(s) which require a secure password
Expand All @@ -39,7 +39,7 @@ npm install bookshelf-secure-password --save
const User = bookshelf.Model.extend({
tableName: 'users',
hasSecurePassword: true
});
})
```

By default, this requires a field on the table named `password_digest`. To use a different column, simply set `true` to be the column name. For example:
Expand All @@ -48,15 +48,50 @@ npm install bookshelf-secure-password --save
const User = bookshelf.Model.extend({
tableName: 'users',
hasSecurePassword: 'custom_password_digest_field'
});
})
```

3. To authenticate against the password, simply call the instance method `authenticate`:

```javascript
let isAuthenticated = user.authenticate('some-password');
user.authenticate('some-password').then(function (user) {
// do something with the authenticated user
}, function (err) {
// invalid password
})
```

## Example

```javascript
const User = require('./models/User')
/**
* Sign up a new user.
*
* @returns {Promise.<User>} A promise resolving to the newly registered User, or rejected with an error.
*/
function signUp (email, password) {
let user = new User({ email: email, password: password })
return user.save()
}
/**
* Sign in with a given email, password combination
*
* @returns {Promise.<User>} A promise resolving to the authenticated User, or rejected with an error.
*/
function signIn (email, password) {
return User.forge({ email: email })
.fetch()
.then(function (user) {
return user.authenticate(password)
})
}
```

## Notes

* This library uses the sync methods for bcrypt. This is to ensure the raw password is never stored on the model.
* This library uses the bcrypt synchronous methods when setting a password. This is to ensure the raw password is never stored on the model (in memory, or otherwise).
* This library enables the built-in `virtuals` plugin on Bookshelf.
43 changes: 0 additions & 43 deletions index.js

This file was deleted.

10 changes: 10 additions & 0 deletions lib/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict'

class PasswordMismatchError extends Error {
constructor (message) {
super(message)
this.name = 'PasswordMismatchError'
}
}

module.exports = PasswordMismatchError
67 changes: 67 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module.exports = function (Bookshelf) {
'use strict'
const bcrypt = require('bcrypt')
const DEFAULT_PASSWORD_FIELD = 'password_digest'
const PasswordMismatchError = require('./error')
const proto = Bookshelf.Model.prototype

Bookshelf.plugin('virtuals')

function passwordField (model) {
if (typeof this.hasSecurePassword === 'string' || this.hasSecurePassword instanceof String) {
return this.hasSecurePassword
}

return DEFAULT_PASSWORD_FIELD
}

var Model = Bookshelf.Model.extend({
hasSecurePassword: false,

constructor: function () {
let passwordDigestField = passwordField(this)

if (this.hasSecurePassword) {
this.virtuals = this.virtuals || {}
this.virtuals.password = {
get: function () {},
set: function (value) {
var salt = bcrypt.genSaltSync(10)
this.set(passwordDigestField, bcrypt.hashSync(value, salt))
}
}
}

proto.constructor.apply(this, arguments)
},

/**
* Authenticate a model's password, returning a Promise which resolves to the model (`this`) if
* the password matches, and rejects with a `PasswordMismatchError` if the it does not match.
*
* @param {String} password - The password to check
* @returns {Promise.<Model>} A promise resolving to `this` model on success, or rejects with
* a `PasswordMismatchError` upon failed check.
*/
authenticate: function (password) {
if (!this.hasSecurePassword) {
return Promise.reject(PasswordMismatchError())
}

return bcrypt
.compare(password, this.get(passwordField(this)))
.then((matches) => {
if (!matches) {
throw new PasswordMismatchError()
}

return this
})
.catch(() => {
throw new PasswordMismatchError()
})
}
})

Bookshelf.Model = Model
}
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "bookshelf-secure-password",
"version": "1.1.0",
"description": "A Bookshelf.js plugin for handling secure passwords",
"main": "index.js",
"main": "lib/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand All @@ -24,7 +24,6 @@
},
"homepage": "https://github.com/venables/bookshelf-secure-password#readme",
"dependencies": {
"bcrypt": "^0.8.7",
"lodash": "^4.13.1"
"bcrypt": "^1.0.2"
}
}
Loading

0 comments on commit 27bdadb

Please sign in to comment.