aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Willy Woitas <dutscher_sbf@hotmail.com>2024-02-18 01:49:42 +0100
committerLibravatar GitHub <noreply@github.com>2024-02-18 06:19:42 +0530
commit315728415b2269981a04ee51af7ef18412d7bf70 (patch)
tree1ee3e41e7022fa376b99bff5413eb6119481b839
parent6.7.1-nightly.20 [skip ci] (diff)
downloadferdium-app-315728415b2269981a04ee51af7ef18412d7bf70.tar.gz
ferdium-app-315728415b2269981a04ee51af7ef18412d7bf70.tar.zst
ferdium-app-315728415b2269981a04ee51af7ef18412d7bf70.zip
feat: Parse 2FA SMS token and copy to clipboard (#1561)
-rw-r--r--src/@types/stores.types.ts2
-rw-r--r--src/components/settings/settings/EditSettingsForm.tsx24
-rw-r--r--src/config.ts2
-rw-r--r--src/containers/settings/EditSettingsScreen.tsx37
-rw-r--r--src/i18n/locales/en-US.json2
-rw-r--r--src/stores/ServicesStore.ts52
-rw-r--r--src/webview/notifications.ts4
7 files changed, 117 insertions, 6 deletions
diff --git a/src/@types/stores.types.ts b/src/@types/stores.types.ts
index c0670ee87..c2d7bd78a 100644
--- a/src/@types/stores.types.ts
+++ b/src/@types/stores.types.ts
@@ -87,6 +87,8 @@ export interface AppStore extends TypedStore {
87 authRequestFailed: () => void; 87 authRequestFailed: () => void;
88 autoLaunchOnStart: () => void; 88 autoLaunchOnStart: () => void;
89 automaticUpdates: boolean; 89 automaticUpdates: boolean;
90 isTwoFactorAutoCatcherEnabled: boolean;
91 twoFactorAutoCatcherMatcher: string;
90 clearAppCacheRequest: () => void; 92 clearAppCacheRequest: () => void;
91 clipboardNotifications: boolean; 93 clipboardNotifications: boolean;
92 darkMode: boolean; 94 darkMode: boolean;
diff --git a/src/components/settings/settings/EditSettingsForm.tsx b/src/components/settings/settings/EditSettingsForm.tsx
index 6a79fcc8f..81cfe8b12 100644
--- a/src/components/settings/settings/EditSettingsForm.tsx
+++ b/src/components/settings/settings/EditSettingsForm.tsx
@@ -303,6 +303,8 @@ interface IProps extends WrappedComponentProps {
303 isClearingAllCache: boolean; 303 isClearingAllCache: boolean;
304 isTodosActivated: boolean; 304 isTodosActivated: boolean;
305 automaticUpdates: boolean; 305 automaticUpdates: boolean;
306 isTwoFactorAutoCatcherEnabled: boolean;
307 twoFactorAutoCatcherMatcher: string;
306 isDarkmodeEnabled: boolean; 308 isDarkmodeEnabled: boolean;
307 isAdaptableDarkModeEnabled: boolean; 309 isAdaptableDarkModeEnabled: boolean;
308 isUseGrayscaleServicesEnabled: boolean; 310 isUseGrayscaleServicesEnabled: boolean;
@@ -354,7 +356,8 @@ class EditSettingsForm extends Component<IProps, IState> {
354 this.props.form.submit({ 356 this.props.form.submit({
355 onSuccess: (form: Form) => { 357 onSuccess: (form: Form) => {
356 const values = form.values(); 358 const values = form.values();
357 const { accentColor } = values; 359 const { accentColor, isTwoFactorAutoCatcherEnabled } = values;
360
358 if (accentColor.trim().length === 0) { 361 if (accentColor.trim().length === 0) {
359 values.accentColor = DEFAULT_ACCENT_COLOR; 362 values.accentColor = DEFAULT_ACCENT_COLOR;
360 } 363 }
@@ -362,6 +365,15 @@ class EditSettingsForm extends Component<IProps, IState> {
362 if (progressbarAccentColor.trim().length === 0) { 365 if (progressbarAccentColor.trim().length === 0) {
363 values.progressbarAccentColor = DEFAULT_ACCENT_COLOR; 366 values.progressbarAccentColor = DEFAULT_ACCENT_COLOR;
364 } 367 }
368
369 // set twoFactorAutoCatcherMatcher to the default value, if its get enabled the input is prefilled
370 if (
371 !isTwoFactorAutoCatcherEnabled &&
372 values.twoFactorAutoCatcherMatcher.length === 0
373 ) {
374 values.twoFactorAutoCatcherMatcher =
375 DEFAULT_APP_SETTINGS.twoFactorAutoCatcherMatcher;
376 }
365 this.props.onSubmit(values); 377 this.props.onSubmit(values);
366 }, 378 },
367 onError: noop, 379 onError: noop,
@@ -386,6 +398,7 @@ class EditSettingsForm extends Component<IProps, IState> {
386 onClearAllCache, 398 onClearAllCache,
387 getCacheSize, 399 getCacheSize,
388 automaticUpdates, 400 automaticUpdates,
401 isTwoFactorAutoCatcherEnabled,
389 isDarkmodeEnabled, 402 isDarkmodeEnabled,
390 isSplitModeEnabled, 403 isSplitModeEnabled,
391 openProcessManager, 404 openProcessManager,
@@ -837,6 +850,15 @@ class EditSettingsForm extends Component<IProps, IState> {
837 <Toggle {...form.$('notifyTaskBarOnMessage').bind()} /> 850 <Toggle {...form.$('notifyTaskBarOnMessage').bind()} />
838 )} 851 )}
839 852
853 <Toggle {...form.$('isTwoFactorAutoCatcherEnabled').bind()} />
854
855 {isTwoFactorAutoCatcherEnabled && (
856 <Input
857 onChange={e => this.submit(e)}
858 {...form.$('twoFactorAutoCatcherMatcher').bind()}
859 />
860 )}
861
840 <Hr /> 862 <Hr />
841 863
842 <Select field={form.$('webRTCIPHandlingPolicy')} /> 864 <Select field={form.$('webRTCIPHandlingPolicy')} />
diff --git a/src/config.ts b/src/config.ts
index 055fc9ad1..5b9a5eaba 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -360,6 +360,8 @@ export const DEFAULT_APP_SETTINGS = {
360 clipboardNotifications: true, 360 clipboardNotifications: true,
361 notifyTaskBarOnMessage: false, 361 notifyTaskBarOnMessage: false,
362 showDisabledServices: true, 362 showDisabledServices: true,
363 isTwoFactorAutoCatcherEnabled: false,
364 twoFactorAutoCatcherMatcher: 'token, code, sms, verify',
363 showServiceName: false, 365 showServiceName: false,
364 showMessageBadgeWhenMuted: true, 366 showMessageBadgeWhenMuted: true,
365 showDragArea: false, 367 showDragArea: false,
diff --git a/src/containers/settings/EditSettingsScreen.tsx b/src/containers/settings/EditSettingsScreen.tsx
index 272297700..5d1b09296 100644
--- a/src/containers/settings/EditSettingsScreen.tsx
+++ b/src/containers/settings/EditSettingsScreen.tsx
@@ -99,6 +99,16 @@ const messages = defineMessages({
99 id: 'settings.app.form.notifyTaskBarOnMessage', 99 id: 'settings.app.form.notifyTaskBarOnMessage',
100 defaultMessage: 'Notify TaskBar/Dock on new message', 100 defaultMessage: 'Notify TaskBar/Dock on new message',
101 }, 101 },
102 isTwoFactorAutoCatcherEnabled: {
103 id: 'settings.app.form.isTwoFactorAutoCatcherEnabled',
104 defaultMessage:
105 'Auto-catch two-factor codes from notifications (Ex.: android messages) and copy to clipboard',
106 },
107 twoFactorAutoCatcherMatcher: {
108 id: 'settings.app.form.twoFactorAutoCatcherMatcher',
109 defaultMessage:
110 'Comma-separated and case-insensitive words/expressions to catch two-factor codes from. Ex.: token, code, sms, verify',
111 },
102 navigationBarBehaviour: { 112 navigationBarBehaviour: {
103 id: 'settings.app.form.navigationBarBehaviour', 113 id: 'settings.app.form.navigationBarBehaviour',
104 defaultMessage: 'Navigation bar behaviour', 114 defaultMessage: 'Navigation bar behaviour',
@@ -383,6 +393,10 @@ class EditSettingsScreen extends Component<
383 privateNotifications: Boolean(settingsData.privateNotifications), 393 privateNotifications: Boolean(settingsData.privateNotifications),
384 clipboardNotifications: Boolean(settingsData.clipboardNotifications), 394 clipboardNotifications: Boolean(settingsData.clipboardNotifications),
385 notifyTaskBarOnMessage: Boolean(settingsData.notifyTaskBarOnMessage), 395 notifyTaskBarOnMessage: Boolean(settingsData.notifyTaskBarOnMessage),
396 isTwoFactorAutoCatcherEnabled: Boolean(
397 settingsData.isTwoFactorAutoCatcherEnabled,
398 ),
399 twoFactorAutoCatcherMatcher: settingsData.twoFactorAutoCatcherMatcher,
386 navigationBarBehaviour: settingsData.navigationBarBehaviour, 400 navigationBarBehaviour: settingsData.navigationBarBehaviour,
387 webRTCIPHandlingPolicy: settingsData.webRTCIPHandlingPolicy, 401 webRTCIPHandlingPolicy: settingsData.webRTCIPHandlingPolicy,
388 searchEngine: settingsData.searchEngine, 402 searchEngine: settingsData.searchEngine,
@@ -680,6 +694,23 @@ class EditSettingsScreen extends Component<
680 default: DEFAULT_APP_SETTINGS.notifyTaskBarOnMessage, 694 default: DEFAULT_APP_SETTINGS.notifyTaskBarOnMessage,
681 type: 'checkbox', 695 type: 'checkbox',
682 }, 696 },
697 isTwoFactorAutoCatcherEnabled: {
698 label: intl.formatMessage(messages.isTwoFactorAutoCatcherEnabled),
699 value: ifUndefined<boolean>(
700 settings.all.app.isTwoFactorAutoCatcherEnabled,
701 DEFAULT_APP_SETTINGS.isTwoFactorAutoCatcherEnabled,
702 ),
703 default: DEFAULT_APP_SETTINGS.isTwoFactorAutoCatcherEnabled,
704 type: 'checkbox',
705 },
706 twoFactorAutoCatcherMatcher: {
707 label: intl.formatMessage(messages.twoFactorAutoCatcherMatcher),
708 value: ifUndefined<string>(
709 settings.all.app.twoFactorAutoCatcherMatcher,
710 DEFAULT_APP_SETTINGS.twoFactorAutoCatcherMatcher,
711 ),
712 default: DEFAULT_APP_SETTINGS.twoFactorAutoCatcherMatcher,
713 },
683 navigationBarBehaviour: { 714 navigationBarBehaviour: {
684 label: intl.formatMessage(messages.navigationBarBehaviour), 715 label: intl.formatMessage(messages.navigationBarBehaviour),
685 value: ifUndefined<string>( 716 value: ifUndefined<string>(
@@ -1257,6 +1288,12 @@ class EditSettingsScreen extends Component<
1257 this.props.stores.settings.app.useGrayscaleServices 1288 this.props.stores.settings.app.useGrayscaleServices
1258 } 1289 }
1259 isSplitModeEnabled={this.props.stores.settings.app.splitMode} 1290 isSplitModeEnabled={this.props.stores.settings.app.splitMode}
1291 isTwoFactorAutoCatcherEnabled={
1292 this.props.stores.settings.app.isTwoFactorAutoCatcherEnabled
1293 }
1294 twoFactorAutoCatcherMatcher={
1295 this.props.stores.settings.app.twoFactorAutoCatcherMatcher
1296 }
1260 isTodosActivated={this.props.stores.todos.isFeatureEnabledByUser} 1297 isTodosActivated={this.props.stores.todos.isFeatureEnabledByUser}
1261 openProcessManager={() => this.openProcessManager()} 1298 openProcessManager={() => this.openProcessManager()}
1262 isOnline={app.isOnline} 1299 isOnline={app.isOnline}
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 2781b67b7..90a45e83c 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -231,6 +231,7 @@
231 "settings.app.form.hideWorkspacesButton": "Hide Workspace Drawer button", 231 "settings.app.form.hideWorkspacesButton": "Hide Workspace Drawer button",
232 "settings.app.form.iconSize": "Service icon size", 232 "settings.app.form.iconSize": "Service icon size",
233 "settings.app.form.inactivityLock": "Lock after inactivity", 233 "settings.app.form.inactivityLock": "Lock after inactivity",
234 "settings.app.form.isTwoFactorAutoCatcherEnabled": "Auto-catch two-factor codes from notifications (Ex.: android messages) and copy to clipboard",
234 "settings.app.form.keepAllWorkspacesLoaded": "Keep all workspaces loaded", 235 "settings.app.form.keepAllWorkspacesLoaded": "Keep all workspaces loaded",
235 "settings.app.form.language": "Language", 236 "settings.app.form.language": "Language",
236 "settings.app.form.lockPassword": "Password", 237 "settings.app.form.lockPassword": "Password",
@@ -260,6 +261,7 @@
260 "settings.app.form.startMinimized": "Start minimized", 261 "settings.app.form.startMinimized": "Start minimized",
261 "settings.app.form.translatorEngine": "Translator Engine", 262 "settings.app.form.translatorEngine": "Translator Engine",
262 "settings.app.form.translatorLanguage": "Default Translator language", 263 "settings.app.form.translatorLanguage": "Default Translator language",
264 "settings.app.form.twoFactorAutoCatcherMatcher": "Comma-separated and case-insensitive words/expressions to catch two-factor codes from. Ex.: token, code, sms, verify",
263 "settings.app.form.universalDarkMode": "Enable universal Dark Mode", 265 "settings.app.form.universalDarkMode": "Enable universal Dark Mode",
264 "settings.app.form.useGrayscaleServices": "Use grayscale services", 266 "settings.app.form.useGrayscaleServices": "Use grayscale services",
265 "settings.app.form.useHorizontalStyle": "Use horizontal style", 267 "settings.app.form.useHorizontalStyle": "Use horizontal style",
diff --git a/src/stores/ServicesStore.ts b/src/stores/ServicesStore.ts
index 175a2ce16..d7804a3fe 100644
--- a/src/stores/ServicesStore.ts
+++ b/src/stores/ServicesStore.ts
@@ -1,5 +1,5 @@
1import { join } from 'node:path'; 1import { join } from 'node:path';
2import { ipcRenderer, shell } from 'electron'; 2import { clipboard, ipcRenderer, shell } from 'electron';
3import { action, reaction, computed, observable, makeObservable } from 'mobx'; 3import { action, reaction, computed, observable, makeObservable } from 'mobx';
4import { debounce, remove } from 'lodash'; 4import { debounce, remove } from 'lodash';
5import ms from 'ms'; 5import ms from 'ms';
@@ -834,6 +834,54 @@ export default class ServicesStore extends TypedStore {
834 break; 834 break;
835 } 835 }
836 case 'notification': { 836 case 'notification': {
837 const { notificationId, options } = args[0];
838
839 const { isTwoFactorAutoCatcherEnabled, twoFactorAutoCatcherMatcher } =
840 this.stores.settings.all.app;
841
842 debug(
843 'Settings for catch tokens',
844 isTwoFactorAutoCatcherEnabled,
845 twoFactorAutoCatcherMatcher,
846 );
847
848 if (isTwoFactorAutoCatcherEnabled) {
849 /*
850 parse the token digits from sms body, find "token" or "code" in options.body which reflect the sms content
851 ---
852 Token: 03624 / SMS-Code = PIN Token
853 ---
854 Prüfcode 010313 für Microsoft-Authentifizierung verwenden.
855 ---
856 483133 is your GitHub authentication code. @github.com #483133
857 ---
858 eBay: Ihr Sicherheitscode lautet 080090. \nEr läuft in 15 Minuten ab. Geben Sie den Code nicht an andere weiter.
859 ---
860 PayPal: Ihr Sicherheitscode lautet: 989605. Geben Sie diesen Code nicht weiter.
861 */
862
863 const rawBody = options.body;
864 const { 0: token } = /\d{5,6}/.exec(options.body) || [];
865
866 const wordsToCatch = twoFactorAutoCatcherMatcher
867 .replaceAll(', ', ',')
868 .split(',');
869
870 debug('wordsToCatch', wordsToCatch);
871
872 if (
873 token &&
874 wordsToCatch.some(a =>
875 options.body.toLowerCase().includes(a.toLowerCase()),
876 )
877 ) {
878 // with the extra "+ " it shows its copied to clipboard in the notification
879 options.body = `+ ${rawBody}`;
880 clipboard.writeText(token);
881 debug('Token parsed and copied to clipboard');
882 }
883 }
884
837 // Check if we are in scheduled Do-not-Disturb time 885 // Check if we are in scheduled Do-not-Disturb time
838 const { scheduledDNDEnabled, scheduledDNDStart, scheduledDNDEnd } = 886 const { scheduledDNDEnabled, scheduledDNDStart, scheduledDNDEnd } =
839 this.stores.settings.all.app; 887 this.stores.settings.all.app;
@@ -845,8 +893,6 @@ export default class ServicesStore extends TypedStore {
845 return; 893 return;
846 } 894 }
847 895
848 const { notificationId, options } = args[0];
849
850 if (service.isMuted || this.stores.settings.all.app.isAppMuted) { 896 if (service.isMuted || this.stores.settings.all.app.isAppMuted) {
851 Object.assign(options, { 897 Object.assign(options, {
852 silent: true, 898 silent: true,
diff --git a/src/webview/notifications.ts b/src/webview/notifications.ts
index aa88f13a2..e4401ab6e 100644
--- a/src/webview/notifications.ts
+++ b/src/webview/notifications.ts
@@ -37,7 +37,7 @@ export const notificationsClassDefinition = `(() => {
37 constructor(title = '', options = {}) { 37 constructor(title = '', options = {}) {
38 this.title = title; 38 this.title = title;
39 this.options = options; 39 this.options = options;
40 try{ 40 try {
41 window.ferdium.displayNotification(title, options) 41 window.ferdium.displayNotification(title, options)
42 .then(() => { 42 .then(() => {
43 if (typeof (this.onClick) === 'function') { 43 if (typeof (this.onClick) === 'function') {
@@ -51,7 +51,7 @@ export const notificationsClassDefinition = `(() => {
51 if (typeof (this.onClick) === 'function') { 51 if (typeof (this.onClick) === 'function') {
52 this.onClick(); 52 this.onClick();
53 } 53 }
54 }); 54 });
55 } 55 }
56 } 56 }
57 57