Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc-browser.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--keepassxc-browser/_locales/en/messages.json16
-rwxr-xr-xkeepassxc-browser/background/event.js11
-rwxr-xr-xkeepassxc-browser/background/page.js6
-rwxr-xr-xkeepassxc-browser/content/keepassxc-browser.js88
-rw-r--r--keepassxc-browser/content/pwgen.js15
-rw-r--r--keepassxc-browser/content/totp-field.js79
-rw-r--r--keepassxc-browser/content/ui.js12
-rw-r--r--keepassxc-browser/content/username-field.js25
-rw-r--r--keepassxc-browser/css/totp.css14
-rw-r--r--keepassxc-browser/icons/otp.svg216
-rwxr-xr-xkeepassxc-browser/manifest.json5
-rw-r--r--keepassxc-browser/options/options.html31
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 />