@@ -1 +1,2 @@ | |||
dist | |||
lib | |||
node_modules |
@@ -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); | |||
} |
@@ -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" | |||
] | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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) | |||
see [lesspass](https://github.com/lesspass/lesspass) project |
@@ -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)); | |||
}); | |||
}); | |||
} |
@@ -0,0 +1,7 @@ | |||
import encryption from './encryption'; | |||
module.exports = { | |||
generatePassword: encryption.generatePassword, | |||
createPassword: encryption.createPassword, | |||
createMasterPassword: encryption.encryptLogin | |||
}; |
@@ -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(''); | |||
} |
@@ -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) | |||
); | |||
}); |
@@ -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) | |||
); | |||
}); |
@@ -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)); | |||
} | |||
}); | |||
}); |
@@ -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) | |||
); | |||
}); | |||
}); | |||
}); |
@@ -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']) | |||
); | |||
}); |
@@ -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)); | |||
}); | |||
}); |
@@ -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; | |||
} |