From 993bf81e430de7caf4c133f4c7dd0b12170e8151 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Sun, 12 Nov 2023 10:04:50 +0530 Subject: [PATCH 01/18] Update simple icons --- assets/simple-icons | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/simple-icons b/assets/simple-icons index 7e1ad45175..8e7701d6a4 160000 --- a/assets/simple-icons +++ b/assets/simple-icons @@ -1 +1 @@ -Subproject commit 7e1ad4517598f36ba625741a4dfbc33610d105d8 +Subproject commit 8e7701d6a40462733043f54b3849faf35af70a83 From a13d2a065e7b4f01cc6352f917d23b5dddf0a25b Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Sun, 12 Nov 2023 10:31:28 +0530 Subject: [PATCH 02/18] Update icon for mstdn.* --- assets/custom-icons/_data/custom-icons.json | 10 ++++++++++ assets/custom-icons/icons/mastodon.svg | 1 + 2 files changed, 11 insertions(+) create mode 100644 assets/custom-icons/icons/mastodon.svg diff --git a/assets/custom-icons/_data/custom-icons.json b/assets/custom-icons/_data/custom-icons.json index ee7dac8f64..e559afe7d0 100644 --- a/assets/custom-icons/_data/custom-icons.json +++ b/assets/custom-icons/_data/custom-icons.json @@ -95,6 +95,16 @@ "title": "La Poste", "slug": "laposte" }, + { + "title": "Mastodon", + "slug": "mastodon", + "hex": "6364FF" + }, + { + "title": "mstdn", + "slug": "mastodon", + "hex": "6364FF" + }, { "title": "Microsoft" }, diff --git a/assets/custom-icons/icons/mastodon.svg b/assets/custom-icons/icons/mastodon.svg new file mode 100644 index 0000000000..5e3b7e13cf --- /dev/null +++ b/assets/custom-icons/icons/mastodon.svg @@ -0,0 +1 @@ +Mastodon \ No newline at end of file From 16bb23d97737f67265be535a02fc5a5e476a8fee Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Mon, 13 Nov 2023 16:32:08 +0530 Subject: [PATCH 03/18] Support multiple names for the same service --- assets/custom-icons/_data/custom-icons.json | 6 +----- lib/ui/utils/icon_utils.dart | 8 ++++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/assets/custom-icons/_data/custom-icons.json b/assets/custom-icons/_data/custom-icons.json index e559afe7d0..96cada4285 100644 --- a/assets/custom-icons/_data/custom-icons.json +++ b/assets/custom-icons/_data/custom-icons.json @@ -97,11 +97,7 @@ }, { "title": "Mastodon", - "slug": "mastodon", - "hex": "6364FF" - }, - { - "title": "mstdn", + "altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"], "slug": "mastodon", "hex": "6364FF" }, diff --git a/lib/ui/utils/icon_utils.dart b/lib/ui/utils/icon_utils.dart index 27da8752ba..7cb1299ac5 100644 --- a/lib/ui/utils/icon_utils.dart +++ b/lib/ui/utils/icon_utils.dart @@ -93,6 +93,14 @@ class IconUtils { icon["slug"], icon["hex"], ); + if (icon["altNames"] != null) { + for (final name in icon["altNames"]) { + _customIcons[name] = CustomIconData( + icon["slug"], + icon["hex"], + ); + } + } } } catch (e) { Logger("IconUtils").severe("Error loading icons", e); From 03b1accfda02731d311184d213753a2bd3af9120 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:21:03 +0530 Subject: [PATCH 04/18] Add lockscreen fixes from photos --- lib/l10n/arb/app_en.arb | 2 + .../local_authentication_service.dart | 5 +- lib/ui/tools/lock_screen.dart | 74 ++++++++++++++++--- lib/utils/auth_util.dart | 3 +- 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 0e883b1c0a..b0d4a3244e 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,5 +1,6 @@ { "account": "Account", + "unlock": "Unlock", "recoveryKey": "Recovery key", "counterAppBarTitle": "Counter", "@counterAppBarTitle": { @@ -97,6 +98,7 @@ "authToViewYourRecoveryKey": "Please authenticate to view your recovery key", "authToChangeYourEmail": "Please authenticate to change your email", "authToChangeYourPassword": "Please authenticate to change your password", + "authToViewSecrets": "Please authenticate to view your secrets", "ok": "Ok", "cancel": "Cancel", "yes": "Yes", diff --git a/lib/services/local_authentication_service.dart b/lib/services/local_authentication_service.dart index f161bb89fa..d21d1bcb26 100644 --- a/lib/services/local_authentication_service.dart +++ b/lib/services/local_authentication_service.dart @@ -1,5 +1,3 @@ - - import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/ui/tools/app_lock.dart'; import 'package:ente_auth/utils/auth_util.dart'; @@ -19,7 +17,7 @@ class LocalAuthenticationService { ) async { if (await _isLocalAuthSupportedOnDevice()) { AppLock.of(context)!.setEnabled(false); - final result = await requestAuthentication(infoMessage); + final result = await requestAuthentication(context, infoMessage); AppLock.of(context)!.setEnabled( Configuration.instance.shouldShowLockScreen(), ); @@ -43,6 +41,7 @@ class LocalAuthenticationService { if (await LocalAuthentication().isDeviceSupported()) { AppLock.of(context)!.disable(); final result = await requestAuthentication( + context, infoMessage, ); if (result) { diff --git a/lib/ui/tools/lock_screen.dart b/lib/ui/tools/lock_screen.dart index a1389fc4ec..dbd050c61b 100644 --- a/lib/ui/tools/lock_screen.dart +++ b/lib/ui/tools/lock_screen.dart @@ -1,5 +1,4 @@ - - +import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/ui/common/gradient_button.dart'; import 'package:ente_auth/ui/tools/app_lock.dart'; import 'package:ente_auth/utils/auth_util.dart'; @@ -13,13 +12,19 @@ class LockScreen extends StatefulWidget { State createState() => _LockScreenState(); } -class _LockScreenState extends State { +class _LockScreenState extends State with WidgetsBindingObserver { final _logger = Logger("LockScreen"); + bool _isShowingLockScreen = false; + bool _hasPlacedAppInBackground = false; + bool _hasAuthenticationFailed = false; + int? lastAuthenticatingTime; @override void initState() { - _showLockScreen(); + _logger.info("initState"); super.initState(); + _showLockScreen(source: "initState"); + WidgetsBinding.instance.addObserver(this); } @override @@ -34,16 +39,16 @@ class _LockScreenState extends State { alignment: Alignment.center, children: [ Opacity( - opacity: 0.3, + opacity: 0.2, child: Image.asset('assets/loading_photos_background.png'), ), SizedBox( - width: 142, + width: 180, child: GradientButton( - text: "Unlock", + text: context.l10n.unlock, iconData: Icons.lock_open_outlined, onTap: () async { - _showLockScreen(); + _showLockScreen(source: "tapUnlock"); }, ), ), @@ -55,14 +60,61 @@ class _LockScreenState extends State { ); } - Future _showLockScreen() async { - _logger.info("Showing lockscreen"); + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + _logger.info(state.toString()); + if (state == AppLifecycleState.resumed && !_isShowingLockScreen) { + // This is triggered either when the lock screen is dismissed or when + // the app is brought to foreground + _hasPlacedAppInBackground = false; + final bool didAuthInLast5Seconds = lastAuthenticatingTime != null && + DateTime.now().millisecondsSinceEpoch - lastAuthenticatingTime! < + 5000; + if (!_hasAuthenticationFailed && !didAuthInLast5Seconds) { + // Show the lock screen again only if the app is resuming from the + // background, and not when the lock screen was explicitly dismissed + _showLockScreen(source: "lifeCycle"); + } else { + _hasAuthenticationFailed = false; // Reset failure state + } + } else if (state == AppLifecycleState.paused || + state == AppLifecycleState.inactive) { + // This is triggered either when the lock screen pops up or when + // the app is pushed to background + if (!_isShowingLockScreen) { + _hasPlacedAppInBackground = true; + _hasAuthenticationFailed = false; // reset failure state + } + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + Future _showLockScreen({String source = ''}) async { + final int id = DateTime.now().millisecondsSinceEpoch; + _logger.info("Showing lock screen $source $id"); try { + _isShowingLockScreen = true; final result = await requestAuthentication( - "Please authenticate to view your secrets", + context, + context.l10n.authToViewSecrets, ); + _logger.finest("LockScreen Result $result $id"); + _isShowingLockScreen = false; if (result) { + lastAuthenticatingTime = DateTime.now().millisecondsSinceEpoch; AppLock.of(context)!.didUnlock(); + } else { + if (!_hasPlacedAppInBackground) { + // Treat this as a failure only if user did not explicitly + // put the app in background + _hasAuthenticationFailed = true; + _logger.info("Authentication failed"); + } } } catch (e, s) { _logger.severe(e, s); diff --git a/lib/utils/auth_util.dart b/lib/utils/auth_util.dart index 77f9732041..4726d7c842 100644 --- a/lib/utils/auth_util.dart +++ b/lib/utils/auth_util.dart @@ -1,8 +1,9 @@ +import 'package:flutter/cupertino.dart'; import 'package:local_auth/local_auth.dart'; import 'package:local_auth_android/local_auth_android.dart'; import 'package:logging/logging.dart'; -Future requestAuthentication(String reason) async { +Future requestAuthentication(BuildContext context, String reason) async { Logger("AuthUtil").info("Requesting authentication"); await LocalAuthentication().stopAuthentication(); return await LocalAuthentication().authenticate( From c0444680c62bb2f3379896d68f2a26869bdd6cae Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:25:02 +0530 Subject: [PATCH 05/18] Extract strings for local auth --- lib/l10n/arb/app_en.arb | 54 +++++++++++++++++++++++++++++++++++++++- lib/utils/auth_util.dart | 52 +++++++++++++++++++++++++++++--------- pubspec.lock | 4 +-- pubspec.yaml | 3 +++ 4 files changed, 98 insertions(+), 15 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index b0d4a3244e..f81b9db848 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -338,5 +338,57 @@ "deleteCodeAuthMessage": "Authenticate to delete code", "showQRAuthMessage": "Authenticate to show QR code", "confirmAccountDeleteTitle": "Confirm account deletion", - "confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted." + "confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.", + "androidBiometricHint": "Verify identity", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Not recognized. Try again.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Success", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Cancel", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Authentication required", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometric required", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Device credentials required", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Device credentials required", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Go to settings", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biometric authentication is not set up on your device. Go to 'Settings > Security' to add biometric authentication.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometric authentication is disabled. Please lock and unlock your screen to enable it.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSGoToSettingsDescription": "Biometric authentication is not set up on your device. Please either enable Touch ID or Face ID on your phone.", + "@iOSGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + } } diff --git a/lib/utils/auth_util.dart b/lib/utils/auth_util.dart index 4726d7c842..73e0b11bb2 100644 --- a/lib/utils/auth_util.dart +++ b/lib/utils/auth_util.dart @@ -1,3 +1,4 @@ +import 'package:ente_auth/l10n/l10n.dart'; import 'package:flutter/cupertino.dart'; import 'package:local_auth/local_auth.dart'; import 'package:local_auth_android/local_auth_android.dart'; @@ -6,22 +7,49 @@ import 'package:logging/logging.dart'; Future requestAuthentication(BuildContext context, String reason) async { Logger("AuthUtil").info("Requesting authentication"); await LocalAuthentication().stopAuthentication(); + final l10n = context.l10n; return await LocalAuthentication().authenticate( localizedReason: reason, authMessages: [ - const AndroidAuthMessages( - biometricHint: "Verify identity", - biometricNotRecognized: "Not recognized, try again", - biometricRequiredTitle: "Biometric required", - biometricSuccess: "Successfully verified", - cancelButton: "Cancel", - deviceCredentialsRequiredTitle: "Device credentials required", - deviceCredentialsSetupDescription: "Device credentials required", - goToSettingsButton: "Go to settings", - goToSettingsDescription: - "Authentication is not setup on your device, go to Settings > Security to set it up", - signInTitle: "Authentication required", + AndroidAuthMessages( + biometricHint: l10n.androidBiometricHint, + biometricNotRecognized: l10n.androidBiometricNotRecognized, + biometricRequiredTitle: l10n.androidBiometricRequiredTitle, + biometricSuccess: l10n.androidBiometricSuccess, + cancelButton: l10n.androidCancelButton, + deviceCredentialsRequiredTitle: + l10n.androidDeviceCredentialsRequiredTitle, + deviceCredentialsSetupDescription: + l10n.androidDeviceCredentialsSetupDescription, + goToSettingsButton: l10n.goToSettings, + goToSettingsDescription: l10n.androidGoToSettingsDescription, + signInTitle: l10n.androidSignInTitle, + ), + IOSAuthMessages( + goToSettingsButton: l10n.goToSettings, + goToSettingsDescription: l10n.goToSettings, + lockOut: l10n.iOSLockOut, + // cancelButton default value is "Ok" + cancelButton: l10n.iOSOkButton, ), ], ); + // return await LocalAuthentication().authenticate( + // localizedReason: reason, + // authMessages: [ + // const AndroidAuthMessages( + // biometricHint: "Verify identity", + // biometricNotRecognized: "Not recognized, try again", + // biometricRequiredTitle: "Biometric required", + // biometricSuccess: "Successfully verified", + // cancelButton: "Cancel", + // deviceCredentialsRequiredTitle: "Device credentials required", + // deviceCredentialsSetupDescription: "Device credentials required", + // goToSettingsButton: "Go to settings", + // goToSettingsDescription: + // "Authentication is not setup on your device, go to Settings > Security to set it up", + // signInTitle: "Authentication required", + // ), + // ], + // ); } diff --git a/pubspec.lock b/pubspec.lock index 77916b086a..be8b19c1dc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -768,7 +768,7 @@ packages: source: hosted version: "2.1.7" local_auth_android: - dependency: transitive + dependency: "direct main" description: name: local_auth_android sha256: "523dd636ce061ddb296cbc3db410cb8f21efb7d8798f7b9532c8038ce2f8bad5" @@ -776,7 +776,7 @@ packages: source: hosted version: "1.0.31" local_auth_ios: - dependency: transitive + dependency: "direct main" description: name: local_auth_ios sha256: edc2977c5145492f3451db9507a2f2f284ee4f408950b3e16670838726761940 diff --git a/pubspec.yaml b/pubspec.yaml index 10b24fbcd4..029f452209 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,9 @@ dependencies: intl: ^0.18.0 json_annotation: ^4.5.0 local_auth: ^2.1.7 + + local_auth_android: ^1.0.31 + local_auth_ios: ^1.1.3 logging: ^1.0.1 modal_bottom_sheet: ^3.0.0-pre move_to_background: ^1.0.2 From 16c36a088fb215e8825c905be0a44a96052e5136 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:28:35 +0530 Subject: [PATCH 06/18] bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 029f452209..2b6b02494d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 2.0.15+215 +version: 2.0.16+216 publish_to: none environment: From d80f783013a16e5629d7a081af7cb7e8d45c1c82 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:29:59 +0530 Subject: [PATCH 07/18] Remove commented out code --- lib/utils/auth_util.dart | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/utils/auth_util.dart b/lib/utils/auth_util.dart index 73e0b11bb2..3da1a53f97 100644 --- a/lib/utils/auth_util.dart +++ b/lib/utils/auth_util.dart @@ -34,22 +34,4 @@ Future requestAuthentication(BuildContext context, String reason) async { ), ], ); - // return await LocalAuthentication().authenticate( - // localizedReason: reason, - // authMessages: [ - // const AndroidAuthMessages( - // biometricHint: "Verify identity", - // biometricNotRecognized: "Not recognized, try again", - // biometricRequiredTitle: "Biometric required", - // biometricSuccess: "Successfully verified", - // cancelButton: "Cancel", - // deviceCredentialsRequiredTitle: "Device credentials required", - // deviceCredentialsSetupDescription: "Device credentials required", - // goToSettingsButton: "Go to settings", - // goToSettingsDescription: - // "Authentication is not setup on your device, go to Settings > Security to set it up", - // signInTitle: "Authentication required", - // ), - // ], - // ); } From 90193eaed9d591ce995592ab28136f645810a4cf Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:30:19 +0530 Subject: [PATCH 08/18] Fix import --- lib/utils/auth_util.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/utils/auth_util.dart b/lib/utils/auth_util.dart index 3da1a53f97..0a63201912 100644 --- a/lib/utils/auth_util.dart +++ b/lib/utils/auth_util.dart @@ -2,6 +2,7 @@ import 'package:ente_auth/l10n/l10n.dart'; import 'package:flutter/cupertino.dart'; import 'package:local_auth/local_auth.dart'; import 'package:local_auth_android/local_auth_android.dart'; +import 'package:local_auth_ios/types/auth_messages_ios.dart'; import 'package:logging/logging.dart'; Future requestAuthentication(BuildContext context, String reason) async { From 572417d3aa35d97ba1f18283463faea379583f75 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:35:00 +0530 Subject: [PATCH 09/18] Fix bug in setting showingLockScreen flag --- lib/ui/tools/lock_screen.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ui/tools/lock_screen.dart b/lib/ui/tools/lock_screen.dart index dbd050c61b..0262de56a8 100644 --- a/lib/ui/tools/lock_screen.dart +++ b/lib/ui/tools/lock_screen.dart @@ -73,7 +73,8 @@ class _LockScreenState extends State with WidgetsBindingObserver { if (!_hasAuthenticationFailed && !didAuthInLast5Seconds) { // Show the lock screen again only if the app is resuming from the // background, and not when the lock screen was explicitly dismissed - _showLockScreen(source: "lifeCycle"); + Future.delayed( + Duration.zero, () => _showLockScreen(source: "lifeCycle")); } else { _hasAuthenticationFailed = false; // Reset failure state } @@ -117,6 +118,7 @@ class _LockScreenState extends State with WidgetsBindingObserver { } } } catch (e, s) { + _isShowingLockScreen = false; _logger.severe(e, s); } } From 6b4b69f0bb841cc80b539122748b94a8ce942944 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:35:19 +0530 Subject: [PATCH 10/18] Bump version 2.0.17 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2b6b02494d..82eceba351 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 2.0.16+216 +version: 2.0.17+217 publish_to: none environment: From f22b0cde8d74d693ebbad6efef70a9d0538450c6 Mon Sep 17 00:00:00 2001 From: Muhammed Ayimen Date: Tue, 14 Nov 2023 21:54:15 +0900 Subject: [PATCH 11/18] Added: Bitwarden option in import screen --- lib/ui/settings/data/import/import_service.dart | 3 +++ lib/ui/settings/data/import_page.dart | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/lib/ui/settings/data/import/import_service.dart b/lib/ui/settings/data/import/import_service.dart index a8a410b216..69706fb5fb 100644 --- a/lib/ui/settings/data/import/import_service.dart +++ b/lib/ui/settings/data/import/import_service.dart @@ -31,6 +31,9 @@ class ImportService { case ImportType.aegis: showAegisImportInstruction(context); break; + case ImportType.bitwarden: + showGoogleAuthImageInstruction(context); + break; } } } diff --git a/lib/ui/settings/data/import_page.dart b/lib/ui/settings/data/import_page.dart index 9c570b66e1..fb8c55d1ce 100644 --- a/lib/ui/settings/data/import_page.dart +++ b/lib/ui/settings/data/import_page.dart @@ -15,6 +15,7 @@ enum ImportType { ravio, googleAuthenticator, aegis, + bitwarden, } class ImportCodePage extends StatelessWidget { @@ -24,6 +25,7 @@ class ImportCodePage extends StatelessWidget { ImportType.ravio, ImportType.aegis, ImportType.googleAuthenticator, + ImportType.bitwarden, ]; ImportCodePage({super.key}); @@ -40,6 +42,8 @@ class ImportCodePage extends StatelessWidget { return 'Google Authenticator'; case ImportType.aegis: return 'Aegis Authenticator'; + case ImportType.bitwarden: + return 'Bitwarden'; } } From 4b66689e0744f26a79e39a76f2886bf723935032 Mon Sep 17 00:00:00 2001 From: Muhammed Ayimen Date: Tue, 14 Nov 2023 22:00:59 +0900 Subject: [PATCH 12/18] Created: Popup for bitwarden import option --- lib/l10n/arb/app_en.arb | 1 + .../data/import/bitwarden_import.dart | 117 ++++++++++++++++++ .../settings/data/import/import_service.dart | 3 +- 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 lib/ui/settings/data/import/bitwarden_import.dart diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index f81b9db848..c9a8b7754e 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -86,6 +86,7 @@ "importSelectJsonFile": "Select JSON file", "importEnteEncGuide": "Select the encrypted JSON file exported from ente", "importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.", + "importBitwardenGuide": "Use the \"Export\" option in Bitwarden Settings.\n\nExtract the zip file and import the JSON file.", "importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.", "exportCodes": "Export codes", "importLabel": "Import", diff --git a/lib/ui/settings/data/import/bitwarden_import.dart b/lib/ui/settings/data/import/bitwarden_import.dart new file mode 100644 index 0000000000..4b9101d00f --- /dev/null +++ b/lib/ui/settings/data/import/bitwarden_import.dart @@ -0,0 +1,117 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/models/code.dart'; +import 'package:ente_auth/services/authenticator_service.dart'; +import 'package:ente_auth/store/code_store.dart'; +import 'package:ente_auth/ui/components/buttons/button_widget.dart'; +import 'package:ente_auth/ui/components/dialog_widget.dart'; +import 'package:ente_auth/ui/components/models/button_type.dart'; +import 'package:ente_auth/ui/settings/data/import/import_success.dart'; +import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +Future showBitwardenImportInstruction(BuildContext context) async { + final l10n = context.l10n; + final result = await showDialogWidget( + context: context, + title: l10n.importFromApp("Bitwarden"), + body: l10n.importBitwardenGuide, + buttons: [ + ButtonWidget( + buttonType: ButtonType.primary, + labelText: l10n.importSelectJsonFile, + isInAlert: true, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.first, + ), + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.l10n.cancel, + buttonSize: ButtonSize.large, + isInAlert: true, + buttonAction: ButtonAction.second, + ), + ], + ); + if (result?.action != null && result!.action != ButtonAction.cancel) { + if (result.action == ButtonAction.first) { + await _pickRaivoJsonFile(context); + } else {} + } +} + +Future _pickRaivoJsonFile(BuildContext context) async { + final l10n = context.l10n; + FilePickerResult? result = await FilePicker.platform.pickFiles(); + if (result == null) { + return; + } + final progressDialog = createProgressDialog(context, l10n.pleaseWait); + await progressDialog.show(); + try { + String path = result.files.single.path!; + int? count = await _processRaivoExportFile(context, path); + await progressDialog.hide(); + if (count != null) { + await importSuccessDialog(context, count); + } + } catch (e) { + await progressDialog.hide(); + await showErrorDialog( + context, + context.l10n.sorry, + context.l10n.importFailureDesc, + ); + } +} + +Future _processRaivoExportFile(BuildContext context, String path) async { + File file = File(path); + if (path.endsWith('.zip')) { + await showErrorDialog( + context, + context.l10n.sorry, + "We don't support zip files yet. Please unzip the file and try again.", + ); + return null; + } + final jsonString = await file.readAsString(); + List jsonArray = jsonDecode(jsonString); + final parsedCodes = []; + for (var item in jsonArray) { + var kind = item['kind']; + var algorithm = item['algorithm']; + var timer = item['timer']; + var digits = item['digits']; + var issuer = item['issuer']; + var secret = item['secret']; + var account = item['account']; + var counter = item['counter']; + + // Build the OTP URL + String otpUrl; + + if (kind.toLowerCase() == 'totp') { + otpUrl = + 'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&period=$timer'; + } else if (kind.toLowerCase() == 'hotp') { + otpUrl = + 'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&counter=$counter'; + } else { + throw Exception('Invalid OTP type'); + } + parsedCodes.add(Code.fromRawData(otpUrl)); + } + + for (final code in parsedCodes) { + await CodeStore.instance.addCode(code, shouldSync: false); + } + unawaited(AuthenticatorService.instance.onlineSync()); + int count = parsedCodes.length; + return count; +} diff --git a/lib/ui/settings/data/import/import_service.dart b/lib/ui/settings/data/import/import_service.dart index 69706fb5fb..0be1f572e3 100644 --- a/lib/ui/settings/data/import/import_service.dart +++ b/lib/ui/settings/data/import/import_service.dart @@ -1,4 +1,5 @@ import 'package:ente_auth/ui/settings/data/import/aegis_import.dart'; +import 'package:ente_auth/ui/settings/data/import/bitwarden_import.dart'; import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart'; import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart'; import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart'; @@ -32,7 +33,7 @@ class ImportService { showAegisImportInstruction(context); break; case ImportType.bitwarden: - showGoogleAuthImageInstruction(context); + showBitwardenImportInstruction(context); break; } } From 27c8111e633b292cdbeffd6c20fb053ffd7dec00 Mon Sep 17 00:00:00 2001 From: Muhammed Ayimen Date: Tue, 14 Nov 2023 22:18:35 +0900 Subject: [PATCH 13/18] Created: Bitward import functionality with json file selecting and extracting the data to a particular format --- .../data/import/bitwarden_import.dart | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/ui/settings/data/import/bitwarden_import.dart b/lib/ui/settings/data/import/bitwarden_import.dart index 4b9101d00f..ec46371462 100644 --- a/lib/ui/settings/data/import/bitwarden_import.dart +++ b/lib/ui/settings/data/import/bitwarden_import.dart @@ -40,12 +40,12 @@ Future showBitwardenImportInstruction(BuildContext context) async { ); if (result?.action != null && result!.action != ButtonAction.cancel) { if (result.action == ButtonAction.first) { - await _pickRaivoJsonFile(context); + await _pickBitwardenJsonFile(context); } else {} } } -Future _pickRaivoJsonFile(BuildContext context) async { +Future _pickBitwardenJsonFile(BuildContext context) async { final l10n = context.l10n; FilePickerResult? result = await FilePicker.platform.pickFiles(); if (result == null) { @@ -55,7 +55,7 @@ Future _pickRaivoJsonFile(BuildContext context) async { await progressDialog.show(); try { String path = result.files.single.path!; - int? count = await _processRaivoExportFile(context, path); + int? count = await _processBitwardenExportFile(context, path); await progressDialog.hide(); if (count != null) { await importSuccessDialog(context, count); @@ -70,7 +70,10 @@ Future _pickRaivoJsonFile(BuildContext context) async { } } -Future _processRaivoExportFile(BuildContext context, String path) async { +Future _processBitwardenExportFile( + BuildContext context, + String path, +) async { File file = File(path); if (path.endsWith('.zip')) { await showErrorDialog( @@ -84,28 +87,25 @@ Future _processRaivoExportFile(BuildContext context, String path) async { List jsonArray = jsonDecode(jsonString); final parsedCodes = []; for (var item in jsonArray) { - var kind = item['kind']; - var algorithm = item['algorithm']; - var timer = item['timer']; - var digits = item['digits']; - var issuer = item['issuer']; - var secret = item['secret']; - var account = item['account']; - var counter = item['counter']; + if (item['login']['totp'] != null) { + var issuer = item['name']; + var account = item['login']['username']; + var secret = item['login']['totp']; - // Build the OTP URL - String otpUrl; + // Build the OTP URL + String otpUrl; - if (kind.toLowerCase() == 'totp') { - otpUrl = - 'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&period=$timer'; - } else if (kind.toLowerCase() == 'hotp') { - otpUrl = - 'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&counter=$counter'; - } else { - throw Exception('Invalid OTP type'); + if (kind.toLowerCase() == 'totp') { + otpUrl = + 'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&period=$timer'; + } else if (kind.toLowerCase() == 'hotp') { + otpUrl = + 'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&counter=$counter'; + } else { + throw Exception('Invalid OTP type'); + } + parsedCodes.add(Code.fromRawData(otpUrl)); } - parsedCodes.add(Code.fromRawData(otpUrl)); } for (final code in parsedCodes) { From bed3bd96127ce2e7b9ff1d225ef85595f88cc6a7 Mon Sep 17 00:00:00 2001 From: Muhammed Ayimen Date: Tue, 14 Nov 2023 22:31:07 +0900 Subject: [PATCH 14/18] Bug fixed: Using the map values from wrong jsonArray --- lib/ui/settings/data/import/bitwarden_import.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ui/settings/data/import/bitwarden_import.dart b/lib/ui/settings/data/import/bitwarden_import.dart index ec46371462..7b14ceacc7 100644 --- a/lib/ui/settings/data/import/bitwarden_import.dart +++ b/lib/ui/settings/data/import/bitwarden_import.dart @@ -84,7 +84,8 @@ Future _processBitwardenExportFile( return null; } final jsonString = await file.readAsString(); - List jsonArray = jsonDecode(jsonString); + final data = jsonDecode(jsonString); + List jsonArray = data['items']; final parsedCodes = []; for (var item in jsonArray) { if (item['login']['totp'] != null) { From 9b759a02a5bb7f0e107a8c259178a02f43527255 Mon Sep 17 00:00:00 2001 From: Muhammed Ayimen Date: Tue, 14 Nov 2023 22:38:55 +0900 Subject: [PATCH 15/18] Completed: Import from bitwarden functionality --- .../data/import/bitwarden_import.dart | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/lib/ui/settings/data/import/bitwarden_import.dart b/lib/ui/settings/data/import/bitwarden_import.dart index 7b14ceacc7..fbf9ed16e9 100644 --- a/lib/ui/settings/data/import/bitwarden_import.dart +++ b/lib/ui/settings/data/import/bitwarden_import.dart @@ -93,19 +93,13 @@ Future _processBitwardenExportFile( var account = item['login']['username']; var secret = item['login']['totp']; - // Build the OTP URL - String otpUrl; - - if (kind.toLowerCase() == 'totp') { - otpUrl = - 'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&period=$timer'; - } else if (kind.toLowerCase() == 'hotp') { - otpUrl = - 'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&counter=$counter'; - } else { - throw Exception('Invalid OTP type'); - } - parsedCodes.add(Code.fromRawData(otpUrl)); + parsedCodes.add( + Code.fromAccountAndSecret( + account, + issuer, + secret, + ), + ); } } From 3b8219020a894f40fe0fa4ac063677c7df082be2 Mon Sep 17 00:00:00 2001 From: Muhammed Ayimen Date: Tue, 14 Nov 2023 23:40:40 +0900 Subject: [PATCH 16/18] Updated: en arb file content for bitwarden data --- lib/l10n/arb/app_en.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index c9a8b7754e..7d516c5b42 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -86,7 +86,7 @@ "importSelectJsonFile": "Select JSON file", "importEnteEncGuide": "Select the encrypted JSON file exported from ente", "importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.", - "importBitwardenGuide": "Use the \"Export\" option in Bitwarden Settings.\n\nExtract the zip file and import the JSON file.", + "importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.", "importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.", "exportCodes": "Export codes", "importLabel": "Import", From adc157fe56d60d0759120c609d7469c7e4111b50 Mon Sep 17 00:00:00 2001 From: Muhammed Ayimen Date: Tue, 14 Nov 2023 23:42:53 +0900 Subject: [PATCH 17/18] Fixes: Based on the review --- lib/ui/settings/data/import/bitwarden_import.dart | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/ui/settings/data/import/bitwarden_import.dart b/lib/ui/settings/data/import/bitwarden_import.dart index fbf9ed16e9..ce2dce3f2a 100644 --- a/lib/ui/settings/data/import/bitwarden_import.dart +++ b/lib/ui/settings/data/import/bitwarden_import.dart @@ -41,7 +41,7 @@ Future showBitwardenImportInstruction(BuildContext context) async { if (result?.action != null && result!.action != ButtonAction.cancel) { if (result.action == ButtonAction.first) { await _pickBitwardenJsonFile(context); - } else {} + } } } @@ -75,14 +75,6 @@ Future _processBitwardenExportFile( String path, ) async { File file = File(path); - if (path.endsWith('.zip')) { - await showErrorDialog( - context, - context.l10n.sorry, - "We don't support zip files yet. Please unzip the file and try again.", - ); - return null; - } final jsonString = await file.readAsString(); final data = jsonDecode(jsonString); List jsonArray = data['items']; @@ -107,6 +99,5 @@ Future _processBitwardenExportFile( await CodeStore.instance.addCode(code, shouldSync: false); } unawaited(AuthenticatorService.instance.onlineSync()); - int count = parsedCodes.length; - return count; + return parsedCodes.length; } From 533e0c413a269ea7ea4fbd35af31b732c40f4d9b Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Tue, 14 Nov 2023 14:57:08 +0000 Subject: [PATCH 18/18] New Crowdin translations by GitHub Action --- lib/l10n/arb/app_zh.arb | 56 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_zh.arb b/lib/l10n/arb/app_zh.arb index d2b14477e7..4f510e7de1 100644 --- a/lib/l10n/arb/app_zh.arb +++ b/lib/l10n/arb/app_zh.arb @@ -1,5 +1,6 @@ { "account": "账户", + "unlock": "解锁", "recoveryKey": "恢复密钥", "counterAppBarTitle": "计数器", "@counterAppBarTitle": { @@ -97,6 +98,7 @@ "authToViewYourRecoveryKey": "请验证以查看您的恢复密钥", "authToChangeYourEmail": "请验证以更改您的电子邮件", "authToChangeYourPassword": "请验证以更改密码", + "authToViewSecrets": "请进行身份验证以查看您的秘密", "ok": "好的", "cancel": "取消", "yes": "是", @@ -336,5 +338,57 @@ "deleteCodeAuthMessage": "删除代码需要身份验证", "showQRAuthMessage": "显示QR码需要身份验证", "confirmAccountDeleteTitle": "确认删除账户", - "confirmAccountDeleteMessage": "该账户已链接到其他ente旗下的应用程序(如果您使用任何相关的应用程序)。\n\n您在所有ente旗下应用程序中上传的数据都将被安排删除,并且您的账户将被永久删除。" + "confirmAccountDeleteMessage": "该账户已链接到其他ente旗下的应用程序(如果您使用任何相关的应用程序)。\n\n您在所有ente旗下应用程序中上传的数据都将被安排删除,并且您的账户将被永久删除。", + "androidBiometricHint": "验证身份", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "未能识别。再试一次。", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "成功", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "取消", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "需要进行身份验证", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "需要进行生物识别认证", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "需要设备凭据", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "需要设备凭据", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "前往设置", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "您的设备上未设置生物识别身份验证。转到“设置 > 安全”以添加生物识别身份验证。", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "生物识别身份验证已禁用。请锁定再解锁您的屏幕以启用它。", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSGoToSettingsDescription": "您的设备上未设置生物识别身份验证。请在您的手机上启用 触控 ID 或 面容 ID。", + "@iOSGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "好的", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + } } \ No newline at end of file