Google auth import (#172)
This commit is contained in:
commit
8a3a64adcc
15 changed files with 775 additions and 60 deletions
|
@ -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
lib/models/protos/googleauth.pb.dart
Normal file
201
lib/models/protos/googleauth.pb.dart
Normal file
|
@ -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
lib/models/protos/googleauth.pbenum.dart
Normal file
72
lib/models/protos/googleauth.pbenum.dart
Normal file
|
@ -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
lib/models/protos/googleauth.pbjson.dart
Normal file
93
lib/models/protos/googleauth.pbjson.dart
Normal file
|
@ -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
lib/models/protos/googleauth.pbserver.dart
Normal file
14
lib/models/protos/googleauth.pbserver.dart
Normal file
|
@ -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';
|
||||||
|
|
|
@ -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:
|
style: Theme.of(context).textTheme.headline6,
|
||||||
Theme.of(context).textTheme.headline6,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -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
lib/ui/scanner_gauth_page.dart
Normal file
97
lib/ui/scanner_gauth_page.dart
Normal file
|
@ -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
lib/ui/settings/data/import/google_auth_import.dart
Normal file
145
lib/ui/settings/data/import/google_auth_import.dart
Normal file
|
@ -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()}');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
switch(type) {
|
||||||
showImportInstructionDialog(context);
|
|
||||||
} else if(type == ImportType.ravio) {
|
case ImportType.plainText:
|
||||||
showRaivoImportInstruction(context);
|
showImportInstructionDialog(context);
|
||||||
} else {
|
break;
|
||||||
showEncryptedImportInstruction(context);
|
case ImportType.encrypted:
|
||||||
|
showEncryptedImportInstruction(context);
|
||||||
|
break;
|
||||||
|
case ImportType.ravio:
|
||||||
|
showRaivoImportInstruction(context);
|
||||||
|
break;
|
||||||
|
case ImportType.googleAuthenticator:
|
||||||
|
showGoogleAuthInstruction(context);
|
||||||
|
|
||||||
|
// showToast(context, 'coming soon');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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!);
|
String path = result.files.single.path!;
|
||||||
final jsonString = await file.readAsString();
|
int? count = await _processRaivoExportFile(context, path);
|
||||||
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());
|
|
||||||
await progressDialog.hide();
|
await progressDialog.hide();
|
||||||
final DialogWidget dialog = choiceDialog(
|
if(count != null) {
|
||||||
title: context.l10n.importSuccessTitle,
|
final DialogWidget dialog = choiceDialog(
|
||||||
body: context.l10n.importSuccessDesc(parsedCodes.length),
|
title: context.l10n.importSuccessTitle,
|
||||||
firstButtonLabel: l10n.ok,
|
body: context.l10n.importSuccessDesc(count ?? 0),
|
||||||
firstButtonType: ButtonType.primary,
|
firstButtonLabel: l10n.ok,
|
||||||
);
|
firstButtonType: ButtonType.primary,
|
||||||
await showConfettiDialog(
|
);
|
||||||
context: context,
|
await showConfettiDialog(
|
||||||
dialogBuilder: (BuildContext context) {
|
context: context,
|
||||||
return dialog;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
protos/googleauth.proto
Normal file
40
protos/googleauth.proto
Normal file
|
@ -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;
|
||||||
|
}
|
12
pubspec.lock
12
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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue