mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 02:01:36 +03:00
Improve UX while generating keys
This commit is contained in:
parent
688211ddf6
commit
41f7fa2c00
@ -100,3 +100,10 @@ class ManagementNode(RpcNode):
|
||||
params.pop("auto_eject_timeout"),
|
||||
)
|
||||
return dict()
|
||||
|
||||
@action(
|
||||
condition=lambda self: issubclass(self._connection_type, SmartCardConnection)
|
||||
)
|
||||
def device_reset(self, params, event, signal):
|
||||
self.session.device_reset()
|
||||
return dict()
|
||||
|
@ -54,4 +54,9 @@ class _AndroidManagementStateNotifier extends ManagementStateNotifier {
|
||||
|
||||
ref.read(attachedDevicesProvider.notifier).refresh();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deviceReset() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -405,7 +405,12 @@ class _DeviceRowState extends ConsumerState<_DeviceRow> {
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
if (data != null && node == data.node)
|
||||
if (data != null &&
|
||||
node == data.node &&
|
||||
resetCapabilities.any((c) =>
|
||||
c.value &
|
||||
(data.info.supportedCapabilities[node!.transport] ?? 0) !=
|
||||
0))
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
showBlurDialog(
|
||||
|
@ -27,6 +27,7 @@ import '../../desktop/models.dart';
|
||||
import '../../fido/models.dart';
|
||||
import '../../fido/state.dart';
|
||||
import '../../management/models.dart';
|
||||
import '../../management/state.dart';
|
||||
import '../../oath/state.dart';
|
||||
import '../../piv/state.dart';
|
||||
import '../../widgets/responsive_dialog.dart';
|
||||
@ -36,6 +37,12 @@ import '../state.dart';
|
||||
|
||||
final _log = Logger('fido.views.reset_dialog');
|
||||
|
||||
const resetCapabilities = [
|
||||
Capability.oath,
|
||||
Capability.fido2,
|
||||
Capability.piv,
|
||||
];
|
||||
|
||||
class ResetDialog extends ConsumerStatefulWidget {
|
||||
final YubiKeyData data;
|
||||
const ResetDialog(this.data, {super.key});
|
||||
@ -75,6 +82,10 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
|
||||
final enabled = widget
|
||||
.data.info.config.enabledCapabilities[widget.data.node.transport] ??
|
||||
0;
|
||||
|
||||
final isBio = [FormFactor.usbABio, FormFactor.usbCBio]
|
||||
.contains(widget.data.info.formFactor);
|
||||
final globalReset = isBio && (supported & Capability.piv.value) != 0;
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
double progress = _currentStep == -1 ? 0.0 : _currentStep / (_totalSteps);
|
||||
return ResponsiveDialog(
|
||||
@ -151,7 +162,18 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
|
||||
showMessage(context, l10n.l_piv_app_reset);
|
||||
});
|
||||
},
|
||||
null => null,
|
||||
null => globalReset
|
||||
? () async {
|
||||
await ref
|
||||
.read(managementStateProvider(widget.data.node.path)
|
||||
.notifier)
|
||||
.deviceReset();
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.s_factory_reset);
|
||||
});
|
||||
}
|
||||
: null,
|
||||
_ => throw UnsupportedError('Application cannot be reset'),
|
||||
},
|
||||
child: Text(l10n.s_reset),
|
||||
@ -162,37 +184,36 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SegmentedButton<Capability>(
|
||||
emptySelectionAllowed: true,
|
||||
segments: [
|
||||
Capability.oath,
|
||||
Capability.fido2,
|
||||
Capability.piv,
|
||||
]
|
||||
.where((c) => supported & c.value != 0)
|
||||
.map((c) => ButtonSegment(
|
||||
value: c,
|
||||
icon: const Icon(null),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(right: 22),
|
||||
child: Text(c.getDisplayName(l10n)),
|
||||
),
|
||||
enabled: enabled & c.value != 0,
|
||||
))
|
||||
.toList(),
|
||||
selected: _application != null ? {_application!} : {},
|
||||
onSelectionChanged: (selected) {
|
||||
setState(() {
|
||||
_application = selected.first;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (!globalReset)
|
||||
SegmentedButton<Capability>(
|
||||
emptySelectionAllowed: true,
|
||||
segments: resetCapabilities
|
||||
.where((c) => supported & c.value != 0)
|
||||
.map((c) => ButtonSegment(
|
||||
value: c,
|
||||
icon: const Icon(null),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(right: 22),
|
||||
child: Text(c.getDisplayName(l10n)),
|
||||
),
|
||||
enabled: enabled & c.value != 0,
|
||||
))
|
||||
.toList(),
|
||||
selected: _application != null ? {_application!} : {},
|
||||
onSelectionChanged: (selected) {
|
||||
setState(() {
|
||||
_application = selected.first;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
switch (_application) {
|
||||
Capability.oath => l10n.p_warning_factory_reset,
|
||||
Capability.piv => l10n.p_warning_piv_reset,
|
||||
Capability.fido2 => l10n.p_warning_deletes_accounts,
|
||||
_ => l10n.p_factory_reset_an_app,
|
||||
_ => globalReset
|
||||
? l10n.p_warning_global_reset
|
||||
: l10n.p_factory_reset_an_app,
|
||||
},
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
@ -204,7 +225,9 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
|
||||
Capability.oath => l10n.p_warning_disable_credentials,
|
||||
Capability.piv => l10n.p_warning_piv_reset_desc,
|
||||
Capability.fido2 => l10n.p_warning_disable_accounts,
|
||||
_ => l10n.p_factory_reset_desc,
|
||||
_ => globalReset
|
||||
? l10n.p_warning_global_reset_desc
|
||||
: l10n.p_factory_reset_desc,
|
||||
},
|
||||
),
|
||||
if (_application == Capability.fido2 && _currentStep >= 0) ...[
|
||||
|
@ -111,4 +111,9 @@ class _DesktopManagementStateNotifier extends ManagementStateNotifier {
|
||||
});
|
||||
ref.read(attachedDevicesProvider.notifier).refresh();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deviceReset() async {
|
||||
await _session.command('device_reset', target: ['ccid', 'management']);
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ class _$SuccessImpl implements Success {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SuccessImpl &&
|
||||
@ -358,7 +358,7 @@ class _$SignalImpl implements Signal {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SignalImpl &&
|
||||
@ -547,7 +547,7 @@ class _$RpcErrorImpl implements RpcError {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$RpcErrorImpl &&
|
||||
@ -773,7 +773,7 @@ class _$RpcStateImpl implements _RpcState {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$RpcStateImpl &&
|
||||
|
@ -132,7 +132,7 @@ class _$FidoStateImpl extends _FidoState {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$FidoStateImpl &&
|
||||
@ -265,7 +265,7 @@ class _$PinSuccessImpl implements _PinSuccess {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$PinSuccessImpl);
|
||||
}
|
||||
@ -392,7 +392,7 @@ class _$PinFailureImpl implements _PinFailure {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$PinFailureImpl &&
|
||||
@ -594,7 +594,7 @@ class _$FingerprintImpl extends _Fingerprint {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$FingerprintImpl &&
|
||||
@ -750,7 +750,7 @@ class _$EventCaptureImpl implements _EventCapture {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$EventCaptureImpl &&
|
||||
@ -900,7 +900,7 @@ class _$EventCompleteImpl implements _EventComplete {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$EventCompleteImpl &&
|
||||
@ -1040,7 +1040,7 @@ class _$EventErrorImpl implements _EventError {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$EventErrorImpl &&
|
||||
@ -1274,7 +1274,7 @@ class _$FidoCredentialImpl implements _FidoCredential {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$FidoCredentialImpl &&
|
||||
|
@ -37,4 +37,6 @@ abstract class ManagementStateNotifier
|
||||
int challengeResponseTimeout = 0,
|
||||
int? autoEjectTimeout,
|
||||
});
|
||||
|
||||
Future<void> deviceReset();
|
||||
}
|
||||
|
@ -103,6 +103,8 @@ class OathPair with _$OathPair {
|
||||
|
||||
@freezed
|
||||
class OathState with _$OathState {
|
||||
const OathState._();
|
||||
|
||||
factory OathState(
|
||||
String deviceId,
|
||||
Version version, {
|
||||
@ -112,6 +114,9 @@ class OathState with _$OathState {
|
||||
required KeystoreState keystore,
|
||||
}) = _OathState;
|
||||
|
||||
int? get capacity =>
|
||||
version.isAtLeast(4) ? (version.isAtLeast(5, 7) ? 64 : 32) : null;
|
||||
|
||||
factory OathState.fromJson(Map<String, dynamic> json) =>
|
||||
_$OathStateFromJson(json);
|
||||
}
|
||||
|
@ -788,12 +788,13 @@ class __$$OathStateImplCopyWithImpl<$Res>
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$OathStateImpl implements _OathState {
|
||||
class _$OathStateImpl extends _OathState {
|
||||
_$OathStateImpl(this.deviceId, this.version,
|
||||
{required this.hasKey,
|
||||
required this.remembered,
|
||||
required this.locked,
|
||||
required this.keystore});
|
||||
required this.keystore})
|
||||
: super._();
|
||||
|
||||
factory _$OathStateImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$OathStateImplFromJson(json);
|
||||
@ -851,12 +852,13 @@ class _$OathStateImpl implements _OathState {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _OathState implements OathState {
|
||||
abstract class _OathState extends OathState {
|
||||
factory _OathState(final String deviceId, final Version version,
|
||||
{required final bool hasKey,
|
||||
required final bool remembered,
|
||||
required final bool locked,
|
||||
required final KeystoreState keystore}) = _$OathStateImpl;
|
||||
_OathState._() : super._();
|
||||
|
||||
factory _OathState.fromJson(Map<String, dynamic> json) =
|
||||
_$OathStateImpl.fromJson;
|
||||
|
@ -222,7 +222,7 @@ class _OathAddMultiAccountPageState
|
||||
if (widget.state != null) {
|
||||
final credsToAdd =
|
||||
_credStates.values.where((element) => element.$1).length;
|
||||
final capacity = widget.state!.version.isAtLeast(4) ? 32 : null;
|
||||
final capacity = widget.state!.capacity;
|
||||
return (credsToAdd > 0) &&
|
||||
(capacity == null || (_numCreds! + credsToAdd <= capacity));
|
||||
} else {
|
||||
|
@ -39,7 +39,7 @@ Widget oathBuildActions(
|
||||
int? used,
|
||||
}) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final capacity = oathState.version.isAtLeast(4) ? 32 : null;
|
||||
final capacity = oathState.capacity;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
|
@ -28,6 +28,7 @@ const defaultKeyType = KeyType.eccp256;
|
||||
const defaultGenerateType = GenerateType.certificate;
|
||||
|
||||
enum GenerateType {
|
||||
// TODO: Add "publicKey"? Needed for X25519
|
||||
certificate,
|
||||
csr;
|
||||
|
||||
@ -116,10 +117,18 @@ enum KeyType {
|
||||
rsa1024,
|
||||
@JsonValue(0x07)
|
||||
rsa2048,
|
||||
@JsonValue(0x05)
|
||||
rsa3072,
|
||||
@JsonValue(0x16)
|
||||
rsa4096,
|
||||
@JsonValue(0x11)
|
||||
eccp256,
|
||||
@JsonValue(0x14)
|
||||
eccp384;
|
||||
eccp384,
|
||||
@JsonValue(0xe0)
|
||||
ed25519,
|
||||
@JsonValue(0xe1)
|
||||
x25519;
|
||||
|
||||
const KeyType();
|
||||
|
||||
|
@ -71,8 +71,12 @@ Map<String, dynamic> _$$SlotMetadataImplToJson(_$SlotMetadataImpl instance) =>
|
||||
const _$KeyTypeEnumMap = {
|
||||
KeyType.rsa1024: 6,
|
||||
KeyType.rsa2048: 7,
|
||||
KeyType.rsa3072: 5,
|
||||
KeyType.rsa4096: 22,
|
||||
KeyType.eccp256: 17,
|
||||
KeyType.eccp384: 20,
|
||||
KeyType.ed25519: 224,
|
||||
KeyType.x25519: 225,
|
||||
};
|
||||
|
||||
const _$PinPolicyEnumMap = {
|
||||
|
@ -65,6 +65,18 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
_validToMax = DateTime.utc(now.year + 10, now.month, now.day);
|
||||
}
|
||||
|
||||
List<KeyType> _getSupportedKeyTypes() => [
|
||||
KeyType.rsa1024,
|
||||
KeyType.rsa2048,
|
||||
if (widget.pivState.version.isAtLeast(5, 7)) ...[
|
||||
KeyType.rsa3072,
|
||||
KeyType.rsa4096,
|
||||
KeyType.ed25519,
|
||||
],
|
||||
KeyType.eccp256,
|
||||
KeyType.eccp384,
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
@ -98,7 +110,6 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
|
||||
final pivNotifier =
|
||||
ref.read(pivSlotsProvider(widget.devicePath).notifier);
|
||||
final withContext = ref.read(withContextProvider);
|
||||
|
||||
if (!await pivNotifier.validateRfc4514(_subject)) {
|
||||
setState(() {
|
||||
@ -108,31 +119,19 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
return;
|
||||
}
|
||||
|
||||
void Function()? close;
|
||||
final PivGenerateResult result;
|
||||
try {
|
||||
close = await withContext<void Function()>(
|
||||
(context) async => showMessage(
|
||||
context,
|
||||
l10n.l_generating_private_key,
|
||||
duration: const Duration(seconds: 30),
|
||||
));
|
||||
result = await pivNotifier.generate(
|
||||
widget.pivSlot.slot,
|
||||
_keyType,
|
||||
parameters: switch (_generateType) {
|
||||
GenerateType.certificate =>
|
||||
PivGenerateParameters.certificate(
|
||||
subject: _subject,
|
||||
validFrom: _validFrom,
|
||||
validTo: _validTo),
|
||||
GenerateType.csr =>
|
||||
PivGenerateParameters.csr(subject: _subject),
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
close?.call();
|
||||
}
|
||||
final result = await pivNotifier.generate(
|
||||
widget.pivSlot.slot,
|
||||
_keyType,
|
||||
parameters: switch (_generateType) {
|
||||
GenerateType.certificate =>
|
||||
PivGenerateParameters.certificate(
|
||||
subject: _subject,
|
||||
validFrom: _validFrom,
|
||||
validTo: _validTo),
|
||||
GenerateType.csr =>
|
||||
PivGenerateParameters.csr(subject: _subject),
|
||||
},
|
||||
);
|
||||
|
||||
await ref.read(withContextProvider)(
|
||||
(context) async {
|
||||
@ -193,7 +192,7 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
ChoiceFilterChip<KeyType>(
|
||||
items: KeyType.values,
|
||||
items: _getSupportedKeyTypes(),
|
||||
value: _keyType,
|
||||
selected: _keyType != defaultKeyType,
|
||||
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
|
||||
@ -240,6 +239,16 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
},
|
||||
),
|
||||
]),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Visibility(
|
||||
visible: _generating,
|
||||
maintainSize: true,
|
||||
maintainAnimation: true,
|
||||
maintainState: true,
|
||||
child: const LinearProgressIndicator(),
|
||||
),
|
||||
),
|
||||
]
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
|
Loading…
Reference in New Issue
Block a user