mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
Merge pull request #625 from keepassxreboot/feature/totp_field_fill
Add TOTP icon filling and improve icon handling
This commit is contained in:
commit
3688b080d6
12 changed files with 473 additions and 45 deletions
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
79
keepassxc-browser/content/totp-field.js
Normal file
79
keepassxc-browser/content/totp-field.js
Normal file
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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 = {};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
14
keepassxc-browser/css/totp.css
Normal file
14
keepassxc-browser/css/totp.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
216
keepassxc-browser/icons/otp.svg
Normal file
216
keepassxc-browser/icons/otp.svg
Normal file
|
|
@ -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>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
Loading…
Reference in a new issue