diff --git a/lib/models/code.dart b/lib/models/code.dart index 7f645ae66..40c8c6455 100644 --- a/lib/models/code.dart +++ b/lib/models/code.dart @@ -64,7 +64,7 @@ class Code { "?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" + updateIssuer + "&period=$updatePeriod&secret=" + - updatedSecret, + updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""), generatedID: generatedID, ); } @@ -216,6 +216,7 @@ class Code { other.digits == digits && other.period == period && other.secret == secret && + other.counter == counter && other.type == type && other.rawData == rawData; } @@ -228,6 +229,7 @@ class Code { period.hashCode ^ secret.hashCode ^ type.hashCode ^ + counter.hashCode ^ rawData.hashCode; } } diff --git a/lib/onboarding/view/setup_enter_secret_key_page.dart b/lib/onboarding/view/setup_enter_secret_key_page.dart index 23ee3a88d..4d0aab9ec 100644 --- a/lib/onboarding/view/setup_enter_secret_key_page.dart +++ b/lib/onboarding/view/setup_enter_secret_key_page.dart @@ -132,7 +132,7 @@ class _SetupEnterSecretKeyPageState extends State { secret: secret, ); // Verify the validity of the code - getTotp(newCode); + getOTP(newCode); Navigator.of(context).pop(newCode); } catch (e) { _showIncorrectDetailsDialog(context); diff --git a/lib/ui/code_widget.dart b/lib/ui/code_widget.dart index daa983236..dea079a87 100644 --- a/lib/ui/code_widget.dart +++ b/lib/ui/code_widget.dart @@ -35,10 +35,12 @@ class _CodeWidgetState extends State { _everySecondTimer = Timer.periodic(const Duration(milliseconds: 500), (Timer t) { - String newCode = _getTotp(); + String newCode = _getCurrentOTP(); if (newCode != _currentCode.value) { _currentCode.value = newCode; - _nextCode.value = _getNextTotp(); + if (widget.code.type == Type.totp) { + _nextCode.value = _getNextTotp(); + } } }); } @@ -54,8 +56,10 @@ class _CodeWidgetState extends State { @override Widget build(BuildContext context) { if (!_isInitialized) { - _currentCode.value = _getTotp(); - _nextCode.value = _getNextTotp(); + _currentCode.value = _getCurrentOTP(); + if (widget.code.type == Type.totp) { + _nextCode.value = _getNextTotp(); + } _isInitialized = true; } final l10n = context.l10n; @@ -113,9 +117,10 @@ class _CodeWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - CodeTimerProgress( - period: widget.code.period, - ), + if (widget.code.type == Type.totp) + CodeTimerProgress( + period: widget.code.period, + ), const SizedBox( height: 16, ), @@ -174,27 +179,47 @@ class _CodeWidgetState extends State { }, ), ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - l10n.nextTotpTitle, - style: Theme.of(context).textTheme.caption, - ), - ValueListenableBuilder( - valueListenable: _nextCode, - builder: (context, value, child) { - return Text( - value, - style: const TextStyle( - fontSize: 18, - color: Colors.grey, + widget.code.type == Type.totp + ? Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + l10n.nextTotpTitle, + style: + Theme.of(context).textTheme.caption, ), - ); - }, - ), - ], - ), + ValueListenableBuilder( + valueListenable: _nextCode, + builder: (context, value, child) { + return Text( + value, + style: const TextStyle( + fontSize: 18, + color: Colors.grey, + ), + ); + }, + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + l10n.nextTotpTitle, + style: + Theme.of(context).textTheme.caption, + ), + InkWell( + onTap: _onNextHotpTapped, + child: const Icon( + Icons.forward_outlined, + size: 32, + color: Colors.grey, + ), + ), + ], + ), ], ), ), @@ -213,10 +238,16 @@ class _CodeWidgetState extends State { } void _copyToClipboard() { - FlutterClipboard.copy(_getTotp()) + FlutterClipboard.copy(_getCurrentOTP()) .then((value) => showToast(context, context.l10n.copiedToClipboard)); } + void _onNextHotpTapped() { + if(widget.code.type == Type.hotp) { + CodeStore.instance.addCode(widget.code.copyWith(counter: widget.code.counter + 1), shouldSync: true).ignore(); + } + } + Future _onEditPressed(_) async { final Code? code = await Navigator.of(context).push( MaterialPageRoute( @@ -287,9 +318,9 @@ class _CodeWidgetState extends State { } } - String _getTotp() { + String _getCurrentOTP() { try { - return getTotp(widget.code); + return getOTP(widget.code); } catch (e) { return context.l10n.error; } @@ -297,6 +328,7 @@ class _CodeWidgetState extends State { String _getNextTotp() { try { + assert(widget.code.type == Type.totp); return getNextTotp(widget.code); } catch (e) { return context.l10n.error; diff --git a/lib/utils/totp_util.dart b/lib/utils/totp_util.dart index d3371ea02..d76318280 100644 --- a/lib/utils/totp_util.dart +++ b/lib/utils/totp_util.dart @@ -1,7 +1,10 @@ import 'package:ente_auth/models/code.dart'; import 'package:otp/otp.dart' as otp; -String getTotp(Code code) { +String getOTP(Code code) { + if(code.type == Type.hotp) { + return _getHOTPCode(code); + } return otp.OTP.generateTOTPCodeString( getSanitizedSecret(code.secret), DateTime.now().millisecondsSinceEpoch, @@ -12,6 +15,16 @@ String getTotp(Code code) { ); } +String _getHOTPCode(Code code) { + return otp.OTP.generateHOTPCodeString( + getSanitizedSecret(code.secret), + code.counter, + length: code.digits, + algorithm: _getAlgorithm(code), + isGoogle: true, + ); +} + String getNextTotp(Code code) { return otp.OTP.generateTOTPCodeString( getSanitizedSecret(code.secret),