@@ -1 +1,2 @@ | |||
node_modules | |||
build |
@@ -9,25 +9,30 @@ var _urlParser = require('./url-parser'); | |||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |||
var emailField = document.querySelector('#login-container-email'); | |||
var passwordField = document.querySelector('#login-container-password'); | |||
var siteField = document.querySelector('#login-container-site'); | |||
function getStore(key, callback) { | |||
chrome.storage.local.get(key, function (result) { | |||
callback(result[key]); | |||
}); | |||
} | |||
function getLocalStore(storeName) { | |||
return new Promise(function (resolve, reject) { | |||
chrome.storage.local.get(storeName, function (result) { | |||
if (result === null) { | |||
return reject(storeName + ' not found'); | |||
} | |||
resolve(result); | |||
}); | |||
function saveStore(key, value, callback) { | |||
var newStore = {}; | |||
newStore[key] = value; | |||
chrome.storage.local.set(newStore, function () { | |||
callback(value); | |||
}); | |||
} | |||
function updateStore(store) { | |||
return new Promise(function (resolve) { | |||
chrome.storage.local.set(store, function () { | |||
resolve(store); | |||
function initStore(callback) { | |||
getStore('lesspassStore', function (store) { | |||
var defaultStore = Object.assign({ | |||
login: '', | |||
counter: 1, | |||
password: { length: 12, settings: ['lowercase', 'uppercase', 'numbers', 'symbols'] } | |||
}, store); | |||
saveStore('lesspassStore', defaultStore, function (store) { | |||
callback(store); | |||
}); | |||
}); | |||
} | |||
@@ -40,80 +45,149 @@ function getCurrentTab() { | |||
}); | |||
} | |||
document.getElementById('login-container-btn').addEventListener('click', function () { | |||
var email = emailField.value; | |||
var site = siteField.value; | |||
var masterPassword = passwordField.value; | |||
function fillForm(data) { | |||
document.getElementById('loginField').value = data.login; | |||
document.getElementById('masterPasswordField').value = ''; | |||
document.getElementById('siteField').value = data.site; | |||
document.getElementById('passwordCounter').value = data.counter; | |||
if (!email || !masterPassword || !site) { | |||
return; | |||
var passwordInfo = data.password; | |||
document.getElementById('passwordLength').value = passwordInfo.length; | |||
document.getElementById('lowercase').checked = false; | |||
document.getElementById('uppercase').checked = false; | |||
document.getElementById('numbers').checked = false; | |||
document.getElementById('symbols').checked = false; | |||
for (var i = 0; i < passwordInfo.settings.length; i++) { | |||
document.querySelector('#' + passwordInfo.settings[i]).checked = true; | |||
} | |||
} | |||
var storagePromise = getLocalStore('lesspassStore').then(function (store) { | |||
store.lesspassStore.email = email; | |||
return updateStore(store); | |||
}); | |||
function selectGoodField() { | |||
var loginField = document.getElementById('loginField'); | |||
var passwordField = document.getElementById('masterPasswordField'); | |||
if (loginField.value === '') { | |||
loginField.focus(); | |||
} else { | |||
passwordField.focus(); | |||
} | |||
} | |||
var hashPromise = _lesspass2.default.createMasterPassword(email, masterPassword); | |||
function displayMessage(message) { | |||
var messageField = document.getElementById('errorMessage'); | |||
messageField.replaceChild(document.createTextNode(message)); | |||
} | |||
var lesspassPromise = Promise.all([hashPromise, storagePromise]).then(function (values) { | |||
var entry = { | |||
site: site, | |||
password: values[1].lesspassStore.password | |||
}; | |||
return _lesspass2.default.createPassword(values[0], entry); | |||
function getData() { | |||
var defaultOptions = { | |||
login: document.getElementById('loginField').value, | |||
counter: document.getElementById('passwordCounter').value, | |||
password: { | |||
length: document.getElementById('passwordLength').value, | |||
settings: [] | |||
} | |||
}; | |||
var options = ['lowercase', 'uppercase', 'numbers', 'symbols']; | |||
for (var i = 0; i < options.length; i++) { | |||
if (document.getElementById(options[i]).checked) { | |||
defaultOptions.password.settings.push(options[i]); | |||
} | |||
} | |||
return defaultOptions; | |||
} | |||
function getFormData() { | |||
var initData = getData(); | |||
initData.masterPassword = document.getElementById('masterPasswordField').value; | |||
initData.site = document.getElementById('siteField').value; | |||
return initData; | |||
} | |||
document.getElementById('saveDefaultOptionButton').addEventListener('click', function () { | |||
var options = getData(); | |||
getStore('lesspassStore', function (store) { | |||
var newStore = Object.assign(store, options); | |||
saveStore('lesspassStore', newStore, function () { | |||
displayMessage('(saved)'); | |||
}); | |||
}); | |||
}); | |||
var tabPromise = getCurrentTab(); | |||
document.getElementById('saveDefaultOptionButton').addEventListener('click', function () { | |||
var options = getData(); | |||
getStore('lesspassStore', function (store) { | |||
var newStore = Object.assign(store, options); | |||
Promise.all([lesspassPromise, tabPromise]).then(function (values) { | |||
chrome.tabs.sendMessage(values[1].id, { login: email, password: values[0] }); | |||
window.close(); | |||
saveStore('lesspassStore', newStore, function () { | |||
displayMessage('(saved)'); | |||
}); | |||
}); | |||
}); | |||
function setDomainName(domain) { | |||
siteField.value = domain; | |||
document.getElementById('loginField').addEventListener('blur', generatePassword); | |||
document.getElementById('masterPasswordField').addEventListener('blur', generatePassword); | |||
document.getElementById('siteField').addEventListener('blur', generatePassword); | |||
function generatePassword() { | |||
var data = getFormData(); | |||
if (!data.login || !data.masterPassword || !data.site) { | |||
return; | |||
} | |||
return _lesspass2.default.generatePassword(data.login, data.masterPassword, data.site, data).then(function (password) { | |||
document.getElementById('generatedPasswordField').value = password; | |||
return password; | |||
}); | |||
} | |||
function displayError(message) { | |||
var messageField = document.getElementById('errorMessage'); | |||
messageField.replaceChild(document.createTextNode(message)); | |||
} | |||
function setEmail(email) { | |||
emailField.value = email; | |||
function copyPassword() { | |||
var generatedPasswordField = document.getElementById('generatedPasswordField'); | |||
generatedPasswordField.disabled = false; | |||
generatedPasswordField.select(); | |||
document.execCommand('copy'); | |||
generatedPasswordField.disabled = true; | |||
} | |||
function initStore(callback) { | |||
chrome.storage.local.get('lesspassStore', function (result) { | |||
var store = { | |||
password: { length: 12, settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], counter: 1 }, | |||
email: '' | |||
}; | |||
var lesspassStore = result.lesspassStore; | |||
if (typeof lesspassStore !== 'undefined') { | |||
if ('email' in lesspassStore) { | |||
store.email = lesspassStore.email; | |||
} | |||
if ('password' in lesspassStore) { | |||
store.password = lesspassStore.password; | |||
} | |||
document.getElementById('loginButton').addEventListener('click', function (event) { | |||
event.preventDefault(); | |||
var data = getFormData(); | |||
if (!data.login || !data.masterPassword || !data.site) { | |||
displayError('login, master password and site are required to generate a password'); | |||
return; | |||
} | |||
var tabPromise = getCurrentTab(); | |||
var passwordPromise = _lesspass2.default.generatePassword(data.login, data.masterPassword, data.site, data); | |||
Promise.all([passwordPromise, tabPromise]).then(function (values) { | |||
if (chrome.tabs && chrome.tabs.sendMessage) { | |||
chrome.tabs.sendMessage(values[1].id, { login: data.login, password: values[0] }); | |||
window.close(); | |||
} else { | |||
copyPassword(); | |||
} | |||
chrome.storage.local.set({ lesspassStore: store }, function () { | |||
callback(store); | |||
}); | |||
}); | |||
} | |||
}); | |||
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { | |||
if (tabs[0]) { | |||
initStore(function (store) { | |||
setEmail(store.email); | |||
store.site = (0, _urlParser.getDomainName)(tabs[0].url); | |||
fillForm(store); | |||
selectGoodField(); | |||
}); | |||
var currentTab = tabs[0]; | |||
setDomainName((0, _urlParser.getDomainName)(currentTab.url)); | |||
passwordField.focus(); | |||
} | |||
}); | |||
},{"./url-parser":2,"lesspass":95}],2:[function(require,module,exports){ | |||
},{"./url-parser":2,"lesspass":96}],2:[function(require,module,exports){ | |||
'use strict'; | |||
Object.defineProperty(exports, "__esModule", { | |||
@@ -14569,7 +14643,7 @@ module.exports={ | |||
"_args": [ | |||
[ | |||
"elliptic@^6.0.0", | |||
"/home/guillaume/workspace/lesspass/extension/node_modules/browserify-sign" | |||
"/home/guillaume/workspace/lesspass/lesspass/webextension/node_modules/browserify-sign" | |||
] | |||
], | |||
"_from": "elliptic@>=6.0.0 <7.0.0", | |||
@@ -14600,7 +14674,7 @@ module.exports={ | |||
"_shasum": "18e46d7306b0951275a2d42063270a14b74ebe99", | |||
"_shrinkwrap": null, | |||
"_spec": "elliptic@^6.0.0", | |||
"_where": "/home/guillaume/workspace/lesspass/extension/node_modules/browserify-sign", | |||
"_where": "/home/guillaume/workspace/lesspass/lesspass/webextension/node_modules/browserify-sign", | |||
"author": { | |||
"email": "fedor@indutny.com", | |||
"name": "Fedor Indutny" | |||
@@ -16319,118 +16393,166 @@ module.exports = Array.isArray || function (arr) { | |||
},{}],95:[function(require,module,exports){ | |||
'use strict'; | |||
var text = require('./text'); | |||
var passwordGenerator = require('./password-generator'); | |||
var _crypto = require('crypto'); | |||
var _crypto2 = _interopRequireDefault(_crypto); | |||
var _render = require('./render'); | |||
var _render2 = _interopRequireDefault(_render); | |||
function _interopRequireDefault(obj) { | |||
return obj && obj.__esModule ? obj : { default: obj }; | |||
} | |||
module.exports = { | |||
createPassword: createPassword, | |||
createMasterPassword: passwordGenerator.createMasterPassword | |||
createPassword: _createPassword, | |||
generatePassword: _generatePassword, | |||
encryptLogin: _encryptLogin, | |||
deriveHash: _deriveHash | |||
}; | |||
function createPassword(masterPassword, entry) { | |||
var hash = passwordGenerator._createHash(masterPassword, entry); | |||
var template = passwordGenerator._getTemplate(entry.password.settings); | |||
return text._encode(hash, template); | |||
function _encryptLogin(login, masterPassword) { | |||
return new Promise(function (resolve, reject) { | |||
if (!login || !masterPassword) { | |||
reject('encryptLogin parameter could not be empty'); | |||
} | |||
var iterations = 8192; | |||
var keylen = 32; | |||
_crypto2.default.pbkdf2(masterPassword, login, iterations, keylen, 'sha256', function (error, key) { | |||
if (error) { | |||
reject('error in pbkdf2'); | |||
} else { | |||
resolve(key.toString('hex')); | |||
} | |||
}); | |||
}); | |||
} | |||
},{"./password-generator":96,"./text":97}],96:[function(require,module,exports){ | |||
'use strict'; | |||
var crypto = require('crypto'); | |||
function _deriveHash(hash, site) { | |||
var _ref = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; | |||
module.exports = { | |||
createMasterPassword: createMasterPassword, | |||
_getTemplate: _getTemplate, | |||
_createHash: _createHash | |||
}; | |||
function createMasterPassword(email, password) { | |||
return new Promise(function (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')); | |||
} | |||
}); | |||
}); | |||
var _ref$password = _ref.password; | |||
var password = _ref$password === undefined ? { length: 12 } : _ref$password; | |||
var _ref$counter = _ref.counter; | |||
var counter = _ref$counter === undefined ? 1 : _ref$counter; | |||
var salt = site + counter.toString(); | |||
var derivedHash = _crypto2.default.createHmac('sha256', hash).update(salt).digest('hex'); | |||
return derivedHash.substring(0, password.length); | |||
} | |||
function _getTemplate() { | |||
var passwordTypes = arguments.length <= 0 || arguments[0] === undefined ? ['strong'] : arguments[0]; | |||
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(function (passwordType) { | |||
return passwordTypesInfo[passwordType]; | |||
}).sort(function (passwordType1, passwordType2) { | |||
return passwordType1.order > passwordType2.order; | |||
}).map(function (passwordType) { | |||
return passwordType.value; | |||
}).join(''); | |||
function _createPassword(hash, entry) { | |||
// createPassword is deprecated use generatePassword instead | |||
var options = { | |||
counter: entry.password.counter || 1, | |||
password: entry.password | |||
}; | |||
var site = entry.site; | |||
var derivedHash = _deriveHash(hash, site, options); | |||
var template = _render2.default.getTemplate(options.password.settings); | |||
return _render2.default.prettyPrint(derivedHash, template); | |||
} | |||
function _createHash(masterPassword, _ref) { | |||
var site = _ref.site; | |||
var _ref$password = _ref.password; | |||
var password = _ref$password === undefined ? { length: 12, counter: 1 } : _ref$password; | |||
function _generatePassword(login, masterPassword, site, options) { | |||
return new Promise(function (resolve, reject) { | |||
if (!login || !masterPassword || !site) { | |||
reject('generatePassword invalid parameter'); | |||
} | |||
var salt = site + password.counter.toString(); | |||
var hash = crypto.createHmac('sha256', masterPassword).update(salt).digest('hex'); | |||
return hash.substring(0, password.length); | |||
_encryptLogin(login, masterPassword).then(function (hash) { | |||
var derivedHash = _deriveHash(hash, site, options); | |||
var template = _render2.default.getTemplate(options.password.settings); | |||
resolve(_render2.default.prettyPrint(derivedHash, template)); | |||
}); | |||
}); | |||
} | |||
},{"crypto":54}],97:[function(require,module,exports){ | |||
"use strict"; | |||
},{"./render":97,"crypto":54}],96:[function(require,module,exports){ | |||
'use strict'; | |||
var _encryption = require('./encryption'); | |||
var _encryption2 = _interopRequireDefault(_encryption); | |||
function _interopRequireDefault(obj) { | |||
return obj && obj.__esModule ? obj : { default: obj }; | |||
} | |||
module.exports = { | |||
generatePassword: _encryption2.default.generatePassword, | |||
createPassword: _encryption2.default.createPassword, | |||
createMasterPassword: _encryption2.default.encryptLogin | |||
}; | |||
},{"./encryption":95}],97:[function(require,module,exports){ | |||
'use strict'; | |||
module.exports = { | |||
_getCharType: _getCharType, | |||
_getPasswordChar: _getPasswordChar, | |||
_encode: _encode, | |||
_string2charCodes: _string2charCodes | |||
prettyPrint: _prettyPrint, | |||
getTemplate: _getTemplate, | |||
_getCharType: _getCharType, | |||
_getPasswordChar: _getPasswordChar, | |||
_string2charCodes: _string2charCodes | |||
}; | |||
function _getCharType(template, index) { | |||
return template[index % template.length]; | |||
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]; | |||
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(function (charCode, index) { | |||
var charType = _getCharType(template, index); | |||
password += _getPasswordChar(charType, charCode); | |||
}); | |||
return password; | |||
function _prettyPrint(hash, template) { | |||
var password = ''; | |||
_string2charCodes(hash).forEach(function (charCode, index) { | |||
var charType = _getCharType(template, index); | |||
password += _getPasswordChar(charType, charCode); | |||
}); | |||
return password; | |||
} | |||
function _string2charCodes(text) { | |||
var charCodes = []; | |||
for (var i = 0; i < text.length; i++) { | |||
charCodes.push(text.charCodeAt(i)); | |||
} | |||
return charCodes; | |||
var charCodes = []; | |||
for (var i = 0; i < text.length; i++) { | |||
charCodes.push(text.charCodeAt(i)); | |||
} | |||
return charCodes; | |||
} | |||
function _getTemplate() { | |||
var passwordTypes = arguments.length <= 0 || arguments[0] === undefined ? ['strong'] : arguments[0]; | |||
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(function (passwordType) { | |||
return passwordTypesInfo[passwordType]; | |||
}).sort(function (passwordType1, passwordType2) { | |||
return passwordType1.order > passwordType2.order; | |||
}).map(function (passwordType) { | |||
return passwordType.value; | |||
}).join(''); | |||
} | |||
},{}],98:[function(require,module,exports){ | |||
var bn = require('bn.js'); | |||
var brorand = require('brorand'); | |||
@@ -2,7 +2,7 @@ | |||
"description": "chrome and firefox web extension for lesspass password manager", | |||
"manifest_version": 2, | |||
"name": "lesspass", | |||
"version": "1.0.0", | |||
"version": "0.0.1", | |||
"homepage_url": "https://github.com/lesspass/webextension", | |||
"icons": { | |||
"64": "icons/logo-64.png" | |||
@@ -23,5 +23,9 @@ | |||
"default_title": "LessPass", | |||
"default_popup": "popup.html" | |||
}, | |||
"options_page": "options.html" | |||
"applications": { | |||
"gecko": { | |||
"id": "contact@lesspass.com" | |||
} | |||
} | |||
} |
@@ -1,70 +0,0 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8"> | |||
<link rel="stylesheet" href="vendor/css/bootstrap.min.css"/> | |||
</head> | |||
<body> | |||
<div class="container m-t-1"> | |||
<div class="row"> | |||
<div class="col-sm-6 offset-sm-3"> | |||
<form> | |||
<legend>LessPass options <span id="message" style="color: green;"></span></legend> | |||
<fieldset class="form-group"> | |||
<label for="passwordLength">Password Length</label> | |||
<input class="form-control" placeholder="Password length" id="passwordLength" type="number" | |||
value="12" min="6" max="64"> | |||
<small class="text-muted">default password length</small> | |||
</fieldset> | |||
<fieldset class="form-group"> | |||
<label for="passwordCounter">Counter</label> | |||
<input class="form-control" id="passwordCounter" type="number" value="1" min="1"> | |||
<small class="text-muted">counter to change password value</small> | |||
</fieldset> | |||
<label>Password Options</label> | |||
<div class="row"> | |||
<div class="col-lg-5"> | |||
<label class="c-input c-checkbox"> | |||
<input type="checkbox" id="lowercase" value="lowercase" checked> | |||
<span class="c-indicator"></span> | |||
lowercase | |||
</label> | |||
</div> | |||
<div class="col-lg-7"> | |||
<label class="c-input c-checkbox"> | |||
<input type="checkbox" id="uppercase" value="uppercase" checked> | |||
<span class="c-indicator"></span> | |||
uppercase | |||
</label> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col-lg-5"> | |||
<label class="c-input c-checkbox"> | |||
<input type="checkbox" id="numbers" value="numbers" | |||
checked> | |||
<span class="c-indicator"></span> | |||
numbers | |||
</label> | |||
</div> | |||
<div class="col-lg-7"> | |||
<label class="c-input c-checkbox"> | |||
<input type="checkbox" id="symbols" value="symbols" | |||
checked> | |||
<span class="c-indicator"></span> | |||
symbols | |||
</label> | |||
</div> | |||
</div> | |||
<button type="submit" class="btn btn-primary">Save</button> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
<script src="options.js"></script> | |||
</body> | |||
</html> | |||
@@ -1,54 +0,0 @@ | |||
function displayMessage(message) { | |||
document.getElementById('message').innerHTML = message; | |||
setTimeout(() => { | |||
document.getElementById('message').innerHTML = ''; | |||
}, 3000); | |||
} | |||
function saveOptions(e) { | |||
e.preventDefault(); | |||
const defaultOptions = { | |||
password: { | |||
counter: document.querySelector('#passwordCounter').value, | |||
length: document.querySelector('#passwordLength').value, | |||
settings: [] | |||
} | |||
}; | |||
const options = ['lowercase', 'uppercase', 'numbers', 'symbols']; | |||
for (let i = 0; i < options.length; i++) { | |||
if (document.querySelector(`#${options[i]}`).checked) { | |||
defaultOptions.password.settings.push(options[i]); | |||
} | |||
} | |||
chrome.storage.local.set({ | |||
lesspassStore: { | |||
options: defaultOptions | |||
} | |||
}); | |||
displayMessage('(saved)'); | |||
} | |||
function restoreOptions() { | |||
chrome.storage.local.get('lesspassStore', value => { | |||
if (value && 'options' in value.lesspassStore) { | |||
const passwordInfo = value.lesspassStore.options.password; | |||
document.querySelector('#passwordCounter').value = passwordInfo.counter; | |||
document.querySelector('#passwordLength').value = passwordInfo.length; | |||
document.querySelector('#lowercase').checked = false; | |||
document.querySelector('#uppercase').checked = false; | |||
document.querySelector('#numbers').checked = false; | |||
document.querySelector('#symbols').checked = false; | |||
for (let i = 0; i < passwordInfo.settings.length; i++) { | |||
document.querySelector(`#${passwordInfo.settings[i]}`).checked = true; | |||
} | |||
} | |||
}); | |||
} | |||
document.addEventListener('DOMContentLoaded', restoreOptions); | |||
document.querySelector('form').addEventListener('submit', saveOptions); |
@@ -1,69 +0,0 @@ | |||
html, body { | |||
width: 400px; | |||
margin: 0; | |||
} | |||
body { | |||
font-family: sans-serif; | |||
} | |||
.login { | |||
width: 400px; | |||
margin: 0 auto; | |||
font-size: 16px; | |||
} | |||
.login-header, | |||
.login p { | |||
margin-top: 0; | |||
margin-bottom: 0; | |||
} | |||
.login-header { | |||
padding: 20px 20px 10px; | |||
font-size: 1.4em; | |||
font-weight: normal; | |||
text-align: center; | |||
text-transform: uppercase; | |||
color: #0275d8; | |||
} | |||
.login-container { | |||
padding: 12px; | |||
} | |||
.login p { | |||
padding: 12px; | |||
} | |||
.login input { | |||
box-sizing: border-box; | |||
display: block; | |||
width: 100%; | |||
border: 1px solid; | |||
padding: 16px; | |||
outline: 0; | |||
font-family: inherit; | |||
font-size: 0.95em; | |||
} | |||
.login input { | |||
background: #fff; | |||
border-color: #bbb; | |||
color: #555; | |||
} | |||
.login input { | |||
border-color: #888; | |||
} | |||
.login input[type="submit"] { | |||
background: #0275d8; | |||
border-color: transparent; | |||
color: #fff; | |||
cursor: pointer; | |||
} | |||
.login input[type="submit"]:focus { | |||
border-color: #05a; | |||
} |
@@ -2,18 +2,176 @@ | |||
<html> | |||
<head> | |||
<meta charset="utf-8"> | |||
<link rel="stylesheet" href="popup.css"/> | |||
<link rel="stylesheet" href="vendor/css/bootstrap.min.css"/> | |||
<style> | |||
body { | |||
min-width: 655px; | |||
padding: 2em; | |||
} | |||
.form-control, .btn, .input-group-addon { | |||
border-radius: 0 !important; | |||
} | |||
#generatedPasswordForm a { | |||
color: inherit; | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div class="login"> | |||
<h2 class="login-header">LessPass</h2> | |||
<form class="login-container"> | |||
<p><input id="login-container-email" type="email" placeholder="Email/Username"></p> | |||
<p><input id="login-container-site" type="text" placeholder="Site"></p> | |||
<p><input id="login-container-password" type="password" placeholder="Master Password"></p> | |||
<p><input id="login-container-btn" type="submit" value="Let's go !"></p> | |||
</form> | |||
<div class="row"> | |||
<div class="col-xs-12 m-b-1"> | |||
<a href="https://lesspass.com/"><img src="icons/logo.png" alt="lesspass favicon" height="32"></a> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col-xs-12"> | |||
<form id="generatedPasswordForm"> | |||
<div class="form-group row"> | |||
<div class="col-xs-12"> | |||
<span id="errorMessage" class="text-danger"></span> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-xs-6"> | |||
<label for="loginField" class="sr-only"> | |||
Login | |||
</label> | |||
<input id="loginField" | |||
class="form-control" | |||
type="text" | |||
placeholder="login" | |||
autofocus | |||
autocomplete="off"> | |||
</div> | |||
<div class="col-xs-6"> | |||
<label for="masterPasswordField" class="sr-only"> | |||
Master Password | |||
</label> | |||
<input id="masterPasswordField" | |||
class="form-control" | |||
type="password" | |||
placeholder="master password" | |||
autocomplete="off"> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-xs-12"> | |||
<label for="siteField" class="sr-only"> | |||
Site | |||
</label> | |||
<input id="siteField" | |||
class="form-control" | |||
type="text" | |||
placeholder="site"> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-xs-12"> | |||
<label for="generatedPasswordField" class="sr-only"> | |||
Generated password | |||
</label> | |||
<div class="input-group"> | |||
<input type="text" | |||
id="generatedPasswordField" | |||
class="form-control" | |||
placeholder="generated password" | |||
disabled> | |||
<span class="input-group-btn"> | |||
<button id="loginButton" class="btn btn-primary" type="submit"> | |||
login | |||
</button> | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-xs-12"> | |||
<img src="vendor/img/settings.png"/> options <span id="message" class="text-success"></span> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-xs-5"> | |||
<label class="c-input c-checkbox"> | |||
<input type="checkbox" id="lowercase" value="lowercase"> | |||
<span class="c-indicator"></span> | |||
lowercase | |||
</label> | |||
</div> | |||
<div class="col-xs-7"> | |||
<label class="c-input c-checkbox"> | |||
<input type="checkbox" id="uppercase" value="uppercase"> | |||
<span class="c-indicator"></span> | |||
uppercase | |||
</label> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-xs-5"> | |||
<label class="c-input c-checkbox"> | |||
<input type="checkbox" id="numbers" value="numbers"> | |||
<span class="c-indicator"></span> | |||
numbers | |||
</label> | |||
</div> | |||
<div class="col-xs-7"> | |||
<label class="c-input c-checkbox"> | |||
<input type="checkbox" id="symbols" value="symbols"> | |||
<span class="c-indicator"></span> | |||
symbols | |||
</label> | |||
</div> | |||
</div> | |||
<div class="row m-t-1"> | |||
<div class="col-xs-5 m-b-1"> | |||
<label for="passwordLength" class="sr-only"> | |||
length | |||
</label> | |||
<div class="input-group"> | |||
<span class="input-group-addon" id="passwordLengthAddon"> | |||
length | |||
</span> | |||
<input type="number" class="form-control" id="passwordLength" | |||
aria-describedby="passwordLengthAddon" value="12" min="6" max="64"> | |||
</div> | |||
</div> | |||
<div class="col-xs-4 m-b-1"> | |||
<label for="passwordCounter" class="sr-only"> | |||
counter | |||
</label> | |||
<div class="input-group"> | |||
<span class="input-group-addon" id="passwordCounterAddon"> | |||
counter | |||
</span> | |||
<input type="number" class="form-control" id="passwordCounter" | |||
aria-describedby="passwordCounterAddon" | |||
value="1" min="1" max="100"> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-xs-12"> | |||
<button id="saveDefaultOptionButton" class="btn btn-primary" type="button"> | |||
save as default | |||
</button> | |||
</div> | |||
</div> | |||
<div class="form-group row"> | |||
<div class="col-xs-12"> | |||
<small class="text-help">options are stored locally within your browser. options are not | |||
synchronised for now. | |||
</small> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
<script src="dist/popup.min.js"></script> | |||
</body> | |||
@@ -1,25 +1,30 @@ | |||
import lesspass from 'lesspass'; | |||
import {getDomainName} from './url-parser'; | |||
const emailField = document.querySelector('#login-container-email'); | |||
const passwordField = document.querySelector('#login-container-password'); | |||
const siteField = document.querySelector('#login-container-site'); | |||
function getLocalStore(storeName) { | |||
return new Promise((resolve, reject) => { | |||
chrome.storage.local.get(storeName, result => { | |||
if (result === null) { | |||
return reject(`${storeName} not found`); | |||
} | |||
resolve(result); | |||
}); | |||
function getStore(key, callback) { | |||
chrome.storage.local.get(key, result => { | |||
callback(result[key]); | |||
}); | |||
} | |||
function updateStore(store) { | |||
return new Promise(resolve => { | |||
chrome.storage.local.set(store, () => { | |||
resolve(store); | |||
function saveStore(key, value, callback) { | |||
const newStore = {}; | |||
newStore[key] = value; | |||
chrome.storage.local.set(newStore, () => { | |||
callback(value); | |||
}); | |||
} | |||
function initStore(callback) { | |||
getStore('lesspassStore', store => { | |||
const defaultStore = Object.assign({ | |||
login: '', | |||
counter: 1, | |||
password: {length: 12, settings: ['lowercase', 'uppercase', 'numbers', 'symbols']} | |||
}, store); | |||
saveStore('lesspassStore', defaultStore, store => { | |||
callback(store); | |||
}); | |||
}); | |||
} | |||
@@ -32,76 +37,144 @@ function getCurrentTab() { | |||
}); | |||
} | |||
document.getElementById('login-container-btn').addEventListener('click', () => { | |||
const email = emailField.value; | |||
const site = siteField.value; | |||
const masterPassword = passwordField.value; | |||
function fillForm(data) { | |||
document.getElementById('loginField').value = data.login; | |||
document.getElementById('masterPasswordField').value = ''; | |||
document.getElementById('siteField').value = data.site; | |||
document.getElementById('passwordCounter').value = data.counter; | |||
if (!email || !masterPassword || !site) { | |||
return; | |||
const passwordInfo = data.password; | |||
document.getElementById('passwordLength').value = passwordInfo.length; | |||
document.getElementById('lowercase').checked = false; | |||
document.getElementById('uppercase').checked = false; | |||
document.getElementById('numbers').checked = false; | |||
document.getElementById('symbols').checked = false; | |||
for (let i = 0; i < passwordInfo.settings.length; i++) { | |||
document.querySelector(`#${passwordInfo.settings[i]}`).checked = true; | |||
} | |||
} | |||
const storagePromise = getLocalStore('lesspassStore').then(store => { | |||
store.lesspassStore.email = email; | |||
return updateStore(store); | |||
}); | |||
function selectGoodField() { | |||
const loginField = document.getElementById('loginField'); | |||
const passwordField = document.getElementById('masterPasswordField'); | |||
if (loginField.value === '') { | |||
loginField.focus(); | |||
} else { | |||
passwordField.focus(); | |||
} | |||
} | |||
function displayMessage(message) { | |||
const messageField = document.getElementById('errorMessage'); | |||
messageField.replaceChild(document.createTextNode(message)); | |||
} | |||
function getData() { | |||
const defaultOptions = { | |||
login: document.getElementById('loginField').value, | |||
counter: document.getElementById('passwordCounter').value, | |||
password: { | |||
length: document.getElementById('passwordLength').value, | |||
settings: [] | |||
} | |||
}; | |||
const options = ['lowercase', 'uppercase', 'numbers', 'symbols']; | |||
for (let i = 0; i < options.length; i++) { | |||
if (document.getElementById(options[i]).checked) { | |||
defaultOptions.password.settings.push(options[i]); | |||
} | |||
} | |||
return defaultOptions; | |||
} | |||
function getFormData() { | |||
const initData = getData(); | |||
initData.masterPassword = document.getElementById('masterPasswordField').value; | |||
initData.site = document.getElementById('siteField').value; | |||
return initData; | |||
} | |||
document.getElementById('saveDefaultOptionButton').addEventListener('click', () => { | |||
const options = getData(); | |||
const hashPromise = lesspass.createMasterPassword(email, masterPassword); | |||
getStore('lesspassStore', store => { | |||
const newStore = Object.assign(store, options); | |||
const lesspassPromise = Promise.all([hashPromise, storagePromise]) | |||
.then(values => { | |||
const entry = { | |||
site, | |||
password: values[1].lesspassStore.password | |||
}; | |||
return lesspass.createPassword(values[0], entry); | |||
saveStore('lesspassStore', newStore, () => { | |||
displayMessage('(saved)'); | |||
}); | |||
}); | |||
}); | |||
const tabPromise = getCurrentTab(); | |||
document.getElementById('saveDefaultOptionButton').addEventListener('click', () => { | |||
const options = getData(); | |||
getStore('lesspassStore', store => { | |||
const newStore = Object.assign(store, options); | |||
Promise.all([lesspassPromise, tabPromise]).then(values => { | |||
chrome.tabs.sendMessage(values[1].id, {login: email, password: values[0]}); | |||
window.close(); | |||
saveStore('lesspassStore', newStore, () => { | |||
displayMessage('(saved)'); | |||
}); | |||
}); | |||
}); | |||
function setDomainName(domain) { | |||
siteField.value = domain; | |||
document.getElementById('loginField').addEventListener('blur', generatePassword); | |||
document.getElementById('masterPasswordField').addEventListener('blur', generatePassword); | |||
document.getElementById('siteField').addEventListener('blur', generatePassword); | |||
function generatePassword() { | |||
const data = getFormData(); | |||
if (!data.login || !data.masterPassword || !data.site) { | |||
return; | |||
} | |||
return lesspass.generatePassword(data.login, data.masterPassword, data.site, data).then(password => { | |||
document.getElementById('generatedPasswordField').value = password; | |||
return password; | |||
}); | |||
} | |||
function setEmail(email) { | |||
emailField.value = email; | |||
function displayError(message) { | |||
const messageField = document.getElementById('errorMessage'); | |||
messageField.replaceChild(document.createTextNode(message)); | |||
} | |||
function initStore(callback) { | |||
chrome.storage.local.get('lesspassStore', result => { | |||
const store = { | |||
password: {length: 12, settings: ['lowercase', 'uppercase', 'numbers', 'symbols'], counter: 1}, | |||
email: '' | |||
}; | |||
const lesspassStore = result.lesspassStore; | |||
if (typeof lesspassStore !== 'undefined') { | |||
if ('email' in lesspassStore) { | |||
store.email = lesspassStore.email; | |||
} | |||
if ('password' in lesspassStore) { | |||
store.password = lesspassStore.password; | |||
} | |||
function copyPassword() { | |||
const generatedPasswordField = document.getElementById('generatedPasswordField'); | |||
generatedPasswordField.disabled = false; | |||
generatedPasswordField.select(); | |||
document.execCommand('copy'); | |||
generatedPasswordField.disabled = true; | |||
} | |||
document.getElementById('loginButton').addEventListener('click', event => { | |||
event.preventDefault(); | |||
const data = getFormData(); | |||
if (!data.login || !data.masterPassword || !data.site) { | |||
displayError('login, master password and site are required to generate a password'); | |||
return; | |||
} | |||
const tabPromise = getCurrentTab(); | |||
const passwordPromise = lesspass.generatePassword(data.login, data.masterPassword, data.site, data); | |||
Promise.all([passwordPromise, tabPromise]).then(values => { | |||
if (chrome.tabs && chrome.tabs.sendMessage) { | |||
chrome.tabs.sendMessage(values[1].id, {login: data.login, password: values[0]}); | |||
window.close(); | |||
} else { | |||
copyPassword(); | |||
} | |||
chrome.storage.local.set({lesspassStore: store}, () => { | |||
callback(store); | |||
}); | |||
}); | |||
} | |||
}); | |||
chrome.tabs.query({active: true, currentWindow: true}, tabs => { | |||
if (tabs[0]) { | |||
initStore(store => { | |||
setEmail(store.email); | |||
store.site = getDomainName(tabs[0].url); | |||
fillForm(store); | |||
selectGoodField(); | |||
}); | |||
const currentTab = tabs[0]; | |||
setDomainName(getDomainName(currentTab.url)); | |||
passwordField.focus(); | |||
} | |||
}); |
@@ -1,7 +1,10 @@ | |||
{ | |||
"scripts": { | |||
"build": "rm -rf extension/dist && mkdir extension/dist && browserify ./extension/content.js -o ./extension/dist/content.min.js && browserify ./extension/popup.js -o ./extension/dist/popup.min.js", | |||
"test": "xo && ava tests --require babel-core/register" | |||
"prebuild": "npm prune && npm install", | |||
"zip": "rm -rf build && mkdir build && cd extension && zip -r extension.zip ./* && cp extension.zip ../build/lesspass.firefox.xpi && mv extension.zip ../build/lesspass.chrome.zip && cd ..", | |||
"browserify": "rm -rf extension/dist && mkdir extension/dist && browserify ./extension/content.js -o ./extension/dist/content.min.js && browserify ./extension/popup.js -o ./extension/dist/popup.min.js", | |||
"build": "npm run browserify && npm run zip", | |||
"test": "ava tests --require babel-core/register && xo" | |||
}, | |||
"devDependencies": { | |||
"ava": "*", | |||
@@ -9,6 +12,7 @@ | |||
"babel-preset-es2015": "*", | |||
"babelify": "^7.3.0", | |||
"browserify": "^13.0.1", | |||
"jpm": "^1.0.7", | |||
"jsdom": "^9.0.0", | |||
"xo": "*" | |||
}, | |||
@@ -17,7 +21,6 @@ | |||
"space": true, | |||
"envs": [ | |||
"browser", | |||
"jquery", | |||
"webextensions" | |||
], | |||
"ignores": [ | |||
@@ -44,7 +47,9 @@ | |||
] | |||
}, | |||
"dependencies": { | |||
"lesspass": "^1.1.1", | |||
"bootstrap": "^4.0.0-alpha.2", | |||
"jquery": "^2.2.3", | |||
"lesspass": "^2.0.1", | |||
"tldjs": "^1.6.2" | |||
} | |||
} |
@@ -4,4 +4,4 @@ | |||
chrome and firefox web extension for lesspass password manager | |||
[lesspass submodule](https://github.com/lesspass/lesspass) | |||
see [lesspass](https://github.com/lesspass/lesspass) project |