Skip to content

Commit

Permalink
Allow digest to be cleared by passing null
Browse files Browse the repository at this point in the history
  • Loading branch information
venables committed Feb 13, 2017
1 parent abf529c commit 23afa8a
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 14 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ function signIn (email, password) {
* BCrypt requires that passwords are 72 characters maximum (it ignores characters after 72).
* 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.
* Passing a `null` value to the password will clear the `password_digest`.
* Passing `undefined` or a zero-length string to the password will leave the `password_digest` as-is

## Testing

Expand Down
36 changes: 30 additions & 6 deletions lib/secure-password.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ module.exports = function (Bookshelf) {

Bookshelf.plugin('virtuals')

/**
* Get the password field from the plugin configuration. defaults to `password_digest`
*
* @param {Model} model - the Bookshelf model
* @returns {String} - The database column name for the password digest
*/
function passwordField (model) {
if (typeof model.hasSecurePassword === 'string' || model.hasSecurePassword instanceof String) {
return model.hasSecurePassword
Expand All @@ -16,7 +22,21 @@ module.exports = function (Bookshelf) {
return DEFAULT_PASSWORD_FIELD
}

var Model = Bookshelf.Model.extend({
/**
* Checks if a string is empty (null, undefined, or length of zero)
*
* @param {String} str - A string
* @returns {Boolean} - Whether or not the string is empty
*/
function isEmpty (str) {
if (str === undefined || str === null) {
return true
}

return ('' + str).length === 0
}

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

constructor: function () {
Expand All @@ -27,11 +47,15 @@ module.exports = function (Bookshelf) {

this.virtuals = this.virtuals || {}
this.virtuals.password = {
get: function () {},
set: function (value) {
let salt = bcrypt.genSaltSync(DEFAULT_SALT_ROUNDS)
let passwordDigest = bcrypt.hashSync(value, salt)
this.set(passwordDigestField, passwordDigest)
get: function getPassword () {},
set: function setPassword (value) {
if (value === null) {
this.set(passwordDigestField, null)
} else if (!isEmpty(value)) {
let salt = bcrypt.genSaltSync(DEFAULT_SALT_ROUNDS)
let digest = bcrypt.hashSync(value, salt)
this.set(passwordDigestField, digest)
}
}
}
}
Expand Down
48 changes: 40 additions & 8 deletions test/secure-password.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,49 @@ describe('bookshelf-secure-password', function () {
})

describe('#constructor', function () {
it('does not keep the raw password on the model', function () {
const Model = bookshelf.Model.extend({
hasSecurePassword: true
describe('with the default column', function () {
let model

before(function () {
const Model = bookshelf.Model.extend({
hasSecurePassword: true
})

model = new Model({ password: 'testing' })
})

let model = new Model({ password: 'testing' })
expect(model.get('password')).to.be.undefined
expect(model.attributes.password).to.be.undefined
it('does not keep the raw password on the model', function () {
expect(model.get('password')).to.be.undefined
expect(model.attributes.password).to.be.undefined

expect(model.get('password_digest')).to.be.a.string
expect(model.attributes.password_digest).to.be.a.string
})

it('sets the password digest field to null if given a `null` value', function () {
expect(model.get('password_digest')).to.be.a.string
model.set('password', null)
expect(model.get('password_digest')).to.be.null
})

expect(model.get('password_digest')).to.be.a.string
expect(model.attributes.password_digest).to.be.a.string
it('does not change the password digest if given undefined', function () {
let originalString = model.get('password_digest')
model.set('password', undefined)
expect(model.get('password_digest')).to.equal(originalString)
})

it('does not change the password digest if given an empty string', function () {
let originalString = model.get('password_digest')
model.set('password', '')
expect(model.get('password_digest')).to.equal(originalString)
})

it('changes the password digest if given a blank (spaces-only) string', function () {
let originalString = model.get('password_digest')
model.set('password', ' ')
expect(model.get('password_digest')).to.be.a.string
expect(model.get('password_digest')).not.to.equal(originalString)
})
})

it('allows the default column to be overwritten', function () {
Expand Down

0 comments on commit 23afa8a

Please sign in to comment.