Parcourir la source

Created: Popup for bitwarden import option

Muhammed Ayimen il y a 1 an
Parent
commit
4b66689e07

+ 1 - 0
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",

+ 117 - 0
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<void> 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<void> _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<int?> _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<dynamic> 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;
+}

+ 2 - 1
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;
     }
   }