Browse Source

Google auth import (#172)

Neeraj Gupta 1 year ago
parent
commit
8a3a64adcc

+ 1 - 0
lib/l10n/arb/app_en.arb

@@ -78,6 +78,7 @@
   "passwordForDecryptingExport" : "Password to decrypt export",
   "passwordForDecryptingExport" : "Password to decrypt export",
   "passwordEmptyError": "Password can not be empty",
   "passwordEmptyError": "Password can not be empty",
   "importFromApp": "Import codes from {appName}",
   "importFromApp": "Import codes from {appName}",
+  "importGoogleAuthGuide": "Export your accounts from Google Authenticator to a QR code using the \"Transfer Accounts\" option. Then using another device, scan the QR code.",
   "importSelectJsonFile": "Select JSON file",
   "importSelectJsonFile": "Select JSON file",
   "importEnteEncGuide": "Select the encrypted JSON file exported from ente",
   "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.",
   "importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",

+ 201 - 0
lib/models/protos/googleauth.pb.dart

@@ -0,0 +1,201 @@
+//
+//  Generated code. Do not modify.
+//  source: googleauth.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:fixnum/fixnum.dart' as $fixnum;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'googleauth.pbenum.dart';
+
+export 'googleauth.pbenum.dart';
+
+class MigrationPayload_OtpParameters extends $pb.GeneratedMessage {
+  factory MigrationPayload_OtpParameters() => create();
+  MigrationPayload_OtpParameters._() : super();
+  factory MigrationPayload_OtpParameters.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory MigrationPayload_OtpParameters.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'MigrationPayload.OtpParameters', package: const $pb.PackageName(_omitMessageNames ? '' : 'googleauth'), createEmptyInstance: create)
+    ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'secret', $pb.PbFieldType.OY)
+    ..aOS(2, _omitFieldNames ? '' : 'name')
+    ..aOS(3, _omitFieldNames ? '' : 'issuer')
+    ..e<MigrationPayload_Algorithm>(4, _omitFieldNames ? '' : 'algorithm', $pb.PbFieldType.OE, defaultOrMaker: MigrationPayload_Algorithm.ALGORITHM_UNSPECIFIED, valueOf: MigrationPayload_Algorithm.valueOf, enumValues: MigrationPayload_Algorithm.values)
+    ..e<MigrationPayload_DigitCount>(5, _omitFieldNames ? '' : 'digits', $pb.PbFieldType.OE, defaultOrMaker: MigrationPayload_DigitCount.DIGIT_COUNT_UNSPECIFIED, valueOf: MigrationPayload_DigitCount.valueOf, enumValues: MigrationPayload_DigitCount.values)
+    ..e<MigrationPayload_OtpType>(6, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: MigrationPayload_OtpType.OTP_TYPE_UNSPECIFIED, valueOf: MigrationPayload_OtpType.valueOf, enumValues: MigrationPayload_OtpType.values)
+    ..aInt64(7, _omitFieldNames ? '' : 'counter')
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  MigrationPayload_OtpParameters clone() => MigrationPayload_OtpParameters()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  MigrationPayload_OtpParameters copyWith(void Function(MigrationPayload_OtpParameters) updates) => super.copyWith((message) => updates(message as MigrationPayload_OtpParameters)) as MigrationPayload_OtpParameters;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static MigrationPayload_OtpParameters create() => MigrationPayload_OtpParameters._();
+  MigrationPayload_OtpParameters createEmptyInstance() => create();
+  static $pb.PbList<MigrationPayload_OtpParameters> createRepeated() => $pb.PbList<MigrationPayload_OtpParameters>();
+  @$core.pragma('dart2js:noInline')
+  static MigrationPayload_OtpParameters getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<MigrationPayload_OtpParameters>(create);
+  static MigrationPayload_OtpParameters? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<$core.int> get secret => $_getN(0);
+  @$pb.TagNumber(1)
+  set secret($core.List<$core.int> v) { $_setBytes(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasSecret() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearSecret() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get name => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set name($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasName() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearName() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get issuer => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set issuer($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasIssuer() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearIssuer() => clearField(3);
+
+  @$pb.TagNumber(4)
+  MigrationPayload_Algorithm get algorithm => $_getN(3);
+  @$pb.TagNumber(4)
+  set algorithm(MigrationPayload_Algorithm v) { setField(4, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasAlgorithm() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearAlgorithm() => clearField(4);
+
+  @$pb.TagNumber(5)
+  MigrationPayload_DigitCount get digits => $_getN(4);
+  @$pb.TagNumber(5)
+  set digits(MigrationPayload_DigitCount v) { setField(5, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasDigits() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearDigits() => clearField(5);
+
+  @$pb.TagNumber(6)
+  MigrationPayload_OtpType get type => $_getN(5);
+  @$pb.TagNumber(6)
+  set type(MigrationPayload_OtpType v) { setField(6, v); }
+  @$pb.TagNumber(6)
+  $core.bool hasType() => $_has(5);
+  @$pb.TagNumber(6)
+  void clearType() => clearField(6);
+
+  @$pb.TagNumber(7)
+  $fixnum.Int64 get counter => $_getI64(6);
+  @$pb.TagNumber(7)
+  set counter($fixnum.Int64 v) { $_setInt64(6, v); }
+  @$pb.TagNumber(7)
+  $core.bool hasCounter() => $_has(6);
+  @$pb.TagNumber(7)
+  void clearCounter() => clearField(7);
+}
+
+class MigrationPayload extends $pb.GeneratedMessage {
+  factory MigrationPayload() => create();
+  MigrationPayload._() : super();
+  factory MigrationPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory MigrationPayload.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'MigrationPayload', package: const $pb.PackageName(_omitMessageNames ? '' : 'googleauth'), createEmptyInstance: create)
+    ..pc<MigrationPayload_OtpParameters>(1, _omitFieldNames ? '' : 'otpParameters', $pb.PbFieldType.PM, subBuilder: MigrationPayload_OtpParameters.create)
+    ..a<$core.int>(2, _omitFieldNames ? '' : 'version', $pb.PbFieldType.O3)
+    ..a<$core.int>(3, _omitFieldNames ? '' : 'batchSize', $pb.PbFieldType.O3)
+    ..a<$core.int>(4, _omitFieldNames ? '' : 'batchIndex', $pb.PbFieldType.O3)
+    ..a<$core.int>(5, _omitFieldNames ? '' : 'batchId', $pb.PbFieldType.O3)
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  MigrationPayload clone() => MigrationPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  MigrationPayload copyWith(void Function(MigrationPayload) updates) => super.copyWith((message) => updates(message as MigrationPayload)) as MigrationPayload;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static MigrationPayload create() => MigrationPayload._();
+  MigrationPayload createEmptyInstance() => create();
+  static $pb.PbList<MigrationPayload> createRepeated() => $pb.PbList<MigrationPayload>();
+  @$core.pragma('dart2js:noInline')
+  static MigrationPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<MigrationPayload>(create);
+  static MigrationPayload? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<MigrationPayload_OtpParameters> get otpParameters => $_getList(0);
+
+  @$pb.TagNumber(2)
+  $core.int get version => $_getIZ(1);
+  @$pb.TagNumber(2)
+  set version($core.int v) { $_setSignedInt32(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasVersion() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearVersion() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.int get batchSize => $_getIZ(2);
+  @$pb.TagNumber(3)
+  set batchSize($core.int v) { $_setSignedInt32(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasBatchSize() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearBatchSize() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.int get batchIndex => $_getIZ(3);
+  @$pb.TagNumber(4)
+  set batchIndex($core.int v) { $_setSignedInt32(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasBatchIndex() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearBatchIndex() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $core.int get batchId => $_getIZ(4);
+  @$pb.TagNumber(5)
+  set batchId($core.int v) { $_setSignedInt32(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasBatchId() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearBatchId() => clearField(5);
+}
+
+
+const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
+const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

+ 72 - 0
lib/models/protos/googleauth.pbenum.dart

@@ -0,0 +1,72 @@
+//
+//  Generated code. Do not modify.
+//  source: googleauth.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class MigrationPayload_Algorithm extends $pb.ProtobufEnum {
+  static const MigrationPayload_Algorithm ALGORITHM_UNSPECIFIED = MigrationPayload_Algorithm._(0, _omitEnumNames ? '' : 'ALGORITHM_UNSPECIFIED');
+  static const MigrationPayload_Algorithm ALGORITHM_SHA1 = MigrationPayload_Algorithm._(1, _omitEnumNames ? '' : 'ALGORITHM_SHA1');
+  static const MigrationPayload_Algorithm ALGORITHM_SHA256 = MigrationPayload_Algorithm._(2, _omitEnumNames ? '' : 'ALGORITHM_SHA256');
+  static const MigrationPayload_Algorithm ALGORITHM_SHA512 = MigrationPayload_Algorithm._(3, _omitEnumNames ? '' : 'ALGORITHM_SHA512');
+  static const MigrationPayload_Algorithm ALGORITHM_MD5 = MigrationPayload_Algorithm._(4, _omitEnumNames ? '' : 'ALGORITHM_MD5');
+
+  static const $core.List<MigrationPayload_Algorithm> values = <MigrationPayload_Algorithm> [
+    ALGORITHM_UNSPECIFIED,
+    ALGORITHM_SHA1,
+    ALGORITHM_SHA256,
+    ALGORITHM_SHA512,
+    ALGORITHM_MD5,
+  ];
+
+  static final $core.Map<$core.int, MigrationPayload_Algorithm> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static MigrationPayload_Algorithm? valueOf($core.int value) => _byValue[value];
+
+  const MigrationPayload_Algorithm._($core.int v, $core.String n) : super(v, n);
+}
+
+class MigrationPayload_DigitCount extends $pb.ProtobufEnum {
+  static const MigrationPayload_DigitCount DIGIT_COUNT_UNSPECIFIED = MigrationPayload_DigitCount._(0, _omitEnumNames ? '' : 'DIGIT_COUNT_UNSPECIFIED');
+  static const MigrationPayload_DigitCount DIGIT_COUNT_SIX = MigrationPayload_DigitCount._(1, _omitEnumNames ? '' : 'DIGIT_COUNT_SIX');
+  static const MigrationPayload_DigitCount DIGIT_COUNT_EIGHT = MigrationPayload_DigitCount._(2, _omitEnumNames ? '' : 'DIGIT_COUNT_EIGHT');
+
+  static const $core.List<MigrationPayload_DigitCount> values = <MigrationPayload_DigitCount> [
+    DIGIT_COUNT_UNSPECIFIED,
+    DIGIT_COUNT_SIX,
+    DIGIT_COUNT_EIGHT,
+  ];
+
+  static final $core.Map<$core.int, MigrationPayload_DigitCount> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static MigrationPayload_DigitCount? valueOf($core.int value) => _byValue[value];
+
+  const MigrationPayload_DigitCount._($core.int v, $core.String n) : super(v, n);
+}
+
+class MigrationPayload_OtpType extends $pb.ProtobufEnum {
+  static const MigrationPayload_OtpType OTP_TYPE_UNSPECIFIED = MigrationPayload_OtpType._(0, _omitEnumNames ? '' : 'OTP_TYPE_UNSPECIFIED');
+  static const MigrationPayload_OtpType OTP_TYPE_HOTP = MigrationPayload_OtpType._(1, _omitEnumNames ? '' : 'OTP_TYPE_HOTP');
+  static const MigrationPayload_OtpType OTP_TYPE_TOTP = MigrationPayload_OtpType._(2, _omitEnumNames ? '' : 'OTP_TYPE_TOTP');
+
+  static const $core.List<MigrationPayload_OtpType> values = <MigrationPayload_OtpType> [
+    OTP_TYPE_UNSPECIFIED,
+    OTP_TYPE_HOTP,
+    OTP_TYPE_TOTP,
+  ];
+
+  static final $core.Map<$core.int, MigrationPayload_OtpType> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static MigrationPayload_OtpType? valueOf($core.int value) => _byValue[value];
+
+  const MigrationPayload_OtpType._($core.int v, $core.String n) : super(v, n);
+}
+
+
+const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');

+ 93 - 0
lib/models/protos/googleauth.pbjson.dart

@@ -0,0 +1,93 @@
+//
+//  Generated code. Do not modify.
+//  source: googleauth.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:convert' as $convert;
+import 'dart:core' as $core;
+import 'dart:typed_data' as $typed_data;
+
+@$core.Deprecated('Use migrationPayloadDescriptor instead')
+const MigrationPayload$json = {
+  '1': 'MigrationPayload',
+  '2': [
+    {'1': 'otp_parameters', '3': 1, '4': 3, '5': 11, '6': '.googleauth.MigrationPayload.OtpParameters', '10': 'otpParameters'},
+    {'1': 'version', '3': 2, '4': 1, '5': 5, '10': 'version'},
+    {'1': 'batch_size', '3': 3, '4': 1, '5': 5, '10': 'batchSize'},
+    {'1': 'batch_index', '3': 4, '4': 1, '5': 5, '10': 'batchIndex'},
+    {'1': 'batch_id', '3': 5, '4': 1, '5': 5, '10': 'batchId'},
+  ],
+  '3': [MigrationPayload_OtpParameters$json],
+  '4': [MigrationPayload_Algorithm$json, MigrationPayload_DigitCount$json, MigrationPayload_OtpType$json],
+};
+
+@$core.Deprecated('Use migrationPayloadDescriptor instead')
+const MigrationPayload_OtpParameters$json = {
+  '1': 'OtpParameters',
+  '2': [
+    {'1': 'secret', '3': 1, '4': 1, '5': 12, '10': 'secret'},
+    {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
+    {'1': 'issuer', '3': 3, '4': 1, '5': 9, '10': 'issuer'},
+    {'1': 'algorithm', '3': 4, '4': 1, '5': 14, '6': '.googleauth.MigrationPayload.Algorithm', '10': 'algorithm'},
+    {'1': 'digits', '3': 5, '4': 1, '5': 14, '6': '.googleauth.MigrationPayload.DigitCount', '10': 'digits'},
+    {'1': 'type', '3': 6, '4': 1, '5': 14, '6': '.googleauth.MigrationPayload.OtpType', '10': 'type'},
+    {'1': 'counter', '3': 7, '4': 1, '5': 3, '10': 'counter'},
+  ],
+};
+
+@$core.Deprecated('Use migrationPayloadDescriptor instead')
+const MigrationPayload_Algorithm$json = {
+  '1': 'Algorithm',
+  '2': [
+    {'1': 'ALGORITHM_UNSPECIFIED', '2': 0},
+    {'1': 'ALGORITHM_SHA1', '2': 1},
+    {'1': 'ALGORITHM_SHA256', '2': 2},
+    {'1': 'ALGORITHM_SHA512', '2': 3},
+    {'1': 'ALGORITHM_MD5', '2': 4},
+  ],
+};
+
+@$core.Deprecated('Use migrationPayloadDescriptor instead')
+const MigrationPayload_DigitCount$json = {
+  '1': 'DigitCount',
+  '2': [
+    {'1': 'DIGIT_COUNT_UNSPECIFIED', '2': 0},
+    {'1': 'DIGIT_COUNT_SIX', '2': 1},
+    {'1': 'DIGIT_COUNT_EIGHT', '2': 2},
+  ],
+};
+
+@$core.Deprecated('Use migrationPayloadDescriptor instead')
+const MigrationPayload_OtpType$json = {
+  '1': 'OtpType',
+  '2': [
+    {'1': 'OTP_TYPE_UNSPECIFIED', '2': 0},
+    {'1': 'OTP_TYPE_HOTP', '2': 1},
+    {'1': 'OTP_TYPE_TOTP', '2': 2},
+  ],
+};
+
+/// Descriptor for `MigrationPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List migrationPayloadDescriptor = $convert.base64Decode(
+    'ChBNaWdyYXRpb25QYXlsb2FkElEKDm90cF9wYXJhbWV0ZXJzGAEgAygLMiouZ29vZ2xlYXV0aC'
+    '5NaWdyYXRpb25QYXlsb2FkLk90cFBhcmFtZXRlcnNSDW90cFBhcmFtZXRlcnMSGAoHdmVyc2lv'
+    'bhgCIAEoBVIHdmVyc2lvbhIdCgpiYXRjaF9zaXplGAMgASgFUgliYXRjaFNpemUSHwoLYmF0Y2'
+    'hfaW5kZXgYBCABKAVSCmJhdGNoSW5kZXgSGQoIYmF0Y2hfaWQYBSABKAVSB2JhdGNoSWQargIK'
+    'DU90cFBhcmFtZXRlcnMSFgoGc2VjcmV0GAEgASgMUgZzZWNyZXQSEgoEbmFtZRgCIAEoCVIEbm'
+    'FtZRIWCgZpc3N1ZXIYAyABKAlSBmlzc3VlchJECglhbGdvcml0aG0YBCABKA4yJi5nb29nbGVh'
+    'dXRoLk1pZ3JhdGlvblBheWxvYWQuQWxnb3JpdGhtUglhbGdvcml0aG0SPwoGZGlnaXRzGAUgAS'
+    'gOMicuZ29vZ2xlYXV0aC5NaWdyYXRpb25QYXlsb2FkLkRpZ2l0Q291bnRSBmRpZ2l0cxI4CgR0'
+    'eXBlGAYgASgOMiQuZ29vZ2xlYXV0aC5NaWdyYXRpb25QYXlsb2FkLk90cFR5cGVSBHR5cGUSGA'
+    'oHY291bnRlchgHIAEoA1IHY291bnRlciJ5CglBbGdvcml0aG0SGQoVQUxHT1JJVEhNX1VOU1BF'
+    'Q0lGSUVEEAASEgoOQUxHT1JJVEhNX1NIQTEQARIUChBBTEdPUklUSE1fU0hBMjU2EAISFAoQQU'
+    'xHT1JJVEhNX1NIQTUxMhADEhEKDUFMR09SSVRITV9NRDUQBCJVCgpEaWdpdENvdW50EhsKF0RJ'
+    'R0lUX0NPVU5UX1VOU1BFQ0lGSUVEEAASEwoPRElHSVRfQ09VTlRfU0lYEAESFQoRRElHSVRfQ0'
+    '9VTlRfRUlHSFQQAiJJCgdPdHBUeXBlEhgKFE9UUF9UWVBFX1VOU1BFQ0lGSUVEEAASEQoNT1RQ'
+    'X1RZUEVfSE9UUBABEhEKDU9UUF9UWVBFX1RPVFAQAg==');
+

+ 14 - 0
lib/models/protos/googleauth.pbserver.dart

@@ -0,0 +1,14 @@
+//
+//  Generated code. Do not modify.
+//  source: googleauth.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+export 'googleauth.pb.dart';
+

+ 8 - 4
lib/ui/code_widget.dart

@@ -27,12 +27,12 @@ class _CodeWidgetState extends State<CodeWidget> {
   final ValueNotifier<String> _currentCode = ValueNotifier<String>("");
   final ValueNotifier<String> _currentCode = ValueNotifier<String>("");
   final ValueNotifier<String> _nextCode = ValueNotifier<String>("");
   final ValueNotifier<String> _nextCode = ValueNotifier<String>("");
   final Logger logger = Logger("_CodeWidgetState");
   final Logger logger = Logger("_CodeWidgetState");
+  bool _isInitialized = false;
 
 
   @override
   @override
   void initState() {
   void initState() {
     super.initState();
     super.initState();
-    _currentCode.value = _getTotp();
-    _nextCode.value = _getNextTotp();
+
     _everySecondTimer =
     _everySecondTimer =
         Timer.periodic(const Duration(milliseconds: 500), (Timer t) {
         Timer.periodic(const Duration(milliseconds: 500), (Timer t) {
       String newCode = _getTotp();
       String newCode = _getTotp();
@@ -53,6 +53,11 @@ class _CodeWidgetState extends State<CodeWidget> {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
+    if (!_isInitialized) {
+      _currentCode.value = _getTotp();
+      _nextCode.value = _getNextTotp();
+      _isInitialized = true;
+    }
     final l10n = context.l10n;
     final l10n = context.l10n;
     return Container(
     return Container(
       margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8),
       margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8),
@@ -125,8 +130,7 @@ class _CodeWidgetState extends State<CodeWidget> {
                               children: [
                               children: [
                                 Text(
                                 Text(
                                   safeDecode(widget.code.issuer).trim(),
                                   safeDecode(widget.code.issuer).trim(),
-                                  style:
-                                      Theme.of(context).textTheme.headline6,
+                                  style: Theme.of(context).textTheme.headline6,
                                 ),
                                 ),
                                 const SizedBox(height: 2),
                                 const SizedBox(height: 2),
                                 Text(
                                 Text(

+ 7 - 2
lib/ui/home_page.dart

@@ -54,6 +54,7 @@ class _HomePageState extends State<HomePage> {
 
 
   @override
   @override
   void initState() {
   void initState() {
+    super.initState();
     _textController.addListener(_applyFilteringAndRefresh);
     _textController.addListener(_applyFilteringAndRefresh);
     _loadCodes();
     _loadCodes();
     _streamSubscription = Bus.instance.on<CodesUpdatedEvent>().listen((event) {
     _streamSubscription = Bus.instance.on<CodesUpdatedEvent>().listen((event) {
@@ -64,7 +65,7 @@ class _HomePageState extends State<HomePage> {
       await autoLogoutAlert(context);
       await autoLogoutAlert(context);
     });
     });
     _initDeepLinks();
     _initDeepLinks();
-    super.initState();
+
   }
   }
 
 
   void _loadCodes() {
   void _loadCodes() {
@@ -222,7 +223,11 @@ class _HomePageState extends State<HomePage> {
       } else {
       } else {
         final list = ListView.builder(
         final list = ListView.builder(
           itemBuilder: ((context, index) {
           itemBuilder: ((context, index) {
-            return CodeWidget(_filteredCodes[index]);
+            try {
+              return CodeWidget(_filteredCodes[index]);
+            } catch(e) {
+              return const Text("Failed");
+            }
           }),
           }),
           itemCount: _filteredCodes.length,
           itemCount: _filteredCodes.length,
         );
         );

+ 97 - 0
lib/ui/scanner_gauth_page.dart

@@ -0,0 +1,97 @@
+import 'dart:io';
+
+import 'package:ente_auth/l10n/l10n.dart';
+import 'package:ente_auth/models/code.dart';
+import 'package:ente_auth/theme/ente_theme.dart';
+import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
+import 'package:ente_auth/utils/toast_util.dart';
+import 'package:flutter/material.dart';
+import 'package:qr_code_scanner/qr_code_scanner.dart';
+
+class ScannerGoogleAuthPage extends StatefulWidget {
+  const ScannerGoogleAuthPage({Key? key}) : super(key: key);
+
+  @override
+  State<ScannerGoogleAuthPage> createState() => ScannerGoogleAuthPageState();
+}
+
+class ScannerGoogleAuthPageState extends State<ScannerGoogleAuthPage> {
+  final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
+  QRViewController? controller;
+  String? totp;
+
+  // In order to get hot reload to work we need to pause the camera if the platform
+  // is android, or resume the camera if the platform is iOS.
+  @override
+  void reassemble() {
+    super.reassemble();
+    if (Platform.isAndroid) {
+      controller!.pauseCamera();
+    } else if (Platform.isIOS) {
+      controller!.resumeCamera();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final l10n = context.l10n;
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(l10n.scan),
+      ),
+      body: Column(
+        children: <Widget>[
+          Expanded(
+            flex: 5,
+            child: QRView(
+              key: qrKey,
+              overlay: QrScannerOverlayShape(
+                  borderColor: getEnteColorScheme(context).primary700),
+              onQRViewCreated: _onQRViewCreated,
+              formatsAllowed: const [BarcodeFormat.qrcode],
+            ),
+          ),
+          Expanded(
+            flex: 1,
+            child: Center(
+              child: (totp != null) ? Text(totp!) : Text(l10n.scanACode),
+            ),
+          )
+        ],
+      ),
+    );
+  }
+
+  void _onQRViewCreated(QRViewController controller) {
+    this.controller = controller;
+    // h4ck to remove black screen on Android scanners: https://github.com/juliuscanute/qr_code_scanner/issues/560#issuecomment-1159611301
+    if (Platform.isAndroid) {
+      controller.pauseCamera();
+      controller.resumeCamera();
+    }
+    controller.scannedDataStream.listen((scanData) {
+      try {
+        if (scanData.code == null) {
+          return;
+        }
+        if (scanData.code!.startsWith(kGoogleAuthExportPrefix)) {
+          List<Code> codes = parseGoogleAuth(scanData.code!);
+          controller.dispose();
+          Navigator.of(context).pop(codes);
+        } else {
+          showToast(context, "Invalid QR code");
+        }
+      } catch (e) {
+        controller.dispose();
+        Navigator.of(context).pop();
+        showToast(context, "Error " + e.toString());
+      }
+    });
+  }
+
+  @override
+  void dispose() {
+    controller?.dispose();
+    super.dispose();
+  }
+}

+ 145 - 0
lib/ui/settings/data/import/google_auth_import.dart

@@ -0,0 +1,145 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+import 'package:base32/base32.dart';
+import 'package:ente_auth/l10n/l10n.dart';
+import 'package:ente_auth/models/code.dart';
+import 'package:ente_auth/models/protos/googleauth.pb.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/scanner_gauth_page.dart';
+import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:logging/logging.dart';
+
+const kGoogleAuthExportPrefix = 'otpauth-migration://offline?data=';
+
+Future<void> showGoogleAuthInstruction(BuildContext context) async {
+  final l10n = context.l10n;
+  final result = await showDialogWidget(
+    context: context,
+    title: l10n.importFromApp("Google Authenticator"),
+    body: l10n.importGoogleAuthGuide,
+    buttons: [
+      ButtonWidget(
+        buttonType: ButtonType.primary,
+        labelText: l10n.scanAQrCode,
+        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) {
+      final List<Code>? codes = await Navigator.of(context).push(
+        MaterialPageRoute(
+          builder: (BuildContext context) {
+            return const ScannerGoogleAuthPage();
+          },
+        ),
+      );
+      if (codes == null || codes.isEmpty) {
+        return;
+      }
+      for (final code in codes) {
+        await CodeStore.instance.addCode(code, shouldSync: false);
+      }
+      unawaited(AuthenticatorService.instance.sync());
+      final DialogWidget dialog = choiceDialog(
+        title: context.l10n.importSuccessTitle,
+        body: context.l10n.importSuccessDesc(codes.length ?? 0),
+        firstButtonLabel: l10n.ok,
+        firstButtonType: ButtonType.primary,
+      );
+      await showConfettiDialog(
+        context: context,
+        dialogBuilder: (BuildContext context) {
+          return dialog;
+        },
+      );
+    }
+  }
+}
+
+List<Code> parseGoogleAuth(String qrCodeData) {
+  try {
+    List<Code> codes = <Code>[];
+    final String payload = qrCodeData.substring(kGoogleAuthExportPrefix.length);
+    debugPrint("GoogleAuthImport: payload: $payload");
+    final Uint8List base64Decoded = base64Decode(Uri.decodeComponent(payload));
+    final MigrationPayload mPayload =
+        MigrationPayload.fromBuffer(base64Decoded);
+    for (var otpParameter in mPayload.otpParameters) {
+      // Build the OTP URL
+      String otpUrl;
+      String issuer = otpParameter.issuer;
+      String account = otpParameter.name;
+      var counter = otpParameter.counter;
+      // Create a list of bytes from the list of integers.
+      Uint8List bytes = Uint8List.fromList(otpParameter.secret);
+
+      // Encode the bytes to base 32.
+      String base32String = base32.encode(bytes);
+      String secret = base32String;
+      // identify digit count
+      int digits = 6;
+      int timer = 30; // default timer, no field in Google Auth
+      Algorithm algorithm = Algorithm.sha1;
+      switch (otpParameter.algorithm) {
+        case MigrationPayload_Algorithm.ALGORITHM_MD5:
+          throw Exception('GoogleAuthImport: MD5 is not supported');
+        case MigrationPayload_Algorithm.ALGORITHM_SHA1:
+          algorithm = Algorithm.sha1;
+          break;
+        case MigrationPayload_Algorithm.ALGORITHM_SHA256:
+          algorithm = Algorithm.sha256;
+          break;
+        case MigrationPayload_Algorithm.ALGORITHM_SHA512:
+          algorithm = Algorithm.sha512;
+          break;
+        case MigrationPayload_Algorithm.ALGORITHM_UNSPECIFIED:
+          algorithm = Algorithm.sha1;
+          break;
+      }
+      switch (otpParameter.digits) {
+        case MigrationPayload_DigitCount.DIGIT_COUNT_EIGHT:
+          digits = 8;
+          break;
+        case MigrationPayload_DigitCount.DIGIT_COUNT_SIX:
+          digits = 6;
+          break;
+        case MigrationPayload_DigitCount.DIGIT_COUNT_UNSPECIFIED:
+          digits = 6;
+      }
+
+      if (otpParameter.type == MigrationPayload_OtpType.OTP_TYPE_TOTP ||
+          otpParameter.type == MigrationPayload_OtpType.OTP_TYPE_UNSPECIFIED) {
+        otpUrl =
+            'otpauth://totp/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&period=$timer';
+      } else if (otpParameter.type == MigrationPayload_OtpType.OTP_TYPE_HOTP) {
+        otpUrl =
+            'otpauth://hotp/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&counter=$counter';
+      } else {
+        throw Exception('Invalid OTP type');
+      }
+      codes.add(Code.fromRawData(otpUrl));
+    }
+    return codes;
+  } catch (e, s) {
+    Logger("GoogleAuthImport")
+        .severe("Error while parsing Google Auth QR code", e, s);
+    throw Exception('Failed to parse Google Auth QR code \n ${e.toString()}');
+  }
+}

+ 17 - 6
lib/ui/settings/data/import/import_service.dart

@@ -1,5 +1,6 @@
 
 
 import 'package:ente_auth/ui/settings/data/import/encrypted_ente_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';
 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/raivo_plain_text_import.dart';
 import 'package:ente_auth/ui/settings/data/import_page.dart';
 import 'package:ente_auth/ui/settings/data/import_page.dart';
@@ -12,12 +13,22 @@ class ImportService {
   ImportService._internal();
   ImportService._internal();
 
 
   Future<void> initiateImport(BuildContext context,ImportType type) async {
   Future<void> initiateImport(BuildContext context,ImportType type) async {
-    if(type == ImportType.plainText) {
-      showImportInstructionDialog(context);
-    } else if(type == ImportType.ravio) {
-      showRaivoImportInstruction(context);
-    } else {
-      showEncryptedImportInstruction(context);
+    switch(type) {
+
+      case ImportType.plainText:
+        showImportInstructionDialog(context);
+        break;
+      case ImportType.encrypted:
+        showEncryptedImportInstruction(context);
+        break;
+      case ImportType.ravio:
+        showRaivoImportInstruction(context);
+        break;
+      case ImportType.googleAuthenticator:
+        showGoogleAuthInstruction(context);
+
+        // showToast(context, 'coming soon');
+        break;
     }
     }
   }
   }
 }
 }

+ 62 - 45
lib/ui/settings/data/import/raivo_plain_text_import.dart

@@ -53,52 +53,23 @@ Future<void> _pickRaivoJsonFile(BuildContext context) async {
   final progressDialog = createProgressDialog(context, l10n.pleaseWait);
   final progressDialog = createProgressDialog(context, l10n.pleaseWait);
   await progressDialog.show();
   await progressDialog.show();
   try {
   try {
-    File file = File(result.files.single.path!);
-    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.sync());
+    String path = result.files.single.path!;
+    int? count = await _processRaivoExportFile(context, path);
     await progressDialog.hide();
     await progressDialog.hide();
-    final DialogWidget dialog = choiceDialog(
-      title: context.l10n.importSuccessTitle,
-      body: context.l10n.importSuccessDesc(parsedCodes.length),
-      firstButtonLabel: l10n.ok,
-      firstButtonType: ButtonType.primary,
-    );
-    await showConfettiDialog(
-      context: context,
-      dialogBuilder: (BuildContext context) {
-        return dialog;
-      },
-    );
+    if(count != null) {
+      final DialogWidget dialog = choiceDialog(
+        title: context.l10n.importSuccessTitle,
+        body: context.l10n.importSuccessDesc(count ?? 0),
+        firstButtonLabel: l10n.ok,
+        firstButtonType: ButtonType.primary,
+      );
+      await showConfettiDialog(
+        context: context,
+        dialogBuilder: (BuildContext context) {
+          return dialog;
+        },
+      );
+    }
   } catch (e) {
   } catch (e) {
     await progressDialog.hide();
     await progressDialog.hide();
     await showErrorDialog(
     await showErrorDialog(
@@ -108,3 +79,49 @@ Future<void> _pickRaivoJsonFile(BuildContext context) async {
     );
     );
   }
   }
 }
 }
+
+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.sync());
+  int count = parsedCodes.length;
+  return count;
+}

+ 5 - 1
lib/ui/settings/data/import_page.dart

@@ -14,6 +14,7 @@ enum ImportType {
   plainText,
   plainText,
   encrypted,
   encrypted,
   ravio,
   ravio,
+  googleAuthenticator,
 }
 }
 
 
 class ImportCodePage extends StatelessWidget {
 class ImportCodePage extends StatelessWidget {
@@ -21,6 +22,7 @@ class ImportCodePage extends StatelessWidget {
     ImportType.plainText,
     ImportType.plainText,
     ImportType.encrypted,
     ImportType.encrypted,
     ImportType.ravio,
     ImportType.ravio,
+    ImportType.googleAuthenticator,
   ];
   ];
 
 
   ImportCodePage({super.key});
   ImportCodePage({super.key});
@@ -32,7 +34,9 @@ class ImportCodePage extends StatelessWidget {
       case ImportType.encrypted:
       case ImportType.encrypted:
         return context.l10n.importTypeEnteEncrypted;
         return context.l10n.importTypeEnteEncrypted;
       case ImportType.ravio:
       case ImportType.ravio:
-        return 'Ravio OTP';
+        return 'Raivo OTP';
+      case ImportType.googleAuthenticator:
+        return 'Google Authenticator';
     }
     }
   }
   }
 
 

+ 40 - 0
protos/googleauth.proto

@@ -0,0 +1,40 @@
+syntax = "proto3";
+package googleauth;
+
+message MigrationPayload {
+  enum Algorithm {
+    ALGORITHM_UNSPECIFIED = 0;
+    ALGORITHM_SHA1 = 1;
+    ALGORITHM_SHA256 = 2;
+    ALGORITHM_SHA512 = 3;
+    ALGORITHM_MD5 = 4;
+  }
+
+  enum DigitCount {
+    DIGIT_COUNT_UNSPECIFIED = 0;
+    DIGIT_COUNT_SIX = 1;
+    DIGIT_COUNT_EIGHT = 2;
+  }
+
+  enum OtpType {
+    OTP_TYPE_UNSPECIFIED = 0;
+    OTP_TYPE_HOTP = 1;
+    OTP_TYPE_TOTP = 2;
+  }
+
+  message OtpParameters {
+    bytes secret = 1;
+    string name = 2;
+    string issuer = 3;
+    Algorithm algorithm = 4;
+    DigitCount digits = 5;
+    OtpType type = 6;
+    int64 counter = 7;
+  }
+
+  repeated OtpParameters otp_parameters = 1;
+  int32 version = 2;
+  int32 batch_size = 3;
+  int32 batch_index = 4;
+  int32 batch_id = 5;
+}

+ 10 - 2
pubspec.lock

@@ -26,7 +26,7 @@ packages:
     source: hosted
     source: hosted
     version: "5.13.0"
     version: "5.13.0"
   archive:
   archive:
-    dependency: transitive
+    dependency: "direct main"
     description:
     description:
       name: archive
       name: archive
       sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
       sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
@@ -50,7 +50,7 @@ packages:
     source: hosted
     source: hosted
     version: "2.10.0"
     version: "2.10.0"
   base32:
   base32:
-    dependency: transitive
+    dependency: "direct main"
     description:
     description:
       name: base32
       name: base32
       sha256: ddad4ebfedf93d4500818ed8e61443b734ffe7cf8a45c668c9b34ef6adde02e2
       sha256: ddad4ebfedf93d4500818ed8e61443b734ffe7cf8a45c668c9b34ef6adde02e2
@@ -1063,6 +1063,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "4.2.4"
     version: "4.2.4"
+  protobuf:
+    dependency: "direct main"
+    description:
+      name: protobuf
+      sha256: "4034a02b7e231e7e60bff30a8ac13a7347abfdac0798595fae0b90a3f0afe759"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.0"
   provider:
   provider:
     dependency: transitive
     dependency: transitive
     description:
     description:

+ 3 - 0
pubspec.yaml

@@ -8,6 +8,8 @@ environment:
 
 
 dependencies:
 dependencies:
   adaptive_theme: ^3.1.0 # done
   adaptive_theme: ^3.1.0 # done
+  archive: ^3.3.7
+  base32: ^2.1.3
   bip39: ^1.0.6 #done
   bip39: ^1.0.6 #done
   bloc: ^8.0.3 #done
   bloc: ^8.0.3 #done
   clipboard: ^0.1.3
   clipboard: ^0.1.3
@@ -58,6 +60,7 @@ dependencies:
   path_provider: ^2.0.11
   path_provider: ^2.0.11
   pinput: ^1.2.2
   pinput: ^1.2.2
   pointycastle: ^3.7.3
   pointycastle: ^3.7.3
+  protobuf: ^3.0.0
   qr_code_scanner: ^1.0.1
   qr_code_scanner: ^1.0.1
   sentry: ^6.12.1
   sentry: ^6.12.1
   sentry_flutter: ^6.12.1
   sentry_flutter: ^6.12.1