mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
First draft
This commit is contained in:
parent
a037a0b656
commit
8c24bf5aa7
9 changed files with 289 additions and 10 deletions
156
keepassxc-browser/content/credit-card-form.js
Normal file
156
keepassxc-browser/content/credit-card-form.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
'use strict';
|
||||
|
||||
const cardNameRegex = /(cc|card|cardholder).*-name|name/i;
|
||||
const cardNumberRegex = /(cc|card).*-(num|number|no)|number|card-no/i;
|
||||
const cardTypeRegex = /(cc|card|cb).*-type|brand/i;
|
||||
const cardCCVRegex = /(cc|card|security|verification).*-(code|cvv|cvc|csc)|cvv|cvc|csc/i;
|
||||
const cardExpirationRegex = /(cc|card).*-(exp|expiry|mm-yy|mm-yyyy)|expiration-date/i;
|
||||
const cardExpirationMonthRegex = /(cc-exp|card-exp|card-expiration|card-expire|expire|expiry).*-(month|mm|mo)/i;
|
||||
const cardExpirationYearRegex = /(cc-exp|card-exp|card-expiration|card-expire|expire|expiry).*-(year|yr|yy|yyyy)/i;
|
||||
|
||||
var kpxcCCIcons = {};
|
||||
kpxcCCIcons.icons = [];
|
||||
|
||||
kpxcCCIcons.ccForm = {
|
||||
ccName: undefined,
|
||||
ccNumber: undefined,
|
||||
ccExpMonth: undefined,
|
||||
ccExpYear: undefined,
|
||||
ccExp: undefined,
|
||||
ccType: undefined,
|
||||
ccCcv: undefined
|
||||
};
|
||||
|
||||
kpxcCCIcons.newIcon = function(field, databaseState = DatabaseState.DISCONNECTED) {
|
||||
kpxcCCIcons.icons.push(new CCFieldIcon(field, databaseState));
|
||||
};
|
||||
|
||||
kpxcCCIcons.switchIcon = function(state) {
|
||||
kpxcCCIcons.icons.forEach(u => u.switchIcon(state));
|
||||
};
|
||||
|
||||
kpxcCCIcons.deleteHiddenIcons = function() {
|
||||
kpxcUI.deleteHiddenIcons(kpxcCCIcons.icons, 'kpxc-totp-field');
|
||||
};
|
||||
|
||||
kpxcCCIcons.regexMatch = function(field, regex) {
|
||||
const id = field.getLowerCaseAttribute('id');
|
||||
const name = field.getLowerCaseAttribute('name');
|
||||
const autocomplete = field.getLowerCaseAttribute('autocomplete');
|
||||
const placeholder = field.getLowerCaseAttribute('placeholder');
|
||||
|
||||
if ((id && id.match(regex))
|
||||
|| (name && name.match(regex))
|
||||
|| (autocomplete && autocomplete.match(regex))
|
||||
|| (placeholder && placeholder.match(regex))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
kpxcCCIcons.detectCreditCardForm = function(field, allInputs) {
|
||||
if (!field || !allInputs || allInputs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
kpxcCCIcons.ccForm.ccName = allInputs.find(i => kpxcCCIcons.regexMatch(i, cardNameRegex));
|
||||
kpxcCCIcons.ccForm.ccNumber = allInputs.find(i => kpxcCCIcons.regexMatch(i, cardNumberRegex));
|
||||
kpxcCCIcons.ccForm.ccExpMonth = allInputs.find(i => kpxcCCIcons.regexMatch(i, cardExpirationMonthRegex));
|
||||
kpxcCCIcons.ccForm.ccExpYear = allInputs.find(i => kpxcCCIcons.regexMatch(i, cardExpirationYearRegex));
|
||||
kpxcCCIcons.ccForm.ccExp = allInputs.find(i => kpxcCCIcons.regexMatch(i, cardExpirationRegex));
|
||||
kpxcCCIcons.ccForm.ccType = allInputs.find(i => kpxcCCIcons.regexMatch(i, cardTypeRegex));
|
||||
kpxcCCIcons.ccForm.ccCcv = allInputs.find(i => kpxcCCIcons.regexMatch(i, cardCCVRegex));
|
||||
|
||||
if (!kpxcCCIcons.ccForm.ccName || !kpxcCCIcons.ccForm.ccCcv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(kpxcCCIcons.ccForm);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// TODO: Adjust this function. Do we need a regex checks here? Those are also used the function above,
|
||||
// so this function could just be a raw check.
|
||||
kpxcCCIcons.isValid = function(field, forced) {
|
||||
if (!forced) {
|
||||
if (ignoredTypes.some(t => t === field.type)
|
||||
|| field.offsetWidth < MIN_INPUT_FIELD_OFFSET_WIDTH
|
||||
|| field.size < 2
|
||||
|| ignoredTypes.some(t => t === field.autocomplete)
|
||||
|| field.readOnly
|
||||
|| field.inputMode === 'email'
|
||||
|| field.getAttribute('kpxc-totp-field') === 'true') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
class CCFieldIcon extends Icon {
|
||||
constructor(field, databaseState = DatabaseState.DISCONNECTED) {
|
||||
super(field, databaseState);
|
||||
|
||||
this.initField(field);
|
||||
kpxcUI.monitorIconPosition(this);
|
||||
}
|
||||
}
|
||||
|
||||
CCFieldIcon.prototype.initField = function(field) {
|
||||
// Observer the visibility
|
||||
if (this.observer) {
|
||||
this.observer.observe(field);
|
||||
}
|
||||
|
||||
this.createIcon(field);
|
||||
this.inputField = field;
|
||||
};
|
||||
|
||||
CCFieldIcon.prototype.createIcon = function(field, segmented = false) {
|
||||
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);
|
||||
const offset = kpxcUI.calculateIconOffset(field, size);
|
||||
|
||||
const icon = kpxcUI.createElement('div', 'kpxc kpxc-cc-icon ' + className,
|
||||
{
|
||||
'title': tr('ccFieldText'),
|
||||
'alt': tr('ccFieldIcon'),
|
||||
'size': size,
|
||||
'offset': offset
|
||||
});
|
||||
icon.style.zIndex = '10000000';
|
||||
icon.style.width = Pixels(size);
|
||||
icon.style.height = Pixels(size);
|
||||
|
||||
if (this.databaseState === DatabaseState.DISCONNECTED || this.databaseState === DatabaseState.LOCKED) {
|
||||
icon.style.filter = 'saturate(0%)';
|
||||
}
|
||||
|
||||
icon.addEventListener('click', async function(e) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
await kpxc.receiveCredentialsIfNecessary();
|
||||
kpxcFill.fillFromCreditCardForm(field);
|
||||
});
|
||||
|
||||
icon.addEventListener('mousedown', ev => ev.stopPropagation());
|
||||
icon.addEventListener('mouseup', ev => ev.stopPropagation());
|
||||
|
||||
kpxcUI.setIconPosition(icon, field, this.rtl, segmented);
|
||||
this.icon = icon;
|
||||
|
||||
const styleSheet = createStylesheet('css/credit-card.css');
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
this.shadowRoot = wrapper.attachShadow({ mode: 'closed' });
|
||||
this.shadowRoot.append(styleSheet);
|
||||
this.shadowRoot.append(icon);
|
||||
document.body.append(wrapper);
|
||||
};
|
||||
|
|
@ -11,6 +11,7 @@ const kpxcFields = {};
|
|||
kpxcFields.getAllCombinations = async function(inputs) {
|
||||
const combinations = [];
|
||||
let usernameField = null;
|
||||
let ccField = undefined;
|
||||
|
||||
for (const input of inputs) {
|
||||
if (!input) {
|
||||
|
|
@ -38,11 +39,26 @@ kpxcFields.getAllCombinations = async function(inputs) {
|
|||
};
|
||||
|
||||
combinations.push(combination);
|
||||
} else if (kpxcCCIcons.detectCreditCardForm(input, inputs)) {
|
||||
ccField = input;
|
||||
} else {
|
||||
usernameField = input;
|
||||
}
|
||||
}
|
||||
|
||||
if (ccField) {
|
||||
const combination = {
|
||||
username: null,
|
||||
password: null,
|
||||
passwordInputs: [],
|
||||
totp: null,
|
||||
form: null,
|
||||
ccInputs: kpxcCCIcons.ccForm
|
||||
};
|
||||
|
||||
combinations.push(combination);
|
||||
}
|
||||
|
||||
if (kpxc.singleInputEnabledForPage && combinations.length === 0 && usernameField) {
|
||||
const combination = {
|
||||
username: usernameField,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
// TODO: Attribute names should be specified properly
|
||||
const KPH_CC_CCV = 'KPH: CC_CCV';
|
||||
const KPH_CC_EXP = 'KPH: CC_EXP';
|
||||
const KPH_CC_EXP_MONTH = 'KPH: CC_EXP_MONTH';
|
||||
const KPH_CC_EXP_YEAR = 'KPH: CC_EXP_YEAR';
|
||||
const KPH_CC_NAME = 'KPH: CC_NAME';
|
||||
const KPH_CC_NUMBER = 'KPH: CC_NUMBER';
|
||||
const KPH_CC_TYPE = 'KPH: CC_TYPE';
|
||||
|
||||
/**
|
||||
* @Object kpxcFill
|
||||
* The class for filling credentials.
|
||||
|
|
@ -94,6 +103,32 @@ kpxcFill.fillFromAutofill = async function() {
|
|||
sendMessage('popup_login', [ { text: `${kpxc.credentials[0].login} (${kpxc.credentials[0].name})`, uuid: kpxc.credentials[0].uuid } ]);
|
||||
};
|
||||
|
||||
// Fill requested from Credit Card form
|
||||
kpxcFill.fillFromCreditCardForm = async function() {
|
||||
if (kpxc.credentials.length === 0) {
|
||||
logDebug('Error: Credential list is empty.');
|
||||
return;
|
||||
}
|
||||
|
||||
const fillCCValue = function(inputField, ccFieldName, fields) {
|
||||
if (inputField) {
|
||||
const newValue = fields.find(s => s[ccFieldName]);
|
||||
if (newValue) {
|
||||
kpxc.setValue(inputField, newValue[ccFieldName]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const stringFields = kpxc.credentials[0].stringFields;
|
||||
fillCCValue(kpxcCCIcons.ccForm.ccCcv, KPH_CC_CCV, stringFields);
|
||||
fillCCValue(kpxcCCIcons.ccForm.ccExp, KPH_CC_EXP, stringFields);
|
||||
fillCCValue(kpxcCCIcons.ccForm.ccExpMonth, KPH_CC_EXP_MONTH, stringFields);
|
||||
fillCCValue(kpxcCCIcons.ccForm.ccExpYear, KPH_CC_EXP_YEAR, stringFields);
|
||||
fillCCValue(kpxcCCIcons.ccForm.ccName, KPH_CC_NAME, stringFields);
|
||||
fillCCValue(kpxcCCIcons.ccForm.ccNumber, KPH_CC_NUMBER, stringFields);
|
||||
fillCCValue(kpxcCCIcons.ccForm.ccType, KPH_CC_TYPE, stringFields);
|
||||
};
|
||||
|
||||
// Fill requested by selecting credentials from the popup
|
||||
kpxcFill.fillFromPopup = async function(id, uuid) {
|
||||
if (!kpxc.credentials.length === 0 || !kpxc.credentials[id] || kpxc.combinations.length === 0) {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
*/
|
||||
const kpxcIcons = {};
|
||||
kpxcIcons.icons = [];
|
||||
kpxcIcons.iconTypes = { USERNAME: 0, PASSWORD: 1, TOTP: 2 };
|
||||
kpxcIcons.iconTypes = { USERNAME: 0, PASSWORD: 1, TOTP: 2, CC: 3 };
|
||||
|
||||
// Adds an icon to input field
|
||||
kpxcIcons.addIcon = async function(field, iconType) {
|
||||
if (!field || iconType < 0 || iconType > 2) {
|
||||
if (!field || iconType < kpxcIcons.iconTypes.USERNAME || iconType > kpxcIcons.iconTypes.CC) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +24,9 @@ kpxcIcons.addIcon = async function(field, iconType) {
|
|||
} else if (iconType === kpxcIcons.iconTypes.TOTP && kpxcTOTPIcons.isValid(field)) {
|
||||
kpxcTOTPIcons.newIcon(field, kpxc.databaseState);
|
||||
iconSet = true;
|
||||
} else if (iconType === kpxcIcons.iconTypes.CC && kpxcCCIcons.isValid(field)) {
|
||||
kpxcCCIcons.newIcon(field, kpxc.databaseState);
|
||||
iconSet = true;
|
||||
}
|
||||
|
||||
if (iconSet) {
|
||||
|
|
@ -69,10 +72,17 @@ kpxcIcons.addIconsFromForm = async function(form) {
|
|||
}
|
||||
};
|
||||
|
||||
const addCCIcons = async function(c) {
|
||||
if (c.ccInputs.ccName/* && kpxc.settings.showCCIcons*/) {
|
||||
kpxcIcons.addIcon(c.ccInputs.ccName, kpxcIcons.iconTypes.CC);
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
await addUsernameIcons(form),
|
||||
await addPasswordIcons(form),
|
||||
await addTOTPIcons(form)
|
||||
await addTOTPIcons(form),
|
||||
await addCCIcons(form)
|
||||
]);
|
||||
};
|
||||
|
||||
|
|
@ -81,6 +91,7 @@ kpxcIcons.deleteHiddenIcons = function() {
|
|||
kpxcUsernameIcons.deleteHiddenIcons();
|
||||
kpxcPasswordIcons.deleteHiddenIcons();
|
||||
kpxcTOTPIcons.deleteHiddenIcons();
|
||||
kpxcCCIcons.deleteHiddenIcons();
|
||||
};
|
||||
|
||||
// Initializes all icons needed to be shown
|
||||
|
|
@ -112,4 +123,5 @@ kpxcIcons.switchIcons = function() {
|
|||
kpxcUsernameIcons.switchIcon(kpxc.databaseState);
|
||||
kpxcPasswordIcons.switchIcon(kpxc.databaseState);
|
||||
kpxcTOTPIcons.switchIcon(kpxc.databaseState);
|
||||
kpxcCCIcons.switchIcon(kpxc.databaseState);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ kpxc.getForm = function(inputField) {
|
|||
|
||||
// Returns form action URL or document origin if it's not found
|
||||
kpxc.getFormActionUrl = function(combination) {
|
||||
if (!combination || (combination.username === null && combination.password === null)) {
|
||||
if (!combination || (combination.username === null && combination.password === null && !combination.ccInputs.ccCcv)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -137,13 +137,9 @@ kpxcObserverHelper.getInputs = function(target, ignoreVisibility = false) {
|
|||
return [];
|
||||
}
|
||||
|
||||
// Filter out any input fields with type 'hidden' right away
|
||||
const inputFields = [];
|
||||
Array.from(target.getElementsByTagName('input')).forEach(e => {
|
||||
if (e.type !== 'hidden' && !e.disabled && !kpxcObserverHelper.alreadyIdentified(e)) {
|
||||
inputFields.push(e);
|
||||
}
|
||||
});
|
||||
addInputsToList(target, 'input', inputFields);
|
||||
addInputsToList(target, 'select', inputFields);
|
||||
|
||||
if (target.nodeName === 'INPUT') {
|
||||
inputFields.push(target);
|
||||
|
|
@ -270,3 +266,13 @@ kpxcObserverHelper.ignoredNode = function(target) {
|
|||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Appends inputs from target to the list with a selected tagName (input, select..)
|
||||
const addInputsToList = function(target, tagName, inputFields) {
|
||||
// Filter out any input fields with type 'hidden' right away
|
||||
Array.from(target.getElementsByTagName(tagName)).forEach(e => {
|
||||
if (e.type !== 'hidden' && !e.disabled && !kpxcObserverHelper.alreadyIdentified(e)) {
|
||||
inputFields.push(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
14
keepassxc-browser/css/credit-card.css
Normal file
14
keepassxc-browser/css/credit-card.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.kpxc-cc-icon {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.kpxc-cc-icon.default {
|
||||
background: url('chrome-extension://__MSG_@@extension_id__/icons/credit-card.svg') right no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.kpxc-cc-icon.moz {
|
||||
background: url('moz-extension://__MSG_@@extension_id__/icons/credit-card.svg') right no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
37
keepassxc-browser/icons/credit-card.svg
Normal file
37
keepassxc-browser/icons/credit-card.svg
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="1536"
|
||||
height="1536"
|
||||
version="1.1"
|
||||
id="svg826"
|
||||
sodipodi:docname="credit-card.svg"
|
||||
inkscape:version="1.1.2 (b8e25be8, 2022-02-05)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs830" />
|
||||
<sodipodi:namedview
|
||||
id="namedview828"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.28417969"
|
||||
inkscape:cx="1060.9485"
|
||||
inkscape:cy="645.71821"
|
||||
inkscape:window-width="1342"
|
||||
inkscape:window-height="983"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg826" />
|
||||
<path
|
||||
d="M 109.67107,1115.4514 V 767.99999 H 1426.3289 v 347.45141 c 0,50.289 -41.1456,91.4346 -91.4346,91.4346 H 201.10564 c -50.28901,0 -91.43457,-41.1456 -91.43457,-91.4346 z M 475.40934,987.44293 v 73.14767 h 219.44299 v -73.14767 z m -219.44296,0 v 73.14767 H 402.2617 V 987.44293 Z M 1334.8943,329.11404 c 50.289,0 91.4346,41.14556 91.4346,91.43457 V 548.55702 H 109.67107 V 420.54861 c 0,-50.28901 41.14556,-91.43457 91.43457,-91.43457 z"
|
||||
id="path824"
|
||||
style="fill:#6cac4d;fill-opacity:1;stroke:#467720;stroke-width:42.9338;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -58,6 +58,7 @@
|
|||
"content/banner.js",
|
||||
"content/autocomplete.js",
|
||||
"content/credential-autocomplete.js",
|
||||
"content/credit-card-form.js",
|
||||
"content/define.js",
|
||||
"content/fields.js",
|
||||
"content/fill.js",
|
||||
|
|
@ -123,6 +124,7 @@
|
|||
}
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
"icons/credit-card.svg",
|
||||
"icons/disconnected.svg",
|
||||
"icons/keepassxc.svg",
|
||||
"icons/key.svg",
|
||||
|
|
@ -132,6 +134,7 @@
|
|||
"css/banner.css",
|
||||
"css/button.css",
|
||||
"css/colors.css",
|
||||
"css/credit-card.css",
|
||||
"css/define.css",
|
||||
"css/notification.css",
|
||||
"css/pwgen.css",
|
||||
|
|
|
|||
Loading…
Reference in a new issue