diff --git a/.gitignore b/.gitignore index 53c37a1..a9f4ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -dist \ No newline at end of file +lib +node_modules \ No newline at end of file diff --git a/lesspass.js b/lesspass.js deleted file mode 100644 index b1dd30c..0000000 --- a/lesspass.js +++ /dev/null @@ -1,13 +0,0 @@ -var text = require('./text'); -var passwordGenerator = require('./password-generator'); - -module.exports = { - createPassword: createPassword, - createMasterPassword: passwordGenerator.createMasterPassword -}; - -function createPassword(masterPassword, entry) { - var hash = passwordGenerator._createHash(masterPassword, entry); - var template = passwordGenerator._getTemplate(entry.password.settings); - return text._encode(hash, template); -} \ No newline at end of file diff --git a/package.json b/package.json index a36b4a3..e0fe81f 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,10 @@ "name": "lesspass", "version": "1.1.1", "description": "node.js stateless password management solution", - "main": "dist/lesspass.js", + "main": "lib/lesspass.js", "scripts": { - "test": "mocha --compilers js:babel-core/register tests", - "test:watch": "npm run test -- -w", - "build": "babel lesspass.js text.js password-generator.js --presets babel-preset-es2015 --out-dir dist", + "test": "ava --require babel-core/register && xo", + "build": "babel src --out-dir lib", "prepublish": "npm test && npm run build" }, "keywords": [ @@ -17,22 +16,55 @@ "type": "git", "url": "git+ssh://git@github.com:oslab-fr/lesspass-npm.git" }, - "author": "Guillaume Vincent", + "author": { + "name": "Guillaume Vincent", + "email": "guillaume@oslab.fr", + "url": "guillaumevincent.com" + }, + "engines": { + "node": ">=4.2.6" + }, "license": "MIT", "bugs": { "url": "https://github.com/oslab-fr/lesspass-npm/issues" }, "homepage": "https://github.com/oslab-fr/lesspass-npm#readme", "devDependencies": { - "babel-cli": "^6.5.1", - "babel-core": "^6.5.2", - "babel-preset-es2015": "^6.5.0", - "mocha": "^2.4.5", - "webpack": "^1.12.13" + "ava": "^0.14.0", + "babel-cli": "^6.9.0", + "babel-core": "^6.9.0", + "babel-preset-es2015": "^6.9.0", + "xo": "^0.15.1" }, "babel": { "presets": [ "es2015" ] + }, + "browserify": { + "transform": [ + "babelify" + ] + }, + "xo": { + "esnext": true, + "space": true, + "envs": [ + "browser", + "webextensions", + "shared-node-browser", + "es6" + ], + "ignores": [ + "lib/**" + ] + }, + "ava": { + "files": [ + "tests/*.js" + ], + "source": [ + "src/*.js" + ] } } diff --git a/password-generator.js b/password-generator.js deleted file mode 100644 index da4b113..0000000 --- a/password-generator.js +++ /dev/null @@ -1,44 +0,0 @@ -var crypto = require('crypto'); - -module.exports = { - createMasterPassword: createMasterPassword, - _getTemplate: _getTemplate, - _createHash:_createHash -}; - - -function createMasterPassword(email, password) { - return new Promise((resolve, reject) => { - var iterations = 8192; - var keylen = 32; - crypto.pbkdf2(password, email, iterations, keylen, 'sha256', function (error, key) { - if (error) { - reject('error in pbkdf2'); - } else { - resolve(key.toString('hex')); - } - }); - }); -} - -function _getTemplate(passwordTypes = ['strong']) { - var passwordTypesInfo = { - lowercase: {value: 'vc', order: 1}, - uppercase: {value: 'VC', order: 2}, - numbers: {value: 'n', order: 3}, - symbols: {value: 's', order: 4}, - strong: {value: 'Cvcvns', order: 5} - }; - return passwordTypes - .map(passwordType => passwordTypesInfo[passwordType]) - .sort((passwordType1, passwordType2) => passwordType1.order > passwordType2.order) - .map(passwordType => passwordType.value) - .join(''); -} - - -function _createHash(masterPassword, {site, password={length: 12, counter: 1}}) { - var salt = site + password.counter.toString(); - var hash = crypto.createHmac('sha256', masterPassword).update(salt).digest('hex'); - return hash.substring(0, password.length); -} \ No newline at end of file diff --git a/readme.md b/readme.md index 2e0f02f..9ac2b9d 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,11 @@ # LessPass Core -LessPass npm module use to generate unique password +LessPass node module use to generate unique password + +# requirements + + * node 4.2.x ## Install @@ -10,21 +14,31 @@ LessPass npm module use to generate unique password ## Usage - var lesspass = require('lesspass'); - var entry = { - site: 'lesspass', + import lesspass from 'lesspass'; + + const login = 'contact@lesspass.com'; + const masterPassword = 'password'; + const site = 'lesspass.com'; + const options = { + counter: 1, password: { - length: 14, - settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], - counter: 1 + length: 12, + settings: ['lowercase', 'uppercase', 'numbers', 'symbols'] } }; - var masterPassword = '90cff82b8847525370a8f29a59ecf45db62c719a535788ad0df58d32304e925d'; - var password = lesspass.createPassword(masterPassword, entry); - console.log(password); // ubUB4[yqAD3?od + lesspass.generatePassword(login, masterPassword, site, options).then(password => { + console.log(password) //azYS7,olOL2] + }); + +## API + +### `generatePassword(login, masterPassword, site, options)` + +generate unique password based on login, masterPassword, site and options. + -## Test +## Tests - npm run test + npm test -[lesspass submodule](https://github.com/lesspass/lesspass) \ No newline at end of file +see [lesspass](https://github.com/lesspass/lesspass) project \ No newline at end of file diff --git a/src/encryption.js b/src/encryption.js new file mode 100644 index 0000000..5487987 --- /dev/null +++ b/src/encryption.js @@ -0,0 +1,58 @@ +import crypto from 'crypto'; +import render from './render'; + +module.exports = { + createPassword: _createPassword, + generatePassword: _generatePassword, + encryptLogin: _encryptLogin, + deriveHash: _deriveHash +}; + +function _encryptLogin(login, masterPassword) { + return new Promise((resolve, reject) => { + if (!login || !masterPassword) { + reject('encryptLogin parameter could not be empty'); + } + const iterations = 8192; + const keylen = 32; + crypto.pbkdf2(masterPassword, login, iterations, keylen, 'sha256', (error, key) => { + if (error) { + reject('error in pbkdf2'); + } else { + resolve(key.toString('hex')); + } + }); + }); +} + +function _deriveHash(hash, site, {password = {length: 12}, counter = 1} = {}) { + const salt = site + counter.toString(); + const derivedHash = crypto.createHmac('sha256', hash).update(salt).digest('hex'); + return derivedHash.substring(0, password.length); +} + +function _createPassword(hash, entry) { + // createPassword is deprecated use generatePassword instead + const options = { + counter: entry.password.counter || 1, + password: entry.password + }; + const site = entry.site; + const derivedHash = _deriveHash(hash, site, options); + const template = render.getTemplate(options.password.settings); + return render.prettyPrint(derivedHash, template); +} + +function _generatePassword(login, masterPassword, site, options) { + return new Promise((resolve, reject) => { + if (!login || !masterPassword || !site) { + reject('generatePassword invalid parameter'); + } + + _encryptLogin(login, masterPassword).then(hash => { + const derivedHash = _deriveHash(hash, site, options); + const template = render.getTemplate(options.password.settings); + resolve(render.prettyPrint(derivedHash, template)); + }); + }); +} diff --git a/src/lesspass.js b/src/lesspass.js new file mode 100644 index 0000000..e98b333 --- /dev/null +++ b/src/lesspass.js @@ -0,0 +1,7 @@ +import encryption from './encryption'; + +module.exports = { + generatePassword: encryption.generatePassword, + createPassword: encryption.createPassword, + createMasterPassword: encryption.encryptLogin +}; diff --git a/src/render.js b/src/render.js new file mode 100644 index 0000000..e6de2f9 --- /dev/null +++ b/src/render.js @@ -0,0 +1,60 @@ +module.exports = { + prettyPrint: _prettyPrint, + getTemplate: _getTemplate, + _getCharType, + _getPasswordChar, + _string2charCodes +}; + +function _getCharType(template, index) { + return template[index % template.length]; +} + +function _getPasswordChar(charType, index) { + const passwordsChars = { + V: 'AEIOUY', + C: 'BCDFGHJKLMNPQRSTVWXZ', + v: 'aeiouy', + c: 'bcdfghjklmnpqrstvwxz', + A: 'AEIOUYBCDFGHJKLMNPQRSTVWXZ', + a: 'AEIOUYaeiouyBCDFGHJKLMNPQRSTVWXZbcdfghjklmnpqrstvwxz', + n: '0123456789', + s: '@&%?,=[]_:-+*$#!\'^~;()/.', + x: 'AEIOUYaeiouyBCDFGHJKLMNPQRSTVWXZbcdfghjklmnpqrstvwxz0123456789@&%?,=[]_:-+*$#!\'^~;()/.' + }; + const passwordChar = passwordsChars[charType]; + return passwordChar[index % passwordChar.length]; +} + +function _prettyPrint(hash, template) { + let password = ''; + + _string2charCodes(hash).forEach((charCode, index) => { + const charType = _getCharType(template, index); + password += _getPasswordChar(charType, charCode); + }); + return password; +} + +function _string2charCodes(text) { + const charCodes = []; + for (let i = 0; i < text.length; i++) { + charCodes.push(text.charCodeAt(i)); + } + return charCodes; +} + +function _getTemplate(passwordTypes = ['strong']) { + const passwordTypesInfo = { + lowercase: {value: 'vc', order: 1}, + uppercase: {value: 'VC', order: 2}, + numbers: {value: 'n', order: 3}, + symbols: {value: 's', order: 4}, + strong: {value: 'Cvcvns', order: 5} + }; + return passwordTypes + .map(passwordType => passwordTypesInfo[passwordType]) + .sort((passwordType1, passwordType2) => passwordType1.order > passwordType2.order) + .map(passwordType => passwordType.value) + .join(''); +} diff --git a/tests/encryption.tests.js b/tests/encryption.tests.js new file mode 100644 index 0000000..b3c06cf --- /dev/null +++ b/tests/encryption.tests.js @@ -0,0 +1,82 @@ +import test from 'ava'; +import {encryptLogin, deriveHash} from '../src/encryption'; + +test('should create encrypted hash with pbkdf2 (8192 iterations and sha 256)', t => { + const login = 'test@lesspass.com'; + const masterPassword = 'password'; + + return encryptLogin(login, masterPassword).then(hash => { + t.is('90cff82b8847525370a8f29a59ecf45db62c719a535788ad0df58d32304e925d', hash); + }); +}); + +test('should create encrypted hash with 64 chars length', t => { + const login = 'test@lesspass.com'; + const masterPassword = 'password'; + + return encryptLogin(login, masterPassword).then(hash => { + t.is(64, hash.length); + }); +}); + +test('should reject promise if no parameter', t => { + t.plan(1); + return encryptLogin('', '').catch(() => { + t.pass('promise rejected with empty parameter'); + }); +}); + +test('should derive hash with default length', t => { + const hash = '9f505f3a95fe0485da3242cb81c9fe25c2f400d8399737655a8dad2b52778d88'; + const site = 'lesspass.com'; + t.is(12, deriveHash(hash, site).length); +}); + +test('should derive hash with default options', t => { + const hash = '90cff82b8847525370a8f29a59ecf45db62c719a535788ad0df58d32304e925d'; + const site = 'lesspass.com'; + const option = { + counter: 1, + password: { + length: 12, + settings: ['lowercase', 'uppercase', 'numbers', 'symbols'] + } + }; + t.is( + deriveHash(hash, site), + deriveHash(hash, site, option) + ); +}); + +test('should derive hash with defined length', t => { + const hash = 'd79d8482f708122288af7b259393a58fe05840f4555cc935cdd3f062b9aa75ed'; + const site = 'lesspass.com'; + const option = { + counter: 1, + password: { + length: 10 + } + }; + t.is(10, deriveHash(hash, site, option).length); +}); + +test('should return two different passwords if site different', t => { + const hash = 'f4fd3885fb70085f2285c3382e2d9adb4c2553285fc45dd896791aa5e79070a9'; + const site = 'google.com'; + const site2 = 'facebook.com'; + t.not( + deriveHash(hash, site), + deriveHash(hash, site2) + ); +}); + +test('should return two different passwords if counter different', t => { + const hash = 'dfba06278c9aa24d992bc2d390a53efef482788859455875f72015335d085fcd'; + const site = 'lesspass.com'; + const option = {counter: 1}; + const option2 = {counter: 2}; + t.not( + deriveHash(hash, site, option), + deriveHash(hash, site, option2) + ); +}); diff --git a/tests/legacy.tests.js b/tests/legacy.tests.js new file mode 100644 index 0000000..542bc0e --- /dev/null +++ b/tests/legacy.tests.js @@ -0,0 +1,50 @@ +import test from 'ava'; +import lesspass from '../src/lesspass'; + +test('should create password', t => { + const masterPassword = 'password'; + const entry = { + site: 'facebook', + password: { + length: 14, + settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], + counter: 1 + } + }; + t.is('iwIQ8[acYT4&oc', lesspass.createPassword(masterPassword, entry)); +}); +test('should create password 2', t => { + const masterPassword = 'password'; + const entry = { + site: 'facebook', + password: { + length: 12, + settings: ['strong'], + counter: 1 + } + }; + t.is('Vexu8[Syce4&', lesspass.createPassword(masterPassword, entry)); +}); +test('should create 2 passwords different if counter different', t => { + const masterPassword = 'password'; + const entry = { + site: 'facebook', + password: { + length: 14, + settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], + counter: 1 + } + }; + const entry2 = { + site: 'facebook', + password: { + length: 14, + settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], + counter: 2 + } + }; + t.not( + lesspass.createPassword(masterPassword, entry), + lesspass.createPassword(masterPassword, entry2) + ); +}); diff --git a/tests/lesspass.tests.js b/tests/lesspass.tests.js index c3aafbf..c3aff6a 100644 --- a/tests/lesspass.tests.js +++ b/tests/lesspass.tests.js @@ -1,52 +1,144 @@ -var assert = require('assert'); -var lesspass = require('../lesspass'); +import test from 'ava'; +import lesspass from '../src/lesspass'; -describe('lesspass', function () { - it('should create password', function () { - var masterPassword = "password"; - var entry = { - site: 'facebook', - password: { - length: 14, - settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], - counter: 1 - } - }; - assert.equal('iwIQ8[acYT4&oc', lesspass.createPassword(masterPassword, entry)); - }); - it('should create password 2', function () { - var masterPassword = "password"; - var entry = { - site: 'facebook', - password: { - length: 12, - settings: ['strong'], - counter: 1 - } - }; - assert.equal('Vexu8[Syce4&', lesspass.createPassword(masterPassword, entry)); - }); - it('should create 2 passwords different if counter different', function () { - var masterPassword = "password"; - var entry = { - site: 'facebook', - password: { - length: 14, - settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], - counter: 1 - } - }; - var entry2 = { - site: 'facebook', - password: { - length: 14, - settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], - counter: 2 - } - }; - assert.notEqual( - lesspass.createPassword(masterPassword, entry), - lesspass.createPassword(masterPassword, entry2) - ); - }); +test('generate password', t => { + const login = 'contact@lesspass.com'; + const masterPassword = 'password'; + const site = 'lesspass.com'; + const options = { + counter: 1, + password: { + length: 12, + settings: ['lowercase', 'uppercase', 'numbers', 'symbols'] + } + }; + return lesspass.generatePassword(login, masterPassword, site, options).then(password => { + t.is(password, 'azYS7,olOL2]'); + }); +}); + +test('generate password2', t => { + const login = 'contact@lesspass.com'; + const masterPassword = 'password'; + const site = 'lesspass.com'; + const options = { + counter: 1, + password: { + length: 12, + settings: ['lowercase', 'uppercase', 'numbers', 'symbols'] + } + }; + return lesspass.generatePassword(login, masterPassword, site, options).then(password => { + t.is(password, 'azYS7,olOL2]'); + }); +}); + +test('generate password3', t => { + const login = 'contact@lesspass.com'; + const masterPassword = 'password'; + const site = 'lesspass.com'; + const options = { + counter: 1, + password: { + length: 12, + settings: ['lowercase', 'uppercase', 'numbers', 'symbols'] + } + }; + return lesspass.generatePassword(login, masterPassword, site, options).then(password => { + t.is(password, 'azYS7,olOL2]'); + }); +}); + +test('auto generated password', t => { + const promises = []; + const entries = [ + { + login: 'contact@lesspass.com', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 1, password: {length: 12, settings: ['lowercase', 'uppercase', 'numbers', 'symbols']}}, + generatedPassword: 'azYS7,olOL2]' + }, + { + login: 'contact@lesspass.com', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 1, password: {length: 14, settings: ['lowercase', 'uppercase', 'numbers', 'symbols']}}, + generatedPassword: 'azYS7,olOL2]iz' + }, + { + login: 'contact@lesspass.com', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 1, password: {length: 12, settings: ['lowercase']}}, + generatedPassword: 'azyseqololat' + }, + { + login: 'contact@lesspass.com', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 1, password: {length: 12, settings: ['uppercase', 'numbers', 'symbols']}}, + generatedPassword: 'AZ3[EQ7@OL2]' + }, + { + login: 'contact@lesspass.com', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 1, password: {length: 12, settings: ['numbers', 'symbols']}}, + generatedPassword: '4?3[7,7@7@2]' + }, + { + login: 'contact@lesspass.com', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 1, password: {length: 12, settings: ['symbols']}}, + generatedPassword: '[?=[&,:@:@[]' + }, + { + login: 'contact@lesspass.com', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 1, password: {length: 12, settings: ['lowercase', 'uppercase', 'numbers']}}, + generatedPassword: 'azYS7uwAW8at' + }, + { + login: 'contact@lesspass.com', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 1, password: {length: 12, settings: ['lowercase', 'uppercase']}}, + generatedPassword: 'azYSeqOLolAT' + }, + { + login: 'contact@lesspass.com', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 2, password: {length: 12, settings: ['lowercase', 'uppercase', 'numbers', 'symbols']}}, + generatedPassword: 'obYT2=olOV9=' + }, + { + login: 'lesspass', + masterPassword: 'password', + site: 'lesspass.com', + options: {counter: 1, password: {length: 12, settings: ['lowercase', 'uppercase', 'numbers', 'symbols']}}, + generatedPassword: 'erOC1%imIW3,' + }, + { + login: 'contact@lesspass.com', + masterPassword: 'password2', + site: 'lesspass.com', + options: {counter: 1, password: {length: 12, settings: ['lowercase', 'uppercase', 'numbers', 'symbols']}}, + generatedPassword: 'uvUM5_ucUP5=' + } + ]; + + for (const entry of entries) { + promises.push(lesspass.generatePassword(entry.login, entry.masterPassword, entry.site, entry.options)); + } + + t.plan(entries.length); + return Promise.all(promises).then(values => { + for (let i = 0; i < values.length; i++) { + t.is(entries[i].generatedPassword, values[i], JSON.stringify(entries[i], null, 2)); + } + }); }); diff --git a/tests/password-generator.tests.js b/tests/password-generator.tests.js deleted file mode 100644 index ab50fe0..0000000 --- a/tests/password-generator.tests.js +++ /dev/null @@ -1,98 +0,0 @@ -var assert = require('assert'); -var passwordGenerator = require('../password-generator'); -var lesspass = require('../lesspass'); - -describe('passwordGenerator', function () { - describe('master password', function () { - it('should create master password with pbkdf2 (8192 iterations and sha 256)', function (done) { - var email = 'test@lesspass.com'; - var password = "password"; - - lesspass.createMasterPassword(email, password).then(function (masterPassword) { - assert.equal("90cff82b8847525370a8f29a59ecf45db62c719a535788ad0df58d32304e925d", masterPassword); - done(); - }); - }); - it('should create 64 char length master password', function (done) { - var email = 'test@lesspass.com'; - var password = "password"; - - lesspass.createMasterPassword(email, password).then(function (masterPassword) { - assert.equal(64, masterPassword.length); - done(); - }); - }); - }); - describe('password templates password', function () { - it('should get default template from password type', function () { - assert.equal('Cvcvns', passwordGenerator._getTemplate()); - }); - it('should get template from password type', function () { - assert.equal('vc', passwordGenerator._getTemplate(['lowercase'])); - assert.equal('VC', passwordGenerator._getTemplate(['uppercase'])); - assert.equal('n', passwordGenerator._getTemplate(['numbers'])); - assert.equal('s', passwordGenerator._getTemplate(['symbols'])); - }); - it('should concatenate template if two password password_types', function () { - assert.equal('vcVC', passwordGenerator._getTemplate(['lowercase', 'uppercase'])); - assert.equal('vcns', passwordGenerator._getTemplate(['lowercase', 'numbers', 'symbols'])); - }); - it('should not care about order of type in password password_types', function () { - assert.equal( - passwordGenerator._getTemplate(['uppercase', 'lowercase']), - passwordGenerator._getTemplate(['lowercase', 'uppercase']) - ); - }); - }); - describe('hash', function () { - it('should have default length of 12', function () { - var masterPassword = 'password'; - var entry = {'site': 'facebook'}; - assert.equal(12, passwordGenerator._createHash(masterPassword, entry).length); - }); - it('should allow to change default length', function () { - var masterPassword = 'password'; - var entry = { - site: 'lesspass', - password: { - length: 10, - counter: 1 - } - }; - assert.equal(10, passwordGenerator._createHash(masterPassword, entry).length); - }); - it('should return two different passwords if site different', function () { - var masterPassword = 'password'; - var entry = {site: 'facebook'}; - var entry2 = {site: 'google'}; - assert.notEqual( - passwordGenerator._createHash(masterPassword, entry), - passwordGenerator._createHash(masterPassword, entry2) - ); - }); - it('should return two different passwords if counter different', function () { - var masterPassword = 'password'; - var entry = { - site: 'facebook', - password: { - length: 14, - settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], - counter: 1 - } - }; - var entry2 = { - site: 'facebook', - password: { - length: 14, - settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], - counter: 2 - } - }; - assert.notEqual( - passwordGenerator._createHash(masterPassword, entry), - passwordGenerator._createHash(masterPassword, entry2) - ); - }); - }); - -}); diff --git a/tests/render.tests.js b/tests/render.tests.js new file mode 100644 index 0000000..33fe289 --- /dev/null +++ b/tests/render.tests.js @@ -0,0 +1,60 @@ +import test from 'ava'; +import render from '../src/render'; + +test('should print different password if templates different', t => { + const hash = '78ae5892055ab59fdd54489ae30928d322841a27590b65cf875fcfdd083f7c32'; + t.not(render.prettyPrint(hash, 'cv'), render.prettyPrint(hash, 'vc')); +}); + +test('must return a string of the same length as the input', t => { + const hash = 'f5785e569ab5d38b02e2248c798ac17df90f57a85f34a9d5382408c2f0d9532d'; + t.is(hash.length, render.prettyPrint(hash, 'cv').length); +}); + +test('should return char inside a string based on modulo of the index', t => { + const template = 'cv'; + t.is('c', render._getCharType(template, 0)); + t.is('v', render._getCharType(template, 1)); + t.is('c', render._getCharType(template, 10)); +}); + +test('should convert a string into an array of char code', t => { + const charCodes = render._string2charCodes('ab40f6ee71'); + t.is(97, charCodes[0]); + t.is(98, charCodes[1]); + t.is(10, charCodes.length); +}); + +test('should get password char based on its type and index', t => { + const typeVowel = 'V'; + t.is('A', render._getPasswordChar(typeVowel, 0)); +}); + +test('should modulo if overflow', t => { + const typeVowel = 'V'; + t.is('E', render._getPasswordChar(typeVowel, 1)); + t.is('E', render._getPasswordChar(typeVowel, 7)); +}); + +test('should get default template', t => { + t.is('Cvcvns', render.getTemplate()); +}); + +test('should get template from password setting', t => { + t.is('vc', render.getTemplate(['lowercase'])); + t.is('VC', render.getTemplate(['uppercase'])); + t.is('n', render.getTemplate(['numbers'])); + t.is('s', render.getTemplate(['symbols'])); +}); + +test('should concatenate template if two password settings', t => { + t.is('vcVC', render.getTemplate(['lowercase', 'uppercase'])); + t.is('vcns', render.getTemplate(['lowercase', 'numbers', 'symbols'])); +}); + +test('should not care about order of password settings', t => { + t.is( + render.getTemplate(['uppercase', 'lowercase']), + render.getTemplate(['lowercase', 'uppercase']) + ); +}); diff --git a/tests/text.tests.js b/tests/text.tests.js deleted file mode 100644 index b567ded..0000000 --- a/tests/text.tests.js +++ /dev/null @@ -1,34 +0,0 @@ -var assert = require('assert'); -var text = require('../text'); - -describe('crypto', function () { - it('should return char inside a string based on modulo of the index', function () { - var template = 'cv'; - assert.equal('c', text._getCharType(template, 0)); - assert.equal('v', text._getCharType(template, 1)); - assert.equal('c', text._getCharType(template, 10)); - }); - it('should convert a string into an array of char code', function () { - var charCodes = text._string2charCodes('ab40f6ee71'); - assert.equal(97, charCodes[0]); - assert.equal(98, charCodes[1]); - assert.equal(10, charCodes.length); - }); - it('must return a string of the same length as the input', function () { - var hash = 'Y2Vi2a112A'; - assert.equal(hash.length, text._encode(hash, 'cv').length); - }); - it('should return different values if strings different', function () { - var hash = 'a'; - assert.notEqual(text._encode(hash, 'cv'), text._encode(hash, 'vc')); - }); - it('should get password char based on its type and index', function () { - var typeVowel = 'V'; - assert.equal('A', text._getPasswordChar(typeVowel, 0)); - }); - it('should modulo if overflow', function () { - var typeVowel = 'V'; - assert.equal('E', text._getPasswordChar(typeVowel, 1)); - assert.equal('E', text._getPasswordChar(typeVowel, 7)); - }); -}); diff --git a/text.js b/text.js deleted file mode 100644 index a394370..0000000 --- a/text.js +++ /dev/null @@ -1,45 +0,0 @@ -module.exports = { - _getCharType: _getCharType, - _getPasswordChar: _getPasswordChar, - _encode: _encode, - _string2charCodes: _string2charCodes -}; - -function _getCharType(template, index) { - return template[index % template.length]; -} - -function _getPasswordChar(charType, index) { - var passwordsChars = { - V: "AEIOUY", - C: "BCDFGHJKLMNPQRSTVWXZ", - v: "aeiouy", - c: "bcdfghjklmnpqrstvwxz", - A: "AEIOUYBCDFGHJKLMNPQRSTVWXZ", - a: "AEIOUYaeiouyBCDFGHJKLMNPQRSTVWXZbcdfghjklmnpqrstvwxz", - n: "0123456789", - s: "@&%?,=[]_:-+*$#!'^~;()/.", - x: "AEIOUYaeiouyBCDFGHJKLMNPQRSTVWXZbcdfghjklmnpqrstvwxz0123456789@&%?,=[]_:-+*$#!'^~;()/." - }; - var passwordChar = passwordsChars[charType]; - return passwordChar[index % passwordChar.length]; -} - -function _encode(hash, template) { - var password = ''; - _string2charCodes(hash).map( - (charCode, index) => { - let charType = _getCharType(template, index); - password += _getPasswordChar(charType, charCode); - } - ); - return password; -} - -function _string2charCodes(text) { - var charCodes = []; - for (let i = 0; i < text.length; i++) { - charCodes.push(text.charCodeAt(i)); - } - return charCodes; -} \ No newline at end of file