Merge pull request #78 from twofas/feature/1.7.0

Feature/1.7.0
This commit is contained in:
Greg Zajac 2024-04-05 09:47:50 +02:00 committed by GitHub
commit bfdf9c05cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 629 additions and 1068 deletions

2
.nvmrc
View File

@ -1 +1 @@
v20.9.0
v20.11.1

View File

@ -431,7 +431,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 54;
CURRENT_PROJECT_VERSION = 57;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ZY8UR5ADFW;
ENABLE_HARDENED_RUNTIME = YES;
@ -445,7 +445,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.6.4;
MARKETING_VERSION = 1.7.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@ -467,7 +467,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 54;
CURRENT_PROJECT_VERSION = 57;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ZY8UR5ADFW;
ENABLE_HARDENED_RUNTIME = YES;
@ -481,7 +481,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.6.4;
MARKETING_VERSION = 1.7.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@ -505,7 +505,7 @@
CODE_SIGN_ENTITLEMENTS = "macOS (App)/2FAS - Two factor authentication.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 54;
CURRENT_PROJECT_VERSION = 57;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ZY8UR5ADFW;
ENABLE_HARDENED_RUNTIME = YES;
@ -520,7 +520,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.6.4;
MARKETING_VERSION = 1.7.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@ -546,7 +546,7 @@
CODE_SIGN_IDENTITY = "Apple Distribution";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 54;
CURRENT_PROJECT_VERSION = 57;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ZY8UR5ADFW;
ENABLE_HARDENED_RUNTIME = YES;
@ -561,7 +561,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.6.4;
MARKETING_VERSION = 1.7.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,

View File

@ -49,13 +49,13 @@
16 - inputFocus
17 - contentOnMessage
18 - neverShowNotificationInfo
*19 - openOptionsPage [redundant]
39 - removedNodes - loadFromLocalStorage
40 - removedNodes - saveToLocalStorage
41 - hiddenNodes - loadFromLocalStorage
42 - hiddenNodes - saveToLocalStorage
43 - pageLoadComplete - loadFromLocalStorage
44 - pageLoadComplete - saveToLocalStorage
*19 - openOptionsPage [deprecated]
*39 - removedNodes - loadFromLocalStorage [deprecated]
*40 - removedNodes - saveToLocalStorage [deprecated]
*41 - hiddenNodes - loadFromLocalStorage [deprecated]
*42 - hiddenNodes - saveToLocalStorage [deprecated]
*43 - pageLoadComplete - loadFromLocalStorage [deprecated]
*44 - pageLoadComplete - saveToLocalStorage [deprecated]
46 - clickSubmit - loadFromLocalStorage
// INSTALL PAGE

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,9 @@
{
"name": "2fas-browser-extension",
"version": "1.6.4",
"version": "1.7.0",
"description": "This is the official Browser Extension for the Open Source 2FAS project.",
"engines": {
"node": "20.9.0"
"node": "20.11.1"
},
"scripts": {
"clean": "npx -y rimraf --glob ./public/*",
@ -23,7 +23,7 @@
"safari-dev": "yon clean && yon generate-locales && npx -y cross-env EXT_PLATFORM=Safari node_modules/.bin/webpack --mode development --progress --config webpack/development.config.js",
"safari-prod": "yon clean && yon generate-locales && yon browserlist-update && npx -y cross-env EXT_PLATFORM=Safari NODE_ENV=production node_modules/.bin/webpack --mode production --progress --config webpack/production.config.js",
"all-build": "npx -y rimraf --glob ./build/* && yon generate-license-info && yon chrome-build && yon opera-build && yon firefox-build && yon edge-build",
"browserlist-update": "npx -y update-browserslist-db@latest",
"browserlist-update": "npx -y browserslist@latest --update-db",
"loco-export": "node webpack/utils/locoExport.js",
"loco-import": "node webpack/utils/locoImport.js",
"check-build-directory": "node webpack/utils/checkBuildDirectoryExists.js",
@ -76,7 +76,7 @@
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-standard": "^5.0.0",
"eslint-webpack-plugin": "^4.0.1",
"eslint-webpack-plugin": "^4.1.0",
"exports-loader": "^5.0.0",
"file-loader": "^6.0.0",
"html-loader": "^5.0.0",
@ -84,13 +84,13 @@
"mini-css-extract-plugin": "^2.8.1",
"noop-loader": "^1.0.0",
"path": "^0.12.7",
"postcss": "^8.4.35",
"postcss": "^8.4.36",
"postcss-loader": "^8.1.1",
"postcss-sass": "^0.5.0",
"precss": "^4.0.0",
"require-dir": "^1.2.0",
"rimraf": "^5.0.1",
"sass": "^1.71.1",
"sass": "^1.72.0",
"sass-loader": "^14.1.1",
"source-map-loader": "^5.0.0",
"stream-browserify": "^3.0.0",

View File

@ -35,10 +35,10 @@
"errorStorageIntegrityMessage": "Please reinstall browser extension or contact with 2FAS Support on Discord",
"warningTooSoonTitle": "Wait a moment",
"warningTooSoonMessage": "Please wait DIFF seconds before sending another request.",
"warningSelectInputTitle": "Select the text field first",
"warningSelectInputMessage": "Select the text field for the 2FA token then click the extension icon or use the chosen shortcut.",
"successPushSentTitle": "Push sent",
"successPushSentMessage": "Please check your phone and accept your login request.",
"successPushSentClipboardTitle": "Push sent",
"successPushSentClipboardMessage": "Please check your phone and accept your login request. Copy your token and paste it into the correct input on the website.",
"successExtNameUpdatedTitle": "Success",
"successExtNameUpdatedMessage": "Extension name updated",
"successDeviceDisconnectedTitle": "Success",
@ -56,6 +56,8 @@
"infoUnsupportedProtocolMessage": "Only HTTP and HTTPS protocols are supported by 2FAS Extension",
"infoBrowserActionWithoutTabTitle": "Info",
"infoBrowserActionWithoutTabMessage": "Using outside the browser is not supported by 2FAS Extension",
"infoCopiedToClipboardTitle": "Successfully copied to clipboard",
"infoCopiedToClipboardMessage": "You can now paste the token into the input field",
"infoTestTitle": "2FAS Notification",
"infoTestMessage": "Hi! This is just a test"
}

View File

@ -0,0 +1,6 @@
{
"tokenHeader": "Your token",
"tokenCopy": "Copy",
"tokenCopied": "Copied",
"tokenDescription": "Copy the token and paste it in the input field. The token will expire in 30 seconds."
}

View File

@ -17,13 +17,25 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
/* global HTMLElement */
const getChildNodes = node => {
if (!(node instanceof HTMLElement)) {
return [];
const config = require('../../config');
const TwoFasNotification = require('../../notification');
const saveToLocalStorage = require('../../localStorage/saveToLocalStorage');
const handleFrontElement = async (activeElements, tabId, storage) => {
let properElements = [];
if (activeElements && activeElements.length > 0) {
properElements = activeElements.filter(el => el.id && (el.nodeName === 'input' || el.nodeName === 'textarea'));
}
return Array.from(node.getElementsByTagName('*'));
if (properElements.length > 0) {
const tabData = storage[`tabData-${tabId}`] || {};
tabData.lastFocusedInput = properElements[0].id;
await saveToLocalStorage({ [`tabData-${tabId}`]: tabData }, storage);
return TwoFasNotification.show(config.Texts.Success.PushSent, tabId);
}
return TwoFasNotification.show(config.Texts.Success.PushSentClipboard, tabId);
};
module.exports = getChildNodes;
module.exports = handleFrontElement;

View File

@ -32,6 +32,7 @@ exports.dummyGetLocalStorage = require('./dummyGetLocalStorage');
exports.generateDefaultStorage = require('./generateDefaultStorage');
exports.getBrowserInfo = require('./getBrowserInfo');
exports.getOSName = require('./getOSName');
exports.handleFrontElement = require('./handleFrontElement');
exports.initBEAction = require('./initBEAction');
exports.onCommand = require('./onCommand');
exports.onConnect = require('./onConnect');
@ -41,6 +42,7 @@ exports.onMessage = require('./onMessage');
exports.onStartup = require('./onStartup');
exports.openBrowserPage = require('./openBrowserPage');
exports.openInstallPage = require('./openInstallPage');
exports.sendMessageToAllFrames = require('./sendMessageToAllFrames');
exports.sendNotificationInfo = require('./sendNotificationInfo');
exports.setIcon = require('./setIcon');
exports.subscribeChannel = require('./subscribeChannel');

View File

@ -23,6 +23,8 @@ const subscribeChannel = require('./subscribeChannel');
const TwoFasNotification = require('../../notification');
const SDK = require('../../sdk');
const storeLog = require('../../partials/storeLog');
const sendMessageToAllFrames = require('./sendMessageToAllFrames');
const handleFrontElement = require('./handleFrontElement');
const initBEAction = (url, tab, storageData) => {
const now = new Date().getTime();
@ -36,10 +38,6 @@ const initBEAction = (url, tab, storageData) => {
const tabData = storage[`tabData-${tab.id}`];
if (!storage[`tabData-${tab?.id}`]?.lastFocusedInput) {
return TwoFasNotification.show(config.Texts.Warning.SelectInput, tab?.id);
}
if (!storage[`tabData-${tab?.id}`]?.lastAction) {
condition = true;
} else {
@ -71,7 +69,8 @@ const initBEAction = (url, tab, storageData) => {
});
})
.then(channel => channel.connect())
.then(() => TwoFasNotification.show(config.Texts.Success.PushSent, tab?.id))
.then(() => sendMessageToAllFrames(tab?.id, { action: 'getActiveElement' }))
.then(elements => handleFrontElement(elements, tab?.id, storage))
.catch(async err => {
await storeLog('error', 5, err, tabData?.url);
return TwoFasNotification.show(config.Texts.Error.UndefinedError, tab?.id);

View File

@ -21,6 +21,7 @@
const getBrowserInfo = require('./getBrowserInfo');
const generateDefaultStorage = require('./generateDefaultStorage');
const storeLog = require('../../partials/storeLog');
const TwoFasNotification = require('../../notification');
const onMessage = (request, sender) => {
return new Promise(resolve => {
@ -56,6 +57,14 @@ const onMessage = (request, sender) => {
.catch(async err => await storeLog('error', 37, err, 'storageReset'));
}
case 'notificationOnBackground': {
if (!request.data) {
return resolve({ status: 'No data' });
}
return TwoFasNotification.show(request.data, request.tabID);
}
default: {
return resolve({ status: 'Empty action' });
}

View File

@ -17,29 +17,30 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const updateEventListener = require('./updateEventListener');
const inputFocus = require('./inputFocus');
const browser = require('webextension-polyfill');
const addInputListener = (elements, tabID) => {
if (!Array.isArray(elements) || elements?.length <= 0) {
return false;
}
let func = event => inputFocus(event, tabID);
const sendMessageToAllFrames = async (tabId, message) => {
const frames = await browser.webNavigation.getAllFrames({ tabId });
elements.map(input => {
if (input?.dataset?.twofasInputListener === 'true' && input?.dataset.twofasInput) {
if (input === document.activeElement || input.matches(':focus')) {
return func({ target: input });
} else {
return Promise.all(frames.map(frame => {
return browser.tabs.sendMessage(tabId, message, { frameId: frame.frameId }).catch(() => Promise.resolve(false));
})).then(res => {
return res.map(frame => {
if (!frame) {
return false;
}
}
return updateEventListener(input, func);
});
func = null;
switch (frame?.status) {
case 'activeElement': {
return frame;
}
default: {
return false;
}
}
});
}).catch(() => false);
};
module.exports = addInputListener;
module.exports = sendMessageToAllFrames;

View File

@ -23,7 +23,7 @@ const t = require('./_locales/en/notifications.json');
const config = {
WebSocketTimeout: 3, // in minutes
ResendPushTimeout: 10, // in seconds
ExtensionVersion: '1.6.4',
ExtensionVersion: '1.7.0',
Texts: {
Error: {
@ -104,10 +104,6 @@ const config = {
Title: browser.i18n.getMessage('warningTooSoonTitle') || t.warningTooSoonTitle,
Message: (browser.i18n.getMessage('warningTooSoonMessage') || t.warningTooSoonMessage).replace('DIFF', 10 - Math.round(diff))
}
},
SelectInput: {
Title: browser.i18n.getMessage('warningSelectInputTitle') || t.warningSelectInputTitle,
Message: browser.i18n.getMessage('warningSelectInputMessage') || t.warningSelectInputMessage
}
},
Success: {
@ -115,6 +111,10 @@ const config = {
Title: browser.i18n.getMessage('successPushSentTitle') || t.successPushSentTitle,
Message: browser.i18n.getMessage('successPushSentMessage') || t.successPushSentMessage
},
PushSentClipboard: {
Title: browser.i18n.getMessage('successPushSentClipboardTitle') || t.successPushSentClipboardTitle,
Message: browser.i18n.getMessage('successPushSentClipboardMessage') || t.successPushSentClipboardMessage
},
ExtNameUpdated: {
Title: browser.i18n.getMessage('successExtNameUpdatedTitle') || t.successExtNameUpdatedTitle,
Message: browser.i18n.getMessage('successExtNameUpdatedMessage') || t.successExtNameUpdatedMessage
@ -150,10 +150,20 @@ const config = {
Title: browser.i18n.getMessage('infoBrowserActionWithoutTabTitle') || t.infoBrowserActionWithoutTabTitle,
Message: browser.i18n.getMessage('infoBrowserActionWithoutTabMessage') || t.infoBrowserActionWithoutTabMessage
},
CopiedToClipboard: {
Title: browser.i18n.getMessage('infoCopiedToClipboardTitle') || t.infoCopiedToClipboardTitle,
Message: browser.i18n.getMessage('infoCopiedToClipboardMessage') || t.infoCopiedToClipboardMessage
},
Test: {
Title: browser.i18n.getMessage('infoTestTitle') || t.infoTestTitle,
Message: browser.i18n.getMessage('infoTestMessage') || t.infoTestMessage
}
},
Token: {
Header: browser.i18n.getMessage('tokenHeader') || t.tokenHeader,
Copy: browser.i18n.getMessage('tokenCopy') || t.tokenCopy,
Copied: browser.i18n.getMessage('tokenCopied') || t.tokenCopied,
Description: browser.i18n.getMessage('tokenDescription') || t.tokenDescription
}
}
};

View File

@ -19,8 +19,7 @@
import './styles/content_script.scss';
const browser = require('webextension-polyfill');
const { observe, createObserver } = require('./observer');
const { getTabData, getInputs, addInputListener, portSetup, isInFrame, addFormElementsNumber, getFormElements } = require('./functions');
const { getTabData, portSetup, isInFrame } = require('./functions');
const contentOnMessage = require('./events/contentOnMessage');
const { loadFromLocalStorage, saveToLocalStorage } = require('../localStorage');
const storeLog = require('../partials/storeLog');
@ -67,14 +66,7 @@ const contentScriptRun = async () => {
storage = null;
}
addInputListener(getInputs(), tabData?.id);
addFormElementsNumber(getFormElements());
const mutationObserver = createObserver(tabData);
observe(mutationObserver);
window.addEventListener('beforeunload', async () => {
mutationObserver.disconnect();
browser.runtime.onMessage.removeListener(onMessageListener);
}, { once: true });
};

View File

@ -19,7 +19,7 @@
const config = require('../../config');
const loadFromLocalStorage = require('../../localStorage/loadFromLocalStorage');
const { notification, inputToken, getTokenInput, showNotificationInfo, loadFonts, isInFrame, pageLoadComplete } = require('../functions');
const { notification, inputToken, getTokenInput, showNotificationInfo, loadFonts, isInFrame, getActiveElement, tokenNotification } = require('../functions');
const storeLog = require('../../partials/storeLog');
const contentOnMessage = async (request, tabData) => {
@ -37,20 +37,7 @@ const contentOnMessage = async (request, tabData) => {
return storeLog('error', 17, err, 'contentOnMessage loadFromLocalStorage');
}
if (!storage || !storage[`tabData-${tabData?.id}`]) {
if (isInFrame()) {
return false;
}
return {
status: 'notification',
title: config.Texts.Warning.SelectInput.Title,
message: config.Texts.Warning.SelectInput.Message
};
}
if (storage[`tabData-${tabData?.id}`].requestID !== request.token_request_id) {
// No matching requestID
if (!storage || !storage[`tabData-${tabData?.id}`] || storage[`tabData-${tabData?.id}`].requestID !== request.token_request_id) {
if (isInFrame()) {
return false;
}
@ -63,17 +50,25 @@ const contentOnMessage = async (request, tabData) => {
}
const lastFocusedInput = storage[`tabData-${tabData?.id}`].lastFocusedInput;
const tokenInput = getTokenInput(lastFocusedInput);
let tokenInput;
if (!tokenInput) {
return { status: 'elementNotFound' };
if (lastFocusedInput) {
tokenInput = getTokenInput(lastFocusedInput);
}
if (!lastFocusedInput || !tokenInput) {
return tokenNotification(request.token);
} else {
return inputToken(request, tokenInput, tabData?.url);
}
}
return inputToken(request, tokenInput, tabData?.url);
case 'getActiveElement': {
return getActiveElement();
}
case 'pageLoadComplete': {
return pageLoadComplete(tabData?.id);
return { status: 'ok' }; // Possibly for future use
}
case 'notification':
@ -83,7 +78,7 @@ const contentOnMessage = async (request, tabData) => {
if (request.action === 'notification') {
return notification(request);
} else if (request.action === 'notificationInfo') {
return showNotificationInfo(request);
return showNotificationInfo();
}
break;

View File

@ -17,28 +17,27 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const updateEventListener = (input, func) => {
const removeListener = () => {
if (typeof input.removeEventListener === 'function') {
input.removeEventListener('focus', func);
const { loadFromLocalStorage, saveToLocalStorage } = require('../../localStorage')
const clearAfterInputToken = (inputElement, tabID) => {
// CLEAR INPUT
if (inputElement) {
if (typeof inputElement?.removeAttribute === 'function') {
inputElement.removeAttribute('data-twofas-input');
}
};
removeListener();
if (typeof input.addEventListener === 'function') {
input.addEventListener('focus', func);
}
if (input && input?.dataset) {
input.dataset.twofasInputListener = 'true';
}
// CLEAR STORAGE
return loadFromLocalStorage([`tabData-${tabID}`])
.then(storage => {
if (storage[`tabData-${tabID}`] && storage[`tabData-${tabID}`].lastFocusedInput) {
delete storage[`tabData-${tabID}`].lastFocusedInput;
return saveToLocalStorage({ [`tabData-${tabID}`]: storage[`tabData-${tabID}`] });
}
if (input === document.activeElement || input.matches(':focus')) {
func({ target: input });
}
window.addEventListener('onbeforeunload', removeListener, { once: true });
return true;
})
.catch(() => {});
};
module.exports = updateEventListener;
module.exports = clearAfterInputToken;

View File

@ -17,14 +17,38 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const addInputListener = require('../functions/addInputListener');
const getInputs = require('../functions/getInputs');
const addFormElementsNumber = require('../functions/addFormElementsNumber');
const getFormElements = require('../functions/getFormElements');
const { v4: uuidv4 } = require('uuid');
const clearFormElementsNumber = require('./clearFormElementsNumber');
const addFormElementsNumber = require('./addFormElementsNumber');
const getFormElements = require('./getFormElements');
const pageLoadComplete = async tabID => {
addInputListener(getInputs(), tabID);
const getActiveElement = () => {
const activeElement = document.activeElement;
let nodeName;
if (activeElement) {
nodeName = activeElement.nodeName.toLowerCase();
}
if (!activeElement || (nodeName !== 'input' && nodeName !== 'textarea')) {
return {
status: 'activeElement',
nodeName,
id: null
};
}
const inputUUID = uuidv4();
activeElement.setAttribute('data-twofas-input', inputUUID);
clearFormElementsNumber();
addFormElementsNumber(getFormElements());
return {
status: 'activeElement',
nodeName,
id: inputUUID
};
};
module.exports = pageLoadComplete;
module.exports = getActiveElement;

View File

@ -1,27 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const inputsSelectors = require('../../partials/inputsSelectors')();
const getInputs = node => {
const el = node || document;
return Array.from(el.querySelectorAll(inputsSelectors));
};
module.exports = getInputs;

View File

@ -18,24 +18,21 @@
//
exports.addFormElementsNumber = require('./addFormElementsNumber');
exports.addInputListener = require('./addInputListener');
exports.clearAfterInputToken = require('./clearAfterInputToken');
exports.clearFormElementsNumber = require('./clearFormElementsNumber');
exports.clickSubmit = require('./clickSubmit');
exports.closeNotificationInfo = require('./closeNotificationInfo');
exports.getActiveElement = require('./getActiveElement');
exports.getFormElements = require('./getFormElements');
exports.getFormSubmitElements = require('./getFormSubmitElements');
exports.getInputs = require('./getInputs');
exports.getTabData = require('./getTabData');
exports.getTokenInput = require('./getTokenInput');
exports.inputFocus = require('./inputFocus');
exports.inputToken = require('./inputToken');
exports.isInFrame = require('./isInFrame');
exports.isVisible = require('./isVisible');
exports.loadFonts = require('./loadFonts');
exports.neverShowNotificationInfo = require('./neverShowNotificationInfo');
exports.notification = require('./notification');
exports.openOptionsPage = require('./openOptionsPage');
exports.pageLoadComplete = require('./pageLoadComplete');
exports.portSetup = require('./portSetup');
exports.showNotificationInfo = require('./showNotificationInfo');
exports.updateEventListener = require('./updateEventListener');
exports.tokenNotification = require('./tokenNotification');

View File

@ -1,61 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const browser = require('webextension-polyfill');
const { loadFromLocalStorage, saveToLocalStorage } = require('../../localStorage');
const storeLog = require('../../partials/storeLog');
const { v4: uuidv4 } = require('uuid');
const inputFocus = async (event, tabID) => {
if (!browser?.runtime?.id || !event || !event?.target) {
return false;
}
let storage;
const el = event.target;
try {
storage = await loadFromLocalStorage([`tabData-${tabID}`]);
} catch (err) {
return storeLog('error', 16, err, 'inputFocus - no URL/tabID');
}
try {
const tabData = storage[`tabData-${tabID}`] ? storage[`tabData-${tabID}`] : {};
if (
el?.dataset?.twofasInput?.length > 0 &&
(typeof el?.dataset?.twofasInput === 'string' || el?.dataset?.twofasInput instanceof String)
) {
tabData.lastFocusedInput = el.dataset.twofasInput;
} else {
if (typeof el?.setAttribute === 'function') {
const inputUUID = uuidv4();
el.setAttribute('data-twofas-input', inputUUID);
tabData.lastFocusedInput = inputUUID;
}
}
return saveToLocalStorage({ [`tabData-${tabID}`]: tabData }, storage);
} catch (err) {
return storeLog('error', 16, err, storage[`tabData-${tabID}`]?.url);
}
};
module.exports = inputFocus;

View File

@ -21,6 +21,7 @@
const delay = require('../../partials/delay');
const getTabData = require('./getTabData');
const clickSubmit = require('./clickSubmit');
const clearAfterInputToken = require('./clearAfterInputToken');
const inputToken = (request, inputElement, siteURL) => {
return new Promise(resolve => {
@ -81,6 +82,8 @@ const inputToken = (request, inputElement, siteURL) => {
clickSubmit(inputElement, siteURL);
}
clearAfterInputToken(inputElement, tab.id);
return resolve({
status: 'completed',
url: siteURL

View File

@ -1,32 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
/* global IntersectionObserver */
const isVisible = domElement => {
return new Promise(resolve => {
const o = new IntersectionObserver(([entry]) => {
resolve(entry.intersectionRatio === 1);
o.disconnect();
});
o.observe(domElement);
});
};
module.exports = isVisible;

View File

@ -69,21 +69,30 @@ const notification = request => {
setTimeout(() => n.notification.classList.add('visible'), 300);
window.addEventListener('beforeunload', () => {
n.notification.classList.remove('visible');
if (n && n.notification) {
n.notification.classList.remove('visible');
}
setTimeout(() => {
n.notification.classList.add('hidden');
n = null;
if (n && n.notification) {
n.notification.classList.add('hidden');
n = null;
}
}, 300);
});
if (request.timeout) {
setTimeout(() => {
n.notification.classList.remove('visible');
if (n && n.notification) {
n.notification.classList.remove('visible');
}
}, 5300);
setTimeout(() => {
n.notification.classList.add('hidden');
n = null;
if (n && n.notification) {
n.notification.classList.add('hidden');
n = null;
}
}, 5600);
}
};

View File

@ -0,0 +1,146 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const config = require('../../config');
const isInFrame = require('./isInFrame');
const { createElement, createSVGElement, createTextElement } = require('../../partials/DOMElements');
const iconSrc = require('../../images/notification-logo.svg');
const copySrc = require('../../images/copy-icon.svg');
const closeSrc = require('../../images/notification-close.svg');
const S = require('../../selectors');
const tokenNotification = token => {
if (isInFrame()) {
return false;
}
let n = {
container: document.querySelector(S.notification.container),
notification: null,
firstCol: null,
logo: null,
secondCol: null,
header: null,
h3: null,
tokenBox: null,
tokenText: null,
tokenIconContainer: null,
tokenIcon: null,
tokenButton: null,
tokenButtonText: null,
notificationText: null,
p: null,
closeBtn: null,
close: null
};
const closeNotification = e => {
if (e) {
e.preventDefault();
e.stopPropagation();
}
if (n && n.notification) {
n.notification.classList.remove('visible');
}
setTimeout(() => {
if (n && n.notification) {
n.notification.classList.add('hidden');
n = null;
}
}, 300);
};
if (!n.container) {
n.container = createElement('div', 'twofas-be-notifications');
window.top.document.body.appendChild(n.container);
}
n.notification = createElement('div', 'twofas-be-notification');
n.closeBtn = createElement('button', 'twofas-be-notification-close');
n.closeBtn.addEventListener('click', closeNotification);
n.close = createSVGElement(closeSrc);
n.closeBtn.appendChild(n.close);
n.notification.appendChild(n.closeBtn);
n.firstCol = createElement('div', 'twofas-be-col');
n.logo = createSVGElement(iconSrc);
n.firstCol.appendChild(n.logo);
n.notification.appendChild(n.firstCol);
n.secondCol = createElement('div', 'twofas-be-col');
n.header = createElement('div', 'twofas-be-notification-header');
n.h3 = createTextElement('h3', config.Texts.Token.Header);
n.header.appendChild(n.h3);
n.secondCol.appendChild(n.header);
n.tokenBox = createElement('div', 'twofas-be-notification-token-box');
n.tokenText = createTextElement('p', token, 'twofas-be-notification-token-box-text');
n.tokenButton = createElement('button', 'twofas-be-notification-token-box-copy-button');
n.tokenButton.addEventListener('click', () => {
navigator.clipboard.writeText(token);
n.tokenButtonText.innerText = config.Texts.Token.Copied;
setTimeout(() => {
n.tokenButtonText.innerText = config.Texts.Token.Copy;
}, 1000);
});
n.tokenButtonText = createTextElement('span', config.Texts.Token.Copy);
n.tokenIconContainer = createElement('div', 'twofas-be-notification-token-box-copy-icon');
n.tokenIcon = createSVGElement(copySrc);
n.tokenButton.appendChild(n.tokenButtonText);
n.tokenIconContainer.appendChild(n.tokenIcon);
n.tokenButton.appendChild(n.tokenIconContainer);
n.tokenBox.appendChild(n.tokenText);
n.tokenBox.appendChild(n.tokenButton);
n.secondCol.appendChild(n.tokenBox);
n.notificationText = createElement('div', 'twofas-be-notification-text');
n.p = createTextElement('p', config.Texts.Token.Description);
n.notificationText.appendChild(n.p);
n.secondCol.appendChild(n.notificationText);
n.notification.appendChild(n.secondCol);
n.container.appendChild(n.notification);
setTimeout(() => n.notification.classList.add('visible'), 300);
window.addEventListener('beforeunload', () => {
closeNotification();
});
setTimeout(() => {
if (n && n.notification) {
n.notification.classList.remove('visible');
}
}, 30300);
setTimeout(() => {
if (n && n.notification) {
n.notification.classList.add('hidden');
n = null;
}
}, 30600);
};
module.exports = tokenNotification;

View File

@ -1,65 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
/* global MutationObserver, HTMLElement */
const addedNodes = require('./observerFunctions/addedNodes');
const hiddenNodes = require('./observerFunctions/hiddenNodes');
const removedNodes = require('./observerFunctions/removedNodes');
const notObservedNodes = require('./observerConstants/notObservedNodes');
const notObservedAttributes = require('./observerConstants/notObservedAttributes');
const createObserver = tabData => {
return new MutationObserver(mutations => {
if (!mutations) {
return false;
}
mutations.forEach(async mutation => {
const mutationNodeName = mutation.target.nodeName.toLowerCase();
if (
!mutation ||
!(mutation?.target instanceof HTMLElement) ||
mutation?.target?.classList?.contains('twofas-be-notification') ||
notObservedAttributes.includes(mutation?.attributeName) ||
notObservedNodes.includes(mutationNodeName)
) {
return false;
}
if (
(mutation?.addedNodes && Array.from(mutation?.addedNodes).length > 0) ||
(mutation?.attributeName === 'disabled' && !mutation?.target?.disabled) ||
(mutation?.attributeName === 'style' && mutation?.target)
) {
addedNodes(mutation, tabData);
}
if (mutation?.type === 'attributes' && mutation?.target) {
hiddenNodes(mutation, tabData);
}
if (mutation?.removedNodes && Array.from(mutation?.removedNodes).length > 0) {
removedNodes(mutation, tabData);
}
});
});
};
module.exports = createObserver;

View File

@ -1,21 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
exports.createObserver = require('./createObserver');
exports.observe = require('./observe');

View File

@ -1,33 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const observe = mutationObserver => {
const el = document?.body || document;
return mutationObserver.observe(el, {
attributes: true,
characterData: false,
childList: true,
subtree: true,
attributeOldValue: false,
characterDataOldValue: false
});
};
module.exports = observe;

View File

@ -1,23 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
exports.notObservedAttributes = require('./notObservedAttributes');
exports.notObservedNodes = require('./notObservedNodes');
exports.significantNodes = require('./significantNodes');
exports.significantInputs = require('./significantInputs');

View File

@ -1,132 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const notObservedAttributes = [
// 2FAS
'data-twofas-element-number',
'data-twofas-input-listener',
// CUSTOM
'data-ng-animate',
'data-submitting',
// GLOBAL ATTRIBUTES
'href',
'acceskey',
'autocapitalize',
'contenteditable',
'dir',
'draggable',
'enterkeyhint',
'exportsparts',
'inert',
'inputmode',
'is',
'itemid',
'itemprop',
'itemref',
'itemscope',
'itemtype',
'lang',
'nonce',
'part',
'popover',
'slot',
'spellcheck',
'tabindex',
'title',
'translate',
'virtualkeyboardpolicy',
'role',
'accept',
'autocomplete',
'autocorrect',
'capture',
'crossorigin',
'dirname',
'elementtiming',
'for',
'max',
'maxlength',
'min',
'minlength',
'multiple',
'pattern',
'placeholder',
'readonly',
'rel',
'required',
'size',
'step',
// ARIA
'aria-activedescendant',
'aria-atomic',
'aria-autocomplete',
'aria-braillelabel',
'aria-brailleroledescription',
'aria-busy',
'aria-checked',
'aria-colcount',
'aria-colindex',
'aria-colindextext',
'aria-colspan',
'aria-controls',
'aria-current',
'aria-describedby',
'aria-description',
'aria-details',
'aria-disabled',
'aria-dropeffect',
'aria-errormessage',
'aria-expanded',
'aria-flowto',
'aria-grabbed',
'aria-haspopup',
'aria-hidden',
'aria-invalid',
'aria-keyshortcuts',
'aria-label',
'aria-labelledby',
'aria-level',
'aria-live',
'aria-modal',
'aria-multiline',
'aria-multiselectable',
'aria-orientation',
'aria-owns',
'aria-placeholder',
'aria-posinset',
'aria-pressed',
'aria-readonly',
'aria-relevant',
'aria-required',
'aria-roledescription',
'aria-rowcount',
'aria-rowindex',
'aria-rowindextext',
'aria-rowspan',
'aria-selected',
'aria-setsize',
'aria-sort',
'aria-valuemax',
'aria-valuemin',
'aria-valuenow',
'aria-valuetext',
'value'
];
module.exports = notObservedAttributes;

View File

@ -1,105 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const notObservedNodes = [
'a',
'g',
'path',
'html',
'body',
'head',
'link',
'style',
'script',
'noscript',
'title',
'#cdata-section',
'#comment',
'#text',
'abbr',
'address',
'area',
'audio',
'b',
'base',
'bdi',
'bdo',
'blockquote',
'br',
'button',
'canvas',
'caption',
'cite',
'code',
'data',
'dd',
'del',
'details',
'dfn',
'dialog',
'dl',
'dt',
'em',
'embed',
'figure',
'hr',
'i',
'img',
'ins',
'label',
'legend',
'map',
'mark',
'meta',
'meter',
'object',
'optgroup',
'option',
'output',
'param',
'picture',
'pre',
'progress',
'q',
'rp',
'rt',
'ruby',
's',
'samp',
'search',
'select',
'small',
'source',
'strong',
'sub',
'sup',
'svg',
'summary',
'template',
'time',
'track',
'u',
'var',
'video',
'wbr',
// custom
'tool-tip'
];
module.exports = notObservedNodes;

View File

@ -1,25 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const significantInputs = [
'input',
'textarea'
];
module.exports = significantInputs;

View File

@ -1,25 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const significantNodes = [
'input',
'textarea'
];
module.exports = significantNodes;

View File

@ -1,101 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const browser = require('webextension-polyfill');
const findSignificantChanges = require('./findSignificantChanges');
const { getInputs, addInputListener, clearFormElementsNumber, addFormElementsNumber, getFormElements } = require('../../functions');
const getChildNodes = require('./getChildNodes');
const storeLog = require('../../../partials/storeLog');
const notObservedNodes = require('../observerConstants/notObservedNodes');
const uniqueOnly = require('../../../partials/uniqueOnly');
let queue = [];
let tabData = null;
let timeout;
const process = nodes => {
if (document.readyState !== 'complete') {
timeout = window.requestAnimationFrame(() => process(nodes));
}
if (!nodes || nodes.length <= 0 || !tabData) {
return false;
}
const addedNodes =
nodes
.filter(uniqueOnly)
.filter(node => !notObservedNodes.includes(node.nodeName.toLowerCase()))
.flatMap(getChildNodes)
.filter(uniqueOnly)
.filter(node => !notObservedNodes.includes(node.nodeName.toLowerCase()));
let newInputs = false;
let inputs = [];
for (const node in addedNodes) {
if (findSignificantChanges(addedNodes[node])) {
newInputs = true;
}
}
if (!newInputs) {
for (const node in addedNodes) {
inputs.push(...getInputs(addedNodes[node]));
}
inputs = inputs.filter(node => !node.hasAttribute('data-twofas-input'));
newInputs = inputs.length > 0;
} else {
inputs = getInputs();
}
if (newInputs) {
try {
addInputListener(inputs, tabData?.id);
clearFormElementsNumber();
addFormElementsNumber(getFormElements());
} catch (err) {
return storeLog('error', 15, err, tabData?.url);
}
}
queue = [];
};
const addedNodes = (mutation, tabInfo) => {
if (!mutation?.target || !browser?.runtime?.id) {
return false;
}
queue.push(mutation.target);
queue.push(...Array.from(mutation.addedNodes));
if (!tabData) {
tabData = tabInfo;
}
if (timeout) {
window.cancelAnimationFrame(timeout);
}
timeout = window.requestAnimationFrame(() => process(queue));
};
module.exports = addedNodes;

View File

@ -1,26 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const significantNodes = require('../observerConstants/significantNodes');
const findSignificantChanges = node => {
return significantNodes.includes(node.nodeName.toLowerCase());
};
module.exports = findSignificantChanges;

View File

@ -1,101 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const browser = require('webextension-polyfill');
const isVisible = require('../../functions/isVisible');
const findSignificantChanges = require('./findSignificantChanges');
const getChildNodes = require('./getChildNodes');
const { loadFromLocalStorage, saveToLocalStorage } = require('../../../localStorage');
const storeLog = require('../../../partials/storeLog');
const { clearFormElementsNumber, addFormElementsNumber, getFormElements } = require('../../functions');
const uniqueOnly = require('../../../partials/uniqueOnly');
let queue = [];
let tabData = null;
let timeout;
const process = async nodes => {
if (document.readyState !== 'complete') {
timeout = window.requestAnimationFrame(() => process(nodes));
}
if (!nodes || nodes.length <= 0 || !tabData) {
return false;
}
const hiddenNodes =
nodes
.filter(uniqueOnly)
.filter(node => findSignificantChanges(node) && node.getAttribute('data-twofas-input'))
.flatMap(getChildNodes)
.filter(uniqueOnly)
.filter(node => findSignificantChanges(node) && node.getAttribute('data-twofas-input'));
let storage;
clearFormElementsNumber();
addFormElementsNumber(getFormElements());
try {
storage = await loadFromLocalStorage([`tabData-${tabData?.id}`]);
} catch (err) {
return storeLog('error', 41, err, tabData?.url);
}
if (!storage[`tabData-${tabData?.id}`] || !storage[`tabData-${tabData?.id}`].lastFocusedInput) {
return false;
}
return hiddenNodes.forEach(async node => {
const visible = await isVisible(node);
if (node.getAttribute('data-twofas-input') === storage[`tabData-${tabData?.id}`].lastFocusedInput && !visible) {
delete storage[`tabData-${tabData?.id}`].lastFocusedInput;
if (document?.activeElement && document?.activeElement?.getAttribute('data-twofas-input')) {
storage[`tabData-${tabData?.id}`].lastFocusedInput = document.activeElement.getAttribute('data-twofas-input');
}
return saveToLocalStorage({ [`tabData-${tabData?.id}`]: storage[`tabData-${tabData?.id}`] })
.catch(err => storeLog('error', 42, err, tabData?.url));
}
queue = [];
});
};
const hiddenNodes = (mutation, tabInfo) => {
if (!mutation?.target || !browser?.runtime?.id) {
return false;
}
queue.push(mutation.target);
if (!tabData) {
tabData = tabInfo;
}
if (timeout) {
window.cancelAnimationFrame(timeout);
}
timeout = window.requestAnimationFrame(() => process(queue));
};
module.exports = hiddenNodes;

View File

@ -1,23 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
exports.addedNodes = require('./addedNodes');
exports.getChildNodes = require('./getChildNodes');
exports.hiddenNodes = require('./hiddenNodes');
exports.removedNodes = require('./removedNodes');

View File

@ -1,113 +0,0 @@
//
// This file is part of the 2FAS Browser Extension (https://github.com/twofas/2fas-browser-extension)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Zając. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
const browser = require('webextension-polyfill');
const significantInputs = require('../observerConstants/significantInputs');
const { loadFromLocalStorage, saveToLocalStorage } = require('../../../localStorage');
const getChildNodes = require('./getChildNodes');
const storeLog = require('../../../partials/storeLog');
const { clearFormElementsNumber, addFormElementsNumber, getFormElements } = require('../../functions');
const notObservedNodes = require('../observerConstants/notObservedNodes');
const uniqueOnly = require('../../../partials/uniqueOnly');
let queue = [];
let tabData = null;
let timeout;
const process = async nodes => {
if (document.readyState !== 'complete') {
timeout = window.requestAnimationFrame(() => process(nodes));
}
if (!nodes || nodes.length <= 0 || !tabData) {
return false;
}
const ids = [];
let storage;
const removedNodes =
nodes
.filter(uniqueOnly)
.filter(node => !notObservedNodes.includes(node.nodeName.toLowerCase()))
.flatMap(getChildNodes)
.filter(uniqueOnly)
.filter(node => !notObservedNodes.includes(node.nodeName.toLowerCase()));
removedNodes.forEach(node => {
const nodeName = node.nodeName.toLowerCase();
if (!significantInputs.includes(nodeName)) {
return false;
}
const twofasInput = node.getAttribute('data-twofas-input');
if (twofasInput) {
ids.push(twofasInput);
}
});
queue = [];
if (ids.length <= 0) {
return false;
}
clearFormElementsNumber();
addFormElementsNumber(getFormElements());
try {
storage = await loadFromLocalStorage([`tabData-${tabData?.id}`]);
} catch (err) {
return storeLog('error', 39, err, tabData?.url);
}
if (!storage[`tabData-${tabData?.id}`] || !storage[`tabData-${tabData?.id}`].lastFocusedInput) {
return false;
}
if (ids.includes(storage[`tabData-${tabData?.id}`].lastFocusedInput)) {
delete storage[`tabData-${tabData?.id}`].lastFocusedInput;
}
return saveToLocalStorage({ [`tabData-${tabData?.id}`]: storage[`tabData-${tabData?.id}`] })
.catch(err => storeLog('error', 40, err, tabData?.url));
};
const removedNodes = (mutation, tabInfo) => {
if (!mutation?.target || !browser?.runtime?.id) {
return false;
}
queue.push(mutation.target);
queue.push(...Array.from(mutation.removedNodes));
if (!tabData) {
tabData = tabInfo;
}
if (timeout) {
window.cancelAnimationFrame(timeout);
}
timeout = window.requestAnimationFrame(() => process(queue));
};
module.exports = removedNodes;

View File

@ -33,6 +33,7 @@
top: 25px !important;
user-select: none !important;
z-index: 999999 !important;
z-index: infinite !important;
@media all and (max-width: 400px) {
padding: 10px !important;
@ -56,9 +57,12 @@
margin-bottom: 25px !important;
max-width: 380px !important;
overflow: hidden !important;
position: relative !important;
text-align: left !important;
transform: translateX(425px) !important;
transition: box-shadow .3s ease-in-out, max-height .3s ease-in-out, margin-bottom .3s ease-in-out, transform .3s ease-in-out !important;
z-index: 999999 !important;
z-index: infinite !important;
&.visible {
box-shadow: 0 0 20px 5px $shadow-color !important;
@ -70,6 +74,24 @@
max-height: 0 !important;
}
&-close {
appearance: none !important;
background: transparent !important;
border: 0 !important;
cursor: pointer !important;
padding: 0 !important;
position: absolute !important;
right: 2px !important;
top: 2px !important;
> svg {
height: auto !important;
margin-right: 0 !important;
max-width: unset !important;
width: 24px !important;
}
}
&-buttons {
border-radius: 0 !important;
box-shadow: none !important;
@ -120,6 +142,84 @@
}
}
&-token-box {
align-items: center !important;
display: flex !important;
flex-direction: row !important;
justify-content: space-between !important;
margin-bottom: 10px !important;
> p {
&.twofas-be-notification-token-box-text {
color: $color !important;
font-size: 32px !important;
font-weight: 700 !important;
@media all and (max-width: 400px) {
font-size: 24px !important;
}
}
}
> button {
&.twofas-be-notification-token-box-copy-button {
align-items: center !important;
appearance: none !important;
background-color: $theme-color !important;
border: 0 !important;
border-radius: 20px !important;
color: $color-2 !important;
cursor: pointer !important;
display: inline-flex !important;
flex-direction: row !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 40px !important;
justify-content: space-between !important;
letter-spacing: .9px !important;
line-height: 40px !important;
padding: 0 5px 0 16px !important;
outline: none !important;
text-align: center !important;
text-decoration: none !important;
text-transform: uppercase !important;
transition: background-color .2s ease-in-out, color .2s ease-in-out !important;
&:hover {
background-color: color.adjust($theme-color, $lightness: -10%) !important;
color: $color-2 !important;
}
&:active {
background-color: color.adjust($theme-color, $lightness: -20%) !important;
color: $color-2 !important;
}
> span {
flex-grow: 1 !important;
margin-right: 8px !important;
}
> .twofas-be-notification-token-box-copy-icon {
align-items: center !important;
background-color: $color-2 !important;
border-radius: 50% !important;
display: flex !important;
justify-content: center !important;
height: 32px !important;
width: 32px !important;
> svg {
height: 16px !important;
margin-right: 0 !important;
max-width: 100% !important;
width: auto !important;
}
}
}
}
}
.twofas-be-col {
flex-shrink: unset !important;
font-family: 'Montserrat', sans-serif !important;
@ -134,6 +234,13 @@
&:last-of-type {
margin-right: 18px !important;
}
> svg {
height: 34px !important;
margin-right: 16px !important;
max-width: 28px !important;
width: 28px !important;
}
}
/* ProtonMail FIX */
@ -146,13 +253,6 @@
}
/* ProtonMail FIX */
svg {
height: 34px !important;
margin-right: 16px;
max-width: 28px !important;
width: 28px !important;
}
h3,
p {
font-family: 'Montserrat', sans-serif !important;

6
src/images/copy-icon.svg Normal file
View File

@ -0,0 +1,6 @@
<svg viewBox="0 0 21 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="copy">
<rect id="Rectangle 2" x="5.74951" y="1.25003" width="14.4997" height="14.4997" rx="1.25" stroke="#ED1C24" stroke-width="1.5"/>
<path id="Rectangle 3" d="M1.00049 4.50011V18.4998C1.00049 19.6044 1.89592 20.4998 3.00049 20.4998H17.0002" stroke="#ED1C24" stroke-width="1.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 373 B

View File

@ -0,0 +1,4 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="2.143" height="21.429" rx="1.071" transform="rotate(-45 21.339 -7.324)" fill="#B3B3B3"/>
<rect x="26.581" y="11.429" width="2.143" height="21.429" rx="1.071" transform="rotate(45 26.581 11.429)" fill="#B3B3B3"/>
</svg>

After

Width:  |  Height:  |  Size: 304 B

View File

@ -3,7 +3,7 @@
"name": "2FAS - Two Factor Authentication",
"short_name": "2FAS",
"author": "Two Factor Authentication Service, Inc.",
"version": "1.6.4",
"version": "1.7.0",
"description": "__MSG_appDesc__",
"default_locale": "en",
"icons": {
@ -71,7 +71,8 @@
"tabs",
"storage",
"notifications",
"contextMenus"
"contextMenus",
"webNavigation"
],
"host_permissions": [
"*://*.2fas.com/*"

View File

@ -3,7 +3,7 @@
"name": "2FAS - Two Factor Authentication",
"short_name": "2FAS",
"author": "Two Factor Authentication Service, Inc.",
"version": "1.6.4",
"version": "1.7.0",
"description": "__MSG_appDesc__",
"default_locale": "en",
"icons": {
@ -71,7 +71,8 @@
"tabs",
"storage",
"notifications",
"contextMenus"
"contextMenus",
"webNavigation"
],
"host_permissions": [
"*://*.2fas.com/*"

View File

@ -3,7 +3,7 @@
"name": "2FAS - Two Factor Authentication",
"short_name": "2FAS",
"author": "Two Factor Authentication Service, Inc.",
"version": "1.6.4",
"version": "1.7.0",
"applications": {
"gecko": {
"id": "admin@2fas.com",
@ -77,6 +77,7 @@
"storage",
"notifications",
"contextMenus",
"webNavigation",
"https://*.2fas.com/*",
"wss://*.2fas.com/*"
],

View File

@ -3,7 +3,7 @@
"name": "2FAS - Two Factor Authentication",
"short_name": "2FAS",
"author": "Two Factor Authentication Service, Inc.",
"version": "1.6.4",
"version": "1.7.0",
"description": "__MSG_appDesc__",
"default_locale": "en",
"icons": {
@ -70,7 +70,8 @@
"tabs",
"storage",
"notifications",
"contextMenus"
"contextMenus",
"webNavigation"
],
"host_permissions": [
"*://*.2fas.com/*"

View File

@ -3,7 +3,7 @@
"name": "2FAS - Two Factor Authentication",
"short_name": "2FAS",
"author": "Two Factor Authentication Service, Inc.",
"version": "1.6.4",
"version": "1.7.0",
"description": "__MSG_appDesc__",
"default_locale": "en",
"icons": {
@ -77,6 +77,7 @@
"storage",
"notifications",
"contextMenus",
"webNavigation",
"https://*.2fas.com/*",
"wss://*.2fas.com/*"
],

View File

@ -92,9 +92,11 @@ const storeLog = async (level, logID = 0, errObj, url = '') => {
(storage?.browserInfo?.browser_name === 'Chrome' && storage?.browserInfo?.browser_version === '107' && logID === 14) ||
(storage?.browserInfo?.browser_name === 'Chrome' && storage?.browserInfo?.browser_version === '107.0.0.0' && logID === 14) ||
(c?.errorInfo?.message?.includes('FILE_ERROR_NO_SPACE')) ||
(c?.status === 407) ||
(c?.errorInfo.status === 407) ||
(c?.errorInfo?.message?.includes('An unexpected error occurred')) ||
(c?.errorInfo?.message?.includes('Refused to run the JavaScript URL'))
(c?.errorInfo?.message?.includes('Refused to run the JavaScript URL')) ||
(c?.errorInfo?.message?.includes('QuotaExceededError: storage.local API call exceeded its quota limitations')) || // @TODO: future
(c?.errorInfo?.statusText?.includes('Proxy Authentication Required'))
) {
return false;
}

View File

@ -30,8 +30,6 @@ const backgroundProdConfig = {
name: 'background',
entry: './src/background/background.js',
mode: 'production',
devtool: 'cheap-module-source-map',
target: 'web',
node: false,
output: {
path: path.join(__dirname, '../../public/background'),

View File

@ -34,8 +34,6 @@ const contentScriptProdConfig = {
name: 'contentScriptGlobal',
entry: './src/content/content_script.js',
mode: 'production',
devtool: 'cheap-module-source-map',
target: 'web',
node: false,
output: {
path: path.join(__dirname, '../../public/content'),

View File

@ -39,8 +39,6 @@ const installPageProdConfig = {
contentPageStyles: './src/content/styles/content_script.scss'
},
mode: 'production',
devtool: 'cheap-module-source-map',
target: 'web',
node: false,
output: {
path: path.join(__dirname, '../../public/installPage'),

View File

@ -39,8 +39,6 @@ const optionsPageProdConfig = {
contentPageStyles: './src/content/styles/content_script.scss'
},
mode: 'production',
devtool: 'cheap-module-source-map',
target: 'web',
node: false,
output: {
path: path.join(__dirname, '../../public/optionsPage'),

View File

@ -116,10 +116,10 @@
"errorStorageIntegrityMessage": "Please reinstall browser extension or contact with 2FAS Support on Discord",
"warningTooSoonTitle": "Wait a moment",
"warningTooSoonMessage": "Please wait DIFF seconds before sending another request.",
"warningSelectInputTitle": "Select the text field first",
"warningSelectInputMessage": "Select the text field for the 2FA token then click the extension icon or use the chosen shortcut.",
"successPushSentTitle": "Push sent",
"successPushSentMessage": "Please check your phone and accept your login request.",
"successPushSentClipboardTitle": "Push sent",
"successPushSentClipboardMessage": "Please check your phone and accept your login request. Copy your token and paste it into the correct input on the website.",
"successExtNameUpdatedTitle": "Success",
"successExtNameUpdatedMessage": "Extension name updated",
"successDeviceDisconnectedTitle": "Success",
@ -137,6 +137,8 @@
"infoUnsupportedProtocolMessage": "Only HTTP and HTTPS protocols are supported by 2FAS Extension",
"infoBrowserActionWithoutTabTitle": "Info",
"infoBrowserActionWithoutTabMessage": "Using outside the browser is not supported by 2FAS Extension",
"infoCopiedToClipboardTitle": "Successfully copied to clipboard",
"infoCopiedToClipboardMessage": "You can now paste the token into the input field",
"infoTestTitle": "2FAS Notification",
"infoTestMessage": "Hi! This is just a test",
"optionsTitle": "2FAS Browser Extension | Options",
@ -181,5 +183,9 @@
"optionsDomainRequired": "Domain is required",
"optionsDomainTooLong": "Domain is too long",
"optionsDomainIncorrect": "Domain is not correct",
"optionsDomainExists": "Domain exists on excluded list"
"optionsDomainExists": "Domain exists on excluded list",
"tokenHeader": "Your token",
"tokenCopy": "Copy",
"tokenCopied": "Copied",
"tokenDescription": "Copy the token and paste it in the input field. The token will expire in 30 seconds."
}