diff --git a/lib/desktop/management/state.dart b/lib/desktop/management/state.dart index 272ff13a..bb5b9b4f 100755 --- a/lib/desktop/management/state.dart +++ b/lib/desktop/management/state.dart @@ -100,9 +100,6 @@ class _DesktopManagementStateNotifier extends ManagementStateNotifier { {String currentLockCode = '', String newLockCode = '', bool reboot = false}) async { - if (reboot) { - state = const AsyncValue.loading(); - } await _session.command('configure', target: _subpath, params: { ...config.toJson(), 'cur_lock_code': currentLockCode, diff --git a/lib/desktop/piv/state.dart b/lib/desktop/piv/state.dart index 2f194c75..4fea7855 100644 --- a/lib/desktop/piv/state.dart +++ b/lib/desktop/piv/state.dart @@ -213,7 +213,8 @@ class _DesktopPivStateNotifier extends PivStateNotifier { return const PinVerificationStatus.success(); } on RpcError catch (e) { if (e.status == 'invalid-pin') { - return PinVerificationStatus.failure(e.body['attempts_remaining']); + return PinVerificationStatus.failure( + PivPinFailureReason.invalidPin(e.body['attempts_remaining'])); } rethrow; } finally { diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 4fc38ec3..24d918b7 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -173,6 +173,8 @@ "@_app_configuration": {}, "s_toggle_applications": "Anwendungen umschalten", "s_toggle_interfaces": null, + "p_toggle_applications_desc": null, + "p_toggle_interfaces_desc": null, "l_toggle_applications_desc": null, "l_toggle_interfaces_desc": null, "s_reconfiguring_yk": "YubiKey wird neu konfiguriert\u2026", @@ -195,6 +197,12 @@ }, "s_fido_disabled": "FIDO2 deaktiviert", "l_webauthn_req_fido2": "WebAuthn erfordert, dass die FIDO2 Anwendung auf Ihrem YubiKey aktiviert ist", + "s_lock_code": null, + "l_wrong_lock_code": null, + "s_show_lock_code": null, + "s_hide_lock_code": null, + "p_lock_code_required_desc": null, + "@_connectivity_issues": {}, "l_helper_not_responding": "Der Helper-Prozess antwortet nicht", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 028f18df..dfe2a552 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -173,6 +173,8 @@ "@_app_configuration": {}, "s_toggle_applications": "Toggle applications", "s_toggle_interfaces": "Toggle interfaces", + "p_toggle_applications_desc": "Independently enable or disable applications over available transports.", + "p_toggle_interfaces_desc": "Independently enable or disable USB interfaces.", "l_toggle_applications_desc": "Enable/disable applications", "l_toggle_interfaces_desc": "Enable/disable interfaces", "s_reconfiguring_yk": "Reconfiguring YubiKey\u2026", @@ -195,6 +197,12 @@ }, "s_fido_disabled": "FIDO2 disabled", "l_webauthn_req_fido2": "WebAuthn requires the FIDO2 application to be enabled on your YubiKey", + "s_lock_code": "Lock code", + "l_wrong_lock_code": "Wrong lock code", + "s_show_lock_code": "Show lock code", + "s_hide_lock_code": "Hide lock code", + "p_lock_code_required_desc": "The action you are about to perform requires the configuration lock code to be entered.", + "@_connectivity_issues": {}, "l_helper_not_responding": "The Helper process isn't responding", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index f0654654..6e6ccd26 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -173,6 +173,8 @@ "@_app_configuration": {}, "s_toggle_applications": "Changer les applications", "s_toggle_interfaces": null, + "p_toggle_applications_desc": null, + "p_toggle_interfaces_desc": null, "l_toggle_applications_desc": null, "l_toggle_interfaces_desc": null, "s_reconfiguring_yk": "Reconfiguration de la YubiKey\u2026", @@ -195,6 +197,12 @@ }, "s_fido_disabled": "FIDO2 désactivé", "l_webauthn_req_fido2": "WebAuthn demande que le FIDO2 soit activé sur votre YubiKey", + "s_lock_code": null, + "l_wrong_lock_code": null, + "s_show_lock_code": null, + "s_hide_lock_code": null, + "p_lock_code_required_desc": null, + "@_connectivity_issues": {}, "l_helper_not_responding": "Le processus Helper ne réponds pas", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 3b7ce577..2b9d072e 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -173,6 +173,8 @@ "@_app_configuration": {}, "s_toggle_applications": "アプリケーションの切替え", "s_toggle_interfaces": null, + "p_toggle_applications_desc": null, + "p_toggle_interfaces_desc": null, "l_toggle_applications_desc": null, "l_toggle_interfaces_desc": null, "s_reconfiguring_yk": "YubiKeyを再構成しています\u2026", @@ -195,6 +197,12 @@ }, "s_fido_disabled": "FIDO2が無効になっています", "l_webauthn_req_fido2": "WebAuthnでは、YubiKeyでFIDO2アプリケーションを有効にする必要があります", + "s_lock_code": null, + "l_wrong_lock_code": null, + "s_show_lock_code": null, + "s_hide_lock_code": null, + "p_lock_code_required_desc": null, + "@_connectivity_issues": {}, "l_helper_not_responding": "ヘルパープロセスが応答していません", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index c334dfab..84e00f94 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -173,6 +173,8 @@ "@_app_configuration": {}, "s_toggle_applications": "Przełączanie funkcji", "s_toggle_interfaces": "Przełącz interfejsy", + "p_toggle_applications_desc": null, + "p_toggle_interfaces_desc": null, "l_toggle_applications_desc": null, "l_toggle_interfaces_desc": null, "s_reconfiguring_yk": "Rekonfigurowanie YubiKey\u2026", @@ -195,6 +197,12 @@ }, "s_fido_disabled": "FIDO2 wyłączone", "l_webauthn_req_fido2": "WebAuthn wymaga włączenia funkcji FIDO2 w kluczu YubiKey", + "s_lock_code": null, + "l_wrong_lock_code": null, + "s_show_lock_code": null, + "s_hide_lock_code": null, + "p_lock_code_required_desc": null, + "@_connectivity_issues": {}, "l_helper_not_responding": "Proces pomocnika nie odpowiada", diff --git a/lib/management/views/management_screen.dart b/lib/management/views/management_screen.dart index 421c6f6d..0d0d7683 100755 --- a/lib/management/views/management_screen.dart +++ b/lib/management/views/management_screen.dart @@ -23,6 +23,8 @@ import 'package:material_symbols_icons/symbols.dart'; import '../../app/message.dart'; import '../../app/models.dart'; import '../../core/models.dart'; +import '../../widgets/app_input_decoration.dart'; +import '../../widgets/app_text_field.dart'; import '../../widgets/delayed_visibility.dart'; import '../../widgets/responsive_dialog.dart'; import '../models.dart'; @@ -176,6 +178,13 @@ class ManagementScreen extends ConsumerStatefulWidget { class _ManagementScreenState extends ConsumerState { late Map _enabled; late int _interfaces; + final _lockCodeController = TextEditingController(); + final _lockCodeFocus = FocusNode(); + bool _lockCodeIsWrong = false; + String _lockCodeError = ''; + bool _isObscure = true; + final lockCodeLength = 32; + bool _configuring = false; @override void initState() { @@ -185,6 +194,60 @@ class _ManagementScreenState extends ConsumerState { widget.deviceData.info.config.enabledCapabilities[Transport.usb] ?? 0); } + @override + void dispose() { + _lockCodeController.dispose(); + _lockCodeFocus.dispose(); + super.dispose(); + } + + Widget _buildLockCodeForm(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(l10n.p_lock_code_required_desc), + AppTextField( + obscureText: _isObscure, + maxLength: lockCodeLength, + autofillHints: const [AutofillHints.password], + controller: _lockCodeController, + focusNode: _lockCodeFocus, + decoration: AppInputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.s_lock_code, + errorText: _lockCodeIsWrong ? _lockCodeError : null, + errorMaxLines: 3, + prefixIcon: const Icon(Symbols.pin), + suffixIcon: IconButton( + icon: Icon( + _isObscure ? Symbols.visibility : Symbols.visibility_off), + onPressed: () { + setState(() { + _isObscure = !_isObscure; + }); + }, + tooltip: + _isObscure ? l10n.s_show_lock_code : l10n.s_hide_lock_code, + ), + ), + textInputAction: TextInputAction.next, + onChanged: (value) { + setState(() { + _lockCodeIsWrong = false; + }); + }, + onSubmitted: (_) => _submitForm, + ).init() + ] + .map((e) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: e, + )) + .toList(), + ); + } + Widget _buildCapabilitiesForm( BuildContext context, WidgetRef ref, DeviceInfo info) { return _CapabilitiesForm( @@ -200,6 +263,20 @@ class _ManagementScreenState extends ConsumerState { void _submitCapabilitiesForm() async { final l10n = AppLocalizations.of(context)!; + final isLocked = widget.deviceData.info.isLocked; + + if (isLocked && !Format.hex.isValid(_lockCodeController.text)) { + _lockCodeController.selection = TextSelection( + baseOffset: 0, extentOffset: _lockCodeController.text.length); + _lockCodeFocus.requestFocus(); + setState(() { + _lockCodeError = + l10n.l_invalid_format_allowed_chars(Format.hex.allowedCharacters); + _lockCodeIsWrong = true; + }); + return; + } + final bool reboot; if (widget.deviceData.node is UsbYubiKeyNode) { // Reboot if USB device descriptor is changed. @@ -215,6 +292,9 @@ class _ManagementScreenState extends ConsumerState { Function()? close; try { + setState(() { + _configuring = true; + }); if (reboot) { // This will take longer, show a message close = showMessage( @@ -226,13 +306,24 @@ class _ManagementScreenState extends ConsumerState { await ref .read(managementStateProvider(widget.deviceData.node.path).notifier) .writeConfig( - widget.deviceData.info.config - .copyWith(enabledCapabilities: _enabled), - reboot: reboot, - ); + widget.deviceData.info.config + .copyWith(enabledCapabilities: _enabled), + reboot: reboot, + currentLockCode: _lockCodeController.text); if (!mounted) return; if (!reboot) Navigator.pop(context); showMessage(context, l10n.s_config_updated); + } catch (_) { + if (isLocked) { + _lockCodeController.selection = TextSelection( + baseOffset: 0, extentOffset: _lockCodeController.text.length); + _lockCodeFocus.requestFocus(); + setState(() { + _lockCodeIsWrong = true; + _configuring = false; + _lockCodeError = l10n.l_wrong_lock_code; + }); + } } finally { close?.call(); } @@ -250,6 +341,9 @@ class _ManagementScreenState extends ConsumerState { void _submitModeForm() async { final l10n = AppLocalizations.of(context)!; + setState(() { + _configuring = true; + }); await ref .read(managementStateProvider(widget.deviceData.node.path).notifier) .setMode(interfaces: _interfaces); @@ -312,8 +406,22 @@ class _ManagementScreenState extends ConsumerState { .enabledCapabilities[Transport.usb] ?? 0); } + if (info.isLocked) { + final lockCode = _lockCodeController.text.replaceAll(' ', ''); + canSave = canSave && + lockCode.length == lockCodeLength && + !_lockCodeIsWrong; + } return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 18.0, vertical: 8), + child: Text(hasConfig + ? l10n.p_toggle_applications_desc + : l10n.p_toggle_interfaces_desc), + ), hasConfig ? Padding( padding: const EdgeInsets.symmetric(horizontal: 18.0), @@ -322,7 +430,27 @@ class _ManagementScreenState extends ConsumerState { : Padding( padding: const EdgeInsets.symmetric(horizontal: 18.0), child: _buildModeForm(context, ref, info), - ) + ), + if (info.isLocked) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 18.0) + .copyWith(top: 28), + child: _buildLockCodeForm(context), + ), + Padding( + padding: EdgeInsets.only( + top: info.isLocked ? 4.0 : 24.0, + bottom: 4, + left: 18.0, + right: 18.0), + child: Visibility( + visible: _configuring, + maintainSize: true, + maintainAnimation: true, + maintainState: true, + child: const LinearProgressIndicator(), + ), + ), ], ); },