First draft

This commit is contained in:
varjolintu 2021-11-20 15:12:55 +02:00
parent a037a0b656
commit 8c24bf5aa7
9 changed files with 289 additions and 10 deletions

View 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);
};

View file

@ -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,

View file

@ -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) {

View file

@ -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);
};

View file

@ -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;
}

View file

@ -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);
}
});
};

View 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;
}

View 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

View file

@ -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",