Merge branch 'develop'

This commit is contained in:
Janek Bevendorff 2018-05-09 21:34:32 +02:00
commit da2685c6d8
No known key found for this signature in database
GPG key ID: 2FDEB0D40BCA5E11
22 changed files with 472 additions and 151 deletions

View file

@ -1,17 +1,19 @@
<!--- Provide a general summary of the issue in the title above -->
## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from the current behavior -->
## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
@ -22,11 +24,8 @@
## Debug info
<!--- Please copy/paste the browser's JavaScript console messages and errors here -->
## General Info
KeePassXC fork - VERSION
keepassxc-browser - VERSION
Operating system: OS
Browser: BROWSER
KeePassXC - {VERSION}
keepassxc-browser - {VERSION}
Operating system: Mac/Win/Linux
Browser: Chrome/Firefox/Vivaldi/Chromium
Proxy used: YES/NO

View file

@ -1,3 +1,15 @@
1.1.0 (09-05-2018)
=========================
- Allow specifying ignored sites
- Add new notification options
- Improve detection of username fields
- Change conflicting shortcuts
- Redetect credential fields after reload
- Don't show popup when database is closed
- Various password generator fixes
- Fix various resource leaks
- Fix searching in all databases
1.0.1 (04-03-2018)
=========================
- Don't fill password fields if they already have data

View file

@ -1,3 +1,5 @@
'use strict';
const browserAction = {};
const BLINK_TIMEOUT_DEFAULT = 7500;
@ -202,6 +204,14 @@ browserAction.removeRememberPopup = function(callback, tab, removeImmediately) {
browserAction.setRememberPopup = function(tabId, username, password, url, usernameExists, credentialsList) {
browser.storage.local.get({'settings': {}}).then(function(item) {
const settings = item.settings;
// Don't show anything if the site is in the ignore list
for (const site in settings.ignoredSites) {
if (site === url) {
return;
}
}
const id = tabId || page.currentTabId;
let timeoutMinMillis = Number(getValueOrDefault(settings, 'blinkMinTimeout', BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT, 0));
@ -239,8 +249,30 @@ browserAction.setRememberPopup = function(tabId, username, password, url, userna
browserAction.show(null, {'id': id});
if (page.settings.showNotifications) {
showNotification('Create or modify the credentials by clicking on the extension icon.');
if (page.settings.showLoginNotifications) {
const message = 'Create or modify the credentials by clicking on the extension icon.';
const buttons = [
{
'title': 'Close'
},
{
'title': 'Never ask for this page'
}];
browser.notifications.create({
'type': 'basic',
'iconUrl': browser.extension.getURL('icons/keepassxc_64x64.png'),
'title': 'KeePassXC-Browser',
'message': message,
'buttons': buttons
});
browser.notifications.onButtonClicked.addListener((id, index) => {
browser.notifications.clear(id);
if (index === 1) {
browserAction.ignoreSite(url);
}
});
}
});
};
@ -269,3 +301,21 @@ browserAction.generateIconName = function(iconType, icon) {
return name;
};
browserAction.ignoreSite = function(url) {
browser.windows.getCurrent().then((win) => {
// Get current active window
browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
const tab = tabs[0];
// Send the message to the current tab's content script
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.sendMessage(tab.id, {
action: 'ignore-site',
args: [url]
});
});
});
});
};

View file

@ -1,3 +1,5 @@
'use strict';
const kpxcEvent = {};
kpxcEvent.onMessage = function(request, sender, callback) {
@ -72,12 +74,15 @@ kpxcEvent.invoke = function(handler, callback, senderTabId, args, secondTime) {
else {
console.log('undefined handler for tab ' + tab.id);
}
}).catch((e) => {console.log(e);});
}).catch((e) => {
console.log(e);
});
};
kpxcEvent.onShowAlert = function(callback, tab, message) {
if (page.settings.supressAlerts) { console.log(message); }
else { alert(message); }
kpxcEvent.onShowNotification = function(callback, tab, message) {
if (page.settings.showNotifications) {
showNotification(message);
}
};
kpxcEvent.showStatus = function(configured, tab, callback) {
@ -159,13 +164,18 @@ kpxcEvent.onReconnect = function(callback, tab) {
if (gdRes) {
keepass.testAssociation((response) => {
keepass.isConfigured().then((configured) => {
browser.tabs.sendMessage(tab.id, {
action: 'redetect_fields'
});
kpxcEvent.showStatus(configured, tab, callback);
}).catch((e) => {console.log(e);});
}).catch((e) => {
console.log(e);
});
}, tab);
}
}, null);
});
}, 2000);
}, 500);
};
kpxcEvent.lockDatabase = function(callback, tab) {
@ -216,7 +226,17 @@ kpxcEvent.onRemoveCredentialsFromTabInformation = function(callback, tab) {
};
kpxcEvent.onSetRememberPopup = function(callback, tab, username, password, url, usernameExists, credentialsList) {
browserAction.setRememberPopup(tab.id, username, password, url, usernameExists, credentialsList);
keepass.testAssociation((response) => {
if (response) {
keepass.isConfigured().then((configured) => {
if (configured) {
browserAction.setRememberPopup(tab.id, username, password, url, usernameExists, credentialsList);
}
}).catch((e) => {
console.log(e);
});
}
}, tab);
};
kpxcEvent.onLoginPopup = function(callback, tab, logins) {
@ -263,20 +283,10 @@ kpxcEvent.pageClearLogins = function(callback, tab, alreadyCalled) {
callback();
};
kpxcEvent.oldDatabaseHash = 'no-hash';
kpxcEvent.checkDatabaseHash = function(callback, tab) {
keepass.checkDatabaseHash((response) => {
callback({old: kpxcEvent.oldDatabaseHash, new: response});
kpxcEvent.oldDatabaseHash = response;
});
};
// all methods named in this object have to be declared BEFORE this!
kpxcEvent.messageHandlers = {
'add_credentials': keepass.addCredentials,
'alert': kpxcEvent.onShowAlert,
'associate': keepass.associate,
'check_databasehash': kpxcEvent.checkDatabaseHash,
'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC,
'generate_password': keepass.generatePassword,
'get_connected_database': kpxcEvent.onGetConnectedDatabase,
@ -298,6 +308,7 @@ kpxcEvent.messageHandlers = {
'update_credentials': keepass.updateCredentials,
'save_settings': kpxcEvent.onSaveSettings,
'set_remember_credentials': kpxcEvent.onSetRememberPopup,
'show_notification': kpxcEvent.onShowNotification,
'stack_add': browserAction.stackAdd,
'update_available_keepassxc': kpxcEvent.onUpdateAvailableKeePassXC
};

View file

@ -1,3 +1,5 @@
'use strict';
const httpAuth = {};
httpAuth.requests = [];

View file

@ -1,3 +1,5 @@
'use strict';
keepass.migrateKeyRing().then(() => {
page.initSettings().then(() => {
page.initOpenedTabs().then(() => {

View file

@ -18,6 +18,7 @@ keepass.keySize = 24;
keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest';
keepass.cacheTimeout = 30 * 1000; // milliseconds
keepass.databaseHash = 'no-hash'; //no-hash = KeePassXC is too old and does not return a hash value
keepass.previousDatabaseHash = 'no-hash';
keepass.keyId = 'keepassxc-browser-cryptokey-name';
keepass.keyBody = 'keepassxc-browser-key';
keepass.messageTimeout = 500; // milliseconds
@ -214,15 +215,24 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb
}
let entries = [];
let keys = [];
const kpAction = kpActions.GET_LOGINS;
const nonce = keepass.getNonce();
const incrementedNonce = keepass.incrementedNonce(nonce);
const {dbid} = keepass.getCryptoKey();
for (let keyHash in keepass.keyRing) {
keys.push({
id: keepass.keyRing[keyHash].id,
key: keepass.keyRing[keyHash].key
});
}
let messageData = {
action: kpAction,
id: dbid,
url: url
url: url,
keys: keys
};
if (submiturl) {
@ -704,7 +714,7 @@ keepass.isAssociated = function() {
keepass.migrateKeyRing = function() {
return new Promise((resolve, reject) => {
browser.storage.local.get('keyRing').then((item) => {
const keyring = item.keyRing;
const keyring = item.keyRing;
// Change dates to numbers, for compatibilty with Chromium based browsers
if (keyring) {
let num = 0;
@ -826,11 +836,22 @@ keepass.onNativeMessage = function(response) {
// Handle database lock/unlock status
if (response.action === kpActions.DATABASE_LOCKED || response.action === kpActions.DATABASE_UNLOCKED) {
keepass.testAssociation((response) => {
keepass.testAssociation((associationResponse) => {
keepass.isConfigured().then((configured) => {
let data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
data.iconType = configured ? 'normal' : 'cross';
browserAction.show(null, {'id': page.currentTabId});
// Send message to content script
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
if (tabs.length) {
browser.tabs.sendMessage(tabs[0].id, {
action: 'check_database_hash',
hash: {old: kpxcEvent.previousDatabaseHash, new: keepass.databaseHash}
});
keepass.previousDatabaseHash = keepass.databaseHash;
}
});
});
}, null);
}

View file

@ -1,3 +1,5 @@
'use strict';
const defaultSettings = {
checkUpdateKeePassXC: 3,
autoCompleteUsernames: true,
@ -5,13 +7,15 @@ const defaultSettings = {
usePasswordGenerator: true,
autoFillSingleEntry: false,
autoRetrieveCredentials: true,
showNotifications: true
showNotifications: true,
showLoginNotifications: true,
saveDomainOnly: true
};
var page = {};
page.tabs = {};
page.tabs = [];
page.currentTabId = -1;
page.blockedTabs = {};
page.blockedTabs = [];
page.initSettings = function() {
return new Promise((resolve, reject) => {
@ -38,6 +42,12 @@ page.initSettings = function() {
if (!('showNotifications' in page.settings)) {
page.settings.showNotifications = defaultSettings.showNotifications;
}
if (!('showLoginNotifications' in page.settings)) {
page.settings.showLoginNotifications = defaultSettings.showLoginNotifications;
}
if (!('saveDomainOnly' in page.settings)) {
page.settings.saveDomainOnly = defaultSettings.saveDomainOnly;
}
browser.storage.local.set({'settings': page.settings});
resolve(page.settings);
});
@ -101,7 +111,7 @@ page.createTabEntry = function(tabId) {
page.tabs[tabId] = {
'stack': [],
'errorMessage': null,
'loginList': {}
'loginList': []
};
};

View file

@ -1,3 +1,5 @@
'use strict';
var isFirefox = function() {
if (!(/Chrome/.test(navigator.userAgent) && /Google/.test(navigator.vendor))) {
return true;

View file

@ -18,6 +18,7 @@
.kpxc .ui-dialog {
font-size: 12px !important;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
z-index: 2147483645;
}
.kpxc .ui-widget-overlay {
@ -57,10 +58,10 @@ input.genpw-text {
}
.genpw-input-group-addon {
font-size: inherit !important;
font-size: .9em !important;
background-color: #eee;
border: 1px solid #ccc;
padding: .4em;
padding: .2em;
border-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
@ -102,6 +103,10 @@ input.genpw-text {
margin-right: 5px;
}
#cip-genpw-quality {
text-align: center;
}
.b2c-modal-backdrop {
position: fixed;
top: 0;

View file

@ -1,8 +1,11 @@
'use strict';
// contains already called method names
var _called = {};
_called.retrieveCredentials = false;
_called.clearLogins = false;
_called.manualFillRequested = 'none';
let _loginId = -1;
// Count of detected form fields on the page
var _detectedFields = 0;
@ -15,10 +18,12 @@ browser.runtime.onMessage.addListener(function(req, sender, callback) {
if (cip.u) {
cip.setValueWithChange(cip.u, cip.credentials[req.id].login);
combination = cipFields.getCombination('username', cip.u);
_loginId = req.id;
cip.u.focus();
}
if (cip.p) {
cip.setValueWithChange(cip.p, cip.credentials[req.id].password);
_loginId = req.id;
combination = cipFields.getCombination('password', cip.p);
}
@ -27,48 +32,44 @@ browser.runtime.onMessage.addListener(function(req, sender, callback) {
cipForm.destroy(false, {'password': list.list[0], 'username': list.list[1]});
}
}
}
else if (req.action === 'fill_user_pass') {
} else if (req.action === 'fill_user_pass') {
_called.manualFillRequested = 'both';
cip.receiveCredentialsIfNecessary().then((response) => {
cip.fillInFromActiveElement(false);
});
}
else if (req.action === 'fill_pass_only') {
} else if (req.action === 'fill_pass_only') {
_called.manualFillRequested = 'pass';
cip.receiveCredentialsIfNecessary().then((response) => {
cip.fillInFromActiveElement(false, true); // passOnly to true
});
}
else if (req.action === 'fill_totp') {
} else if (req.action === 'fill_totp') {
cip.receiveCredentialsIfNecessary().then((response) => {
cip.fillInFromActiveElementTOTPOnly(false);
});
}
else if (req.action === 'activate_password_generator') {
} else if (req.action === 'activate_password_generator') {
cip.initPasswordGenerator(cipFields.getAllFields());
}
else if (req.action === 'remember_credentials') {
} else if (req.action === 'remember_credentials') {
cip.contextMenuRememberCredentials();
}
else if (req.action === 'choose_credential_fields') {
} else if (req.action === 'choose_credential_fields') {
cipDefine.init();
}
else if (req.action === 'clear_credentials') {
} else if (req.action === 'clear_credentials') {
cipEvents.clearCredentials();
callback();
}
else if (req.action === 'activated_tab') {
} else if (req.action === 'activated_tab') {
cipEvents.triggerActivatedTab();
callback();
}
else if (req.action === 'redetect_fields') {
} else if (req.action === 'redetect_fields') {
browser.runtime.sendMessage({
action: 'load_settings',
}).then((response) => {
cip.settings = response;
cip.initCredentialFields(true);
});
} else if (req.action === 'ignore-site') {
cip.ignoreSite(req.args);
}
else if (req.action === 'check_database_hash' && 'hash' in req) {
cip.detectDatabaseChange(req.hash);
}
}
});
@ -231,7 +232,7 @@ cipPassword.createDialog = function() {
.addClass('genpw-input-group-addon')
.addClass('b2c-add-on')
.attr('id', 'cip-genpw-quality')
.text('123 Bits');
.text('??? Bits');
$inputGroup.append($textfieldPassword).append($quality);
const $checkGroup = jQuery('<div>').addClass('genpw-input-group');
@ -305,7 +306,11 @@ cipPassword.createDialog = function() {
$password = $password.substring(0, field.attr('maxlength'));
jQuery('input#cip-genpw-textfield-password:first').val($password);
jQuery('#cip-genpw-btn-clipboard:first').removeClass('b2c-btn-success');
alert('The generated password is longer than the allowed length!\nIt has been cut to fit the length.\n\nPlease remember the new password!');
const message = 'Error:\nThe generated password is longer than the allowed length!\nIt has been cut to fit the length.\n\nPlease remember the new password!';
browser.runtime.sendMessage({
action: 'show_notification',
args: [message]
});
}
}
@ -489,7 +494,7 @@ cipForm.init = function(form, credentialFields) {
// TODO: could be called multiple times --> update credentialFields
// not already initialized && password-field is not null
if (!form.data('cipForm-initialized') && credentialFields.password) {
if (!form.data('cipForm-initialized') && (credentialFields.password || credentialFields.username)) {
form.data('cipForm-initialized', true);
cipForm.setInputFields(form, credentialFields);
form.submit(cipForm.onSubmit);
@ -807,7 +812,7 @@ cipFields.prepareId = function(id) {
// Check aria-hidden attribute by looping the parent elements of input field
cipFields.getAriaHidden = function(field) {
let $par = jQuery(field).parents();
for (p of $par) {
for (const p of $par) {
const val = $(p).attr('aria-hidden');
if (val) {
return val;
@ -818,7 +823,7 @@ cipFields.getAriaHidden = function(field) {
cipFields.getOverflowHidden = function(field) {
let $par = jQuery(field).parents();
for (p of $par) {
for (const p of $par) {
const val = $(p).css('overflow');
if (val === 'hidden') {
return true;
@ -889,6 +894,15 @@ cipFields.getAllCombinations = function(inputs) {
}
}
// If only username field found, add it to the array
if (fields.length === 0 && uField) {
const combination = {
username: uField[0].getAttribute('data-cip-id'),
password: null
};
fields.push(combination);
}
return fields;
};
@ -1033,7 +1047,7 @@ cipFields.getPasswordField = function(usernameId, checkDisabled) {
// search all inputs on this one form
if (form) {
passwordField = jQuery('input[type=\'password\']:first', form);
if (passwordField.length < 1) {
if (passwordField && passwordField.length < 1) {
passwordField = null;
}
@ -1139,7 +1153,6 @@ cip.credentials = [];
jQuery(function() {
cip.init();
cip.detectNewActiveFields();
cip.detectDatabaseChange();
});
cip.init = function() {
@ -1166,46 +1179,36 @@ cip.detectNewActiveFields = function() {
};
// Switch credentials if database is changed or closed
cip.detectDatabaseChange = function() {
let dbDetectInterval = setInterval(function() {
if (document.visibilityState !== 'hidden') {
cip.detectDatabaseChange = function(response) {
if (document.visibilityState !== 'hidden') {
if (response.new === 'no-hash' && response.old !== 'no-hash') {
cipEvents.clearCredentials();
browser.runtime.sendMessage({
action: 'check_databasehash'
action: 'page_clear_logins'
});
// Switch back to default popup
browser.runtime.sendMessage({
action: 'get_status',
args: [ true ] // Set polling to true, this is an internal function call
});
} else if (response.new !== 'no-hash' && response.new !== response.old) {
browser.runtime.sendMessage({
action: 'load_settings',
}).then((response) => {
if (response.new === 'no-hash') {
cipEvents.clearCredentials();
cip.settings = response;
cip.initCredentialFields(true);
browser.runtime.sendMessage({
action: 'page_clear_logins'
});
// Switch back to default popup
browser.runtime.sendMessage({
action: 'get_status',
args: [ true ] // Set polling to true, this is an internal function call
});
} else {
if (response.new !== 'no-hash' && response.new !== response.old) {
browser.runtime.sendMessage({
action: 'load_settings',
}).then((response) => {
cip.settings = response;
cip.initCredentialFields(true);
// If user has requested a manual fill through context menu the actual credential filling
// is handled here when the opened database has been regognized. It's not a pretty hack.
if (_called.manualFillRequested && _called.manualFillRequested !== 'none') {
cip.fillInFromActiveElement(false, (_called.manualFillRequested === 'pass' ? true : false));
_called.manualFillRequested = 'none';
}
});
}
// If user has requested a manual fill through context menu the actual credential filling
// is handled here when the opened database has been regognized. It's not a pretty hack.
if (_called.manualFillRequested && _called.manualFillRequested !== 'none') {
cip.fillInFromActiveElement(false, _called.manualFillRequested === 'pass');
_called.manualFillRequested = 'none';
}
}).catch((e) => {
console.log(e);
});
}
}, 1000);
}
};
cip.initCredentialFields = function(forceCall) {
@ -1236,6 +1239,11 @@ cip.initCredentialFields = function(forceCall) {
cip.url = document.location.origin;
cip.submitUrl = cip.getFormActionUrl(cipFields.combinations[0]);
// Get submitUrl for a single input
if (!cip.submitUrl && cipFields.combinations.length === 1 && inputs.length === 1) {
cip.submitUrl = cip.getFormActionUrlFromSingleInput(inputs[0]);
}
if (cip.settings.autoRetrieveCredentials && _called.retrieveCredentials === false && (cip.url && cip.submitUrl)) {
browser.runtime.sendMessage({
action: 'retrieve_credentials',
@ -1358,7 +1366,9 @@ cip.preparePageForMultipleCredentials = function(credentials) {
cipAutocomplete.init(_f(i.username));
}
} else if (_detectedFields == 1) {
// If only password field is the visible one
if (_f(i.username)) {
cipAutocomplete.init(_f(i.username));
}
if (_f(i.password)) {
cipAutocomplete.init(_f(i.password));
}
@ -1392,6 +1402,20 @@ cip.getFormActionUrl = function(combination) {
return action;
};
cip.getFormActionUrlFromSingleInput = function(field) {
if (!field) {
return null;
}
let action = field.formAction;
if (typeof(action) !== 'string' || action === '') {
action = document.location.origin + document.location.pathname;
}
return action;
};
cip.fillInCredentials = function(combination, onlyPassword, suppressWarnings) {
const action = cip.getFormActionUrl(combination);
@ -1455,9 +1479,9 @@ cip.fillInFromActiveElement = function(suppressWarnings, passOnly = false) {
if (passOnly) {
if (!_f(combination.password)) {
const message = 'Unable to find a password field';
const message = 'Error:\nUnable to find a password field';
browser.runtime.sendMessage({
action: 'alert',
action: 'show_notification',
args: [message]
});
return;
@ -1473,17 +1497,21 @@ cip.fillInFromActiveElementTOTPOnly = function(suppressWarnings) {
const el = document.activeElement;
cipFields.setUniqueId(jQuery(el));
const fieldId = cipFields.prepareId(jQuery(el).attr('data-cip-id'));
const pos = _loginId;
if (cip.credentials[0]) {
if (pos >= 0 && cip.credentials[pos]) {
// Check the value from stringFields (to be removed)
const $sf = _fs(fieldId);
if (cip.credentials[0].stringFields && cip.credentials[0].stringFields.length > 0) {
const sFields = cip.credentials[0].stringFields;
if (cip.credentials[pos].stringFields && cip.credentials[pos].stringFields.length > 0) {
const sFields = cip.credentials[pos].stringFields;
for (const s of sFields) {
const val = s["KPH: {TOTP}"];
if (val) {
cip.setValue($sf, val);
}
}
} else if (cip.credentials[pos].totp && cip.credentials[pos].totp.length > 0) {
cip.setValue($sf, cip.credentials[pos].totp);
}
}
};
@ -1539,9 +1567,9 @@ cip.setValueWithChange = function(field, value) {
cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
// no credentials available
if (cip.credentials.length === 0 && !suppressWarnings) {
const message = 'No logins found.';
const message = 'Error:\nNo logins found.';
browser.runtime.sendMessage({
action: 'alert',
action: 'show_notification',
args: [message]
});
return;
@ -1555,12 +1583,14 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
let filledIn = false;
if (uField && !onlyPassword) {
cip.setValueWithChange(uField, cip.credentials[0].login);
_loginId = 0;
filledIn = true;
}
if (pField) {
pField.attr('type', 'password');
cip.setValueWithChange(pField, cip.credentials[0].password);
pField.data('unchanged', true);
_loginId = 0;
filledIn = true;
}
@ -1572,9 +1602,9 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
if (!filledIn) {
if (!suppressWarnings) {
const message = 'Error #101\nCannot find fields to fill in.';
const message = 'Error:\nCannot find fields to fill in.';
browser.runtime.sendMessage({
action: 'alert',
action: 'show_notification',
args: [message]
});
}
@ -1585,12 +1615,14 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
let filledIn = false;
if (uField) {
cip.setValueWithChange(uField, cip.credentials[combination.loginId].login);
_loginId = combination.loginId;
filledIn = true;
}
if (pField) {
cip.setValueWithChange(pField, cip.credentials[combination.loginId].password);
pField.data('unchanged', true);
_loginId = combination.loginId;
filledIn = true;
}
@ -1602,9 +1634,9 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
if (!filledIn) {
if (!suppressWarnings) {
const message = 'Error #102\nCannot find fields to fill in.';
const message = 'Error:\nCannot find fields to fill in.';
browser.runtime.sendMessage({
action: 'alert',
action: 'show_notification',
args: [message]
});
}
@ -1631,7 +1663,7 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
}
}
// for the correct alert message: 0 = no logins, X > 1 = too many logins
// for the correct notification message: 0 = no logins, X > 1 = too many logins
if (countPasswords === 0) {
countPasswords = cip.credentials.length;
}
@ -1656,19 +1688,17 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
// user has to select correct credentials by himself
if (countPasswords > 1) {
if (!suppressWarnings) {
const message = 'Error #105\nMore than one login was found in KeePassXC!\n' +
'Press the KeePassXC-Browser icon for more options.';
browser.runtime.sendMessage({
action: 'alert',
args: [message]
});
const $target = onlyPassword ? pField : uField;
cipAutocomplete.init($target);
$target.focus();
jQuery($target).autocomplete('search', jQuery($target).val());
}
}
else if (countPasswords < 1) {
if (!suppressWarnings) {
const message = 'Error #103\nNo credentials for given username found.';
const message = 'Error:\nNo credentials for given username found.';
browser.runtime.sendMessage({
action: 'alert',
action: 'show_notification',
args: [message]
});
}
@ -1676,12 +1706,10 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
}
else {
if (!suppressWarnings) {
const message = 'Error #104\nMore than one login was found in KeePassXC!\n' +
'Press the KeePassXC-Browser icon for more options.';
browser.runtime.sendMessage({
action: 'alert',
args: [message]
});
const $target = onlyPassword ? pField : uField;
cipAutocomplete.init($target);
$target.focus();
jQuery($target).autocomplete('search', jQuery($target).val());
}
}
}
@ -1717,7 +1745,11 @@ cip.contextMenuRememberCredentials = function() {
}
if (!cip.rememberCredentials(usernameValue, passwordValue)) {
alert('Could not detect changed credentials.');
const message = 'Error:\nCould not detect changed credentials.';
browser.runtime.sendMessage({
action: 'show_notification',
args: [message]
});
}
};
@ -1762,7 +1794,7 @@ cip.rememberCredentials = function(usernameValue, passwordValue) {
let url = jQuery(this)[0].action;
if (!url) {
url = document.location.href;
url = cip.settings.saveDomainOnly ? document.location.origin : document.location.href;
if (url.indexOf('?') > 0) {
url = url.substring(0, url.indexOf('?'));
if (url.length < document.location.origin.length) {
@ -1782,6 +1814,25 @@ cip.rememberCredentials = function(usernameValue, passwordValue) {
return false;
};
cip.ignoreSite = function(sites) {
if (!sites || sites.length === 0) {
return;
}
const site = sites[0];
if (!cip.settings['ignoredSites']) {
cip.settings['ignoredSites'] = {};
}
cip.settings['ignoredSites'][site] = {
url: site
};
browser.runtime.sendMessage({
action: 'save_settings',
args: [cip.settings]
});
};
var cipEvents = {};

View file

@ -1,8 +1,8 @@
{
"manifest_version": 2,
"name": "KeePassXC-Browser",
"version": "1.0.1",
"version_name": "1.0.1",
"version": "1.1.0",
"version_name": "1.1.0",
"description": "KeePassXC integration for modern web browsers",
"author": "KeePassXC Team",
"icons": {
@ -72,8 +72,8 @@
"fill-password": {
"description": "Insert a password",
"suggested_key": {
"default": "Alt+Shift+P",
"mac": "MacCtrl+Shift+P"
"default": "Alt+Shift+I",
"mac": "MacCtrl+Shift+I"
}
},
"fill-totp": {

View file

@ -20,6 +20,7 @@
<li class="active"><a href="#general-settings">General</a></li>
<li><a href="#connected-databases">Connected Databases</a></li>
<li><a href="#specified-fields">Specified credential fields</a></li>
<li><a href="#ignored-sites">Ignored sites</a></li>
<li><a href="#about">About</a></li>
</ul>
</nav>
@ -29,9 +30,9 @@
<h2>General Settings</h2>
<hr />
<p>
If you just want to insert username + password into the fields where your focus is, press <code>Alt + Shift + U</code>.
If you just want to insert username + password into the fields where your focus is, press <code><span id="mac-user-shortcut">Ctrl + Shift + U</span><span id="default-user-shortcut">Alt + Shift + U</span></code>.
<br />
If you only want to insert the password, just press <code>Alt + Shift + P</code>.
If you only want to insert the password, just press <code><span id="mac-pass-shortcut">Ctrl + Shift + I</span><span id="default-pass-shortcut">Alt + Shift + I</span></code>.
<span id="chrome-only">You can customize these shortcuts on <code>chrome://extensions/configureCommands</code> page</span>
</p>
<p>
@ -39,7 +40,7 @@
<label for="blinkTimeout">Blink Time:</label>
<div class="control-group">
<div class="input-append">
<input type="number" id="blinkTimeout" placeholder="7500" value="7500" />
<input type="number" id="blinkTimeout" placeholder="7500" value="7500" min="0"/>
<button class="btn btn-sm btn-primary" id="blinkTimeoutButton" type="button"><span class="glyphicon glyphicon-floppy-disk"></span> Save</button>
</div>
</div>
@ -55,7 +56,7 @@
<label for="blinkMinTimeout">Redirect Offset:</label>
<div class="control-group">
<div class="input-append">
<input type="number" id="blinkMinTimeout" placeholder="2000" value="2000" />
<input type="number" id="blinkMinTimeout" placeholder="2000" value="2000" min="-1"/>
<button class="btn btn-sm btn-primary" id="blinkMinTimeoutButton" type="button"><span class="glyphicon glyphicon-floppy-disk"></span> Save</button>
</div>
</div>
@ -73,7 +74,7 @@
<label for="allowedRedirect">Redirect Allowance:</label>
<div class="control-group">
<div class="input-append">
<input type="number" id="allowedRedirect" placeholder="1" value="1" />
<input type="number" id="allowedRedirect" placeholder="1" value="1" min="1"/>
<button class="btn btn-sm btn-primary" id="allowedRedirectButton" type="button"><span class="glyphicon glyphicon-floppy-disk"></span> Save</button>
</div>
</div>
@ -130,7 +131,21 @@
<label class="checkbox">
<input type="checkbox" name="showNotifications" value="true" /> Show notifications.
</label>
<span class="help-block">Allow KeePassXC-Browser to show notifications when user interaction is needed from the extension icon.</span>
<span class="help-block">Show notifications for errors and when user interaction is required.</span>
</div>
<div class="checkbox">
<label class="checkbox">
<input type="checkbox" name="showLoginNotifications" value="true" /> Show a notification when new credentials can be saved to the database.
</label>
</div>
</p>
<hr />
<p>
<div class="checkbox">
<label class="checkbox">
<input type="checkbox" name="saveDomainOnly" value="true" /> Save domain only.
</label>
<span class="help-block">When saving new credentials save only the domain instead of full URL.</span>
</div>
</p>
<hr />
@ -274,6 +289,52 @@
</div>
</div>
<!-- Ignored sites -->
<div class="tab" id="tab-ignored-sites">
<h2>Ignored sites</h2>
<hr />
<p>
Sites in this list are ignored when new credentials are detected.
<br />
Go to the page with new credentials, click the blinking KeePassXC-Browser icon or the notification and select <em>Never ask for this page</em>.
</p>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Page URL</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr class="empty">
<td colspan="2">No ignored sites found.</td>
</tr>
<tr class="clone">
<td></td>
<td><button class="btn delete btn-danger btn"><span class="glyphicon glyphicon-remove-sign"></span> Remove</button></td>
</tr>
</tbody>
</table>
<div id="dialogDeleteIgnoredSite" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Remove site?</h3>
</div>
<div class="modal-body">
<p>Do you really want to remove the specified site from the ignore list?</p>
<p class="help-block">KeePassXC-Browser will detect new credentials the next time you visit this page.</p>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
<button class="btn yes btn-primary">Yes, remove now</button>
</div>
</div>
</div>
</div>
</div>
<!-- About -->
<div class="tab" id="tab-about">
<h2>About</h2>

View file

@ -1,7 +1,15 @@
'use strict';
if (jQuery) {
var $ = jQuery.noConflict(true);
}
const defaultSettings = {
blinkTimeout: 7500,
redirectOffset: -1,
redirectAllowance: 1
};
$(function() {
browser.runtime.sendMessage({ action: 'load_settings' }).then((settings) => {
options.settings = settings;
@ -11,6 +19,7 @@ $(function() {
options.initGeneralSettings();
options.initConnectedDatabases();
options.initSpecifiedCredentialFields();
options.initIgnoredSites();
options.initAbout();
});
});
@ -111,7 +120,7 @@ options.initGeneralSettings = function() {
$('#blinkTimeoutButton').click(function(){
const blinkTimeout = $.trim($('#blinkTimeout').val());
const blinkTimeoutval = Number(blinkTimeout);
const blinkTimeoutval = blinkTimeout !== '' ? Number(blinkTimeout) : defaultSettings.blinkTimeout;
options.settings['blinkTimeout'] = String(blinkTimeoutval);
options.saveSetting('blinkTimeout');
@ -119,7 +128,7 @@ options.initGeneralSettings = function() {
$('#blinkMinTimeoutButton').click(function(){
const blinkMinTimeout = $.trim($('#blinkMinTimeout').val());
const blinkMinTimeoutval = Number(blinkMinTimeout);
const blinkMinTimeoutval = blinkMinTimeout !== '' ? Number(blinkMinTimeout) : defaultSettings.redirectOffset;
options.settings['blinkMinTimeout'] = String(blinkMinTimeoutval);
options.saveSetting('blinkMinTimeout');
@ -127,7 +136,7 @@ options.initGeneralSettings = function() {
$('#allowedRedirectButton').click(function(){
const allowedRedirect = $.trim($('#allowedRedirect').val());
const allowedRedirectval = Number(allowedRedirect);
const allowedRedirectval = allowedRedirect !== '' ? Number(allowedRedirect) : defaultSettings.redirectAllowance;
options.settings['allowedRedirect'] = String(allowedRedirectval);
options.saveSetting('allowedRedirect');
@ -167,8 +176,7 @@ options.initConnectedDatabases = function() {
if ($('#tab-connected-databases table tbody:first tr').length > 2) {
$('#tab-connected-databases table tbody:first tr.empty:first').hide();
}
else {
} else {
$('#tab-connected-databases table tbody:first tr.empty:first').show();
}
});
@ -195,8 +203,7 @@ options.initConnectedDatabases = function() {
if ($('#tab-connected-databases table tbody:first tr').length > 2) {
$('#tab-connected-databases table tbody:first tr.empty:first').hide();
}
else {
} else {
$('#tab-connected-databases table tbody:first tr.empty:first').show();
}
@ -229,8 +236,7 @@ options.initSpecifiedCredentialFields = function() {
if ($('#tab-specified-fields table tbody:first tr').length > 2) {
$('#tab-specified-fields table tbody:first tr.empty:first').hide();
}
else {
} else {
$('#tab-specified-fields table tbody:first tr.empty:first').show();
}
});
@ -242,7 +248,7 @@ options.initSpecifiedCredentialFields = function() {
const $tr = $trClone.clone(true);
$tr.data('url', url);
$tr.attr('id', 'tr-scf' + counter);
counter += 1;
++counter;
$tr.children('td:first').text(url);
$('#tab-specified-fields table tbody:first').append($tr);
@ -250,15 +256,73 @@ options.initSpecifiedCredentialFields = function() {
if ($('#tab-specified-fields table tbody:first tr').length > 2) {
$('#tab-specified-fields table tbody:first tr.empty:first').hide();
}
else {
} else {
$('#tab-specified-fields table tbody:first tr.empty:first').show();
}
};
options.initIgnoredSites = function() {
$('#dialogDeleteIgnoredSite').modal({keyboard: true, show: false, backdrop: true});
$('#tab-ignored-sites tr.clone:first button.delete:first').click(function(e) {
e.preventDefault();
$('#dialogDeleteIgnoredSite').data('url', $(this).closest('tr').data('url'));
$('#dialogDeleteIgnoredSite').data('tr-id', $(this).closest('tr').attr('id'));
$('#dialogDeleteIgnoredSite .modal-body:first strong:first').text($(this).closest('tr').children('td:first').text());
$('#dialogDeleteIgnoredSite').modal('show');
});
$('#dialogDeleteIgnoredSite .modal-footer:first button.yes:first').click(function(e) {
$('#dialogDeleteIgnoredSite').modal('hide');
const $url = $('#dialogDeleteIgnoredSite').data('url');
const $trId = $('#dialogDeleteIgnoredSite').data('tr-id');
$('#tab-ignored-sites #' + $trId).remove();
delete options.settings['ignoredSites'][$url];
options.saveSettings();
if ($('#tab-ignored-sites table tbody:first tr').length > 2) {
$('#tab-ignored-sites table tbody:first tr.empty:first').hide();
} else {
$('#tab-ignored-sites table tbody:first tr.empty:first').show();
}
});
const $trClone = $('#tab-ignored-sites table tr.clone:first').clone(true);
$trClone.removeClass('clone');
let counter = 1;
for (let url in options.settings['ignoredSites']) {
const $tr = $trClone.clone(true);
$tr.data('url', url);
$tr.attr('id', 'tr-scf' + counter);
++counter;
$tr.children('td:first').text(url);
$('#tab-ignored-sites table tbody:first').append($tr);
}
if ($('#tab-ignored-sites table tbody:first tr').length > 2) {
$('#tab-ignored-sites table tbody:first tr.empty:first').hide();
} else {
$('#tab-ignored-sites table tbody:first tr.empty:first').show();
}
};
options.initAbout = function() {
$('#tab-about em.versionCIP').text(browser.runtime.getManifest().version);
if (isFirefox()) {
$('#chrome-only').remove();
}
if (navigator.platform === 'MacIntel') {
$('#default-user-shortcut').hide();
$('#default-pass-shortcut').hide();
$('#mac-user-shortcut').show();
$('#mac-pass-shortcut').show();
} else {
$('#mac-user-shortcut').hide();
$('#mac-pass-shortcut').hide();
$('#default-user-shortcut').show();
$('#default-pass-shortcut').show();
}
};

View file

@ -18,7 +18,7 @@
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-list-alt"></span> Choose own credential fields for this page</button>
<button id="lock-database-button" class="btn btn-danger" title="Lock database"><span class="glyphicon glyphicon-lock"></span></button>
<div id="update-available" class="alert alert-danger">
<div id="update-available" class="alert alert-danger" style="display: none">
You use an old version of KeePassXC.
<br />
<a target="_blank" class="alert-link" href="https://keepassxc.org/download">Please download the latest version from keepassxc.org</a>.

View file

@ -1,3 +1,5 @@
'use strict';
function status_response(r) {
$('#initial-state').hide();
$('#error-encountered').hide();
@ -6,6 +8,7 @@ function status_response(r) {
$('#configured-and-associated').hide();
$('#configured-not-associated').hide();
$('#lock-database-button').hide();
$('#update-available').hide();
if (!r.keePassXCAvailable) {
$('#error-message').html(r.error);

View file

@ -1,12 +1,11 @@
'use strict';
var $ = jQuery.noConflict(true);
function updateAvailableResponse(available) {
if (available) {
$('#update-available').show();
}
else {
$('#update-available').hide();
}
}
function initSettings() {

View file

@ -1,3 +1,5 @@
'use strict';
$(function() {
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.query({'active': true, 'currentWindow': true}).then((tabs) => {

View file

@ -1,3 +1,5 @@
'use strict';
$(function() {
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.query({'active': true, 'currentWindow': true}).then((tabs) => {

View file

@ -32,6 +32,7 @@
<button id="btn-new" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-pencil"></span> New</button>
<button id="btn-update" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-refresh"></span> Update</button>
<button id="btn-dismiss" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-remove"></span> Dismiss</button>
<button id="btn-ignore" class="btn btn-sm btn-default"><span class="glyphicon glyphicon-remove"></span> Never ask for this page</button>
</p>
</div>

View file

@ -1,3 +1,5 @@
'use strict';
var _tab;
function _initialize(tab) {
@ -78,6 +80,21 @@ function _initialize(tab) {
e.preventDefault();
_close();
});
$('#btn-ignore').click(function(e) {
browser.windows.getCurrent().then((win) => {
browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
const tab = tabs[0];
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.sendMessage(tab.id, {
action: 'ignore-site',
args: [_tab.credentials.url]
});
_close();
});
});
});
});
}
function _connected_database(db) {

View file

@ -158,7 +158,14 @@ Unencrypted message:
{
"action": "get-logins",
"url": "<snip>",
"submitUrl": optional
"submitUrl": optional,
"keys": [
{
"id": <connected_id>,
"key": <connected_key>
},
...
]
}
```