@@ -1,108 +1,86 @@ | |||
import crypto from 'crypto'; | |||
module.exports = { | |||
generatePassword: _generatePassword, | |||
encryptLogin: _encryptLogin, | |||
renderPassword: _renderPassword, | |||
_deriveHash, | |||
_prettyPrint, | |||
_getTemplate, | |||
_getCharType, | |||
_getPasswordChar, | |||
_string2charCodes | |||
}; | |||
export default { | |||
encryptLogin(login, masterPassword) { | |||
return new Promise((resolve, reject) => { | |||
if (!login || !masterPassword) { | |||
reject('login and master password parameters 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 _generatePassword(login, masterPassword, site, options) { | |||
return new Promise((resolve, reject) => { | |||
if (!login || !masterPassword || !site) { | |||
reject('generatePassword invalid parameter'); | |||
} | |||
deriveEncryptedLogin(encryptedLogin, site, passwordOptions) { | |||
const derivedHash = this._deriveEncryptedLogin(encryptedLogin, site, passwordOptions); | |||
const template = this._getPasswordTemplate(passwordOptions); | |||
return this._prettyPrint(derivedHash, template); | |||
}, | |||
_encryptLogin(login, masterPassword).then(hash => { | |||
resolve(_renderPassword(hash, site, options)); | |||
}); | |||
}); | |||
} | |||
_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); | |||
}, | |||
function _renderPassword(hash, site, options) { | |||
const derivedHash = _deriveHash(hash, site, options); | |||
const template = _getTemplate(options.password.settings); | |||
return _prettyPrint(derivedHash, template); | |||
} | |||
_getPasswordTemplate(passwordTypes) { | |||
const templates = { | |||
lowercase: 'vc', | |||
uppercase: 'VC', | |||
numbers: 'n', | |||
symbols: 's', | |||
}; | |||
let template = ''; | |||
for (let templateKey in templates) { | |||
if (passwordTypes.hasOwnProperty(templateKey) && passwordTypes[templateKey]) { | |||
template += templates[templateKey] | |||
} | |||
} | |||
return template; | |||
}, | |||
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')); | |||
} | |||
}); | |||
}); | |||
} | |||
_prettyPrint(hash, template) { | |||
let password = ''; | |||
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); | |||
} | |||
this._string2charCodes(hash).forEach((charCode, index) => { | |||
const charType = this._getCharType(template, index); | |||
password += this._getPasswordChar(charType, charCode); | |||
}); | |||
return password; | |||
}, | |||
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(''); | |||
} | |||
_string2charCodes(text) { | |||
const charCodes = []; | |||
for (let i = 0; i < text.length; i++) { | |||
charCodes.push(text.charCodeAt(i)); | |||
} | |||
return charCodes; | |||
}, | |||
function _prettyPrint(hash, template) { | |||
let password = ''; | |||
_getCharType(template, index) { | |||
return template[index % template.length]; | |||
}, | |||
_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 _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]; | |||
_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]; | |||
} | |||
} | |||
@@ -1,86 +0,0 @@ | |||
import crypto from 'crypto'; | |||
export default { | |||
encryptLogin(login, masterPassword) { | |||
return new Promise((resolve, reject) => { | |||
if (!login || !masterPassword) { | |||
reject('login and master password parameters 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')); | |||
} | |||
}); | |||
}) | |||
}, | |||
renderPassword(site, encryptedLogin, passwordOptions) { | |||
const derivedHash = this._deriveEncryptedLogin(encryptedLogin, site, passwordOptions); | |||
const template = this._getPasswordTemplate(passwordOptions); | |||
return this._prettyPrint(derivedHash, template); | |||
}, | |||
_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); | |||
}, | |||
_getPasswordTemplate(passwordTypes) { | |||
const templates = { | |||
lowercase: 'vc', | |||
uppercase: 'VC', | |||
numbers: 'n', | |||
symbols: 's', | |||
}; | |||
let template = ''; | |||
for (let templateKey in templates) { | |||
if (passwordTypes.hasOwnProperty(templateKey) && passwordTypes[templateKey]) { | |||
template += templates[templateKey] | |||
} | |||
} | |||
return template; | |||
}, | |||
_prettyPrint(hash, template) { | |||
let password = ''; | |||
this._string2charCodes(hash).forEach((charCode, index) => { | |||
const charType = this._getCharType(template, index); | |||
password += this._getPasswordChar(charType, charCode); | |||
}); | |||
return password; | |||
}, | |||
_string2charCodes(text) { | |||
const charCodes = []; | |||
for (let i = 0; i < text.length; i++) { | |||
charCodes.push(text.charCodeAt(i)); | |||
} | |||
return charCodes; | |||
}, | |||
_getCharType(template, index) { | |||
return template[index % template.length]; | |||
}, | |||
_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]; | |||
} | |||
} |
@@ -1,144 +1,242 @@ | |||
import test from 'ava'; | |||
import lesspass from '../src/lesspass'; | |||
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('encrypt login', t => { | |||
return lesspass.encryptLogin('test@example.org', 'password').then(encryptedLogin => { | |||
t.is('d8af5f918db6b65b1db3d3984e5a400e39e1dbb19462220e4431de283809f472', encryptedLogin); | |||
}) | |||
}); | |||
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('encrypt login with utf8 parameter', t => { | |||
return lesspass.encryptLogin('test@example.org', '♥ LessPass ♥').then(encryptedLogin => { | |||
t.is('063092c809334979f505df88ed37845d298c01f7e8a03cbd661edbc084c650ca', encryptedLogin); | |||
}) | |||
}); | |||
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('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.deriveEncryptedLogin(encryptedLogin, site, passwordOptions)); | |||
}); | |||
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=' | |||
test('auto generated encrypt login tests', t => { | |||
const promises = []; | |||
const passwords = [ | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
}, | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
}, | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
}, | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
}, | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
}, | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
}, | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
}, | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
}, | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password', | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
}, | |||
{ | |||
login: 'lesspass', | |||
masterPassword: 'password', | |||
encryptedLogin: '7d05ee25597dcc3ac16d082aa910e7707f75be620ed8db5bef7245e2a8579116', | |||
}, | |||
{ | |||
login: 'contact@lesspass.com', | |||
masterPassword: 'password2', | |||
encryptedLogin: 'ce853092fc54fe88c281e38df97bd5826d64e6bee315dc94939cbba8930df0e4', | |||
} | |||
]; | |||
for (const entry of passwords) { | |||
promises.push(lesspass.encryptLogin(entry.login, entry.masterPassword)); | |||
} | |||
]; | |||
for (const entry of entries) { | |||
promises.push(lesspass.generatePassword(entry.login, entry.masterPassword, entry.site, entry.options)); | |||
} | |||
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 = [ | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 12, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true, | |||
generatedPassword: 'azYS7,olOL2]' | |||
}, | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 14, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true, | |||
generatedPassword: 'azYS7,olOL2]iz' | |||
}, | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 12, | |||
lowercase: true, | |||
uppercase: false, | |||
numbers: false, | |||
symbols: false, | |||
generatedPassword: 'azyseqololat' | |||
}, | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 12, | |||
lowercase: false, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true, | |||
generatedPassword: 'AZ3[EQ7@OL2]' | |||
}, | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 12, | |||
lowercase: false, | |||
uppercase: false, | |||
numbers: true, | |||
symbols: true, | |||
generatedPassword: '4?3[7,7@7@2]' | |||
}, | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 12, | |||
lowercase: false, | |||
uppercase: false, | |||
numbers: false, | |||
symbols: true, | |||
generatedPassword: '[?=[&,:@:@[]' | |||
}, | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 12, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: false, | |||
generatedPassword: 'azYS7uwAW8at' | |||
}, | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 12, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: false, | |||
symbols: false, | |||
generatedPassword: 'azYSeqOLolAT' | |||
}, | |||
{ | |||
encryptedLogin: '63d850713d0b2f7f2c4396fe93f4ac0c6bc7485f9e7473c4b8c4a33ec12199c0', | |||
site: 'lesspass.com', | |||
counter: 2, | |||
length: 12, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true, | |||
generatedPassword: 'obYT2=olOV9=' | |||
}, | |||
{ | |||
encryptedLogin: '7d05ee25597dcc3ac16d082aa910e7707f75be620ed8db5bef7245e2a8579116', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 12, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true, | |||
generatedPassword: 'erOC1%imIW3,' | |||
}, | |||
{ | |||
encryptedLogin: 'ce853092fc54fe88c281e38df97bd5826d64e6bee315dc94939cbba8930df0e4', | |||
site: 'lesspass.com', | |||
counter: 1, | |||
length: 12, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true, | |||
generatedPassword: 'uvUM5_ucUP5=' | |||
} | |||
]; | |||
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)); | |||
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, | |||
}; | |||
t.is(password.generatedPassword, lesspass.deriveEncryptedLogin(password.encryptedLogin, password.site, passwordOption)); | |||
} | |||
}); | |||
}); |
@@ -1,5 +1,5 @@ | |||
import test from 'ava'; | |||
import lesspass from '../src/lesspass2'; | |||
import lesspass from '../src/lesspass'; | |||
test('should derive encrypted login with default length', t => { | |||
const encryptedLogin = '9f505f3a95fe0485da3242cb81c9fe25c2f400d8399737655a8dad2b52778d88'; | |||
@@ -1,5 +1,5 @@ | |||
import test from 'ava'; | |||
import lesspass from '../src/lesspass2'; | |||
import lesspass from '../src/lesspass'; | |||
test('should get default template', t => { | |||
t.is('vcVCns', lesspass._getPasswordTemplate({ | |||
@@ -6,21 +6,24 @@ | |||
<body> | |||
<script src="../dist/lesspass.min.js"></script> | |||
<script> | |||
var site = 'lesspass.com'; | |||
var login = 'contact@lesspass.com'; | |||
var masterPassword = 'password'; | |||
var site = 'lesspass.com'; | |||
var options = { | |||
counter: 1, | |||
password: { | |||
length: 12, | |||
settings: ['lowercase', 'uppercase', 'numbers', 'symbols'] | |||
} | |||
length: 12, | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true | |||
}; | |||
lesspass.generatePassword(login, masterPassword, site, options).then(function (generatedPassword) { | |||
lesspass.encryptLogin(login, masterPassword).then(function (encryptedLogin) => { | |||
var generatedPassword = lesspass.deriveEncryptedLogin(encryptedLogin, site, options); | |||
if (generatedPassword === 'azYS7,olOL2]') { | |||
document.body.innerHTML = "generatePassword ok"; | |||
} | |||
}); | |||
</script> | |||
</body> | |||
</html> | |||
</html> |
@@ -1,137 +0,0 @@ | |||
import test from 'ava'; | |||
import lesspass from '../src/lesspass'; | |||
test('should create encrypted hash with pbkdf2 (8192 iterations and sha 256)', t => { | |||
const login = 'test@lesspass.com'; | |||
const masterPassword = 'password'; | |||
return lesspass.encryptLogin(login, masterPassword).then(hash => { | |||
t.is('90cff82b8847525370a8f29a59ecf45db62c719a535788ad0df58d32304e925d', hash); | |||
}); | |||
}); | |||
test('should create encrypted hash with 64 chars length', t => { | |||
return lesspass.encryptLogin('♥', '♥ ♥').then(hash => { | |||
t.is(64, hash.length); | |||
}); | |||
}); | |||
test('should reject promise if no parameter', t => { | |||
t.plan(1); | |||
return lesspass.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, lesspass._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( | |||
lesspass._deriveHash(hash, site), | |||
lesspass._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, lesspass._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( | |||
lesspass._deriveHash(hash, site), | |||
lesspass._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( | |||
lesspass._deriveHash(hash, site, option), | |||
lesspass._deriveHash(hash, site, option2) | |||
); | |||
}); | |||
test('should print different password if templates different', t => { | |||
const hash = '78ae5892055ab59fdd54489ae30928d322841a27590b65cf875fcfdd083f7c32'; | |||
t.not(lesspass._prettyPrint(hash, 'cv'), lesspass._prettyPrint(hash, 'vc')); | |||
}); | |||
test('must return a string of the same length as the input', t => { | |||
const hash = 'f5785e569ab5d38b02e2248c798ac17df90f57a85f34a9d5382408c2f0d9532d'; | |||
t.is(hash.length, lesspass._prettyPrint(hash, 'cv').length); | |||
}); | |||
test('should return char inside a string based on modulo of the index', t => { | |||
const template = 'cv'; | |||
t.is('c', lesspass._getCharType(template, 0)); | |||
t.is('v', lesspass._getCharType(template, 1)); | |||
t.is('c', lesspass._getCharType(template, 10)); | |||
}); | |||
test('should convert a string into an array of char code', t => { | |||
const charCodes = lesspass._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', lesspass._getPasswordChar(typeVowel, 0)); | |||
}); | |||
test('should modulo if overflow', t => { | |||
const typeVowel = 'V'; | |||
t.is('E', lesspass._getPasswordChar(typeVowel, 1)); | |||
t.is('E', lesspass._getPasswordChar(typeVowel, 7)); | |||
}); | |||
test('should get default template', t => { | |||
t.is('Cvcvns', lesspass._getTemplate()); | |||
}); | |||
test('should get template from password setting', t => { | |||
t.is('vc', lesspass._getTemplate(['lowercase'])); | |||
t.is('VC', lesspass._getTemplate(['uppercase'])); | |||
t.is('n', lesspass._getTemplate(['numbers'])); | |||
t.is('s', lesspass._getTemplate(['symbols'])); | |||
}); | |||
test('should concatenate template if two password settings', t => { | |||
t.is('vcVC', lesspass._getTemplate(['lowercase', 'uppercase'])); | |||
t.is('vcns', lesspass._getTemplate(['lowercase', 'numbers', 'symbols'])); | |||
}); | |||
test('should not care about order of password settings', t => { | |||
t.is( | |||
lesspass._getTemplate(['uppercase', 'lowercase']), | |||
lesspass._getTemplate(['lowercase', 'uppercase']) | |||
); | |||
}); |
@@ -1,28 +0,0 @@ | |||
import test from 'ava'; | |||
import lesspass from '../src/lesspass2'; | |||
test('encrypt login', t => { | |||
return lesspass.encryptLogin('test@example.org', 'password').then(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(site, encryptedLogin, passwordOptions)); | |||
}); |
@@ -1,16 +1,19 @@ | |||
var lesspass = require('../lib/lesspass'); | |||
var assert = require('assert'); | |||
var site = 'lesspass.com'; | |||
var login = 'contact@lesspass.com'; | |||
var masterPassword = 'password'; | |||
var site = 'lesspass.com'; | |||
var options = { | |||
counter: 1, | |||
password: { | |||
counter: 1, | |||
length: 12, | |||
settings: ['lowercase', 'uppercase', 'numbers', 'symbols'] | |||
} | |||
lowercase: true, | |||
uppercase: true, | |||
numbers: true, | |||
symbols: true | |||
}; | |||
lesspass.generatePassword(login, masterPassword, site, options).then(function (generatedPassword) { | |||
console.log(generatedPassword); //azYS7,olOL2] | |||
}); | |||
lesspass.encryptLogin(login, masterPassword).then(function (encryptedLogin) => { | |||
var generatedPassword = lesspass.deriveEncryptedLogin(encryptedLogin, site, options); | |||
assert(generatedPassword, 'azYS7,olOL2]'); | |||
}); |
@@ -1,5 +1,5 @@ | |||
import test from 'ava'; | |||
import lesspass from '../src/lesspass2'; | |||
import lesspass from '../src/lesspass'; | |||
test('should print different password if templates different', t => { | |||
const encryptedLogin = '78ae5892055ab59fdd54489ae30928d322841a27590b65cf875fcfdd083f7c32'; | |||