diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js index c98faff..e7183bd 100755 --- a/keepassxc-browser/background/keepass.js +++ b/keepassxc-browser/background/keepass.js @@ -326,7 +326,7 @@ keepass.generatePassword = async function(tab) { return []; } - let passwords = []; + let password; const kpAction = kpActions.GENERATE_PASSWORD; const [ nonce, incrementedNonce ] = keepass.getNonces(); @@ -349,22 +349,18 @@ keepass.generatePassword = async function(tab) { keepass.setcurrentKeePassXCVersion(parsed.version); if (keepass.verifyResponse(parsed, incrementedNonce)) { - if (parsed.entries) { - passwords = parsed.entries; - keepass.updateLastUsed(keepass.databaseHash); - } else { - console.log('No entries returned. Is KeePassXC up-to-date?'); - } + password = parsed.entries ?? parsed.password; + keepass.updateLastUsed(keepass.databaseHash); } else { console.log('GeneratePassword rejected'); } - return passwords; + return password; } else if (response.error && response.errorCode) { keepass.handleError(tab, response.errorCode, response.error); } - return passwords; + return password; } catch (err) { console.log('generatePassword failed: ', err); return []; @@ -1318,6 +1314,12 @@ keepass.compareVersion = function(minimum, current, canBeEqual = true) { return false; } + // Handle snapshot builds as stable version + const snapshot = '-snapshot'; + if (current.endsWith(snapshot)) { + current = current.slice(0, -snapshot.length); + } + const min = minimum.split('.', 3).map(s => s.padStart(4, '0')).join('.'); const cur = current.split('.', 3).map(s => s.padStart(4, '0')).join('.'); return (canBeEqual ? (min <= cur) : (min < cur)); diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index 9482dfb..8b1eaf4 100755 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -187,7 +187,10 @@ page.switchTab = async function(tab) { browser.contextMenus.update('fill_attribute', { visible: false }); // Clears all logins from other tabs after a timeout - clearTimeout(page.clearCredentialsTimeout); + if (page.clearCredentialsTimeout) { + clearTimeout(page.clearCredentialsTimeout); + } + page.clearCredentialsTimeout = setTimeout(() => { for (const pageTabId of Object.keys(page.tabs)) { if (tab.id !== Number(pageTabId)) { diff --git a/keepassxc-browser/content/pwgen.js b/keepassxc-browser/content/pwgen.js index 2f444b3..a5dc8fe 100644 --- a/keepassxc-browser/content/pwgen.js +++ b/keepassxc-browser/content/pwgen.js @@ -70,12 +70,18 @@ PasswordIcon.prototype.createIcon = function(field) { icon.style.filter = 'saturate(0%)'; } - icon.addEventListener('click', function(e) { + icon.addEventListener('click', async function(e) { if (!e.isTrusted) { return; } e.stopPropagation(); + + if (await useKeePassXCPasswordGenerator()) { + kpxcPasswordDialog.generate(null, field); + return; + } + kpxcPasswordDialog.showDialog(field, icon); }); @@ -207,7 +213,12 @@ kpxcPasswordDialog.openDialog = function() { } }; -kpxcPasswordDialog.trigger = function() { +kpxcPasswordDialog.trigger = async function() { + if (await useKeePassXCPasswordGenerator()) { + kpxcPasswordDialog.generate(null, document.activeElement); + return; + } + kpxcPasswordDialog.showDialog(document.activeElement, kpxcPasswordDialog.icon); }; @@ -244,7 +255,7 @@ kpxcPasswordDialog.showDialog = function(field, icon) { } }; -kpxcPasswordDialog.generate = async function(e) { +kpxcPasswordDialog.generate = async function(e, field) { // This function can be also called from non-events if (e) { if (!e.isTrusted) { @@ -253,6 +264,11 @@ kpxcPasswordDialog.generate = async function(e) { e.preventDefault(); } + if (await useKeePassXCPasswordGenerator()) { + kpxcPasswordDialog.newFill(field, await sendMessage('generate_password')); + return; + } + callbackGeneratedPassword(await sendMessage('generate_password')); }; @@ -278,7 +294,7 @@ kpxcPasswordDialog.fill = function(e) { const message = tr('passwordGeneratorErrorTooLong') + '\r\n' + tr('passwordGeneratorErrorTooLongCut') + '\r\n' + tr('passwordGeneratorErrorTooLongRemember'); message.style.whiteSpace = 'pre'; - sendMessage('show_notification', [ message ]); + kpxcUI.createNotification('error', message); return; } } @@ -298,6 +314,32 @@ kpxcPasswordDialog.fill = function(e) { } }; +// New way to fill the password +kpxcPasswordDialog.newFill = function(elem, password) { + if (!elem || !password) { + return; + } + + if (password.length === 0) { + kpxcUI.createNotification('error', tr('usernameLockedFieldText')); + return; + } + + if (elem.getAttribute('maxlength')) { + if (password.length > elem.getAttribute('maxlength')) { + const message = tr('passwordGeneratorErrorTooLong') + '\r\n' + + tr('passwordGeneratorErrorTooLongCut') + '\r\n' + tr('passwordGeneratorErrorTooLongRemember'); + message.style.whiteSpace = 'pre'; + kpxcUI.createNotification('error', message); + return; + } + } + + elem.value = password; + elem.dispatchEvent(new Event('input', { bubbles: true })); + elem.dispatchEvent(new Event('change', { bubbles: true })); +}; + kpxcPasswordDialog.copyPasswordToClipboard = function() { kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input').select(); try { @@ -308,8 +350,8 @@ kpxcPasswordDialog.copyPasswordToClipboard = function() { return false; }; -const callbackGeneratedPassword = function(entries) { - if (entries && entries.length >= 1) { +const callbackGeneratedPassword = function(passwords) { + if (passwords && passwords.length >= 1) { const errorMessage = kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-error'); if (errorMessage) { enableButtons(); @@ -319,7 +361,7 @@ const callbackGeneratedPassword = function(entries) { errorMessage.remove(); } - kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input').value = entries[0].password; + kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input').value = passwords[0].password; } else { if (kpxcPasswordDialog.shadowSelectorAll('div#kpxc-pwgen-error').length === 0) { const input = kpxcPasswordDialog.shadowSelector('.kpxc-pwgen-input'); @@ -346,3 +388,16 @@ const disableButtons = function() { kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-btn-copy').style.display = 'none'; kpxcPasswordDialog.shadowSelector('#kpxc-pwgen-btn-fill').style.display = 'none'; }; + +const useKeePassXCPasswordGenerator = async function() { + const response = await browser.runtime.sendMessage({ + action: 'get_keepassxc_versions' + }); + + const result = await browser.runtime.sendMessage({ + action: 'compare_version', + args: [ '2.7.0', response.current ] + }); + + return result; +}; diff --git a/keepassxc-protocol.md b/keepassxc-protocol.md index be529e5..5861a38 100644 --- a/keepassxc-protocol.md +++ b/keepassxc-protocol.md @@ -169,6 +169,16 @@ Response message data (success, decrypted): } ``` +Response message data (success, decrypted, KeePassXC 2.7.0 and later): +```json +{ + "version": "2.7.0", + "password": "thePassword", + "success": "true", + "nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q" +} +``` + ### get-logins Unencrypted message: ```json