From ba590fe2e58b73a6cf4bde4875365d6bf3ae1ae2 Mon Sep 17 00:00:00 2001 From: Kusha Gharahi <3326002+kushagharahi@users.noreply.github.com> Date: Thu, 1 Feb 2024 19:42:16 -0600 Subject: [PATCH 1/3] first pass at adding lastpass import --- lib/l10n/arb/app_en.arb | 1 + lib/ui/settings/data/import/import_service.dart | 4 ++++ lib/ui/settings/data/import_page.dart | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8af8fcc757..da9332daaa 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -90,6 +90,7 @@ "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.", "import2FasGuide": "Use the \"Settings->Backup -Export\" option in 2FAS.\n\nIf your backup is encrypted, you will need to enter the password to decrypt the backup", + "importLastpassGuide": "Use the \"Transfer accounts\" option within Lastpass Authenticator Settings and press \"Export accounts to file\". Import the JSON downloaded.", "exportCodes": "Export codes", "importLabel": "Import", "importInstruction": "Please select a file that contains a list of your codes in the following format", diff --git a/lib/ui/settings/data/import/import_service.dart b/lib/ui/settings/data/import/import_service.dart index b2ce9effd6..9eedb6d5e2 100644 --- a/lib/ui/settings/data/import/import_service.dart +++ b/lib/ui/settings/data/import/import_service.dart @@ -5,6 +5,7 @@ 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'; import 'package:ente_auth/ui/settings/data/import/raivo_plain_text_import.dart'; +import 'package:ente_auth/ui/settings/data/import/lastpass_import.dart'; import 'package:ente_auth/ui/settings/data/import_page.dart'; import 'package:flutter/cupertino.dart'; @@ -39,6 +40,9 @@ class ImportService { case ImportType.bitwarden: showBitwardenImportInstruction(context); break; + case ImportType.lastpass: + showLastpassImportInstruction(context); + break; } } } diff --git a/lib/ui/settings/data/import_page.dart b/lib/ui/settings/data/import_page.dart index 1a14cf3190..5d588c504d 100644 --- a/lib/ui/settings/data/import_page.dart +++ b/lib/ui/settings/data/import_page.dart @@ -17,6 +17,7 @@ enum ImportType { aegis, twoFas, bitwarden, + lastpass, } class ImportCodePage extends StatelessWidget { @@ -28,6 +29,7 @@ class ImportCodePage extends StatelessWidget { ImportType.bitwarden, ImportType.googleAuthenticator, ImportType.ravio, + ImportType.lastpass, ]; ImportCodePage({super.key}); @@ -48,6 +50,8 @@ class ImportCodePage extends StatelessWidget { return '2FAS Authenticator'; case ImportType.bitwarden: return 'Bitwarden'; + case ImportType.lastpass: + return 'Lastpass'; } } From 2b090027beb5edcbd1bfcb9e560c02cbee62a850 Mon Sep 17 00:00:00 2001 From: Kusha Gharahi <3326002+kushagharahi@users.noreply.github.com> Date: Thu, 1 Feb 2024 19:42:24 -0600 Subject: [PATCH 2/3] first pass at adding lastpass import --- .../settings/data/import/lastpass_import.dart | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 lib/ui/settings/data/import/lastpass_import.dart diff --git a/lib/ui/settings/data/import/lastpass_import.dart b/lib/ui/settings/data/import/lastpass_import.dart new file mode 100644 index 0000000000..53f8b453d2 --- /dev/null +++ b/lib/ui/settings/data/import/lastpass_import.dart @@ -0,0 +1,102 @@ +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 showLastpassImportInstruction(BuildContext context) async { + final l10n = context.l10n; + final result = await showDialogWidget( + context: context, + title: l10n.importFromApp("LastPass"), + body: l10n.importLastpassGuide, + 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 _pickLastpassJsonFile(context); + } + } +} + +Future _pickLastpassJsonFile(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 _processLastpassExportFile(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 _processLastpassExportFile( + BuildContext context, + String path, +) async { + File file = File(path); + final jsonString = await file.readAsString(); + Map jsonData = json.decode(jsonString); + List accounts = jsonData["accounts"]; + final parsedCodes = []; + for (var item in accounts) { + var algorithm = item['algorithm']; + var timer = item['timeStep']; + var digits = item['digits']; + var issuer = item['issuerName']; + var secret = item['secret']; + var account = item['userName']; + + // Build the OTP URL + String otpUrl = + 'otpauth://totp/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&period=$timer'; + 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; +} From 11d40103a3b5e13eda05a34b0ec7a6563e3d7404 Mon Sep 17 00:00:00 2001 From: Kusha Gharahi <3326002+kushagharahi@users.noreply.github.com> Date: Thu, 1 Feb 2024 22:01:45 -0600 Subject: [PATCH 3/3] Update name --- lib/ui/settings/data/import_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/settings/data/import_page.dart b/lib/ui/settings/data/import_page.dart index 5d588c504d..a763cb8552 100644 --- a/lib/ui/settings/data/import_page.dart +++ b/lib/ui/settings/data/import_page.dart @@ -51,7 +51,7 @@ class ImportCodePage extends StatelessWidget { case ImportType.bitwarden: return 'Bitwarden'; case ImportType.lastpass: - return 'Lastpass'; + return 'LastPass Authenticator'; } }