From 617be8698a5017e5ca20b6662973089abd877a65 Mon Sep 17 00:00:00 2001 From: Tencho Tenev Date: Sat, 7 Feb 2015 10:20:26 +0200 Subject: [PATCH] Basic package --- .editorconfig | 18 ++++++++ .jshintrc | 32 +++++++++++++ .travis.yml | 5 +++ Makefile | 26 +++++++++++ index.js | 80 +++++++++++++++++++++++++++++++++ package.json | 39 ++++++++++++++++ test/helpers/chai.js | 11 +++++ test/helpers/env.js | 2 + test/index.spec.js | 105 +++++++++++++++++++++++++++++++++++++++++++ test/mocha.opts | 4 ++ 10 files changed, 322 insertions(+) create mode 100644 .editorconfig create mode 100644 .jshintrc create mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 index.js create mode 100644 package.json create mode 100644 test/helpers/chai.js create mode 100644 test/helpers/env.js create mode 100644 test/index.spec.js create mode 100644 test/mocha.opts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b4fd231 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = crlf +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..a01ae12 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,32 @@ +{ + // Environment + "browser": true, + "node": true, + "mocha": true, + "predef": [ + "expect" + ], + "devel": false, + + // Enforcing + "bitwise": true, + "curly": false, + "eqeqeq": true, + "latedef": true, + "maxparams": 4, + "maxdepth": 4, + "maxstatements": 15, + "maxcomplexity": 6, + "noarg": true, + "shadow": false, + "undef": true, + + // Relaxing + "asi": true, + "boss": false, + "debug": false, + "eqnull": true, + "evil": false, + "expr": false, + "loopfunc": false +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..70183fb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - '0.10' +script: 'make test-cov' +after_success: 'make coveralls' diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..82f7a39 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +# Detect Windows OS +ifdef SystemRoot + # Fix slashes of path to work on Windows + FixPath = $(subst /,\,$1) +else + FixPath = $1 +endif + +NODE_MODULES = ./node_modules/.bin/ +MOCHA = ./node_modules/mocha/bin/ +TESTS_PATH = test/ +COVERAGE_REPORT = ./coverage/lcov.info +COVERALLS = ./node_modules/coveralls/bin/coveralls.js + +test: + $(call FixPath, $(NODE_MODULES))mocha $(TEST_PATH) + +test-cov: istanbul + +istanbul: + $(call FixPath, $(NODE_MODULES))istanbul cover$(call FixPath, $(MOCHA))_mocha $(TESTS_INTEGRATION) $(TESTS_UNIT) + +coveralls: + cat $(COVERAGE_REPORT) | $(COVERALLS) + +.PHONY: test diff --git a/index.js b/index.js new file mode 100644 index 0000000..9dc7d43 --- /dev/null +++ b/index.js @@ -0,0 +1,80 @@ +var bcrypt = require('bcrypt') + +function verify(value, hash, next) { + bcrypt.compare(value, hash, function(err, res) { + if (err) + return next(err) + return next(null, res) + }) +} + +function set(value, hashField, next) { + var self = this + + bcrypt.genSalt(10, function(err, salt) { + if (err) + return next(err) + bcrypt.hash(value, salt, function(err, hash) { + if (err) + return next(err) + + // Set new hash + self[hashField] = hash + return next() + }) + }) +} + +function getVerifierForField(fieldName) { + return function(value, next) { + bcrypt.compare(value, this[fieldName], function(err, res) { + if (err) + return next(err) + return next(null, res) + }) + } +} + +function getSetterForField(fieldName) { + return function(value, next) { + var self = this + + bcrypt.genSalt(10, function(err, salt) { + if (err) + return next(err) + bcrypt.hash(value, salt, function(err, hash) { + if (err) + return next(err) + + // Set new hash + self[fieldName] = hash + return next() + }) + }) + } +} + +function setEncryption(schema, options) { + var fieldName, capitlaizedFieldName, + verifyMethodName, setMethodName + + if (typeof options === 'string') { + capitlaizedFieldName = options.charAt(0).toUpperCase() + options.slice(1) + + options = { + field: options, + verify: 'verify' + capitlaizedFieldName, + set: 'set' + capitlaizedFieldName + } + } + schema.methods[options.verify] = getVerifierForField(options.field) + schema.methods[options.set] = getSetterForField(options.field) +} + +module.exports = { + verify: verify, + set: set, + setEncryption: setEncryption, + _getVerifierForField: getVerifierForField, + _getSetterForField: getSetterForField +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..94ae327 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "bcrypt-schema", + "version": "0.1.0", + "description": "Encrypt schema fields. Use it as a mongoose plugin or as a standalone module.", + "main": "index.js", + "scripts": { + "test": "make test", + "test-cov": "make test-cov" + }, + "repository": { + "type": "git", + "url": "https://github.com/tenevdev/bcrypt-schema.git" + }, + "keywords": [ + "encrypt", + "mongoose", + "bcrypt", + "password", + "auth", + "hash" + ], + "author": "Tencho Tenev", + "license": "MIT", + "bugs": { + "url": "https://github.com/tenevdev/bcrypt-schema/issues" + }, + "homepage": "https://github.com/tenevdev/bcrypt-schema", + "dependencies": { + "bcrypt": "^0.8.1" + }, + "devDependencies": { + "mocha": "^2.1.0", + "istanbul": "^0.3.5", + "coveralls": "^2.11.2", + "sinon": "^1.12.2", + "chai": "^1.10.0", + "sinon-chai": "^2.6.0" + } +} diff --git a/test/helpers/chai.js b/test/helpers/chai.js new file mode 100644 index 0000000..35d58d1 --- /dev/null +++ b/test/helpers/chai.js @@ -0,0 +1,11 @@ +// Configure chai for global usage +var chai = require('chai'), + sinonChai = require('sinon-chai') + +chai.use(sinonChai) +chai.config.includeStack = true + +global.expect = chai.expect +global.AssertionError = chai.AssertionError +global.Assertion = chai.Assertion +global.assert = chai.assert diff --git a/test/helpers/env.js b/test/helpers/env.js new file mode 100644 index 0000000..baa3089 --- /dev/null +++ b/test/helpers/env.js @@ -0,0 +1,2 @@ +// Configure test environment +process.env.NODE_ENV = 'test' diff --git a/test/index.spec.js b/test/index.spec.js new file mode 100644 index 0000000..7984b59 --- /dev/null +++ b/test/index.spec.js @@ -0,0 +1,105 @@ +var bcryptSchema = require('..'), + bcrypt = require('bcrypt'), + sinon = require('sinon'), + compare, genSalt, hash + +describe('bcrypt-schema', function() { + before('mock bcrypt', function() { + compare = sinon.stub(bcrypt, 'compare').yields(null, true) + genSalt = sinon.stub(bcrypt, 'genSalt').yields(null, 'test-salt') + hash = sinon.stub(bcrypt, 'hash').yields(null, 'test-hash') + }) + after('restore bcrypt', function() { + bcrypt.compare.restore() + bcrypt.genSalt.restore() + bcrypt.hash.restore() + }) + describe('verify(value, hash, next)', function() { + beforeEach('call verify', function(done) { + var value = 'test-value', + hash = 'test-hash' + bcryptSchema.verify(value, hash, function() { + done() + }) + }) + it('should compare a given value with a given hash', function() { + expect(compare).calledOnce + }) + }) + describe('set(value, hashField, next)', function() { + beforeEach('call set', function(done) { + var value = 'test-value', + hashField = 'hashedValue' + bcryptSchema.set(value, hashField, function() { + done() + }) + }) + it('should hash a value', function() { + expect(genSalt).calledOnce + expect(hash).calledOnce + }) + it('should set the field with the generate hash', function() { + expect(bcryptSchema.hashedValue).to.equal('test-hash') + }) + }) + describe('setEncyption(schema, options)', function() { + var schema = { + methods: {} + } + afterEach('reset schema object', function() { + schema = { + methods: {} + } + }) + describe('when shorthand', function() { + beforeEach('call setEncryption(schema, field)', function() { + var field = 'hashedFieldName' + bcryptSchema.setEncryption(schema, field) + }) + it('should set schema methods names', function() { + expect(schema.methods).to.have.property('verifyHashedFieldName') + expect(schema.methods).to.have.property('setHashedFieldName') + }) + it('should set schema methods', function() { + expect(schema.methods.verifyHashedFieldName).to.be.a('function') + expect(schema.methods.setHashedFieldName).to.be.a('function') + }) + }) + describe('when full', function() { + beforeEach('call setEncryption(schema, options)', function() { + var options = { + field: 'hashedFieldName', + verify: 'customVerifyName', + set: 'customSetName' + } + bcryptSchema.setEncryption(schema, options) + }) + it('should set schema method names', function() { + expect(schema.methods).to.have.property('customVerifyName') + expect(schema.methods).to.have.property('customSetName') + }) + it('should set schema methods', function() { + expect(schema.methods.customSetName).to.be.a('function') + expect(schema.methods.customVerifyName).to.be.a('function') + }) + }) + }) + describe('getVerifierForField(fieldName)', function() { + var result + beforeEach('call getVerifierForField', function() { + result = bcryptSchema._getVerifierForField('test-field') + }) + it('should return a function', function() { + expect(result).to.be.a('function') + }) + }) + describe('getSetterForField(fieldName)', function() { + var result + beforeEach('call getSetterForField', function() { + result = bcryptSchema._getSetterForField('test-field') + }) + it('should return a function', function() { + expect(result).to.be.a('function') + }) + }) +}) diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..85c1641 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,4 @@ +-r test/helpers/env +-r test/helpers/chai +-R spec +-u bdd