@@ -1,2 +1,2 @@ | |||
lib | |||
node_modules | |||
node_modules/ | |||
npm-debug.log |
@@ -1,2 +0,0 @@ | |||
.travis.yml | |||
tests |
@@ -1,3 +1,3 @@ | |||
language: node_js | |||
node_js: | |||
- 4 | |||
- 6 |
@@ -9,6 +9,7 @@ module.exports = { | |||
_string2charCodes, | |||
_getCharType, | |||
_getPasswordChar, | |||
_createHmac | |||
}; | |||
function _encryptLogin(login, masterPassword) { | |||
@@ -29,15 +30,23 @@ function _encryptLogin(login, masterPassword) { | |||
} | |||
function _renderPassword(encryptedLogin, site, passwordOptions) { | |||
const derivedEncryptedLogin = this._deriveEncryptedLogin(encryptedLogin, site, passwordOptions); | |||
const template = this._getPasswordTemplate(passwordOptions); | |||
return this._prettyPrint(derivedEncryptedLogin, template); | |||
return _deriveEncryptedLogin(encryptedLogin, site, passwordOptions).then(function (derivedEncryptedLogin) { | |||
const template = _getPasswordTemplate(passwordOptions); | |||
return _prettyPrint(derivedEncryptedLogin, template); | |||
}); | |||
} | |||
function _createHmac(encryptedLogin, salt) { | |||
return new Promise(resolve => { | |||
resolve(crypto.createHmac('sha256', encryptedLogin).update(salt).digest('hex')); | |||
}); | |||
} | |||
function _deriveEncryptedLogin(encryptedLogin, site, passwordOptions = {length: 12, counter: 1}) { | |||
const salt = site + passwordOptions.counter.toString(); | |||
const derivedHash = crypto.createHmac('sha256', encryptedLogin).update(salt).digest('hex'); | |||
return derivedHash.substring(0, passwordOptions.length); | |||
return _createHmac(encryptedLogin, salt).then(derivedHash => { | |||
return derivedHash.substring(0, passwordOptions.length); | |||
}); | |||
} | |||
function _getPasswordTemplate(passwordTypes) { | |||
@@ -59,9 +68,9 @@ function _getPasswordTemplate(passwordTypes) { | |||
function _prettyPrint(hash, template) { | |||
let password = ''; | |||
this._string2charCodes(hash).forEach((charCode, index) => { | |||
const charType = this._getCharType(template, index); | |||
password += this._getPasswordChar(charType, charCode); | |||
_string2charCodes(hash).forEach((charCode, index) => { | |||
const charType = _getCharType(template, index); | |||
password += _getPasswordChar(charType, charCode); | |||
}); | |||
return password; | |||
} | |||
@@ -1,19 +1,21 @@ | |||
{ | |||
"name": "lesspass", | |||
"version": "4.0.4", | |||
"version": "5.0.0", | |||
"author": "Guillaume Vincent <guillaume@oslab.fr>", | |||
"description": "LessPass javascript module to generate idempotent passwords", | |||
"description": "LessPass node module used to generate LessPass passwords", | |||
"main": "lib/index.js", | |||
"jsnext:main": "index.js", | |||
"repository": "lesspass/core", | |||
"homepage": "https://github.com/lesspass/core#readme", | |||
"bugs": "https://github.com/lesspass/core/issues", | |||
"scripts": { | |||
"test-node": "cd tests && babel-node --presets es2015 node.js && cd ..", | |||
"test:node": "npm run build && cd tests && babel-node --presets es2015 node.js && cd ..", | |||
"test": "ava --require babel-core/register", | |||
"build": "rimraf lib && babel index.js -d lib", | |||
"prepublish": "npm test && npm run build" | |||
}, | |||
"files": [ | |||
"lib/", | |||
"index.js" | |||
], | |||
"keywords": [ | |||
"password", | |||
"crypto", | |||
@@ -4,7 +4,7 @@ | |||
## Requirements | |||
- node v4.6.x | |||
- node LTS v6 | |||
## Install | |||
@@ -12,12 +12,12 @@ | |||
## Usage | |||
import LessPass from 'lesspass'; | |||
const site = 'lesspass.com'; | |||
const login = 'contact@lesspass.com'; | |||
const masterPassword = 'password'; | |||
const options = { | |||
var LessPass = require('lesspass'); | |||
var site = 'lesspass.com'; | |||
var login = 'contact@lesspass.com'; | |||
var masterPassword = 'password'; | |||
var options = { | |||
counter: 1, | |||
length: 12, | |||
lowercase: true, | |||
@@ -25,11 +25,12 @@ | |||
numbers: true, | |||
symbols: true | |||
}; | |||
LessPass.encryptLogin(login, masterPassword) | |||
.then(encryptedLogin => { | |||
var generatedPassword = LessPass.renderPassword(encryptedLogin, site, options); | |||
console.log(generatedPassword); //azYS7,olOL2] | |||
LessPass.encryptLogin(login, masterPassword).then(encryptedLogin => { | |||
LessPass.renderPassword(encryptedLogin, site, options).then(generatedPassword => { | |||
console.log(generatedPassword); //azYS7,olOL2] | |||
}); | |||
}); | |||
see [tests/api.tests.js](tests/api.tests.js) for more examples | |||
@@ -1,36 +1,20 @@ | |||
import test from 'ava'; | |||
import lesspass from '../index'; | |||
test('encrypt login with pbkdf2 8192 iterations and sha256', t => { | |||
return lesspass.encryptLogin('test@example.org', 'password').then(encryptedLogin => { | |||
test('should use pbkdf2 with 8192 iterations and sha256', t=> { | |||
lesspass.encryptLogin('test@example.org', 'password').then(function (encryptedLogin) { | |||
t.is('d8af5f918db6b65b1db3d3984e5a400e39e1dbb19462220e4431de283809f472', encryptedLogin); | |||
}) | |||
}); | |||
test('encrypt login with utf8 parameter', t => { | |||
return lesspass.encryptLogin('test@example.org', '♥ LessPass ♥').then(encryptedLogin => { | |||
t.is('063092c809334979f505df88ed37845d298c01f7e8a03cbd661edbc084c650ca', encryptedLogin); | |||
}) | |||
}); | |||
}); | |||
test('render password', t => { | |||
const site = 'lesspass.com'; | |||
const encryptedLogin = '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0'; | |||
const passwordOptions = { | |||
counter: 1, | |||
length: 12, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true | |||
}; | |||
t.is('azYS7,olOL2]', lesspass.renderPassword(encryptedLogin, site, passwordOptions)); | |||
test('should allow utf8 parameter', t=> { | |||
lesspass.encryptLogin('test@example.org', '♥ LessPass ♥').then(function (encryptedLogin) { | |||
t.is('997fe81d3d0db236e039c75efdb487f17a902fdf94f9dacaa9884329c85d9651', encryptedLogin); | |||
}); | |||
}); | |||
test('auto generated encrypt login tests', t => { | |||
const promises = []; | |||
const passwords = [ | |||
var promises = []; | |||
var passwords = [ | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
@@ -88,20 +72,34 @@ test('auto generated encrypt login tests', t => { | |||
} | |||
]; | |||
for (const entry of passwords) { | |||
for (var entry of passwords) { | |||
promises.push(lesspass.encryptLogin(entry.login, entry.masterPassword)); | |||
} | |||
t.plan(passwords.length); | |||
return Promise.all(promises).then(values => { | |||
for (let i = 0; i < values.length; i++) { | |||
t.is(passwords[i].encryptedLogin, values[i]); | |||
} | |||
}); | |||
}); | |||
test('auto generated derive encrypted login tests', t => { | |||
const passwords = [ | |||
test('render password', t => { | |||
var site = 'lesspass.com'; | |||
var encryptedLogin = '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0'; | |||
var passwordOptions = { | |||
counter: 1, | |||
length: 12, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true | |||
}; | |||
lesspass.renderPassword(encryptedLogin, site, passwordOptions).then(function (generatedPassword) { | |||
t.is('azYS7,olOL2]', generatedPassword); | |||
}) | |||
}); | |||
test('auto generated render password tests', t => { | |||
var promises = []; | |||
var passwords = [ | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
@@ -225,18 +223,22 @@ test('auto generated derive encrypted login tests', t => { | |||
} | |||
]; | |||
t.plan(passwords.length); | |||
for (let i = 0; i < passwords.length; i++) { | |||
let password = passwords[i]; | |||
let passwordOption = { | |||
counter: password.counter, | |||
length: password.length, | |||
lowercase: password.lowercase, | |||
uppercase: password.uppercase, | |||
numbers: password.numbers, | |||
symbols: password.symbols, | |||
for (var entry of passwords) { | |||
var passwordOption = { | |||
counter: entry.counter, | |||
length: entry.length, | |||
lowercase: entry.lowercase, | |||
uppercase: entry.uppercase, | |||
numbers: entry.numbers, | |||
symbols: entry.symbols, | |||
}; | |||
t.is(password.generatedPassword, lesspass.renderPassword(password.encryptedLogin, password.site, passwordOption)); | |||
promises.push(lesspass.renderPassword(entry.encryptedLogin, entry.site, passwordOption)); | |||
} | |||
return Promise.all(promises).then(values => { | |||
for (let i = 0; i < values.length; i++) { | |||
t.is(passwords[i].generatedPassword, values[i]); | |||
} | |||
}); | |||
}); | |||
@@ -1,12 +1,13 @@ | |||
import test from 'ava'; | |||
import lesspass from '../index'; | |||
test('should derive encrypted login with default length', t => { | |||
const encryptedLogin = '9f505f3a95fe0485da3242cb81c9fe25c2f400d8399737655a8dad2b52778d88'; | |||
const site = 'lesspass.com'; | |||
t.is(12, lesspass._deriveEncryptedLogin(encryptedLogin, site).length); | |||
test('should createHmac', t => { | |||
var encryptedLogin = '9f505f3a95fe0485da3242cb81c9fe25c2f400d8399737655a8dad2b52778d88'; | |||
var salt = 'lesspass.com1'; | |||
return lesspass._createHmac(encryptedLogin, salt).then(hmac => { | |||
t.is('be00f942fc8aa67d8e76fc2456862b9d66d166ebfdd3dc2f0116e278209532ed', hmac); | |||
}); | |||
}); | |||
test('should derive encrypted login with default options', t => { | |||
const encryptedLogin = '90cff82b8847525370a8f29a59ecf45db62c719a535788ad0df58d32304e925d'; | |||
const site = 'lesspass.com'; | |||
@@ -18,30 +19,32 @@ test('should derive encrypted login with default options', t => { | |||
numbers: true, | |||
symbols: true, | |||
}; | |||
t.is( | |||
lesspass._deriveEncryptedLogin(encryptedLogin, site), | |||
lesspass._deriveEncryptedLogin(encryptedLogin, site, option) | |||
); | |||
var p1 = lesspass._deriveEncryptedLogin(encryptedLogin, site); | |||
var p2 = lesspass._deriveEncryptedLogin(encryptedLogin, site, option); | |||
Promise.all([p1, p2]).then(generatedPasswords => { | |||
t.is(generatedPasswords[0], generatedPasswords[1]) | |||
}); | |||
}); | |||
test('should derive encrypted login with defined length', t => { | |||
const encryptedLogin = 'd79d8482f708122288af7b259393a58fe05840f4555cc935cdd3f062b9aa75ed'; | |||
const site = 'lesspass.com'; | |||
const option = { | |||
var encryptedLogin = 'd79d8482f708122288af7b259393a58fe05840f4555cc935cdd3f062b9aa75ed'; | |||
var site = 'lesspass.com'; | |||
var option = { | |||
counter: 1, | |||
length: 10, | |||
}; | |||
t.is(10, lesspass._deriveEncryptedLogin(encryptedLogin, site, option).length); | |||
return lesspass._deriveEncryptedLogin(encryptedLogin, site, option).then(function (generatedPassword) { | |||
t.is(10, generatedPassword.length); | |||
}) | |||
}); | |||
test('should return two different passwords if site different', t => { | |||
const encryptedLogin = 'f4fd3885fb70085f2285c3382e2d9adb4c2553285fc45dd896791aa5e79070a9'; | |||
const site = 'google.com'; | |||
const site2 = 'facebook.com'; | |||
t.not( | |||
lesspass._deriveEncryptedLogin(encryptedLogin, site), | |||
lesspass._deriveEncryptedLogin(encryptedLogin, site2) | |||
); | |||
var p1 = lesspass._deriveEncryptedLogin(encryptedLogin, site); | |||
var p2 = lesspass._deriveEncryptedLogin(encryptedLogin, site2); | |||
Promise.all([p1, p2]).then(derivedEncryptedLogins => { | |||
t.not(derivedEncryptedLogins[0], derivedEncryptedLogins[1]) | |||
}); | |||
}); | |||
test('should return two different passwords if counter different', t => { | |||
@@ -49,14 +52,17 @@ test('should return two different passwords if counter different', t => { | |||
const site = 'lesspass.com'; | |||
const option = {counter: 1}; | |||
const option2 = {counter: 2}; | |||
t.not( | |||
lesspass._deriveEncryptedLogin(encryptedLogin, site, option), | |||
lesspass._deriveEncryptedLogin(encryptedLogin, site, option2) | |||
); | |||
var p1 = lesspass._deriveEncryptedLogin(encryptedLogin, site, option); | |||
var p2 = lesspass._deriveEncryptedLogin(encryptedLogin, site, option2); | |||
Promise.all([p1, p2]).then(derivedEncryptedLogins => { | |||
t.not(derivedEncryptedLogins[0], derivedEncryptedLogins[1]) | |||
}); | |||
}); | |||
test('should derive encrypted login with sha 256', t => { | |||
const encryptedLogin = '9f505f3a95fe0485da3242cb81c9fe25c2f400d8399737655a8dad2b52778d88'; | |||
const site = 'lesspass.com'; | |||
t.is('be00f942fc8a', lesspass._deriveEncryptedLogin(encryptedLogin, site)); | |||
return lesspass._deriveEncryptedLogin(encryptedLogin, site).then(function (encryptedLogin) { | |||
t.is('be00f942fc8a', encryptedLogin); | |||
}); | |||
}); |
@@ -1,4 +1,4 @@ | |||
var lesspass = require('../lib/index'); | |||
var LessPass = require('../lib/index'); | |||
var assert = require('assert'); | |||
var site = 'lesspass.com'; | |||
@@ -13,13 +13,13 @@ var options = { | |||
symbols: true | |||
}; | |||
var generatedPassword; | |||
lesspass.encryptLogin(login, masterPassword) | |||
.then(function (encryptedLogin) { | |||
generatedPassword = lesspass.renderPassword(encryptedLogin, site, options); | |||
assert.equal(generatedPassword, 'azYS7,olOL2]'); | |||
console.log('generated password ok'); | |||
LessPass.encryptLogin(login, masterPassword) | |||
.then(encryptedLogin => { | |||
LessPass.renderPassword(encryptedLogin, site, options).then(generatedPassword => { | |||
assert.equal(generatedPassword, 'azYS7,olOL2]'); | |||
console.log('generated password ok'); | |||
}); | |||
}) | |||
.catch(e => { | |||
console.log(e); | |||
}); | |||
}); |