diff options
-rw-r--r-- | keepassxc-browser/_locales/en/messages.json | 16 | ||||
-rwxr-xr-x | keepassxc-browser/background/event.js | 11 | ||||
-rwxr-xr-x | keepassxc-browser/background/page.js | 6 | ||||
-rwxr-xr-x | keepassxc-browser/content/keepassxc-browser.js | 88 | ||||
-rw-r--r-- | keepassxc-browser/content/pwgen.js | 15 | ||||
-rw-r--r-- | keepassxc-browser/content/totp-field.js | 79 | ||||
-rw-r--r-- | keepassxc-browser/content/ui.js | 12 | ||||
-rw-r--r-- | keepassxc-browser/content/username-field.js | 25 | ||||
-rw-r--r-- | keepassxc-browser/css/totp.css | 14 | ||||
-rw-r--r-- | keepassxc-browser/icons/otp.svg | 216 | ||||
-rwxr-xr-x | keepassxc-browser/manifest.json | 5 | ||||
-rw-r--r-- | keepassxc-browser/options/options.html | 31 |
12 files changed, 473 insertions, 45 deletions
diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json index 99935c5..df44b7c 100644 --- a/keepassxc-browser/_locales/en/messages.json +++ b/keepassxc-browser/_locales/en/messages.json @@ -167,6 +167,14 @@ "message": "Username field icon", "description": "Username field icon text." }, + "totpFieldText": { + "message": "Fill TOTP from KeePassXC", + "description": "OTP field icon hover text." + }, + "totpFieldIcon": { + "message": "TOTP field icon", + "description": "OTP field icon text." + }, "defineDismiss": { "message": "Dismiss", "description": "Dismiss button text when choosing custom login fields." @@ -567,6 +575,10 @@ "message": "Activate username field icons.", "description": "Activate username field icons textbox text." }, + "optionsCheckboxOTPIcons": { + "message": "Activate 2FA/OTP field icons.", + "description": "Activate OTP field icons textbox text." + }, "optionsCheckboxAutoRetrieveCredentials": { "message": "Automatically retrieve credentials.", "description": "Automatically retrieve credentials checkbox text." @@ -647,6 +659,10 @@ "message": "Adds an icon to username fields for filling credentials with a single mouse click.", "description": "Username field icon option help text." }, + "optionsShowOTPIconHelpText": { + "message": "Adds an icon to input fields that are recognized as OTP/2FA.", + "description": "OTP field icon option help text." + }, "optionsAutoRetrieveCredentialsHelpText": { "message": "KeePassXC-Browser will immediately retrieve credentials when a tab is activated.", "description": "Auto-Retrive Credentials option help text." diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js index 42b2727..0994835 100755 --- a/keepassxc-browser/background/event.js +++ b/keepassxc-browser/background/event.js @@ -228,6 +228,15 @@ kpxcEvent.onUsernameFieldDetected = function(tab, detected) { page.usernameFieldDetected = detected; }; +kpxcEvent.passwordGetFilled = async function() { + return page.passwordFilled; +} + +kpxcEvent.passwordSetFilled = function(tab, state) { + page.passwordFilled = state; + return Promise.resolve(); +} + // All methods named in this object have to be declared BEFORE this! kpxcEvent.messageHandlers = { 'add_credentials': keepass.addCredentials, @@ -255,6 +264,8 @@ kpxcEvent.messageHandlers = { 'page_get_submitted': kpxcEvent.pageGetSubmitted, 'page_set_login_id': kpxcEvent.pageSetLoginId, 'page_set_submitted': kpxcEvent.pageSetSubmitted, + 'password_get_filled': kpxcEvent.passwordGetFilled, + 'password_set_filled': kpxcEvent.passwordSetFilled, 'pop_stack': kpxcEvent.onPopStack, 'popup_login': kpxcEvent.onLoginPopup, 'popup_multiple-fields': kpxcEvent.onMultipleFieldsPopup, diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index a703faf..50c9a30 100755 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -11,6 +11,7 @@ const defaultSettings = { showNotifications: true, showLoginNotifications: true, showLoginFormIcon: true, + showOTPIcon: true, saveDomainOnly: true, autoReconnect: false, defaultGroup: '', @@ -21,6 +22,7 @@ var page = {}; page.blockedTabs = []; page.currentTabId = -1; page.loginId = -1; +page.passwordFilled = false; page.submitted = false; page.submittedCredentials = {}; page.tabs = []; @@ -61,6 +63,9 @@ page.initSettings = async function() { if (!('showLoginFormIcon' in page.settings)) { page.settings.showLoginFormIcon = defaultSettings.showLoginFormIcon; } + if (!('showOTPIcon' in page.settings)) { + page.settings.showOTPIcon = defaultSettings.showOTPIcon; + } if (!('saveDomainOnly' in page.settings)) { page.settings.saveDomainOnly = defaultSettings.saveDomainOnly; } @@ -114,6 +119,7 @@ page.clearCredentials = function(tabId, complete) { } page.usernameFieldDetected = false; + page.passwordFilled = false; page.tabs[tabId].credentials = []; delete page.tabs[tabId].credentials; diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js index f2f71cd..2ad8389 100755 --- a/keepassxc-browser/content/keepassxc-browser.js +++ b/keepassxc-browser/content/keepassxc-browser.js @@ -6,12 +6,23 @@ const ManualFill = { BOTH: 2 }; -// contains already called method names +const acceptedOTPFields = [ + '2fa', + 'auth', + 'challenge', + 'code', + 'mfa', + 'otp', + 'token' +]; + +// Contains already called method names const _called = {}; _called.retrieveCredentials = false; _called.clearLogins = false; _called.manualFillRequested = ManualFill.NONE; let _singleInputEnabledForPage = false; +let _databaseClosed = true; const _maximumInputs = 100; // Count of detected form fields on the page @@ -425,7 +436,7 @@ kpxcFields.getCombination = function(givenType, fieldId) { /** * Return the username field or null if it not exists */ -kpxcFields.getUsernameField = function(passwordId, checkDisabled) { +kpxcFields.getUsernameField = async function(passwordId, checkDisabled) { const passwordField = _f(passwordId); if (!passwordField) { return null; @@ -447,8 +458,8 @@ kpxcFields.getUsernameField = function(passwordId, checkDisabled) { return true; // Continue } - if (kpxc.settings.showLoginFormIcon) { - kpxcUsernameFields.newIcon(usernameField); + if (kpxc.settings.showLoginFormIcon && await kpxc.passwordFilled() === false) { + kpxcUsernameIcons.newIcon(usernameField); } usernameField = i; } @@ -506,7 +517,7 @@ kpxcFields.getPasswordField = function(usernameId, checkDisabled) { passwordField = null; } - kpxcPasswordIcons.newIcon(kpxc.settings.usePasswordGeneratorIcons, passwordField); + kpxcPasswordIcons.newIcon(kpxc.settings.usePasswordGeneratorIcons, passwordField, [], undefined, _databaseClosed); } else { // Search all inputs on page const inputs = kpxcFields.getAllFields(); @@ -556,14 +567,8 @@ kpxcFields.prepareCombinations = async function(combinations) { // If no username field is found, handle the single password field as such const usernameField = c.username ? _f(c.username) : field; - // Change icon for username field - const res = await browser.runtime.sendMessage({ - action: 'get_status', - args: [ true ] - }); - - if (kpxc.settings.showLoginFormIcon) { - kpxcUsernameFields.newIcon(usernameField, res.databaseClosed); + if (kpxc.settings.showLoginFormIcon && await kpxc.passwordFilled() === false) { + kpxcUsernameIcons.newIcon(usernameField, _databaseClosed); } // Initialize form-submit for remembering credentials @@ -854,8 +859,9 @@ kpxc.clearAllFromPage = function() { // Switch credentials if database is changed or closed kpxc.detectDatabaseChange = async function(response) { + _databaseClosed = true; kpxc.clearAllFromPage(); - kpxcUsernameFields.switchIcon(true); + kpxc.switchIcons(true); if (document.visibilityState !== 'hidden') { if (response.new !== '' && response.new !== response.old) { @@ -865,7 +871,8 @@ kpxc.detectDatabaseChange = async function(response) { }); kpxc.settings = settings; await kpxc.initCredentialFields(true); - kpxcUsernameFields.switchIcon(false); // Unlocked + kpxc.switchIcons(false); // Unlocked + _databaseClosed = false; // 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. @@ -926,9 +933,20 @@ kpxc.initCredentialFields = async function(forceCall) { return; } + // Update database closed status + const res = await browser.runtime.sendMessage({ + action: 'get_status', + args: [ true ] + }); + _databaseClosed = res.databaseClosed; + kpxcFields.prepareVisibleFieldsWithID('select'); kpxc.initPasswordGenerator(inputs); + if (kpxc.settings.showOTPIcon) { + kpxc.initOTPFields(inputs); + } + if (!kpxcFields.useDefinedCredentialFields()) { // Get all combinations of username + password fields kpxcFields.combinations = kpxcFields.getAllCombinations(inputs); @@ -964,7 +982,18 @@ kpxc.initCredentialFields = async function(forceCall) { kpxc.initPasswordGenerator = function(inputs) { for (let i = 0; i < inputs.length; i++) { if (inputs[i] && inputs[i].getLowerCaseAttribute('type') === 'password') { - kpxcPasswordIcons.newIcon(kpxc.settings.usePasswordGeneratorIcons, inputs[i], inputs, i); + kpxcPasswordIcons.newIcon(kpxc.settings.usePasswordGeneratorIcons, inputs[i], inputs, i, _databaseClosed); + } + } +}; + +kpxc.initOTPFields = function(inputs, databaseClosed) { + for (const i of inputs) { + const id = i.getLowerCaseAttribute('id'); + const name = i.getLowerCaseAttribute('name'); + + if (acceptedOTPFields.some(f => (id && id.includes(f)) || (name && name.includes(f)))) { + kpxcTOTPIcons.newIcon(i, _databaseClosed); } } }; @@ -1244,8 +1273,8 @@ kpxc.fillInFromActiveElement = function(suppressWarnings, passOnly = false) { kpxc.fillInCredentials(combination, passOnly, suppressWarnings); }; -kpxc.fillInFromActiveElementTOTPOnly = async function() { - const el = document.activeElement; +kpxc.fillInFromActiveElementTOTPOnly = async function(target) { + const el = target || document.activeElement; kpxcFields.setUniqueId(el); const fieldId = kpxcFields.prepareId(el.getAttribute('data-kpxc-id')); @@ -1379,6 +1408,7 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) { action: 'page_set_login_id', args: 0 }); filledIn = true; + kpxc.setPasswordFilled(true); } let list = []; @@ -1416,6 +1446,7 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) { action: 'page_set_login_id', args: combination.loginId }); filledIn = true; + kpxc.setPasswordFilled(true); } let list = []; @@ -1468,6 +1499,7 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) { if (pField) { kpxc.setValueWithChange(pField, valPassword); pField.setAttribute('unchanged', true); + kpxc.setPasswordFilled(true); } let list = []; @@ -1754,6 +1786,24 @@ kpxc.getDocumentLocation = function() { return kpxc.settings.saveDomainOnly ? document.location.origin : document.location.href; }; +// Sets the icons to corresponding database lock status +kpxc.switchIcons = function(locked) { + kpxcUsernameIcons.switchIcon(locked); + kpxcPasswordIcons.switchIcon(locked); + kpxcTOTPIcons.switchIcon(locked); +}; + +kpxc.setPasswordFilled = function(state) { + browser.runtime.sendMessage({ + action: 'password_set_filled', + args: state + }); +}; + +kpxc.passwordFilled = async function() { + return await browser.runtime.sendMessage({ action: 'password_get_filled' }); +}; + const kpxcEvents = {}; @@ -1780,7 +1830,7 @@ kpxcEvents.triggerActivatedTab = async function() { // Update username field lock state const state = await browser.runtime.sendMessage({ action: 'check_database_hash' }); - kpxcUsernameFields.switchIcon(state === ''); + kpxc.switchIcons(state === ''); // initCredentialFields calls also "retrieve_credentials", to prevent it // check of init() was already called diff --git a/keepassxc-browser/content/pwgen.js b/keepassxc-browser/content/pwgen.js index c95b131..052bd2c 100644 --- a/keepassxc-browser/content/pwgen.js +++ b/keepassxc-browser/content/pwgen.js @@ -3,15 +3,20 @@ const kpxcPasswordIcons = {}; kpxcPasswordIcons.icons = []; -kpxcPasswordIcons.newIcon = function(field, databaseClosed = true) { - kpxcPasswordIcons.icons.push(new PasswordIcon(field, databaseClosed)); +kpxcPasswordIcons.newIcon = function(useIcons, field, inputs, pos, databaseClosed = true) { + kpxcPasswordIcons.icons.push(new PasswordIcon(useIcons, field, inputs, pos, databaseClosed)); +}; + +kpxcPasswordIcons.switchIcon = function(locked) { + kpxcPasswordIcons.icons.forEach(u => u.switchIcon(locked)); }; class PasswordIcon extends Icon { - constructor(useIcons, field, inputs, pos) { + constructor(useIcons, field, inputs, pos, databaseClosed) { super(); this.useIcons = useIcons; + this.databaseClosed = databaseClosed; this.initField(field, inputs, pos); kpxcUI.monitorIconPosition(this); @@ -72,6 +77,10 @@ PasswordIcon.prototype.createIcon = function(field) { icon.style.width = Pixels(size); icon.style.height = Pixels(size); + if (this.databaseClosed) { + icon.style.filter = 'saturate(0%)'; + } + icon.addEventListener('click', function(e) { if (!e.isTrusted) { return; diff --git a/keepassxc-browser/content/totp-field.js b/keepassxc-browser/content/totp-field.js new file mode 100644 index 0000000..9b30714 --- /dev/null +++ b/keepassxc-browser/content/totp-field.js @@ -0,0 +1,79 @@ +'use strict'; + +var kpxcTOTPIcons = {}; +kpxcTOTPIcons.icons = []; + +kpxcTOTPIcons.newIcon = function(field, databaseClosed = true) { + kpxcTOTPIcons.icons.push(new TOTPFieldIcon(field, databaseClosed)); +}; + +kpxcTOTPIcons.switchIcon = function(locked) { + kpxcTOTPIcons.icons.forEach(u => u.switchIcon(locked)); +}; + + +class TOTPFieldIcon extends Icon { + constructor(field, databaseClosed = true) { + super(); + this.icon = null; + this.inputField = null; + this.databaseClosed = databaseClosed; + + this.initField(field); + kpxcUI.monitorIconPosition(this); + } +}; + +TOTPFieldIcon.prototype.initField = function(field) { + if (!field || field.getAttribute('kpxc-totp-field') === 'true') { + return; + } + + field.setAttribute('kpxc-totp-field', 'true'); + + // Observer the visibility + if (this.observer) { + this.observer.observe(field); + } + + this.createIcon(field); + this.inputField = field; +}; + +TOTPFieldIcon.prototype.createIcon = function(field) { + const className = (isFirefox() ? 'moz' : 'default'); + + // Size the icon dynamically, but not greater than 24 or smaller than 14 + const size = Math.max(Math.min(24, field.offsetHeight - 4), 14); + let offset = Math.floor((field.offsetHeight - size) / 3); + offset = (offset < 0) ? 0 : offset; + + const icon = kpxcUI.createElement('div', 'kpxc kpxc-totp-icon ' + className, + { + 'title': tr('totpFieldText'), + 'alt': tr('totpFieldIcon'), + 'size': size, + 'offset': offset + }); + icon.style.zIndex = '10000000'; + icon.style.width = Pixels(size); + icon.style.height = Pixels(size); + + if (this.databaseClosed) { + icon.style.filter = 'saturate(0%)'; + } + + icon.addEventListener('click', async function(e) { + if (!e.isTrusted) { + return; + } + + e.preventDefault(); + await kpxc.receiveCredentialsIfNecessary(); + kpxc.fillInFromActiveElementTOTPOnly(field); + }); + + kpxcUI.setIconPosition(icon, field); + this.icon = icon; + document.body.appendChild(icon); +}; diff --git a/keepassxc-browser/content/ui.js b/keepassxc-browser/content/ui.js index 8f603c4..8da039d 100644 --- a/keepassxc-browser/content/ui.js +++ b/keepassxc-browser/content/ui.js @@ -21,6 +21,18 @@ class Icon { console.log(err); } } + + switchIcon(locked) { + if (!this.icon) { + return; + } + + if (locked) { + this.icon.style.filter = 'saturate(0%)'; + } else { + this.icon.style.filter = 'saturate(100%)'; + } + } }; const kpxcUI = {}; diff --git a/keepassxc-browser/content/username-field.js b/keepassxc-browser/content/username-field.js index 4db5f98..bfaf0f1 100644 --- a/keepassxc-browser/content/username-field.js +++ b/keepassxc-browser/content/username-field.js @@ -1,24 +1,25 @@ 'use strict'; -const kpxcUsernameFields = {}; -kpxcUsernameFields.icons = []; +const kpxcUsernameIcons = {}; +kpxcUsernameIcons.icons = []; -kpxcUsernameFields.newIcon = function(field, databaseClosed = true) { - kpxcUsernameFields.icons.push(new UsernameFieldIcon(field, databaseClosed)); +kpxcUsernameIcons.newIcon = function(field, databaseClosed = true) { + kpxcUsernameIcons.icons.push(new UsernameFieldIcon(field, databaseClosed)); }; -kpxcUsernameFields.switchIcon = function(locked) { - kpxcUsernameFields.icons.forEach(u => u.switchIcon(locked)); +kpxcUsernameIcons.switchIcon = function(locked) { + kpxcUsernameIcons.icons.forEach(u => u.switchIcon(locked)); }; class UsernameFieldIcon extends Icon { constructor(field, databaseClosed = true) { super(); + this.databaseClosed = databaseClosed; this.icon = null; this.inputField = null; - this.initField(field, databaseClosed); + this.initField(field,); kpxcUI.monitorIconPosition(this); } @@ -39,7 +40,7 @@ class UsernameFieldIcon extends Icon { } }; -UsernameFieldIcon.prototype.initField = function(field, databaseClosed = true) { +UsernameFieldIcon.prototype.initField = function(field) { if (!field || field.getAttribute('kpxc-username-field') === 'true') { return; } @@ -51,18 +52,18 @@ UsernameFieldIcon.prototype.initField = function(field, databaseClosed = true) { this.observer.observe(field); } - this.createIcon(field, databaseClosed); + this.createIcon(field); this.inputField = field; }; -UsernameFieldIcon.prototype.createIcon = function(target, databaseClosed) { +UsernameFieldIcon.prototype.createIcon = function(target) { // Remove any existing password generator icons from the input field if (target.getAttribute('kpxc-password-generator')) { kpxcPasswordDialog.removeIcon(target); } const field = target; - const className = getIconClassName(databaseClosed); + const className = getIconClassName(this.databaseClosed); // Size the icon dynamically, but not greater than 24 or smaller than 14 const size = Math.max(Math.min(24, field.offsetHeight - 4), 14); @@ -78,7 +79,7 @@ UsernameFieldIcon.prototype.createIcon = function(target, databaseClosed) { const icon = kpxcUI.createElement('div', 'kpxc kpxc-username-icon ' + className, { - 'title': databaseClosed ? tr('usernameLockedFieldText') : tr('usernameFieldText'), + 'title': this.databaseClosed ? tr('usernameLockedFieldText') : tr('usernameFieldText'), 'alt': tr('usernameFieldIcon'), 'size': size, 'offset': offset, diff --git a/keepassxc-browser/css/totp.css b/keepassxc-browser/css/totp.css new file mode 100644 index 0000000..8281132 --- /dev/null +++ b/keepassxc-browser/css/totp.css @@ -0,0 +1,14 @@ +.kpxc-totp-icon { + position: absolute; + cursor: pointer; +} + +.kpxc-totp-icon.default { + background: url('chrome-extension://__MSG_@@extension_id__/icons/otp.svg') right no-repeat; + background-size: contain; +} + +.kpxc-totp-icon.moz { + background: url('moz-extension://__MSG_@@extension_id__/icons/otp.svg') right no-repeat; + background-size: contain; +}
\ No newline at end of file diff --git a/keepassxc-browser/icons/otp.svg b/keepassxc-browser/icons/otp.svg new file mode 100644 index 0000000..f0fe378 --- /dev/null +++ b/keepassxc-browser/icons/otp.svg @@ -0,0 +1,216 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="100" + height="100" + viewBox="0 0 99.999997 100" + id="svg2" + version="1.1" + sodipodi:docname="otp.svg" + inkscape:version="0.92.2 5c3e80d, 2017-08-06"> + <title + id="title836">OTP icon</title> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1402" + inkscape:window-height="1053" + id="namedview29" + showgrid="false" + inkscape:zoom="4.72" + inkscape:cx="19.20533" + inkscape:cy="22.698472" + inkscape:window-x="867" + inkscape:window-y="158" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <defs + id="defs4"> + <linearGradient + id="linearGradient4257"> + <stop + style="stop-color:#808080;stop-opacity:1" + offset="0" + id="stop4259" /> + <stop + style="stop-color:#4d4d4d;stop-opacity:1" + offset="1" + id="stop4261" /> + </linearGradient> + <linearGradient + id="linearGradient4316-3-6"> + <stop + style="stop-color:#226e23;stop-opacity:1" + offset="0" + id="stop4173" /> + <stop + style="stop-color:#63ab3a;stop-opacity:1" + offset="1" + id="stop4175" /> + </linearGradient> + <linearGradient + id="linearGradient4316-3"> + <stop + id="stop4167" + offset="0" + style="stop-color:#226e23;stop-opacity:1" /> + <stop + id="stop4169" + offset="1" + style="stop-color:#63ab3a;stop-opacity:1" /> + </linearGradient> + <linearGradient + id="linearGradient4316"> + <stop + style="stop-color:#226e23;stop-opacity:1" + offset="0" + id="stop4318" /> + <stop + style="stop-color:#63ab3a;stop-opacity:1" + offset="1" + id="stop4320" /> + </linearGradient> + <linearGradient + id="linearGradient4153" + osb:paint="solid"> + <stop + style="stop-color:#b3b3b3;stop-opacity:1;" + offset="0" + id="stop4155" /> + </linearGradient> + <linearGradient + xlink:href="#linearGradient4316" + id="linearGradient4324" + x1="50.757614" + y1="964.83679" + x2="50.757614" + y2="1042.2632" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0561225,0,0,1.0561225,-2.8061215,-1008.6172)" /> + <radialGradient + gradientUnits="userSpaceOnUse" + r="43.571938" + fy="41.189114" + fx="-82.91127" + cy="41.189114" + cx="-82.91127" + id="radialGradient5106" + xlink:href="#linearGradient4316" /> + <linearGradient + xlink:href="#linearGradient4316" + id="linearGradient4324-3" + x1="50.757614" + y1="964.83679" + x2="50.757614" + y2="1042.2632" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0561225,0,0,1.0561225,118.96071,-1109.1994)" /> + <linearGradient + xlink:href="#linearGradient4316" + id="linearGradient4324-3-6" + x1="50.757614" + y1="964.83679" + x2="50.757614" + y2="1042.2632" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0561225,0,0,1.0561225,-2.8061235,-1008.6171)" /> + <linearGradient + gradientUnits="userSpaceOnUse" + y2="86.356995" + x2="53.238865" + y1="12.753036" + x1="53.238865" + id="linearGradient5199" + xlink:href="#linearGradient4316-3-6" /> + <linearGradient + xlink:href="#linearGradient4257" + id="linearGradient4263" + x1="50.09866" + y1="86.831215" + x2="49.526104" + y2="8.6772995" + gradientUnits="userSpaceOnUse" /> + </defs> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title>OTP icon</dc:title> + <cc:license + rdf:resource="http://scripts.sil.org/OFL" /> + <dc:creator> + <cc:Agent> + <dc:title>KeePassXC Team</dc:title> + </cc:Agent> + </dc:creator> + <dc:rights> + <cc:Agent> + <dc:title>KeePassXC Team</dc:title> + </cc:Agent> + </dc:rights> + <dc:subject> + <rdf:Bag> + <rdf:li>otp</rdf:li> + <rdf:li>totp</rdf:li> + </rdf:Bag> + </dc:subject> + <dc:date>2019</dc:date> + </cc:Work> + <cc:License + rdf:about="http://scripts.sil.org/OFL"> + <cc:permits + rdf:resource="http://scripts.sil.org/pub/OFL/Reproduction" /> + <cc:permits + rdf:resource="http://scripts.sil.org/pub/OFL/Distribution" /> + <cc:permits + rdf:resource="http://scripts.sil.org/pub/OFL/Embedding" /> + <cc:permits + rdf:resource="http://scripts.sil.org/pub/OFL/DerivativeWorks" /> + <cc:requires + rdf:resource="http://scripts.sil.org/pub/OFL/Notice" /> + <cc:requires + rdf:resource="http://scripts.sil.org/pub/OFL/Attribution" /> + <cc:requires + rdf:resource="http://scripts.sil.org/pub/OFL/ShareAlike" /> + <cc:requires + rdf:resource="http://scripts.sil.org/pub/OFL/DerivativeRenaming" /> + <cc:requires + rdf:resource="http://scripts.sil.org/pub/OFL/BundlingWhenSelling" /> + </cc:License> + </rdf:RDF> + </metadata> + <path + style="fill:#6cac4d;fill-opacity:1;stroke:#467720;stroke-width:3.28421712;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" + d="m 17.573148,24.970678 h 64.853704 c 4.426642,0 7.990329,4.214625 7.990329,9.449831 v 27.398502 c 0,5.235207 -3.563687,9.449832 -7.990329,9.449832 H 78.373496 74.320139 L 74.28734,83.531602 64.520559,71.268843 H 62.160069 58.106713 54.053356 50 45.946644 41.893287 37.839931 33.786574 29.733218 25.679861 21.626504 17.573148 c -4.426642,0 -7.9903288,-4.214625 -7.9903288,-9.449832 V 34.420509 c 0,-5.235206 3.5636868,-9.449831 7.9903288,-9.449831 z" + id="rect872" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssssscccccccccccccccssss" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.60000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + x="20" + y="69.295219" + id="text868"><tspan + sodipodi:role="line" + id="tspan866" + x="26" + y="69.295219">***</tspan></text> +</svg> diff --git a/keepassxc-browser/manifest.json b/keepassxc-browser/manifest.json index 0225a14..49ecbf5 100755 --- a/keepassxc-browser/manifest.json +++ b/keepassxc-browser/manifest.json @@ -58,6 +58,7 @@ "content/keepassxc-browser.js", "content/pwgen.js", "content/sites.js", + "content/totp-field.js", "content/username-field.js" ], "css": [ @@ -67,6 +68,7 @@ "css/define.css", "css/notification.css", "css/pwgen.css", + "css/totp.css", "css/username.css" ], "run_at": "document_idle", @@ -106,7 +108,8 @@ "web_accessible_resources": [ "icons/keepassxc.svg", "icons/key.svg", - "icons/locked.svg" + "icons/locked.svg", + "icons/otp.svg" ], "permissions": [ "activeTab", diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html index 0f51046..7f54ee9 100644 --- a/keepassxc-browser/options/options.html +++ b/keepassxc-browser/options/options.html @@ -70,15 +70,15 @@ </p> <hr /> <p> - <div class="checkbox"> - <label class="checkbox"> - <input type="checkbox" name="showLoginFormIcon" value="true"/><span data-i18n="optionsCheckboxUsernameIcons"></span> - </label> - <span class="help-block"> - <span data-i18n="optionsShowLoginFormIconHelpText"></span> - </span> - </div> - </p> + <div class="checkbox"> + <label class="checkbox"> + <input type="checkbox" name="showLoginFormIcon" value="true"/><span data-i18n="optionsCheckboxUsernameIcons"></span> + </label> + <span class="help-block"> + <span data-i18n="optionsShowLoginFormIconHelpText"></span> + </span> + </div> + </p> <p> <div class="checkbox"> <label class="checkbox"> @@ -87,7 +87,18 @@ <span class="help-block"> <span data-i18n="optionsUsePasswordGeneratorHelpText"></span> <br /> - <span data-i18n="optionsUsePasswordGeneratorHelpTextSecond"></span></span> + <span data-i18n="optionsUsePasswordGeneratorHelpTextSecond"></span> + </span> + </div> + </p> + <p> + <div class="checkbox"> + <label class="checkbox"> + <input type="checkbox" name="showOTPIcon" value="true"/><span data-i18n="optionsCheckboxOTPIcons"></span> + </label> + <span class="help-block"> + <span data-i18n="optionsShowOTPIconHelpText"></span> + </span> </div> </p> <hr /> |