Big refactor

This commit is contained in:
varjolintu 2020-07-07 23:00:42 +03:00
parent 4212c7eff4
commit 75b3120cb0
22 changed files with 1987 additions and 2308 deletions

View file

@ -263,6 +263,10 @@
"message": "No credentials for the given username found.",
"description": "Alert message when no credentials are found for the given username."
},
"credentialsNoTOTPFound": {
"message": "No TOTP found.",
"description": "Alert message when no TOTP is found."
},
"credentialExpired": {
"message": "Expired",
"description": "If a credential is expired, this is appended to the title label."

View file

@ -2,33 +2,30 @@
const browserAction = {};
browserAction.show = function(tab) {
let data = {};
if (!page.tabs[tab.id] || page.tabs[tab.id].stack.length === 0) {
browserAction.showDefault(tab);
return;
} else {
data = page.tabs[tab.id].stack[page.tabs[tab.id].stack.length - 1];
browserAction.show = function(tab, popupData) {
if (!popupData) {
popupData = page.popupData;
}
page.popupData = popupData;
browser.browserAction.setIcon({
tabId: tab.id,
path: '/icons/toolbar/' + browserAction.generateIconName(data.iconType, data.icon)
path: browserAction.generateIconName(popupData.iconType)
});
if (data.popup) {
if (popupData.popup) {
browser.browserAction.setPopup({
tabId: tab.id,
popup: 'popups/' + data.popup
popup: `popups/${popupData.popup}.html`
});
}
};
browserAction.showDefault = async function(tab) {
const stackData = {
level: 1,
const popupData = {
iconType: 'normal',
popup: 'popup.html'
popup: 'popup'
};
const response = await keepass.isConfigured().catch((err) => {
@ -36,77 +33,41 @@ browserAction.showDefault = async function(tab) {
});
if (!response && !keepass.isKeePassXCAvailable) {
stackData.iconType = 'cross';
popupData.iconType = 'cross';
} else if (keepass.isKeePassXCAvailable && keepass.isDatabaseClosed) {
stackData.iconType = 'locked';
popupData.iconType = 'locked';
}
if (page.tabs[tab.id] && page.tabs[tab.id].loginList.length > 0) {
stackData.iconType = 'questionmark';
stackData.popup = 'popup_login.html';
popupData.iconType = 'questionmark';
popupData.popup = 'popup_login';
}
browserAction.stackUnshift(stackData, tab.id);
browserAction.show(tab);
browserAction.show(tab, popupData);
};
browserAction.removeLevelFromStack = function(tab, level, type, dontShow) {
if (!page.tabs[tab.id]) {
return;
}
if (!type) {
type = '<=';
}
const newStack = [];
for (const i of page.tabs[tab.id].stack) {
if ((type === '<' && i.level >= level)
|| (type === '<=' && i.level > level)
|| (type === '=' && i.level !== level)
|| (type === '==' && i.level !== level)
|| (type === '!=' && i.level === level)
|| (type === '>' && i.level <= level)
|| (type === '>=' && i.level < level)) {
newStack.push(i);
browserAction.updateIcon = async function(tab, iconType) {
if (!tab) {
const tabs = await browser.tabs.query({ 'active': true, 'currentWindow': true });
if (tabs.length === 0) {
return;
}
tab = tabs[0];
}
page.tabs[tab.id].stack = newStack;
if (!dontShow) {
browserAction.show(tab);
}
browser.browserAction.setIcon({
tabId: tab.id,
path: browserAction.generateIconName(iconType)
});
};
browserAction.stackPop = function(tabId) {
const id = tabId || page.currentTabId;
page.tabs[id].stack.pop();
};
browserAction.stackPush = function(data, tabId) {
const id = tabId || page.currentTabId;
browserAction.removeLevelFromStack({ 'id': id }, data.level, '<=', true);
page.tabs[id].stack.push(data);
};
browserAction.stackUnshift = function(data, tabId) {
const id = tabId || page.currentTabId;
browserAction.removeLevelFromStack({ 'id': id }, data.level, '<=', true);
page.tabs[id].stack.unshift(data);
};
browserAction.generateIconName = function(iconType, icon) {
if (icon) {
return icon;
}
browserAction.generateIconName = function(iconType) {
let name = 'icon_';
name += (keepass.keePassXCUpdateAvailable()) ? 'new_' : '';
name += (!iconType || iconType === 'normal') ? 'normal' : iconType;
name += '.png';
return name;
return `/icons/toolbar/${name}.png`;
};
browserAction.ignoreSite = async function(url) {

View file

@ -13,13 +13,16 @@ kpxcEvent.onMessage = async function(request, sender) {
}
};
kpxcEvent.showStatus = async function(tab, configured) {
kpxcEvent.showStatus = async function(tab, configured, internalPoll) {
let keyId = null;
if (configured && keepass.databaseHash !== '') {
keyId = keepass.keyRing[keepass.databaseHash].id;
}
browserAction.showDefault(tab);
if (!internalPoll) {
browserAction.showDefault(tab);
}
const errorMessage = page.tabs[tab.id].errorMessage;
return {
identifier: keyId,
@ -29,7 +32,7 @@ kpxcEvent.showStatus = async function(tab, configured) {
encryptionKeyUnrecognized: keepass.isEncryptionKeyUnrecognized,
associated: keepass.isAssociated(),
error: errorMessage || null,
usernameFieldDetected: page.usernameFieldDetected
usernameFieldDetected: page.tabs[tab.id].usernameFieldDetected
};
};
@ -57,11 +60,9 @@ kpxcEvent.onLoadKeyRing = async function() {
return item.keyRing;
};
kpxcEvent.onSaveSettings = async function(tab, args = []) {
const [ settings ] = args;
kpxcEvent.onSaveSettings = async function(tab, settings) {
browser.storage.local.set({ 'settings': settings });
kpxcEvent.onLoadSettings(tab);
return Promise.resolve();
};
kpxcEvent.onGetStatus = async function(tab, args = []) {
@ -76,7 +77,7 @@ kpxcEvent.onGetStatus = async function(tab, args = []) {
}
const configured = await keepass.isConfigured();
return kpxcEvent.showStatus(tab, configured);
return kpxcEvent.showStatus(tab, configured, internalPoll);
} catch (err) {
console.log('Error: No status shown: ' + err);
return Promise.reject();
@ -107,18 +108,12 @@ kpxcEvent.lockDatabase = async function(tab) {
}
};
kpxcEvent.onPopStack = function(tab) {
browserAction.stackPop(tab.id);
browserAction.show(tab);
return Promise.resolve();
};
kpxcEvent.onGetTabInformation = async function(tab) {
const id = tab.id || page.currentTabId;
return page.tabs[id];
};
kpxcEvent.onGetConnectedDatabase = function() {
kpxcEvent.onGetConnectedDatabase = async function() {
return Promise.resolve({
count: Object.keys(keepass.keyRing).length,
identifier: (keepass.keyRing[keepass.associated.hash]) ? keepass.keyRing[keepass.associated.hash].id : null
@ -143,102 +138,46 @@ kpxcEvent.onUpdateAvailableKeePassXC = async function() {
return (page.settings.checkUpdateKeePassXC > 0) ? keepass.keePassXCUpdateAvailable() : false;
};
kpxcEvent.onRemoveCredentialsFromTabInformation = function(tab) {
kpxcEvent.onRemoveCredentialsFromTabInformation = async function(tab) {
const id = tab.id || page.currentTabId;
page.clearCredentials(id);
page.clearSubmittedCredentials();
return Promise.resolve();
};
kpxcEvent.onLoginPopup = function(tab, logins) {
const stackData = {
level: 1,
kpxcEvent.onLoginPopup = async function(tab, logins) {
const popupData = {
iconType: 'questionmark',
popup: 'popup_login.html'
popup: 'popup_login'
};
browserAction.stackUnshift(stackData, tab.id);
page.tabs[tab.id].loginList = logins;
browserAction.show(tab);
return Promise.resolve();
browserAction.show(tab, popupData);
};
kpxcEvent.initHttpAuth = function() {
kpxcEvent.initHttpAuth = async function() {
httpAuth.init();
return Promise.resolve();
};
kpxcEvent.onHTTPAuthPopup = function(tab, data) {
const stackData = {
level: 1,
kpxcEvent.onHTTPAuthPopup = async function(tab, data) {
const popupData = {
iconType: 'questionmark',
popup: 'popup_httpauth.html'
popup: 'popup_httpauth'
};
browserAction.stackUnshift(stackData, tab.id);
page.tabs[tab.id].loginList = data;
browserAction.show(tab);
return Promise.resolve();
browserAction.show(tab, popupData);
};
kpxcEvent.onMultipleFieldsPopup = function(tab) {
const stackData = {
level: 1,
iconType: 'normal',
popup: 'popup_multiple-fields.html'
};
browserAction.stackUnshift(stackData, tab.id);
browserAction.show(tab);
return Promise.resolve();
};
kpxcEvent.pageClearLogins = function(tab, alreadyCalled) {
if (!alreadyCalled) {
page.clearLogins(tab.id);
}
return Promise.resolve();
};
kpxcEvent.pageGetLoginId = async function() {
return page.loginId;
};
kpxcEvent.pageSetLoginId = function(tab, loginId) {
page.loginId = loginId;
return Promise.resolve();
};
kpxcEvent.pageClearSubmitted = function() {
page.clearSubmittedCredentials();
return Promise.resolve();
};
kpxcEvent.pageGetSubmitted = async function(tab) {
// Do not return any credentials if the tab ID does not match.
if (tab.id !== page.submittedCredentials.tabId) {
return {};
}
return page.submittedCredentials;
};
kpxcEvent.pageSetSubmitted = function(tab, args = []) {
const [ submitted, username, password, url, oldCredentials ] = args;
page.setSubmittedCredentials(submitted, username, password, url, oldCredentials, tab.id);
return Promise.resolve();
};
kpxcEvent.onUsernameFieldDetected = function(tab, detected) {
page.usernameFieldDetected = detected;
kpxcEvent.onUsernameFieldDetected = async function(tab, detected) {
page.tabs[tab.id].usernameFieldDetected = detected;
};
kpxcEvent.passwordGetFilled = async function() {
return page.passwordFilled;
};
kpxcEvent.passwordSetFilled = function(tab, state) {
kpxcEvent.passwordSetFilled = async function(tab, state) {
page.passwordFilled = state;
return Promise.resolve();
};
kpxcEvent.getColorTheme = async function(tab) {
@ -249,6 +188,12 @@ kpxcEvent.pageGetRedirectCount = async function() {
return page.redirectCount;
};
kpxcEvent.pageClearLogins = async function(tab, alreadyCalled) {
if (!alreadyCalled) {
page.clearLogins(tab.id);
}
};
// All methods named in this object have to be declared BEFORE this!
kpxcEvent.messageHandlers = {
'add_credentials': keepass.addCredentials,
@ -266,26 +211,27 @@ kpxcEvent.messageHandlers = {
'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions,
'get_status': kpxcEvent.onGetStatus,
'get_tab_information': kpxcEvent.onGetTabInformation,
'get_totp': keepass.getTotp,
'init_http_auth': kpxcEvent.initHttpAuth,
'is_connected': keepass.getIsKeePassXCAvailable,
'load_keyring': kpxcEvent.onLoadKeyRing,
'load_settings': kpxcEvent.onLoadSettings,
'lock-database': kpxcEvent.lockDatabase,
'lock_database': kpxcEvent.lockDatabase,
'page_clear_logins': kpxcEvent.pageClearLogins,
'page_clear_submitted': kpxcEvent.pageClearSubmitted,
'page_get_login_id': kpxcEvent.pageGetLoginId,
'page_clear_submitted': page.clearSubmittedCredentials,
'page_get_login_id': page.getLoginId,
'page_get_manual_fill': page.getManualFill,
'page_get_redirect_count': kpxcEvent.pageGetRedirectCount,
'page_get_submitted': kpxcEvent.pageGetSubmitted,
'page_set_login_id': kpxcEvent.pageSetLoginId,
'page_set_submitted': kpxcEvent.pageSetSubmitted,
'page_get_submitted': page.getSubmitted,
'page_set_login_id': page.setLoginId,
'page_set_manual_fill': page.setManualFill,
'page_set_submitted': page.setSubmitted,
'password_get_filled': kpxcEvent.passwordGetFilled,
'password_set_filled': kpxcEvent.passwordSetFilled,
'pop_stack': kpxcEvent.onPopStack,
'popup_login': kpxcEvent.onLoginPopup,
'popup_multiple-fields': kpxcEvent.onMultipleFieldsPopup,
'reconnect': kpxcEvent.onReconnect,
'remove_credentials_from_tab_information': kpxcEvent.onRemoveCredentialsFromTabInformation,
'retrieve_credentials': keepass.retrieveCredentials,
'retrieve_credentials': page.retrieveCredentials,
'show_default_browseraction': browserAction.showDefault,
'update_credentials': keepass.updateCredentials,
'username_field_detected': kpxcEvent.onUsernameFieldDetected,

View file

@ -8,6 +8,7 @@
await httpAuth.init();
await keepass.reconnect(null, 5000); // 5 second timeout for the first connect
await keepass.enableAutomaticReconnect();
await keepass.updateDatabase();
} catch (e) {
console.log('init.js failed');
}
@ -48,9 +49,6 @@ browser.tabs.onRemoved.addListener((tabId, removeInfo) => {
* @param {object} activeInfo
*/
browser.tabs.onActivated.addListener(async function(activeInfo) {
// Remove possible credentials from old tab information
page.clearCredentials(page.currentTabId, true);
try {
const info = await browser.tabs.get(activeInfo.tabId);
if (info && info.id) {

View file

@ -36,7 +36,8 @@ const kpActions = {
DATABASE_LOCKED: 'database-locked',
DATABASE_UNLOCKED: 'database-unlocked',
GET_DATABASE_GROUPS: 'get-database-groups',
CREATE_NEW_GROUP: 'create-new-group'
CREATE_NEW_GROUP: 'create-new-group',
GET_TOTP: 'get-totp'
};
const kpErrors = {
@ -86,6 +87,27 @@ browser.storage.local.get({ 'latestKeePassXC': { 'version': '', 'lastChecked': n
keepass.keyRing = item.keyRing;
});
const messageBuffer = {
buffer: [],
addMessage(msg) {
if (!this.buffer.includes(msg)) {
this.buffer.push(msg);
}
},
matchAndRemove(msg) {
for (let i = 0; i < this.buffer.length; ++i) {
if (msg.nonce && msg.nonce === keepass.incrementedNonce(this.buffer[i].nonce)) {
this.buffer.splice(i, 1);
return true;
}
}
return false;
}
};
keepass.sendNativeMessage = function(request, enableTimeout = false, timeoutValue) {
return new Promise((resolve, reject) => {
let timeout;
@ -95,11 +117,16 @@ keepass.sendNativeMessage = function(request, enableTimeout = false, timeoutValu
const listener = ((port, action) => {
const handler = (msg) => {
if (msg && msg.action === action) {
port.removeListener(handler);
if (enableTimeout) {
clearTimeout(timeout);
// Only resolve a matching response or a notification (without nonce)
if (!msg.nonce || messageBuffer.matchAndRemove(msg)) {
port.removeListener(handler);
if (enableTimeout) {
clearTimeout(timeout);
}
resolve(msg);
return;
}
resolve(msg);
}
};
return handler;
@ -122,6 +149,9 @@ keepass.sendNativeMessage = function(request, enableTimeout = false, timeoutValu
}, messageTimeout);
}
// Store the request to the buffer
messageBuffer.addMessage(request);
// Send the request
if (keepass.nativePort) {
keepass.nativePort.postMessage(request);
@ -777,6 +807,51 @@ keepass.createNewGroup = async function(tab, args = []) {
}
};
keepass.getTotp = async function(tab, args = []) {
const [ uuid, oldTotp ] = args;
if (!keepass.compareVersion('2.6.1', keepass.currentKeePassXC, true)) {
return oldTotp;
}
const taResponse = await keepass.testAssociation(tab, [ false ]);
if (!taResponse || !keepass.isConnected) {
return;
}
const kpAction = kpActions.GET_TOTP;
const [ nonce, incrementedNonce ] = keepass.getNonces();
const messageData = {
action: kpAction,
uuid: uuid
};
try {
const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID);
const response = await keepass.sendNativeMessage(request);
if (response.message && response.nonce) {
const res = keepass.decrypt(response.message, response.nonce);
if (!res) {
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
return;
}
const message = nacl.util.encodeUTF8(res);
const parsed = JSON.parse(message);
if (keepass.verifyResponse(parsed, incrementedNonce) && parsed.totp) {
keepass.updateLastUsed(keepass.databaseHash);
return parsed.totp;
}
} else if (response.error && response.errorCode) {
keepass.handleError(tab, response.errorCode, response.error);
}
return;
} catch (err) {
console.log('getTotp failed: ', err);
}
};
keepass.generateNewKeyPair = function() {
keepass.keyPair = nacl.box.keyPair();
//console.log(nacl.util.encodeBase64(keepass.keyPair.publicKey) + ' ' + nacl.util.encodeBase64(keepass.keyPair.secretKey));
@ -1159,9 +1234,7 @@ keepass.reconnect = async function(tab, connectionTimeout) {
keepass.updatePopup = function(iconType) {
if (page && page.tabs.length > 0) {
const data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
data.iconType = iconType;
browserAction.show({ 'id': page.currentTabId });
browserAction.updateIcon(undefined, iconType);
}
};
@ -1169,7 +1242,8 @@ keepass.updatePopup = function(iconType) {
keepass.updateDatabase = async function() {
keepass.associated.value = false;
keepass.associated.hash = null;
await keepass.testAssociation(null);
page.clearAllLogins();
await keepass.testAssociation(null, [ true ]);
const configured = await keepass.isConfigured();
keepass.updatePopup(configured ? 'normal' : 'locked');
keepass.updateDatabaseHashToContent();

View file

@ -24,14 +24,20 @@ const defaultSettings = {
var page = {};
page.blockedTabs = [];
page.currentRequest = {};
page.currentTabId = -1;
page.loginId = -1;
page.manualFill = ManualFill.NONE;
page.passwordFilled = false;
page.redirectCount = 0;
page.submitted = false;
page.submittedCredentials = {};
page.tabs = [];
page.usernameFieldDetected = false;
page.popupData = {
iconType: 'normal',
popup: 'popup'
};
page.initSettings = async function() {
try {
@ -136,11 +142,11 @@ page.initOpenedTabs = async function() {
// Set initial tab-ID
const currentTabs = await browser.tabs.query({ active: true, currentWindow: true });
if (currentTabs.length === 0) {
return Promise.resolve();
return;
}
page.currentTabId = currentTabs[0].id;
browserAction.show(currentTabs[0]);
return Promise.resolve();
browserAction.showDefault(currentTabs[0]);
} catch (err) {
console.log('page.initOpenedTabs error: ' + err);
return Promise.reject();
@ -149,18 +155,18 @@ page.initOpenedTabs = async function() {
page.switchTab = function(tab) {
browserAction.showDefault(tab);
browser.tabs.sendMessage(tab.id, { action: 'activated_tab' }).catch((e) => {});
browser.tabs.sendMessage(tab.id, { action: 'activated_tab' }).catch((e) => {
console.log('Cannot send activated_tab message: ', e);
});
};
page.clearCredentials = function(tabId, complete) {
page.clearCredentials = async function(tabId, complete) {
if (!page.tabs[tabId]) {
return;
}
page.usernameFieldDetected = false;
page.passwordFilled = false;
page.tabs[tabId].credentials = [];
delete page.tabs[tabId].credentials;
if (complete) {
page.clearLogins(tabId);
@ -176,10 +182,19 @@ page.clearLogins = function(tabId) {
return;
}
page.tabs[tabId].credentials = [];
page.tabs[tabId].loginList = [];
page.currentRequest = {};
page.passwordFilled = false;
};
// Clear all logins from all pages and update the content scripts
page.clearAllLogins = function() {
for (const tabId of Object.keys(page.tabs)) {
page.clearCredentials(Number(tabId), true);
}
};
page.setSubmittedCredentials = function(submitted, username, password, url, oldCredentials, tabId) {
page.submittedCredentials.submitted = submitted;
page.submittedCredentials.username = username;
@ -189,17 +204,18 @@ page.setSubmittedCredentials = function(submitted, username, password, url, oldC
page.submittedCredentials.tabId = tabId;
};
page.clearSubmittedCredentials = function() {
page.clearSubmittedCredentials = async function() {
page.submitted = false;
page.submittedCredentials = {};
};
page.createTabEntry = function(tabId) {
page.tabs[tabId] = {
'stack': [],
'errorMessage': null,
'loginList': []
credentials: [],
errorMessage: null,
loginList: []
};
page.clearSubmittedCredentials();
};
@ -221,3 +237,61 @@ page.removePageInformationFromNotExistingTabs = async function() {
}
}
};
// Retrieves the credentials. Returns cached values when found.
// Page reload or tab switch clears the cache.
page.retrieveCredentials = async function(tab, args = []) {
const [ url, submitUrl ] = args;
if (page.tabs[tab.id] && page.tabs[tab.id].credentials.length > 0) {
return page.tabs[tab.id].credentials;
}
// Ignore duplicate requests
if (page.currentRequest.url === url && page.currentRequest.submitUrl === submitUrl) {
return [];
} else {
page.currentRequest.url = url;
page.currentRequest.submitUrl = submitUrl;
}
const credentials = await keepass.retrieveCredentials(tab, args);
page.tabs[tab.id].credentials = credentials;
return credentials;
};
page.getLoginId = async function(tab) {
// If there's only one credential available and loginId is not set
if (page.loginId < 0
&& page.tabs[tab.id]
&& page.tabs[tab.id].credentials.length === 1) {
return 0; // Index to the first credential
}
return page.loginId;
};
page.setLoginId = async function(tab, loginId) {
page.loginId = loginId;
};
page.getManualFill = async function(tab) {
return page.manualFill;
};
page.setManualFill = async function(tab, manualFill) {
page.manualFill = manualFill;
};
page.getSubmitted = async function(tab) {
// Do not return any credentials if the tab ID does not match.
if (tab.id !== page.submittedCredentials.tabId) {
return {};
}
return page.submittedCredentials;
};
page.setSubmitted = async function(tab, args = []) {
const [ submitted, username, password, url, oldCredentials ] = args;
page.setSubmittedCredentials(submitted, username, password, url, oldCredentials, tab.id);
};

File diff suppressed because one or more lines are too long

View file

@ -9,7 +9,7 @@ kpxcAutocomplete.input = undefined;
kpxcAutocomplete.shadowRoot = undefined;
kpxcAutocomplete.wrapper = undefined;
kpxcAutocomplete.create = function(input, showListInstantly = false, autoSubmit = false) {
kpxcAutocomplete.create = async function(input, showListInstantly = false, autoSubmit = false) {
if (input.readOnly) {
return;
}
@ -63,22 +63,20 @@ kpxcAutocomplete.showList = function(inputField) {
item.textContent += c.label;
const itemInput = kpxcUI.createElement('input', '', { 'type': 'hidden', 'value': c.value });
item.append(itemInput);
item.addEventListener('click', function(e) {
item.addEventListener('click', async function(e) {
if (!e.isTrusted) {
return;
}
// Save index for combination.loginId
const index = Array.prototype.indexOf.call(e.currentTarget.parentElement.childNodes, e.currentTarget);
browser.runtime.sendMessage({
action: 'page_set_login_id', args: index
});
await sendMessage('page_set_login_id', index);
inputField.value = this.getElementsByTagName('input')[0].value;
kpxcAutocomplete.fillPassword(inputField.value, index);
const usernameValue = this.getElementsByTagName('input')[0].value;
await kpxcAutocomplete.fillPassword(usernameValue, index, c.uuid);
kpxcAutocomplete.closeList();
inputField.focus();
document.body.removeChild(wrapper);
});
// These events prevent the double hover effect if both keyboard and mouse are used
@ -87,6 +85,7 @@ kpxcAutocomplete.showList = function(inputField) {
item.classList.add('kpxcAutocomplete-active');
kpxcAutocomplete.index = Array.from(div.childNodes).indexOf(item);
});
item.addEventListener('mouseout', function(e) {
item.classList.remove('kpxcAutocomplete-active');
});
@ -153,6 +152,7 @@ kpxcAutocomplete.getAllItems = function() {
if (!list) {
return [];
}
return list.getElementsByTagName('div');
};
@ -189,7 +189,7 @@ kpxcAutocomplete.keyPress = function(e) {
if (kpxcAutocomplete.index >= 0 && items && items[kpxcAutocomplete.index] !== undefined) {
e.preventDefault();
kpxcAutocomplete.input.value = e.currentTarget.value;
kpxcAutocomplete.input.value = kpxcAutocomplete.elements[kpxcAutocomplete.index].value;
kpxcAutocomplete.fillPassword(kpxcAutocomplete.input.value, kpxcAutocomplete.index);
kpxcAutocomplete.closeList();
}
@ -212,15 +212,12 @@ kpxcAutocomplete.keyPress = function(e) {
}
};
kpxcAutocomplete.fillPassword = async function(value, index) {
const fieldId = kpxcAutocomplete.input.getAttribute('data-kpxc-id');
kpxcFields.prepareId(fieldId);
const givenType = kpxcAutocomplete.input.type === 'password' ? 'password' : 'username';
const combination = await kpxcFields.getCombination(givenType, fieldId);
kpxcAutocomplete.fillPassword = async function(value, index, uuid) {
const combination = await kpxcFields.getCombination(kpxcAutocomplete.input);
combination.loginId = index;
kpxc.fillInCredentials(combination, givenType === 'password', false);
const manualFill = await sendMessage('page_get_manual_fill');
await kpxc.fillInCredentials(combination, value, uuid, manualFill === ManualFill.PASSWORD);
kpxcAutocomplete.input.setAttribute('fetched', true);
};
@ -232,12 +229,10 @@ kpxcAutocomplete.updatePosition = function(inputField, elem) {
const rect = inputField.getBoundingClientRect();
div.style.minWidth = Pixels(inputField.offsetWidth);
const bodyRect = document.body.getBoundingClientRect();
const bodyStyle = getComputedStyle(document.body);
if (bodyStyle.position.toLowerCase() === 'relative') {
div.style.top = Pixels(rect.top - bodyRect.top + document.scrollingElement.scrollTop + inputField.offsetHeight);
div.style.left = Pixels(rect.left - bodyRect.left + document.scrollingElement.scrollLeft);
if (kpxcUI.bodyStyle.position.toLowerCase() === 'relative') {
div.style.top = Pixels(rect.top - kpxcUI.bodyRect.top + document.scrollingElement.scrollTop + inputField.offsetHeight);
div.style.left = Pixels(rect.left - kpxcUI.bodyRect.left + document.scrollingElement.scrollLeft);
} else {
div.style.top = Pixels(rect.top + document.scrollingElement.scrollTop + inputField.offsetHeight);
div.style.left = Pixels(rect.left + document.scrollingElement.scrollLeft);

View file

@ -8,7 +8,7 @@ kpxcBanner.created = false;
kpxcBanner.credentials = {};
kpxcBanner.wrapper = undefined;
kpxcBanner.destroy = function() {
kpxcBanner.destroy = async function() {
kpxcBanner.created = false;
kpxcBanner.credentials = {};
@ -17,9 +17,7 @@ kpxcBanner.destroy = function() {
kpxcBanner.banner.removeChild(dialog);
}
browser.runtime.sendMessage({
action: 'remove_credentials_from_tab_information'
});
await sendMessage('remove_credentials_from_tab_information');
if (kpxcBanner.wrapper && window.parent.document.body.contains(kpxcBanner.wrapper)) {
window.parent.document.body.removeChild(kpxcBanner.wrapper);
@ -29,23 +27,20 @@ kpxcBanner.destroy = function() {
};
kpxcBanner.create = async function(credentials = {}) {
const connectedDatabase = await browser.runtime.sendMessage({
action: 'get_connected_database'
});
const connectedDatabase = await sendMessage('get_connected_database');
if (!kpxc.settings.showLoginNotifications || kpxcBanner.created || connectedDatabase.identifier === null) {
return;
}
// Check if database is closed
const state = await browser.runtime.sendMessage({ action: 'check_database_hash' });
const state = await sendMessage('check_database_hash');
if (state === '') {
//kpxcUI.createNotification('error', tr('rememberErrorDatabaseClosed'));
return;
}
// Don't show anything if the site is in the ignore
if (kpxc.siteIgnored(IGNORE_NORMAL)) {
if (await kpxc.siteIgnored(IGNORE_NORMAL)) {
return;
}
@ -141,64 +136,48 @@ kpxcBanner.create = async function(credentials = {}) {
};
kpxcBanner.saveNewCredentials = async function(credentials = {}) {
const result = await browser.runtime.sendMessage({
action: 'get_database_groups'
});
const result = await sendMessage('get_database_groups');
if (!result || !result.groups) {
console.log('Error: Empty result from get_database_groups');
return;
}
if (!result.defaultGroupAlwaysAsk) {
if (result.defaultGroup === '' || result.defaultGroup === DEFAULT_BROWSER_GROUP) {
// Default group is used
const args = [ credentials.username, credentials.password, credentials.url ];
const res = await sendMessage('add_credentials', args);
kpxcBanner.verifyResult(res);
return;
} else {
// A specified group is used
let gname = '';
let guuid = '';
if (!result.defaultGroupAlwaysAsk && (result.defaultGroup !== '' && result.defaultGroup !== DEFAULT_BROWSER_GROUP)) {
// Another group name has been specified
const [ gname, guuid ] = kpxcBanner.getDefaultGroup(result.groups[0].children, result.defaultGroup);
if (gname === '' && guuid === '') {
// Root group is used -> use the root path
if (result.defaultGroup.toLowerCase() === 'root') {
result.defaultGroup = '/';
}
// Create a new group
const newGroup = await browser.runtime.sendMessage({
action: 'create_new_group',
args: [ result.defaultGroup ]
});
if (newGroup.name && newGroup.uuid) {
const res = await browser.runtime.sendMessage({
action: 'add_credentials',
args: [ credentials.username, credentials.password, credentials.url, newGroup.name, newGroup.uuid ]
});
kpxcBanner.verifyResult(res);
gname = result.groups[0].name;
guuid = result.groups[0].uuid;
} else {
kpxcUI.createNotification('error', tr('rememberErrorCreatingNewGroup'));
[ gname, guuid ] = kpxcBanner.getDefaultGroup(result.groups[0].children, result.defaultGroup);
if (gname === '' && guuid === '') {
// Create a new group
const newGroup = await sendMessage('create_new_group', [ result.defaultGroup ]);
if (newGroup.name && newGroup.uuid) {
const res = await sendMessage('add_credentials', [ credentials.username, credentials.password, credentials.url, newGroup.name, newGroup.uuid ]);
kpxcBanner.verifyResult(res);
} else {
kpxcUI.createNotification('error', tr('rememberErrorCreatingNewGroup'));
}
return;
}
}
const res = await sendMessage('add_credentials', [ credentials.username, credentials.password, credentials.url, gname, guuid ]);
kpxcBanner.verifyResult(res);
return;
}
const res = await browser.runtime.sendMessage({
action: 'add_credentials',
args: [ credentials.username, credentials.password, credentials.url, gname, guuid ]
});
kpxcBanner.verifyResult(res);
return;
} else if ((result.groups === undefined || (result.groups.length > 0 && result.groups[0].children.length === 0))
|| (!result.defaultGroupAlwaysAsk && (result.defaultGroup === '' || result.defaultGroup === DEFAULT_BROWSER_GROUP))) {
// Only the Root group and no KeePassXC-Browser passwords -> save to default
// Or when default group is not set and defaultGroupAskAlways is disabled -> save to default
const args = [ credentials.username, credentials.password, credentials.url ];
// If root group is defined by the user, and there's no default browser group, save the credentials to the root group
if (result.groups !== undefined
&& result.groups.length > 0
&& result.groups[0].children.length === 0
&& (result.defaultGroup.toLowerCase() === 'root'
|| result.defaultGroup === '/')) {
args.push(result.groups[0].name, result.groups[0].uuid);
}
const res = await browser.runtime.sendMessage({
action: 'add_credentials',
args: args
});
kpxcBanner.verifyResult(res);
return;
}
const addChildren = function(group, parentElement, depth) {
@ -227,11 +206,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
return;
}
const res = await browser.runtime.sendMessage({
action: 'add_credentials',
args: [ credentials.username, credentials.password, credentials.url, group, groupUuid ]
});
const res = await sendMessage('add_credentials', [ credentials.username, credentials.password, credentials.url, group, groupUuid ]);
kpxcBanner.verifyResult(res);
});
@ -264,10 +239,7 @@ kpxcBanner.updateCredentials = async function(credentials = {}) {
credentials.username = credentials.list[0].login;
}
const res = await browser.runtime.sendMessage({
action: 'update_credentials',
args: [ credentials.list[0].uuid, credentials.username, credentials.password, credentials.url ]
});
const res = await sendMessage('update_credentials', [ credentials.list[0].uuid, credentials.username, credentials.password, credentials.url ]);
kpxcBanner.verifyResult(res);
} else {
await kpxcBanner.createCredentialDialog();
@ -310,10 +282,7 @@ kpxcBanner.updateCredentials = async function(credentials = {}) {
return;
}
const res = await browser.runtime.sendMessage({
action: 'update_credentials',
args: [ credentials.list[entryId].uuid, credentials.username, credentials.password, credentials.url ]
});
const res = await sendMessage('update_credentials', [ credentials.list[entryId].uuid, credentials.username, credentials.password, credentials.url ]);
kpxcBanner.verifyResult(res);
});
});
@ -369,9 +338,7 @@ kpxcBanner.createCredentialDialog = async function() {
kpxcBanner.shadowSelector('#kpxc-banner-btn-update').hidden = true;
kpxcBanner.shadowSelector('.kpxc-checkbox').disabled = true;
const connectedDatabase = await browser.runtime.sendMessage({
action: 'get_connected_database'
});
const connectedDatabase = await sendMessage('get_connected_database');
const databaseName = connectedDatabase.count > 0 ? connectedDatabase.identifier : '';
const dialog = kpxcUI.createElement('div', 'kpxc-banner-dialog');

View file

@ -8,15 +8,18 @@ kpxcDefine.selection = {
totp: null,
fields: []
};
kpxcDefine.eventFieldClick = null;
kpxcDefine.dialog = null;
kpxcDefine.startPosX = 0;
kpxcDefine.startPosY = 0;
kpxcDefine.diffX = 0;
kpxcDefine.diffY = 0;
kpxcDefine.eventFieldClick = null;
kpxcDefine.inputQueryPattern = 'input[type=\'text\'], input[type=\'email\'], input[type=\'password\'], input[type=\'tel\'], input[type=\'number\'], input[type=\'username\'], input:not([type])';
kpxcDefine.markedFields= [];
kpxcDefine.keyDown = null;
kpxcDefine.startPosX = 0;
kpxcDefine.startPosY = 0;
kpxcDefine.init = function() {
kpxcDefine.init = async function() {
const backdrop = kpxcUI.createElement('div', 'kpxcDefine-modal-backdrop', { 'id': 'kpxcDefine-backdrop' });
const chooser = kpxcUI.createElement('div', '', { 'id': 'kpxcDefine-fields' });
const description = kpxcUI.createElement('div', '', { 'id': 'kpxcDefine-description' });
@ -25,9 +28,6 @@ kpxcDefine.init = function() {
document.body.append(backdrop);
document.body.append(chooser);
kpxcFields.getAllFields();
kpxcFields.prepareVisibleFieldsWithID('select');
kpxcDefine.initDescription();
kpxcDefine.resetSelection();
kpxcDefine.prepareStep1();
@ -117,21 +117,24 @@ kpxcDefine.resetSelection = function() {
fields: []
};
kpxcDefine.markedFields = [];
const fields = $('#kpxcDefine-fields');
if (fields) {
fields.textContent = '';
}
};
kpxcDefine.isFieldSelected = function(kpxcId) {
if (kpxcId) {
kpxcDefine.isFieldSelected = function(field) {
if (kpxcDefine.markedFields.some(f => f === field)) {
return (
kpxcId === kpxcDefine.selection.username
|| kpxcId === kpxcDefine.selection.password
|| kpxcId === kpxcDefine.selection.totp
|| kpxcId in kpxcDefine.selection.fields
(kpxcDefine.selection.username && kpxcDefine.selection.username.originalElement === field)
|| (kpxcDefine.selection.password && kpxcDefine.selection.password.originalElement === field)
|| (kpxcDefine.selection.totp && kpxcDefine.selection.totp.originalElement === field)
|| kpxcDefine.selection.fields.includes(field)
);
}
return false;
};
@ -142,35 +145,37 @@ kpxcDefine.markAllUsernameFields = function(chooser) {
}
const field = elem || e.currentTarget;
kpxcDefine.selection.username = field.getAttribute('data-kpxc-id');
field.classList.add('kpxcDefine-fixed-username-field');
field.textContent = tr('username');
field.onclick = null;
kpxcDefine.selection.username = field;
kpxcDefine.markedFields.push(field.originalElement);
kpxcDefine.prepareStep2();
kpxcDefine.markAllPasswordFields('#kpxcDefine-fields');
};
kpxcDefine.markFields(chooser, kpxcFields.inputQueryPattern);
kpxcDefine.markFields(chooser, kpxcDefine.inputQueryPattern);
};
kpxcDefine.markAllPasswordFields = function(chooser, more = false) {
kpxcDefine.markAllPasswordFields = function(chooser) {
kpxcDefine.eventFieldClick = function(e, elem) {
if (!e.isTrusted) {
return;
}
const field = elem || e.currentTarget;
kpxcDefine.selection.password = field.getAttribute('data-kpxc-id');
field.classList.add('kpxcDefine-fixed-password-field');
field.textContent = tr('password');
field.onclick = null;
kpxcDefine.selection.password = field;
kpxcDefine.markedFields.push(field.originalElement);
kpxcDefine.prepareStep3();
kpxcDefine.markAllTOTPFields('#kpxcDefine-fields');
};
if (more) {
kpxcDefine.markFields(chooser, kpxcFields.inputQueryPattern);
} else {
kpxcDefine.markFields(chooser, 'input[type=\'password\']');
}
kpxcDefine.markFields(chooser, 'input[type=\'password\']');
};
kpxcDefine.markAllStringFields = function(chooser) {
@ -180,15 +185,19 @@ kpxcDefine.markAllStringFields = function(chooser) {
}
const field = elem || e.currentTarget;
const value = field.getAttribute('data-kpxc-id');
kpxcDefine.selection.fields[value] = true;
if (kpxcDefine.isFieldSelected(field.originalElement)) {
return;
}
kpxcDefine.selection.fields.push(field.originalElement);
kpxcDefine.markedFields.push(field.originalElement);
const count = Object.keys(kpxcDefine.selection.fields).length;
field.classList.add('kpxcDefine-fixed-string-field');
field.textContent = tr('defineStringField') + String(count);
field.textContent = tr('defineStringField') + String(kpxcDefine.selection.fields.length);
field.onclick = null;
};
kpxcDefine.markFields(chooser, kpxcFields.inputQueryPattern + ', select');
kpxcDefine.markFields(chooser, kpxcDefine.inputQueryPattern + ', select');
};
kpxcDefine.markAllTOTPFields = function(chooser) {
@ -198,14 +207,17 @@ kpxcDefine.markAllTOTPFields = function(chooser) {
}
const field = elem || e.currentTarget;
kpxcDefine.selection.totp = field.getAttribute('data-kpxc-id');
field.classList.add('kpxcDefine-fixed-totp-field');
field.textContent = 'TOTP';
field.onclick = null;
kpxcDefine.selection.totp = field;
kpxcDefine.markedFields.push(field.originalElement);
kpxcDefine.prepareStep4();
kpxcDefine.markAllStringFields('#kpxcDefine-fields');
};
kpxcDefine.markFields(chooser, kpxcFields.inputQueryPattern);
kpxcDefine.markFields(chooser, kpxcDefine.inputQueryPattern);
};
kpxcDefine.markFields = function(chooser, pattern) {
@ -214,39 +226,49 @@ kpxcDefine.markFields = function(chooser, pattern) {
const inputs = document.querySelectorAll(pattern);
for (const i of inputs) {
if (kpxcDefine.isFieldSelected(i.getAttribute('data-kpxc-id'))) {
if (kpxcDefine.isFieldSelected(i)) {
continue;
}
if (kpxcFields.isVisible(i)) {
const field = kpxcUI.createElement('div', 'kpxcDefine-fixed-field', { 'data-kpxc-id': i.getAttribute('data-kpxc-id') });
const rect = i.getBoundingClientRect();
field.style.top = Pixels(rect.top);
field.style.left = Pixels(rect.left);
field.style.width = Pixels(rect.width);
field.style.height = Pixels(rect.height);
field.textContent = String(index);
field.addEventListener('click', function(e) {
kpxcDefine.eventFieldClick(e);
});
field.addEventListener('mouseenter', function() {
field.classList.add('kpxcDefine-fixed-hover-field');
});
field.addEventListener('mouseleave', function() {
field.classList.remove('kpxcDefine-fixed-hover-field');
});
i.addEventListener('focus', function() {
field.classList.add('kpxcDefine-fixed-hover-field');
});
i.addEventListener('blur', function() {
field.classList.remove('kpxcDefine-fixed-hover-field');
});
const elem = $(chooser);
if (elem) {
elem.append(field);
firstInput = field;
++index;
}
if (!kpxcFields.isVisible(i)) {
continue;
}
const field = kpxcUI.createElement('div', 'kpxcDefine-fixed-field');
field.originalElement = i;
const rect = i.getBoundingClientRect();
field.style.top = Pixels(rect.top);
field.style.left = Pixels(rect.left);
field.style.width = Pixels(rect.width);
field.style.height = Pixels(rect.height);
field.textContent = String(index);
field.addEventListener('click', function(e) {
kpxcDefine.eventFieldClick(e);
});
field.addEventListener('mouseenter', function() {
field.classList.add('kpxcDefine-fixed-hover-field');
});
field.addEventListener('mouseleave', function() {
field.classList.remove('kpxcDefine-fixed-hover-field');
});
i.addEventListener('focus', function() {
field.classList.add('kpxcDefine-fixed-hover-field');
});
i.addEventListener('blur', function() {
field.classList.remove('kpxcDefine-fixed-hover-field');
});
const elem = $(chooser);
if (elem) {
elem.append(field);
firstInput = field;
++index;
}
}
@ -298,7 +320,7 @@ kpxcDefine.prepareStep4 = function() {
$('#kpxcDefine-help').style.marginBottom = '10px';
$('#kpxcDefine-help').textContent = tr('defineHelpText');
removeContent('div.kpxcDefine-fixed-field:not(.kpxcDefine-fixed-username-field):not(.kpxcDefine-fixed-password-field):not(.kpxcDefine-fixed-totp-field)');
removeContent('div.kpxcDefine-fixed-field:not(.kpxcDefine-fixed-username-field):not(.kpxcDefine-fixed-password-field):not(.kpxcDefine-fixed-totp-field):not(.kpxcDefine-fixed-string-field)');
$('#kpxcDefine-chooser-headline').textContent = tr('defineConfirmSelection');
kpxcDefine.dataStep = 4;
$('#kpxcDefine-btn-skip').style.display = 'none';
@ -330,10 +352,17 @@ kpxcDefine.again = function() {
};
kpxcDefine.more = function() {
if (kpxcDefine.dataStep === 2) {
if (kpxcDefine.dataStep === 1) {
kpxcDefine.prepareStep1();
} else if (kpxcDefine.dataStep === 2) {
kpxcDefine.prepareStep2();
kpxcDefine.markAllPasswordFields('#kpxcDefine-fields', true);
} else if (kpxcDefine.dataStep === 3) {
kpxcDefine.prepareStep3();
} else if (kpxcDefine.dataStep === 4) {
kpxcDefine.prepareStep4();
}
kpxcDefine.markFields('#kpxcDefine-fields', kpxcDefine.inputQueryPattern + ', select');
};
kpxcDefine.confirm = async function() {
@ -346,21 +375,20 @@ kpxcDefine.confirm = async function() {
}
if (kpxcDefine.selection.username) {
kpxcDefine.selection.username = kpxcFields.prepareId(kpxcDefine.selection.username);
kpxcDefine.selection.username = kpxcFields.getId(kpxcDefine.selection.username.originalElement);
}
if (kpxcDefine.selection.password) {
kpxcDefine.selection.password = kpxcFields.prepareId(kpxcDefine.selection.password);
kpxcDefine.selection.password = kpxcFields.getId(kpxcDefine.selection.password.originalElement);
}
if (kpxcDefine.selection.totp) {
kpxcDefine.selection.totp = kpxcFields.prepareId(kpxcDefine.selection.totp);
kpxcDefine.selection.totp = kpxcFields.getId(kpxcDefine.selection.totp.originalElement);
}
const fieldIds = [];
const fieldKeys = Object.keys(kpxcDefine.selection.fields);
for (const i of fieldKeys) {
fieldIds.push(kpxcFields.prepareId(i));
for (const i of kpxcDefine.selection.fields) {
fieldIds.push(kpxcFields.getId(i));
}
const location = kpxc.getDocumentLocation();
@ -371,11 +399,7 @@ kpxcDefine.confirm = async function() {
fields: fieldIds
};
await browser.runtime.sendMessage({
action: 'save_settings',
args: [ kpxc.settings ]
});
await sendMessage('save_settings', kpxc.settings);
kpxcDefine.close();
};
@ -387,19 +411,13 @@ kpxcDefine.discard = async function() {
const location = kpxc.getDocumentLocation();
delete kpxc.settings['defined-custom-fields'][location];
await browser.runtime.sendMessage({
action: 'save_settings',
args: [ kpxc.settings ]
});
await browser.runtime.sendMessage({
action: 'load_settings'
});
await sendMessage('save_settings', kpxc.settings);
await sendMessage('load_settings');
$('div.alreadySelected').remove();
};
// Handle the keyboard events
// Handle keyboard events
kpxcDefine.keyDown = function(e) {
if (!e.isTrusted) {
return;
@ -413,7 +431,7 @@ kpxcDefine.keyDown = function(e) {
// Select input field by number
e.preventDefault();
const index = e.keyCode - 48;
const inputFields = document.querySelectorAll('div.kpxcDefine-fixed-field:not(.kpxcDefine-fixed-username-field):not(.kpxcDefine-fixed-password-field)');
const inputFields = document.querySelectorAll('div.kpxcDefine-fixed-field:not(.kpxcDefine-fixed-username-field):not(.kpxcDefine-fixed-password-field):not(.kpxcDefine-fixed-totp-field)');
if (inputFields.length >= index) {
kpxcDefine.eventFieldClick(e, inputFields[index - 1]);

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@
const kpxcPasswordIcons = {};
kpxcPasswordIcons.icons = [];
kpxcPasswordIcons.newIcon = function(useIcons, field, inputs, pos, databaseState = DatabaseState.DISCONNECTED) {
kpxcPasswordIcons.icons.push(new PasswordIcon(useIcons, field, inputs, pos, databaseState));
kpxcPasswordIcons.newIcon = function(field, databaseState = DatabaseState.DISCONNECTED) {
kpxcPasswordIcons.icons.push(new PasswordIcon(field, databaseState));
};
kpxcPasswordIcons.switchIcon = function(state) {
@ -15,57 +15,38 @@ kpxcPasswordIcons.deleteHiddenIcons = function() {
kpxcUI.deleteHiddenIcons(kpxcPasswordIcons.icons, 'kpxc-password-field');
};
kpxcPasswordIcons.isValid = function(field) {
if (!field
|| field.readOnly
|| field.offsetWidth < MINIMUM_INPUT_FIELD_WIDTH
|| kpxcIcons.hasIcon(field)
|| !kpxcFields.isVisible(field)) {
return false;
}
return true;
};
class PasswordIcon extends Icon {
constructor(useIcons, field, inputs, pos, databaseState) {
constructor(field, databaseState = DatabaseState.DISCONNECTED) {
super();
this.useIcons = useIcons;
this.databaseState = databaseState;
this.nextFieldExists = false;
if (this.initField(field, inputs, pos)) {
kpxcUI.monitorIconPosition(this);
}
this.initField(field);
kpxcUI.monitorIconPosition(this);
}
}
PasswordIcon.prototype.initField = function(field, inputs, pos) {
if (!field
|| field.readOnly
|| field.offsetWidth < MINIMUM_INPUT_FIELD_WIDTH) {
return false;
}
if (field.getAttribute('kpxc-password-field')
|| (field.hasAttribute('kpxc-defined') && field.getAttribute('kpxc-defined') !== 'password')) {
return false;
}
field.setAttribute('kpxc-password-field', true);
if (this.useIcons) {
// Observer the visibility
if (this.observer) {
this.observer.observe(field);
}
this.createIcon(field);
PasswordIcon.prototype.initField = function(field) {
// Observer the visibility
if (this.observer) {
this.observer.observe(field);
}
this.createIcon(field);
this.inputField = field;
let found = false;
if (inputs) {
for (let i = pos + 1; i < inputs.length; i++) {
if (inputs[i] && inputs[i].getLowerCaseAttribute('type') === 'password') {
field.setAttribute('kpxc-pwgen-next-field-id', inputs[i].getAttribute('data-kpxc-id'));
field.setAttribute('kpxc-pwgen-next-is-password-field', (i === 0));
found = true;
break;
}
}
}
field.setAttribute('kpxc-pwgen-next-field-exists', found);
return true;
};
PasswordIcon.prototype.createIcon = function(field) {
@ -81,6 +62,7 @@ PasswordIcon.prototype.createIcon = function(field) {
'offset': offset,
'kpxc-pwgen-field-id': field.getAttribute('data-kpxc-id')
});
icon.style.zIndex = '10000000';
icon.style.width = Pixels(size);
icon.style.height = Pixels(size);
@ -116,6 +98,8 @@ PasswordIcon.prototype.createIcon = function(field) {
const kpxcPasswordDialog = {};
kpxcPasswordDialog.created = false;
kpxcPasswordDialog.icon = null;
kpxcPasswordDialog.input = null;
kpxcPasswordDialog.nextField = null;
kpxcPasswordDialog.selected = null;
kpxcPasswordDialog.startPosX = 0;
kpxcPasswordDialog.startPosY = 0;
@ -124,17 +108,6 @@ kpxcPasswordDialog.diffY = 0;
kpxcPasswordDialog.dialog = null;
kpxcPasswordDialog.titleBar = null;
kpxcPasswordDialog.removeIcon = function(field) {
if (field.getAttribute('kpxc-password-field')) {
const pwgenIcons = document.querySelectorAll('.kpxc-pwgen-icon');
for (const i of pwgenIcons) {
if (i.getAttribute('kpxc-pwgen-field-id') === field.getAttribute('data-kpxc-id')) {
document.body.removeChild(i);
}
}
}
};
kpxcPasswordDialog.createDialog = function() {
if (kpxcPasswordDialog.created) {
// If database is open again, generate a new password right away
@ -245,6 +218,15 @@ kpxcPasswordDialog.showDialog = function(field, icon) {
return;
}
kpxcPasswordDialog.input = field;
// Save next password field if found
if (kpxc.inputs.length > 0) {
const index = kpxc.inputs.indexOf(field);
const nextField = kpxc.inputs[index+1];
kpxcPasswordDialog.nextField = (nextField && nextField.getLowerCaseAttribute('type') === 'password') ? nextField : undefined;
}
kpxcPasswordDialog.createDialog();
initColorTheme(kpxcPasswordDialog.dialog);
kpxcPasswordDialog.openDialog();
@ -259,10 +241,6 @@ kpxcPasswordDialog.showDialog = function(field, icon) {
kpxcPasswordDialog.dialog.style.top = Pixels(rect.top + rect.height);
kpxcPasswordDialog.dialog.style.left = Pixels(rect.left);
}
kpxcPasswordDialog.dialog.setAttribute('kpxc-pwgen-field-id', field.getAttribute('data-kpxc-id'));
kpxcPasswordDialog.dialog.setAttribute('kpxc-pwgen-next-field-id', field.getAttribute('kpxc-pwgen-next-field-id'));
kpxcPasswordDialog.dialog.setAttribute('kpxc-pwgen-next-is-password-field', field.getAttribute('kpxc-pwgen-next-is-password-field'));
}
};
@ -275,9 +253,7 @@ kpxcPasswordDialog.generate = async function(e) {
e.preventDefault();
}
callbackGeneratedPassword(await browser.runtime.sendMessage({
action: 'generate_password'
}));
callbackGeneratedPassword(await sendMessage('generate_password'));
};
kpxcPasswordDialog.copy = function(e) {
@ -290,35 +266,27 @@ kpxcPasswordDialog.copy = function(e) {
};
kpxcPasswordDialog.fill = function(e) {
if (!e.isTrusted) {
if (!e.isTrusted || !kpxcPasswordDialog.input) {
return;
}
e.preventDefault();
// Use the active input field
const field = _f(kpxcPasswordDialog.dialog.getAttribute('kpxc-pwgen-field-id'));
if (field) {
const password = kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input');
if (field.getAttribute('maxlength')) {
if (password.value.length > field.getAttribute('maxlength')) {
const message = tr('passwordGeneratorErrorTooLong') + '\r\n'
+ tr('passwordGeneratorErrorTooLongCut') + '\r\n' + tr('passwordGeneratorErrorTooLongRemember');
message.style.whiteSpace = 'pre';
browser.runtime.sendMessage({
action: 'show_notification',
args: [ message ]
});
return;
}
const password = kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input');
if (kpxcPasswordDialog.input.getAttribute('maxlength')) {
if (password.value.length > kpxcPasswordDialog.input.getAttribute('maxlength')) {
const message = tr('passwordGeneratorErrorTooLong') + '\r\n'
+ tr('passwordGeneratorErrorTooLongCut') + '\r\n' + tr('passwordGeneratorErrorTooLongRemember');
message.style.whiteSpace = 'pre';
sendMessage('show_notification' [ message ]);
return;
}
}
field.value = password.value;
const nextFieldId = field.getAttribute('kpxc-pwgen-next-field-id');
const nextField = $('input[data-kpxc-id=\'' + nextFieldId + '\']');
if (nextField) {
nextField.value = password.value;
}
kpxcPasswordDialog.input.value = password.value;
if (kpxcPasswordDialog.nextField) {
kpxcPasswordDialog.nextField.value = password.value;
}
};

View file

@ -3,6 +3,17 @@
const ignoreRegex = /(zip|postal).*code/i;
const ignoredTypes = [ 'email', 'password', 'username' ];
const acceptedOTPFields = [
'2fa',
'auth',
'challenge',
'code',
'mfa',
'otp',
'token',
'twofactor'
];
var kpxcTOTPIcons = {};
kpxcTOTPIcons.icons = [];
@ -18,23 +29,22 @@ kpxcTOTPIcons.deleteHiddenIcons = function() {
kpxcUI.deleteHiddenIcons(kpxcTOTPIcons.icons, 'kpxc-totp-field');
};
// Quick check for a valid TOTP field
kpxcTOTPIcons.isAcceptedTOTPField = function(field) {
const id = field.getLowerCaseAttribute('id');
const name = field.getLowerCaseAttribute('name');
const autocomplete = field.getLowerCaseAttribute('autocomplete');
class TOTPFieldIcon extends Icon {
constructor(field, databaseState = DatabaseState.DISCONNECTED, forced = false) {
super();
this.icon = null;
this.inputField = null;
this.databaseState = databaseState;
if (this.initField(field, forced)) {
kpxcUI.monitorIconPosition(this);
}
if (autocomplete === 'one-time-code' || acceptedOTPFields.some(f => (id && id.includes(f)) || (name && name.includes(f)))) {
return true;
}
}
TOTPFieldIcon.prototype.initField = function(field, forced) {
if (!field) {
return;
return false;
};
kpxcTOTPIcons.isValid = function(field, forced) {
if (!field || !kpxcTOTPIcons.isAcceptedTOTPField(field)) {
return false;
}
if (!forced) {
@ -56,8 +66,22 @@ TOTPFieldIcon.prototype.initField = function(field, forced) {
}
}
field.setAttribute('kpxc-totp-field', 'true');
return true;
};
class TOTPFieldIcon extends Icon {
constructor(field, databaseState = DatabaseState.DISCONNECTED, forced = false) {
super();
this.icon = null;
this.inputField = null;
this.databaseState = databaseState;
this.initField(field, forced);
kpxcUI.monitorIconPosition(this);
}
}
TOTPFieldIcon.prototype.initField = function(field, forced) {
// Observer the visibility
if (this.observer) {
this.observer.observe(field);
@ -65,7 +89,6 @@ TOTPFieldIcon.prototype.initField = function(field, forced) {
this.createIcon(field);
this.inputField = field;
return false;
};
TOTPFieldIcon.prototype.createIcon = function(field) {
@ -97,7 +120,7 @@ TOTPFieldIcon.prototype.createIcon = function(field) {
e.preventDefault();
await kpxc.receiveCredentialsIfNecessary();
kpxc.fillInFromActiveElementTOTPOnly(field);
kpxc.fillFromTOTP(field);
});
kpxcUI.setIconPosition(icon, field);

View file

@ -2,6 +2,12 @@
const MINIMUM_INPUT_FIELD_WIDTH = 60;
const DatabaseState = {
DISCONNECTED: 0,
LOCKED: 1,
UNLOCKED: 2
};
// jQuery style wrapper for querySelector()
const $ = function(elem) {
return document.querySelector(elem);
@ -44,6 +50,8 @@ class Icon {
}
const kpxcUI = {};
kpxcUI.bodyRect = document.body.getBoundingClientRect();
kpxcUI.bodyStyle = getComputedStyle(document.body);
kpxcUI.mouseDown = false;
// Wrapper for creating elements
@ -95,14 +103,12 @@ kpxcUI.calculateIconOffset = function(field, size) {
kpxcUI.setIconPosition = function(icon, field) {
const rect = field.getBoundingClientRect();
const bodyRect = document.body.getBoundingClientRect();
const bodyStyle = getComputedStyle(document.body);
const size = (document.dir !== 'rtl') ? Number(icon.getAttribute('size')) : 0;
const offset = kpxcUI.calculateIconOffset(field, size);
if (bodyStyle.position.toLowerCase() === 'relative') {
icon.style.top = Pixels(rect.top - bodyRect.top + document.scrollingElement.scrollTop + offset + 1);
icon.style.left = Pixels(rect.left - bodyRect.left + document.scrollingElement.scrollLeft + field.offsetWidth - size - offset);
if (kpxcUI.bodyStyle.position.toLowerCase() === 'relative') {
icon.style.top = Pixels(rect.top - kpxcUI.bodyRect.top + document.scrollingElement.scrollTop + offset + 1);
icon.style.left = Pixels(rect.left - kpxcUI.bodyRect.left + document.scrollingElement.scrollLeft + field.offsetWidth - size - offset);
} else {
icon.style.top = Pixels(rect.top + document.scrollingElement.scrollTop + offset + 1);
icon.style.left = Pixels(rect.left + document.scrollingElement.scrollLeft + field.offsetWidth - size - offset);
@ -110,11 +116,21 @@ kpxcUI.setIconPosition = function(icon, field) {
};
kpxcUI.deleteHiddenIcons = function(iconList, attr) {
const deletedIcons = [];
for (const icon of iconList) {
if (icon.inputField && !kpxcFields.isVisible(icon.inputField)) {
const index = iconList.indexOf(icon);
icon.removeIcon(attr);
iconList.splice(index, 1);
deletedIcons.push(icon.inputField);
}
}
// Remove the same icons from kpxcIcons.icons array
for (const input of deletedIcons) {
const index = kpxcIcons.icons.findIndex(e => e.field === input);
if (index >= 0) {
kpxcIcons.icons.splice(index, 1);
}
}
};
@ -139,7 +155,7 @@ kpxcUI.updateFromIntersectionObserver = function(iconClass, entries) {
// Wait for possible DOM animations
setTimeout(() => {
kpxcUI.setIconPosition(iconClass.icon, entry.target);
}, 500);
}, 400);
}
}
};

View file

@ -2,6 +2,7 @@
const kpxcUsernameIcons = {};
kpxcUsernameIcons.icons = [];
kpxcUsernameIcons.detectedFields = [];
kpxcUsernameIcons.newIcon = function(field, databaseState = DatabaseState.DISCONNECTED) {
kpxcUsernameIcons.icons.push(new UsernameFieldIcon(field, databaseState));
@ -15,6 +16,18 @@ kpxcUsernameIcons.deleteHiddenIcons = function() {
kpxcUI.deleteHiddenIcons(kpxcUsernameIcons.icons, 'kpxc-username-field');
};
kpxcUsernameIcons.isValid = function(field) {
if (!field
|| field.offsetWidth < MINIMUM_INPUT_FIELD_WIDTH
|| field.readOnly
|| kpxcIcons.hasIcon(field)
|| !kpxcFields.isVisible(field)) {
return false;
}
return true;
};
class UsernameFieldIcon extends Icon {
constructor(field, databaseState = DatabaseState.DISCONNECTED) {
@ -23,14 +36,15 @@ class UsernameFieldIcon extends Icon {
this.icon = null;
this.inputField = null;
if (this.initField(field)) {
kpxcUI.monitorIconPosition(this);
}
this.initField(field);
kpxcUI.monitorIconPosition(this);
}
switchIcon(state) {
if (!this.icon) {
return;
} else {
this.observer.disconnect();
}
this.icon.classList.remove('lock', 'lock-moz', 'unlock', 'unlock-moz', 'disconnected', 'disconnected-moz');
@ -40,18 +54,6 @@ class UsernameFieldIcon extends Icon {
}
UsernameFieldIcon.prototype.initField = function(field) {
if (!field
|| field.offsetWidth < MINIMUM_INPUT_FIELD_WIDTH
|| field.readOnly
|| field.getAttribute('kpxc-username-field') === 'true'
|| field.getAttribute('kpxc-totp-field') === 'true'
|| (field.hasAttribute('kpxc-defined') && field.getAttribute('kpxc-defined') !== 'username')
|| !kpxcFields.isVisible(field)) {
return false;
}
field.setAttribute('kpxc-username-field', 'true');
// Observer the visibility
if (this.observer) {
this.observer.observe(field);
@ -59,15 +61,9 @@ UsernameFieldIcon.prototype.initField = function(field) {
this.createIcon(field);
this.inputField = field;
return true;
};
UsernameFieldIcon.prototype.createIcon = function(target) {
// Remove any existing password generator icons from the input field
if (target.getAttribute('kpxc-password-field')) {
kpxcPasswordDialog.removeIcon(target);
}
const field = target;
const className = getIconClassName(this.databaseState);
@ -124,20 +120,18 @@ const iconClicked = async function(field, icon) {
return;
}
const connected = await browser.runtime.sendMessage({ action: 'is_connected' });
const connected = await sendMessage('is_connected');
if (!connected) {
kpxcUI.createNotification('error', tr('errorNotConnected'));
return;
}
const databaseHash = await browser.runtime.sendMessage({ action: 'check_database_hash' });
const databaseHash = await sendMessage('check_database_hash');
if (databaseHash === '') {
// Triggers database unlock
_called.manualFillRequested = ManualFill.BOTH;
await browser.runtime.sendMessage({
action: 'get_database_hash',
args: [ false, true ] // Set triggerUnlock to true
});
await sendMessage('page_set_manual_fill', ManualFill.BOTH);
await sendMessage('get_database_hash', [ false, true ]); // Set triggerUnlock to true
field.focus();
}
if (icon.className.includes('unlock')) {
@ -165,11 +159,6 @@ const getIconText = function(state) {
};
const fillCredentials = async function(field) {
const fieldId = field.getAttribute('data-kpxc-id');
kpxcFields.prepareId(fieldId);
const givenType = field.type === 'password' ? 'password' : 'username';
const combination = await kpxcFields.getCombination(givenType, fieldId);
kpxc.fillInCredentials(combination, givenType === 'password', false);
const combination = await kpxcFields.getCombination(field);
kpxc.fillFromUsernameIcon(combination);
};

View file

@ -32,6 +32,12 @@ const AssociatedAction = {
CANCELED: 3
};
const ManualFill = {
NONE: 0,
PASSWORD: 1,
BOTH: 2
};
/**
* Transforms a valid match pattern into a regular expression
* which matches all URLs included by that pattern.

View file

@ -1,8 +1,8 @@
{
"manifest_version": 2,
"name": "KeePassXC-Browser",
"version": "1.6.6",
"version_name": "1.6.6",
"version": "1.7.0",
"version_name": "1.7.0",
"description": "__MSG_extensionDescription__",
"author": "KeePassXC Team",
"icons": {

View file

@ -48,8 +48,6 @@ options.saveSetting = async function(name) {
await browser.runtime.sendMessage({
action: 'load_settings'
});
return Promise.resolve();
};
options.saveSettings = async function() {
@ -66,8 +64,6 @@ options.saveKeyRing = async function() {
await browser.runtime.sendMessage({
action: 'load_keyring'
});
return Promise.resolve();
};
options.initGeneralSettings = function() {
@ -175,10 +171,8 @@ options.initGeneralSettings = function() {
$('#defaultGroupButton').click(async function() {
const value = $('#defaultGroup').val();
if (value.length > 0) {
options.settings['defaultGroup'] = value;
await options.saveSettings();
}
options.settings['defaultGroup'] = (value.length > 0 ? value : '');
await options.saveSettings();
});
$('#defaultGroupButtonReset').click(async function() {

View file

@ -93,7 +93,7 @@ $(async () => {
$('#lock-database-button').click(async () => {
statusResponse(await browser.runtime.sendMessage({
action: 'lock-database'
action: 'lock_database'
}));
});

View file

@ -33,7 +33,7 @@ $(async () => {
$('#lock-database-button').click(function() {
browser.runtime.sendMessage({
action: 'lock-database'
action: 'lock_database'
}).then(statusResponse);
});

View file

@ -8,15 +8,18 @@ $(async () => {
if (tabs.length === 0) {
return; // For example: only the background devtools or a popup are opened
}
const tab = tabs[0];
const tab = tabs[0];
const logins = global.page.tabs[tab.id].loginList;
const ll = document.getElementById('login-list');
for (let i = 0; i < logins.length; i++) {
const uuid = logins[i].uuid;
const a = document.createElement('a');
a.textContent = logins[i];
a.textContent = logins[i].text;
a.setAttribute('class', 'list-group-item');
a.setAttribute('id', '' + i);
a.addEventListener('click', (e) => {
if (!e.isTrusted) {
return;
@ -25,10 +28,13 @@ $(async () => {
const id = e.target.id;
browser.tabs.sendMessage(tab.id, {
action: 'fill_user_pass_with_specific_login',
id: Number(id)
id: Number(id),
uuid: uuid
});
close();
});
ll.appendChild(a);
}
@ -50,12 +56,13 @@ $(async () => {
}
}
});
filter.focus();
}
$('#lock-database-button').click((e) => {
browser.runtime.sendMessage({
action: 'lock-database'
action: 'lock_database'
});
$('#credentialsList').hide();
$('#database-not-opened').show();

View file

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title data-i18n="popupTitle"></title>
<meta charset="UTF-8">
<link rel="stylesheet" href="../css/colors.css" />
<link rel="stylesheet" href="popup.css" />
<link rel="stylesheet" href="../bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="../fonts/fork-awesome.min.css" />
<script src="../browser-polyfill.min.js"></script>
<script src="../global.js"></script>
<script src="../bootstrap/jquery-3.4.1.min.js"></script>
<script src="../bootstrap/bootstrap.min.js"></script>
<script src="popup_functions.js"></script>
<script defer src="../translate.js"></script>
</head>
<body>
<div class="container">
<div id="settings" class="settings">
<button id="btn-options" class="btn btn-sm btn-success"><i class="fa fa-cog" aria-hidden="true"></i> <span data-i18n="popupSettingsText"></span></button>
<button id="btn-choose-credential-fields" class="btn btn-sm btn-warning"><i class="fa fa-list-alt" aria-hidden="true"></i> <span data-i18n="popupChooseCredentialsText"></span></button>
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase"><i class="fa fa-lock" aria-hidden="true"></i></button>
<div id="update-available" class="alert alert-danger">
<span data-i18n="popupUpdateAvailable"></span>
<br />
<a target="_blank" class="alert-link" href="https://keepassxc.org/download"><span data-i18n="popupDownloadNewVersion"></span></a>.
</div>
</div>
<div>
<p>
<span data-i18n="popupMultiplePasswordFields"></span>
"<code><span data-i18n="contextMenuFillUsernameAndPassword"></span></code>", "<code><span data-i18n="contextMenuFillPassword"></span></code>"
</p>
</div>
</div>
</body>
</html>