From 276005b86842b2c12c14554353fe5f036933fe4a Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 21 Jan 2022 11:30:14 +0100 Subject: [PATCH 1/2] Add OATH reset dialog. --- lib/oath/menu_actions.dart | 13 +++---- lib/oath/state.dart | 26 ++++++++------ lib/oath/views/password_dialog.dart | 4 +-- lib/oath/views/reset_dialog.dart | 56 +++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 20 deletions(-) create mode 100755 lib/oath/views/reset_dialog.dart diff --git a/lib/oath/menu_actions.dart b/lib/oath/menu_actions.dart index 67103d60..4c893aeb 100755 --- a/lib/oath/menu_actions.dart +++ b/lib/oath/menu_actions.dart @@ -6,6 +6,7 @@ import '../app/state.dart'; import 'state.dart'; import 'views/add_account_page.dart'; import 'views/password_dialog.dart'; +import 'views/reset_dialog.dart'; List buildOathMenuActions( BuildContext context, AutoDisposeProviderRef ref) { @@ -41,14 +42,10 @@ List buildOathMenuActions( text: 'Factory reset', icon: const Icon(Icons.delete_forever), action: () { - ScaffoldMessenger.of(context) - ..clearSnackBars() - ..showSnackBar( - const SnackBar( - content: Text('Not implemented'), - duration: Duration(seconds: 2), - ), - ); + showDialog( + context: context, + builder: (context) => ResetDialog(device), + ); }, ), ]; diff --git a/lib/oath/state.dart b/lib/oath/state.dart index 1a91f978..473c01bb 100755 --- a/lib/oath/state.dart +++ b/lib/oath/state.dart @@ -39,7 +39,7 @@ final oathStateProvider = StateNotifierProvider.autoDispose .family>( (ref, devicePath) { final session = ref.watch(_sessionProvider(devicePath)); - final notifier = OathStateNotifier(session, ref.read); + final notifier = OathStateNotifier(session, ref); session ..setErrorHandler('state-reset', (_) async { ref.refresh(_sessionProvider(devicePath)); @@ -58,20 +58,20 @@ final oathStateProvider = StateNotifierProvider.autoDispose class OathStateNotifier extends StateNotifier { final RpcNodeSession _session; - final Reader _read; - OathStateNotifier(this._session, this._read) : super(null); + final Ref _ref; + OathStateNotifier(this._session, this._ref) : super(null); refresh() async { var result = await _session.command('get'); log.config('application status', jsonEncode(result)); var oathState = OathState.fromJson(result['data']); - final key = _read(_lockKeyProvider(_session.devicePath)); + final key = _ref.read(_lockKeyProvider(_session.devicePath)); if (oathState.locked && key != null) { final result = await _session.command('validate', params: {'key': key}); if (result['unlocked']) { oathState = oathState.copyWith(locked: false); } else { - _read(_lockKeyProvider(_session.devicePath).notifier).unsetKey(); + _ref.read(_lockKeyProvider(_session.devicePath).notifier).unsetKey(); } } if (mounted) { @@ -79,6 +79,12 @@ class OathStateNotifier extends StateNotifier { } } + Future reset() async { + await _session.command('reset'); + _ref.read(_lockKeyProvider(_session.devicePath).notifier).unsetKey(); + _ref.refresh(_sessionProvider(_session.devicePath)); + } + Future unlock(String password) async { var result = await _session.command('derive', params: {'password': password}); @@ -86,7 +92,7 @@ class OathStateNotifier extends StateNotifier { final status = await _session.command('validate', params: {'key': key}); if (mounted && status['unlocked']) { log.config('applet unlocked'); - _read(_lockKeyProvider(_session.devicePath).notifier).setKey(key); + _ref.read(_lockKeyProvider(_session.devicePath).notifier).setKey(key); state = state?.copyWith(locked: false); } return status['unlocked']; @@ -97,8 +103,8 @@ class OathStateNotifier extends StateNotifier { var result = await _session.command('derive', params: {'password': password}); log.info( - 'Check ${_read(_lockKeyProvider(_session.devicePath))} == ${result['key']}'); - return _read(_lockKeyProvider(_session.devicePath)) == result['key']; + 'Check ${_ref.read(_lockKeyProvider(_session.devicePath))} == ${result['key']}'); + return _ref.read(_lockKeyProvider(_session.devicePath)) == result['key']; } Future setPassword(String? current, String password) async { @@ -117,7 +123,7 @@ class OathStateNotifier extends StateNotifier { var key = result['key']; await _session.command('set_key', params: {'key': key}); log.config('OATH key set'); - _read(_lockKeyProvider(_session.devicePath).notifier).setKey(key); + _ref.read(_lockKeyProvider(_session.devicePath).notifier).setKey(key); if (mounted) { state = state?.copyWith(hasKey: true); } @@ -131,7 +137,7 @@ class OathStateNotifier extends StateNotifier { } } await _session.command('unset_key'); - _read(_lockKeyProvider(_session.devicePath).notifier).unsetKey(); + _ref.read(_lockKeyProvider(_session.devicePath).notifier).unsetKey(); if (mounted) { state = state?.copyWith(hasKey: false, locked: false); } diff --git a/lib/oath/views/password_dialog.dart b/lib/oath/views/password_dialog.dart index b9d9284f..ba7a7a1d 100755 --- a/lib/oath/views/password_dialog.dart +++ b/lib/oath/views/password_dialog.dart @@ -140,13 +140,13 @@ class _ManagePasswordDialogState extends ConsumerState { ], ), actions: [ - TextButton( + OutlinedButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('Cancel'), ), - TextButton( + ElevatedButton( onPressed: _newPassword.isNotEmpty && _newPassword == _confirmPassword && (!hasKey || _currentPassword.isNotEmpty) diff --git a/lib/oath/views/reset_dialog.dart b/lib/oath/views/reset_dialog.dart new file mode 100755 index 00000000..2c7685eb --- /dev/null +++ b/lib/oath/views/reset_dialog.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yubico_authenticator/oath/state.dart'; + +import '../../app/models.dart'; +import '../../app/state.dart'; + +class ResetDialog extends ConsumerWidget { + final DeviceNode device; + const ResetDialog(this.device, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // If current device changes, we need to pop back to the main Page. + ref.listen(currentDeviceProvider, (previous, next) { + Navigator.of(context).pop(); + }); + + return AlertDialog( + title: const Text('Reset to defaults?'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Warning! This will irrevocably delete all OATH TOTP/HOTP accounts from your YubiKey.'), + const Text(''), + Text( + 'You OATH credentials, as well as any password set, will be removed from this YubiKey. Make sure to first disable these from their respective web sites to avoid being locked out of your accounts.', + style: Theme.of(context).textTheme.bodyText1, + ), + ], + ), + actions: [ + OutlinedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () async { + await ref.read(oathStateProvider(device.path).notifier).reset(); + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('OATH application reset'), + duration: Duration(seconds: 2), + ), + ); + }, + child: const Text('Reset YubiKey'), + ), + ], + ); + } +} From ac8cf7a461d813d68939713a4c2d14f4851c9af4 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 21 Jan 2022 11:30:23 +0100 Subject: [PATCH 2/2] Remove leftover logging from debugging. --- lib/oath/state.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/oath/state.dart b/lib/oath/state.dart index 473c01bb..c3340083 100755 --- a/lib/oath/state.dart +++ b/lib/oath/state.dart @@ -99,11 +99,8 @@ class OathStateNotifier extends StateNotifier { } Future _checkPassword(String password) async { - log.info('Calling check password $password'); var result = await _session.command('derive', params: {'password': password}); - log.info( - 'Check ${_ref.read(_lockKeyProvider(_session.devicePath))} == ${result['key']}'); return _ref.read(_lockKeyProvider(_session.devicePath)) == result['key']; }