Google auth import (#172)

This commit is contained in:
Neeraj Gupta 2023-08-01 13:22:06 +05:30 committed by GitHub
commit 8a3a64adcc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 775 additions and 60 deletions

View file

@ -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.",

View 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');

View 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');

View 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==');

View 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';

View file

@ -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(

View file

@ -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,
); );

View 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();
}
}

View 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()}');
}
}

View file

@ -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;
} }
} }
} }

View file

@ -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;
}

View file

@ -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
View 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;
}

View file

@ -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:

View file

@ -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