mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 02:01:36 +03:00
Merge PR #33.
This commit is contained in:
commit
af652b3609
@ -12,12 +12,26 @@ class YubiKeyData with _$YubiKeyData {
|
|||||||
_YubiKeyData;
|
_YubiKeyData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _listEquality = ListEquality();
|
||||||
|
|
||||||
|
class DevicePath {
|
||||||
|
final List<String> segments;
|
||||||
|
|
||||||
|
DevicePath(List<String> path) : segments = List.unmodifiable(path);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
other is DevicePath && _listEquality.equals(segments, other.segments);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hashAll(segments);
|
||||||
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class DeviceNode with _$DeviceNode {
|
class DeviceNode with _$DeviceNode {
|
||||||
factory DeviceNode.usbYubiKey(
|
factory DeviceNode.usbYubiKey(
|
||||||
List<String> path, String name, int pid, DeviceInfo info) =
|
DevicePath path, String name, int pid, DeviceInfo info) = UsbYubiKeyNode;
|
||||||
UsbYubiKeyNode;
|
factory DeviceNode.nfcReader(DevicePath path, String name) = NfcReaderNode;
|
||||||
factory DeviceNode.nfcReader(List<String> path, String name) = NfcReaderNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -206,7 +206,7 @@ class _$DeviceNodeTearOff {
|
|||||||
const _$DeviceNodeTearOff();
|
const _$DeviceNodeTearOff();
|
||||||
|
|
||||||
UsbYubiKeyNode usbYubiKey(
|
UsbYubiKeyNode usbYubiKey(
|
||||||
List<String> path, String name, int pid, DeviceInfo info) {
|
DevicePath path, String name, int pid, DeviceInfo info) {
|
||||||
return UsbYubiKeyNode(
|
return UsbYubiKeyNode(
|
||||||
path,
|
path,
|
||||||
name,
|
name,
|
||||||
@ -215,7 +215,7 @@ class _$DeviceNodeTearOff {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
NfcReaderNode nfcReader(List<String> path, String name) {
|
NfcReaderNode nfcReader(DevicePath path, String name) {
|
||||||
return NfcReaderNode(
|
return NfcReaderNode(
|
||||||
path,
|
path,
|
||||||
name,
|
name,
|
||||||
@ -228,29 +228,29 @@ const $DeviceNode = _$DeviceNodeTearOff();
|
|||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$DeviceNode {
|
mixin _$DeviceNode {
|
||||||
List<String> get path => throw _privateConstructorUsedError;
|
DevicePath get path => throw _privateConstructorUsedError;
|
||||||
String get name => throw _privateConstructorUsedError;
|
String get name => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function(
|
required TResult Function(
|
||||||
List<String> path, String name, int pid, DeviceInfo info)
|
DevicePath path, String name, int pid, DeviceInfo info)
|
||||||
usbYubiKey,
|
usbYubiKey,
|
||||||
required TResult Function(List<String> path, String name) nfcReader,
|
required TResult Function(DevicePath path, String name) nfcReader,
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult Function(List<String> path, String name, int pid, DeviceInfo info)?
|
TResult Function(DevicePath path, String name, int pid, DeviceInfo info)?
|
||||||
usbYubiKey,
|
usbYubiKey,
|
||||||
TResult Function(List<String> path, String name)? nfcReader,
|
TResult Function(DevicePath path, String name)? nfcReader,
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function(List<String> path, String name, int pid, DeviceInfo info)?
|
TResult Function(DevicePath path, String name, int pid, DeviceInfo info)?
|
||||||
usbYubiKey,
|
usbYubiKey,
|
||||||
TResult Function(List<String> path, String name)? nfcReader,
|
TResult Function(DevicePath path, String name)? nfcReader,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -284,7 +284,7 @@ abstract class $DeviceNodeCopyWith<$Res> {
|
|||||||
factory $DeviceNodeCopyWith(
|
factory $DeviceNodeCopyWith(
|
||||||
DeviceNode value, $Res Function(DeviceNode) then) =
|
DeviceNode value, $Res Function(DeviceNode) then) =
|
||||||
_$DeviceNodeCopyWithImpl<$Res>;
|
_$DeviceNodeCopyWithImpl<$Res>;
|
||||||
$Res call({List<String> path, String name});
|
$Res call({DevicePath path, String name});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -304,7 +304,7 @@ class _$DeviceNodeCopyWithImpl<$Res> implements $DeviceNodeCopyWith<$Res> {
|
|||||||
path: path == freezed
|
path: path == freezed
|
||||||
? _value.path
|
? _value.path
|
||||||
: path // ignore: cast_nullable_to_non_nullable
|
: path // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,
|
as DevicePath,
|
||||||
name: name == freezed
|
name: name == freezed
|
||||||
? _value.name
|
? _value.name
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
@ -320,7 +320,7 @@ abstract class $UsbYubiKeyNodeCopyWith<$Res>
|
|||||||
UsbYubiKeyNode value, $Res Function(UsbYubiKeyNode) then) =
|
UsbYubiKeyNode value, $Res Function(UsbYubiKeyNode) then) =
|
||||||
_$UsbYubiKeyNodeCopyWithImpl<$Res>;
|
_$UsbYubiKeyNodeCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
$Res call({List<String> path, String name, int pid, DeviceInfo info});
|
$Res call({DevicePath path, String name, int pid, DeviceInfo info});
|
||||||
|
|
||||||
$DeviceInfoCopyWith<$Res> get info;
|
$DeviceInfoCopyWith<$Res> get info;
|
||||||
}
|
}
|
||||||
@ -346,7 +346,7 @@ class _$UsbYubiKeyNodeCopyWithImpl<$Res> extends _$DeviceNodeCopyWithImpl<$Res>
|
|||||||
path == freezed
|
path == freezed
|
||||||
? _value.path
|
? _value.path
|
||||||
: path // ignore: cast_nullable_to_non_nullable
|
: path // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,
|
as DevicePath,
|
||||||
name == freezed
|
name == freezed
|
||||||
? _value.name
|
? _value.name
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
@ -376,7 +376,7 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
|||||||
_$UsbYubiKeyNode(this.path, this.name, this.pid, this.info);
|
_$UsbYubiKeyNode(this.path, this.name, this.pid, this.info);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final List<String> path;
|
final DevicePath path;
|
||||||
@override
|
@override
|
||||||
final String name;
|
final String name;
|
||||||
@override
|
@override
|
||||||
@ -417,9 +417,9 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function(
|
required TResult Function(
|
||||||
List<String> path, String name, int pid, DeviceInfo info)
|
DevicePath path, String name, int pid, DeviceInfo info)
|
||||||
usbYubiKey,
|
usbYubiKey,
|
||||||
required TResult Function(List<String> path, String name) nfcReader,
|
required TResult Function(DevicePath path, String name) nfcReader,
|
||||||
}) {
|
}) {
|
||||||
return usbYubiKey(path, name, pid, info);
|
return usbYubiKey(path, name, pid, info);
|
||||||
}
|
}
|
||||||
@ -427,9 +427,9 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult Function(List<String> path, String name, int pid, DeviceInfo info)?
|
TResult Function(DevicePath path, String name, int pid, DeviceInfo info)?
|
||||||
usbYubiKey,
|
usbYubiKey,
|
||||||
TResult Function(List<String> path, String name)? nfcReader,
|
TResult Function(DevicePath path, String name)? nfcReader,
|
||||||
}) {
|
}) {
|
||||||
return usbYubiKey?.call(path, name, pid, info);
|
return usbYubiKey?.call(path, name, pid, info);
|
||||||
}
|
}
|
||||||
@ -437,9 +437,9 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function(List<String> path, String name, int pid, DeviceInfo info)?
|
TResult Function(DevicePath path, String name, int pid, DeviceInfo info)?
|
||||||
usbYubiKey,
|
usbYubiKey,
|
||||||
TResult Function(List<String> path, String name)? nfcReader,
|
TResult Function(DevicePath path, String name)? nfcReader,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
if (usbYubiKey != null) {
|
if (usbYubiKey != null) {
|
||||||
@ -482,11 +482,11 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
|||||||
|
|
||||||
abstract class UsbYubiKeyNode implements DeviceNode {
|
abstract class UsbYubiKeyNode implements DeviceNode {
|
||||||
factory UsbYubiKeyNode(
|
factory UsbYubiKeyNode(
|
||||||
List<String> path, String name, int pid, DeviceInfo info) =
|
DevicePath path, String name, int pid, DeviceInfo info) =
|
||||||
_$UsbYubiKeyNode;
|
_$UsbYubiKeyNode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get path;
|
DevicePath get path;
|
||||||
@override
|
@override
|
||||||
String get name;
|
String get name;
|
||||||
int get pid;
|
int get pid;
|
||||||
@ -504,7 +504,7 @@ abstract class $NfcReaderNodeCopyWith<$Res>
|
|||||||
NfcReaderNode value, $Res Function(NfcReaderNode) then) =
|
NfcReaderNode value, $Res Function(NfcReaderNode) then) =
|
||||||
_$NfcReaderNodeCopyWithImpl<$Res>;
|
_$NfcReaderNodeCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
$Res call({List<String> path, String name});
|
$Res call({DevicePath path, String name});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -526,7 +526,7 @@ class _$NfcReaderNodeCopyWithImpl<$Res> extends _$DeviceNodeCopyWithImpl<$Res>
|
|||||||
path == freezed
|
path == freezed
|
||||||
? _value.path
|
? _value.path
|
||||||
: path // ignore: cast_nullable_to_non_nullable
|
: path // ignore: cast_nullable_to_non_nullable
|
||||||
as List<String>,
|
as DevicePath,
|
||||||
name == freezed
|
name == freezed
|
||||||
? _value.name
|
? _value.name
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
@ -541,7 +541,7 @@ class _$NfcReaderNode implements NfcReaderNode {
|
|||||||
_$NfcReaderNode(this.path, this.name);
|
_$NfcReaderNode(this.path, this.name);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final List<String> path;
|
final DevicePath path;
|
||||||
@override
|
@override
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
@ -574,9 +574,9 @@ class _$NfcReaderNode implements NfcReaderNode {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function(
|
required TResult Function(
|
||||||
List<String> path, String name, int pid, DeviceInfo info)
|
DevicePath path, String name, int pid, DeviceInfo info)
|
||||||
usbYubiKey,
|
usbYubiKey,
|
||||||
required TResult Function(List<String> path, String name) nfcReader,
|
required TResult Function(DevicePath path, String name) nfcReader,
|
||||||
}) {
|
}) {
|
||||||
return nfcReader(path, name);
|
return nfcReader(path, name);
|
||||||
}
|
}
|
||||||
@ -584,9 +584,9 @@ class _$NfcReaderNode implements NfcReaderNode {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult Function(List<String> path, String name, int pid, DeviceInfo info)?
|
TResult Function(DevicePath path, String name, int pid, DeviceInfo info)?
|
||||||
usbYubiKey,
|
usbYubiKey,
|
||||||
TResult Function(List<String> path, String name)? nfcReader,
|
TResult Function(DevicePath path, String name)? nfcReader,
|
||||||
}) {
|
}) {
|
||||||
return nfcReader?.call(path, name);
|
return nfcReader?.call(path, name);
|
||||||
}
|
}
|
||||||
@ -594,9 +594,9 @@ class _$NfcReaderNode implements NfcReaderNode {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function(List<String> path, String name, int pid, DeviceInfo info)?
|
TResult Function(DevicePath path, String name, int pid, DeviceInfo info)?
|
||||||
usbYubiKey,
|
usbYubiKey,
|
||||||
TResult Function(List<String> path, String name)? nfcReader,
|
TResult Function(DevicePath path, String name)? nfcReader,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
if (nfcReader != null) {
|
if (nfcReader != null) {
|
||||||
@ -638,10 +638,10 @@ class _$NfcReaderNode implements NfcReaderNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class NfcReaderNode implements DeviceNode {
|
abstract class NfcReaderNode implements DeviceNode {
|
||||||
factory NfcReaderNode(List<String> path, String name) = _$NfcReaderNode;
|
factory NfcReaderNode(DevicePath path, String name) = _$NfcReaderNode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get path;
|
DevicePath get path;
|
||||||
@override
|
@override
|
||||||
String get name;
|
String get name;
|
||||||
@override
|
@override
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:yubico_authenticator/management/models.dart';
|
import 'package:yubico_authenticator/management/models.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
|
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'device_avatar.dart';
|
import 'device_avatar.dart';
|
||||||
|
|
||||||
Function _listEquals = const ListEquality().equals;
|
|
||||||
|
|
||||||
class MainActionsDialog extends ConsumerWidget {
|
class MainActionsDialog extends ConsumerWidget {
|
||||||
const MainActionsDialog({Key? key}) : super(key: key);
|
const MainActionsDialog({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@ -20,7 +17,7 @@ class MainActionsDialog extends ConsumerWidget {
|
|||||||
final actions = ref.watch(menuActionsProvider)(context);
|
final actions = ref.watch(menuActionsProvider)(context);
|
||||||
|
|
||||||
if (currentNode != null) {
|
if (currentNode != null) {
|
||||||
devices.removeWhere((e) => _listEquals(e.path, currentNode.path));
|
devices.removeWhere((e) => e.path == currentNode.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SimpleDialog(
|
return SimpleDialog(
|
||||||
|
@ -67,7 +67,7 @@ class UsbDeviceNotifier extends StateNotifier<List<UsbYubiKeyNode>> {
|
|||||||
var deviceResult = await _rpc.command('get', path);
|
var deviceResult = await _rpc.command('get', path);
|
||||||
var deviceData = deviceResult['data'];
|
var deviceData = deviceResult['data'];
|
||||||
usbDevices.add(DeviceNode.usbYubiKey(
|
usbDevices.add(DeviceNode.usbYubiKey(
|
||||||
path,
|
DevicePath(path),
|
||||||
deviceData['name'],
|
deviceData['name'],
|
||||||
deviceData['pid'],
|
deviceData['pid'],
|
||||||
DeviceInfo.fromJson(deviceData['info']),
|
DeviceInfo.fromJson(deviceData['info']),
|
||||||
@ -131,8 +131,8 @@ class NfcDeviceNotifier extends StateNotifier<List<NfcReaderNode>> {
|
|||||||
log.info('NFC state change', jsonEncode(children));
|
log.info('NFC state change', jsonEncode(children));
|
||||||
_nfcState = newState;
|
_nfcState = newState;
|
||||||
state = children.entries
|
state = children.entries
|
||||||
.map((e) =>
|
.map((e) => DeviceNode.nfcReader(
|
||||||
DeviceNode.nfcReader(['nfc', e.key], e.value['name'] as String)
|
DevicePath(['nfc', e.key]), e.value['name'] as String)
|
||||||
as NfcReaderNode)
|
as NfcReaderNode)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@ -207,7 +207,7 @@ class CurrentDeviceDataNotifier extends StateNotifier<YubiKeyData?> {
|
|||||||
_pollTimer?.cancel();
|
_pollTimer?.cancel();
|
||||||
final node = _deviceNode!;
|
final node = _deviceNode!;
|
||||||
try {
|
try {
|
||||||
var result = await _rpc.command('get', node.path);
|
var result = await _rpc.command('get', node.path.segments);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (result['data']['present']) {
|
if (result['data']['present']) {
|
||||||
state = YubiKeyData(node, result['data']['name'],
|
state = YubiKeyData(node, result['data']['name'],
|
||||||
|
@ -15,13 +15,35 @@ import '../state.dart';
|
|||||||
final log = Logger('desktop.oath.state');
|
final log = Logger('desktop.oath.state');
|
||||||
|
|
||||||
final _sessionProvider =
|
final _sessionProvider =
|
||||||
Provider.autoDispose.family<RpcNodeSession, List<String>>(
|
Provider.autoDispose.family<RpcNodeSession, DevicePath>(
|
||||||
(ref, devicePath) =>
|
(ref, devicePath) =>
|
||||||
RpcNodeSession(ref.watch(rpcProvider), devicePath, ['ccid', 'oath']),
|
RpcNodeSession(ref.watch(rpcProvider), devicePath, ['ccid', 'oath']),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// This remembers the key for all devices for the duration of the process.
|
||||||
|
final _oathLockKeyProvider =
|
||||||
|
StateNotifierProvider.family<_LockKeyNotifier, String?, DevicePath>(
|
||||||
|
(ref, devicePath) => _LockKeyNotifier(null));
|
||||||
|
|
||||||
|
class _LockKeyNotifier extends StateNotifier<String?> {
|
||||||
|
_LockKeyNotifier(String? state) : super(state);
|
||||||
|
|
||||||
|
setKey(String key) {
|
||||||
|
state = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsetKey() {
|
||||||
|
state = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final desktopOathState = StateNotifierProvider.autoDispose
|
final desktopOathState = StateNotifierProvider.autoDispose
|
||||||
.family<OathStateNotifier, OathState?, List<String>>(
|
.family<OathStateNotifier, OathState?, DevicePath>(
|
||||||
(ref, devicePath) {
|
(ref, devicePath) {
|
||||||
final session = ref.watch(_sessionProvider(devicePath));
|
final session = ref.watch(_sessionProvider(devicePath));
|
||||||
final notifier = _DesktopOathStateNotifier(session, ref);
|
final notifier = _DesktopOathStateNotifier(session, ref);
|
||||||
@ -50,13 +72,15 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
|
|||||||
var result = await _session.command('get');
|
var result = await _session.command('get');
|
||||||
log.config('application status', jsonEncode(result));
|
log.config('application status', jsonEncode(result));
|
||||||
var oathState = OathState.fromJson(result['data']);
|
var oathState = OathState.fromJson(result['data']);
|
||||||
final key = _ref.read(oathLockKeyProvider(_session.devicePath));
|
final key = _ref.read(_oathLockKeyProvider(_session.devicePath));
|
||||||
if (oathState.locked && key != null) {
|
if (oathState.locked && key != null) {
|
||||||
final result = await _session.command('validate', params: {'key': key});
|
final result = await _session.command('validate', params: {'key': key});
|
||||||
if (result['unlocked']) {
|
if (result['success']) {
|
||||||
oathState = oathState.copyWith(locked: false);
|
oathState = oathState.copyWith(locked: false);
|
||||||
} else {
|
} else {
|
||||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
_ref
|
||||||
|
.read(_oathLockKeyProvider(_session.devicePath).notifier)
|
||||||
|
.unsetKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -67,28 +91,32 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<void> reset() async {
|
Future<void> reset() async {
|
||||||
await _session.command('reset');
|
await _session.command('reset');
|
||||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
||||||
_ref.refresh(_sessionProvider(_session.devicePath));
|
_ref.refresh(_sessionProvider(_session.devicePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> unlock(String password) async {
|
Future<bool> unlock(String password, {bool remember = false}) async {
|
||||||
var result =
|
var result =
|
||||||
await _session.command('derive', params: {'password': password});
|
await _session.command('derive', params: {'password': password});
|
||||||
var key = result['key'];
|
var key = result['key'];
|
||||||
final status = await _session.command('validate', params: {'key': key});
|
final status = await _session
|
||||||
if (mounted && status['unlocked']) {
|
.command('validate', params: {'key': key, 'remember': remember});
|
||||||
|
if (mounted && status['success']) {
|
||||||
log.config('applet unlocked');
|
log.config('applet unlocked');
|
||||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
|
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
|
||||||
state = state?.copyWith(locked: false);
|
state = state?.copyWith(
|
||||||
|
locked: false,
|
||||||
|
remembered: remember || state?.remembered == true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return status['unlocked'];
|
return status['success'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _checkPassword(String password) async {
|
Future<bool> _checkPassword(String password) async {
|
||||||
var result =
|
var result =
|
||||||
await _session.command('derive', params: {'password': password});
|
await _session.command('validate', params: {'password': password});
|
||||||
return _ref.read(oathLockKeyProvider(_session.devicePath)) == result['key'];
|
return result['success'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -108,7 +136,7 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
|
|||||||
var key = result['key'];
|
var key = result['key'];
|
||||||
await _session.command('set_key', params: {'key': key});
|
await _session.command('set_key', params: {'key': key});
|
||||||
log.config('OATH key set');
|
log.config('OATH key set');
|
||||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
|
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
state = state?.copyWith(hasKey: true);
|
state = state?.copyWith(hasKey: true);
|
||||||
}
|
}
|
||||||
@ -123,16 +151,25 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await _session.command('unset_key');
|
await _session.command('unset_key');
|
||||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
state = state?.copyWith(hasKey: false, locked: false);
|
state = state?.copyWith(hasKey: false, locked: false);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> forgetPassword() async {
|
||||||
|
await _session.command('forget');
|
||||||
|
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
||||||
|
if (mounted) {
|
||||||
|
state = state?.copyWith(remembered: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final desktopOathCredentialListProvider = StateNotifierProvider.autoDispose
|
final desktopOathCredentialListProvider = StateNotifierProvider.autoDispose
|
||||||
.family<OathCredentialListNotifier, List<OathPair>?, List<String>>(
|
.family<OathCredentialListNotifier, List<OathPair>?, DevicePath>(
|
||||||
(ref, devicePath) {
|
(ref, devicePath) {
|
||||||
var notifier = _DesktopCredentialListNotifier(
|
var notifier = _DesktopCredentialListNotifier(
|
||||||
ref.watch(_sessionProvider(devicePath)),
|
ref.watch(_sessionProvider(devicePath)),
|
||||||
|
@ -5,6 +5,7 @@ import 'dart:io';
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:async/async.dart';
|
import 'package:async/async.dart';
|
||||||
|
|
||||||
|
import '../app/models.dart';
|
||||||
import 'models.dart';
|
import 'models.dart';
|
||||||
|
|
||||||
final log = Logger('rpc');
|
final log = Logger('rpc');
|
||||||
@ -147,7 +148,7 @@ typedef ErrorHandler = Future<void> Function(RpcError e);
|
|||||||
|
|
||||||
class RpcNodeSession {
|
class RpcNodeSession {
|
||||||
final RpcSession _rpc;
|
final RpcSession _rpc;
|
||||||
final List<String> devicePath;
|
final DevicePath devicePath;
|
||||||
final List<String> subPath;
|
final List<String> subPath;
|
||||||
final Map<String, ErrorHandler> _errorHandlers = {};
|
final Map<String, ErrorHandler> _errorHandlers = {};
|
||||||
|
|
||||||
@ -170,7 +171,7 @@ class RpcNodeSession {
|
|||||||
try {
|
try {
|
||||||
return await _rpc.command(
|
return await _rpc.command(
|
||||||
action,
|
action,
|
||||||
devicePath + subPath + target,
|
devicePath.segments + subPath + target,
|
||||||
params: params,
|
params: params,
|
||||||
signal: signal,
|
signal: signal,
|
||||||
);
|
);
|
||||||
|
@ -55,7 +55,12 @@ class OathPair with _$OathPair {
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class OathState with _$OathState {
|
class OathState with _$OathState {
|
||||||
factory OathState(String deviceId, bool hasKey, bool locked) = _OathState;
|
factory OathState(
|
||||||
|
String deviceId, {
|
||||||
|
required bool hasKey,
|
||||||
|
required bool remembered,
|
||||||
|
required bool locked,
|
||||||
|
}) = _OathState;
|
||||||
|
|
||||||
factory OathState.fromJson(Map<String, dynamic> json) =>
|
factory OathState.fromJson(Map<String, dynamic> json) =>
|
||||||
_$OathStateFromJson(json);
|
_$OathStateFromJson(json);
|
||||||
|
@ -652,11 +652,13 @@ OathState _$OathStateFromJson(Map<String, dynamic> json) {
|
|||||||
class _$OathStateTearOff {
|
class _$OathStateTearOff {
|
||||||
const _$OathStateTearOff();
|
const _$OathStateTearOff();
|
||||||
|
|
||||||
_OathState call(String deviceId, bool hasKey, bool locked) {
|
_OathState call(String deviceId,
|
||||||
|
{required bool hasKey, required bool remembered, required bool locked}) {
|
||||||
return _OathState(
|
return _OathState(
|
||||||
deviceId,
|
deviceId,
|
||||||
hasKey,
|
hasKey: hasKey,
|
||||||
locked,
|
remembered: remembered,
|
||||||
|
locked: locked,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,6 +674,7 @@ const $OathState = _$OathStateTearOff();
|
|||||||
mixin _$OathState {
|
mixin _$OathState {
|
||||||
String get deviceId => throw _privateConstructorUsedError;
|
String get deviceId => throw _privateConstructorUsedError;
|
||||||
bool get hasKey => throw _privateConstructorUsedError;
|
bool get hasKey => throw _privateConstructorUsedError;
|
||||||
|
bool get remembered => throw _privateConstructorUsedError;
|
||||||
bool get locked => throw _privateConstructorUsedError;
|
bool get locked => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@ -684,7 +687,7 @@ mixin _$OathState {
|
|||||||
abstract class $OathStateCopyWith<$Res> {
|
abstract class $OathStateCopyWith<$Res> {
|
||||||
factory $OathStateCopyWith(OathState value, $Res Function(OathState) then) =
|
factory $OathStateCopyWith(OathState value, $Res Function(OathState) then) =
|
||||||
_$OathStateCopyWithImpl<$Res>;
|
_$OathStateCopyWithImpl<$Res>;
|
||||||
$Res call({String deviceId, bool hasKey, bool locked});
|
$Res call({String deviceId, bool hasKey, bool remembered, bool locked});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -699,6 +702,7 @@ class _$OathStateCopyWithImpl<$Res> implements $OathStateCopyWith<$Res> {
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? deviceId = freezed,
|
Object? deviceId = freezed,
|
||||||
Object? hasKey = freezed,
|
Object? hasKey = freezed,
|
||||||
|
Object? remembered = freezed,
|
||||||
Object? locked = freezed,
|
Object? locked = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
@ -710,6 +714,10 @@ class _$OathStateCopyWithImpl<$Res> implements $OathStateCopyWith<$Res> {
|
|||||||
? _value.hasKey
|
? _value.hasKey
|
||||||
: hasKey // ignore: cast_nullable_to_non_nullable
|
: hasKey // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
remembered: remembered == freezed
|
||||||
|
? _value.remembered
|
||||||
|
: remembered // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
locked: locked == freezed
|
locked: locked == freezed
|
||||||
? _value.locked
|
? _value.locked
|
||||||
: locked // ignore: cast_nullable_to_non_nullable
|
: locked // ignore: cast_nullable_to_non_nullable
|
||||||
@ -724,7 +732,7 @@ abstract class _$OathStateCopyWith<$Res> implements $OathStateCopyWith<$Res> {
|
|||||||
_OathState value, $Res Function(_OathState) then) =
|
_OathState value, $Res Function(_OathState) then) =
|
||||||
__$OathStateCopyWithImpl<$Res>;
|
__$OathStateCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
$Res call({String deviceId, bool hasKey, bool locked});
|
$Res call({String deviceId, bool hasKey, bool remembered, bool locked});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -740,6 +748,7 @@ class __$OathStateCopyWithImpl<$Res> extends _$OathStateCopyWithImpl<$Res>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? deviceId = freezed,
|
Object? deviceId = freezed,
|
||||||
Object? hasKey = freezed,
|
Object? hasKey = freezed,
|
||||||
|
Object? remembered = freezed,
|
||||||
Object? locked = freezed,
|
Object? locked = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_OathState(
|
return _then(_OathState(
|
||||||
@ -747,11 +756,15 @@ class __$OathStateCopyWithImpl<$Res> extends _$OathStateCopyWithImpl<$Res>
|
|||||||
? _value.deviceId
|
? _value.deviceId
|
||||||
: deviceId // ignore: cast_nullable_to_non_nullable
|
: deviceId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
hasKey == freezed
|
hasKey: hasKey == freezed
|
||||||
? _value.hasKey
|
? _value.hasKey
|
||||||
: hasKey // ignore: cast_nullable_to_non_nullable
|
: hasKey // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
locked == freezed
|
remembered: remembered == freezed
|
||||||
|
? _value.remembered
|
||||||
|
: remembered // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
locked: locked == freezed
|
||||||
? _value.locked
|
? _value.locked
|
||||||
: locked // ignore: cast_nullable_to_non_nullable
|
: locked // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
@ -762,7 +775,8 @@ class __$OathStateCopyWithImpl<$Res> extends _$OathStateCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$_OathState implements _OathState {
|
class _$_OathState implements _OathState {
|
||||||
_$_OathState(this.deviceId, this.hasKey, this.locked);
|
_$_OathState(this.deviceId,
|
||||||
|
{required this.hasKey, required this.remembered, required this.locked});
|
||||||
|
|
||||||
factory _$_OathState.fromJson(Map<String, dynamic> json) =>
|
factory _$_OathState.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$_OathStateFromJson(json);
|
_$$_OathStateFromJson(json);
|
||||||
@ -772,11 +786,13 @@ class _$_OathState implements _OathState {
|
|||||||
@override
|
@override
|
||||||
final bool hasKey;
|
final bool hasKey;
|
||||||
@override
|
@override
|
||||||
|
final bool remembered;
|
||||||
|
@override
|
||||||
final bool locked;
|
final bool locked;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'OathState(deviceId: $deviceId, hasKey: $hasKey, locked: $locked)';
|
return 'OathState(deviceId: $deviceId, hasKey: $hasKey, remembered: $remembered, locked: $locked)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -786,6 +802,8 @@ class _$_OathState implements _OathState {
|
|||||||
other is _OathState &&
|
other is _OathState &&
|
||||||
const DeepCollectionEquality().equals(other.deviceId, deviceId) &&
|
const DeepCollectionEquality().equals(other.deviceId, deviceId) &&
|
||||||
const DeepCollectionEquality().equals(other.hasKey, hasKey) &&
|
const DeepCollectionEquality().equals(other.hasKey, hasKey) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other.remembered, remembered) &&
|
||||||
const DeepCollectionEquality().equals(other.locked, locked));
|
const DeepCollectionEquality().equals(other.locked, locked));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -794,6 +812,7 @@ class _$_OathState implements _OathState {
|
|||||||
runtimeType,
|
runtimeType,
|
||||||
const DeepCollectionEquality().hash(deviceId),
|
const DeepCollectionEquality().hash(deviceId),
|
||||||
const DeepCollectionEquality().hash(hasKey),
|
const DeepCollectionEquality().hash(hasKey),
|
||||||
|
const DeepCollectionEquality().hash(remembered),
|
||||||
const DeepCollectionEquality().hash(locked));
|
const DeepCollectionEquality().hash(locked));
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -808,7 +827,10 @@ class _$_OathState implements _OathState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class _OathState implements OathState {
|
abstract class _OathState implements OathState {
|
||||||
factory _OathState(String deviceId, bool hasKey, bool locked) = _$_OathState;
|
factory _OathState(String deviceId,
|
||||||
|
{required bool hasKey,
|
||||||
|
required bool remembered,
|
||||||
|
required bool locked}) = _$_OathState;
|
||||||
|
|
||||||
factory _OathState.fromJson(Map<String, dynamic> json) =
|
factory _OathState.fromJson(Map<String, dynamic> json) =
|
||||||
_$_OathState.fromJson;
|
_$_OathState.fromJson;
|
||||||
@ -818,6 +840,8 @@ abstract class _OathState implements OathState {
|
|||||||
@override
|
@override
|
||||||
bool get hasKey;
|
bool get hasKey;
|
||||||
@override
|
@override
|
||||||
|
bool get remembered;
|
||||||
|
@override
|
||||||
bool get locked;
|
bool get locked;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
|
@ -48,14 +48,16 @@ Map<String, dynamic> _$$_OathCodeToJson(_$_OathCode instance) =>
|
|||||||
|
|
||||||
_$_OathState _$$_OathStateFromJson(Map<String, dynamic> json) => _$_OathState(
|
_$_OathState _$$_OathStateFromJson(Map<String, dynamic> json) => _$_OathState(
|
||||||
json['device_id'] as String,
|
json['device_id'] as String,
|
||||||
json['has_key'] as bool,
|
hasKey: json['has_key'] as bool,
|
||||||
json['locked'] as bool,
|
remembered: json['remembered'] as bool,
|
||||||
|
locked: json['locked'] as bool,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$_OathStateToJson(_$_OathState instance) =>
|
Map<String, dynamic> _$$_OathStateToJson(_$_OathState instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'device_id': instance.deviceId,
|
'device_id': instance.deviceId,
|
||||||
'has_key': instance.hasKey,
|
'has_key': instance.hasKey,
|
||||||
|
'remembered': instance.remembered,
|
||||||
'locked': instance.locked,
|
'locked': instance.locked,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,31 +5,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
import '../app/models.dart';
|
||||||
import '../app/state.dart';
|
import '../app/state.dart';
|
||||||
import '../core/state.dart';
|
import '../core/state.dart';
|
||||||
import 'models.dart';
|
import 'models.dart';
|
||||||
|
|
||||||
final log = Logger('oath.state');
|
final log = Logger('oath.state');
|
||||||
|
|
||||||
// This remembers the key for all devices for the duration of the process.
|
|
||||||
final oathLockKeyProvider =
|
|
||||||
StateNotifierProvider.family<_LockKeyNotifier, String?, List<String>>(
|
|
||||||
(ref, devicePath) => _LockKeyNotifier(null));
|
|
||||||
|
|
||||||
class _LockKeyNotifier extends StateNotifier<String?> {
|
|
||||||
_LockKeyNotifier(String? state) : super(state);
|
|
||||||
|
|
||||||
setKey(String key) {
|
|
||||||
state = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsetKey() {
|
|
||||||
state = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final oathStateProvider = StateNotifierProvider.autoDispose
|
final oathStateProvider = StateNotifierProvider.autoDispose
|
||||||
.family<OathStateNotifier, OathState?, List<String>>(
|
.family<OathStateNotifier, OathState?, DevicePath>(
|
||||||
(ref, devicePath) => throw UnimplementedError(),
|
(ref, devicePath) => throw UnimplementedError(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -37,13 +21,14 @@ abstract class OathStateNotifier extends StateNotifier<OathState?> {
|
|||||||
OathStateNotifier() : super(null);
|
OathStateNotifier() : super(null);
|
||||||
|
|
||||||
Future<void> reset();
|
Future<void> reset();
|
||||||
Future<bool> unlock(String password);
|
Future<bool> unlock(String password, {bool remember = false});
|
||||||
Future<bool> setPassword(String? current, String password);
|
Future<bool> setPassword(String? current, String password);
|
||||||
Future<bool> unsetPassword(String current);
|
Future<bool> unsetPassword(String current);
|
||||||
|
Future<void> forgetPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
final credentialListProvider = StateNotifierProvider.autoDispose
|
final credentialListProvider = StateNotifierProvider.autoDispose
|
||||||
.family<OathCredentialListNotifier, List<OathPair>?, List<String>>(
|
.family<OathCredentialListNotifier, List<OathPair>?, DevicePath>(
|
||||||
(ref, arg) => throw UnimplementedError(),
|
(ref, arg) => throw UnimplementedError(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -23,20 +23,13 @@ class OathScreen extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.locked) {
|
if (state.locked) {
|
||||||
return Padding(
|
return ListView(
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
const Text('Password required'),
|
_UnlockForm(
|
||||||
TextField(
|
onSubmit: (password, remember) async {
|
||||||
autofocus: true,
|
|
||||||
obscureText: true,
|
|
||||||
decoration: const InputDecoration(labelText: 'Password'),
|
|
||||||
onSubmitted: (value) async {
|
|
||||||
final result = await ref
|
final result = await ref
|
||||||
.read(oathStateProvider(deviceData.node.path).notifier)
|
.read(oathStateProvider(deviceData.node.path).notifier)
|
||||||
.unlock(value);
|
.unlock(password, remember: remember);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
@ -48,7 +41,6 @@ class OathScreen extends ConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final accounts = ref.watch(credentialListProvider(deviceData.node.path));
|
final accounts = ref.watch(credentialListProvider(deviceData.node.path));
|
||||||
@ -68,3 +60,77 @@ class OathScreen extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _UnlockForm extends StatefulWidget {
|
||||||
|
final Function(String, bool) onSubmit;
|
||||||
|
const _UnlockForm({Key? key, required this.onSubmit}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _UnlockFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UnlockFormState extends State<_UnlockForm> {
|
||||||
|
String _password = '';
|
||||||
|
bool _remember = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
//mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
//crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||||
|
child: Text(
|
||||||
|
'Unlock YubiKey',
|
||||||
|
style: Theme.of(context).textTheme.headline5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'Enter the password for your YubiKey. If you don\'t know your password, you\'ll need to reset the YubiKey.',
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
autofocus: true,
|
||||||
|
obscureText: true,
|
||||||
|
decoration: const InputDecoration(labelText: 'Password'),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_password = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSubmitted: (value) {
|
||||||
|
widget.onSubmit(value, _remember);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: const Text('Remember password'),
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
value: _remember,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_remember = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: ElevatedButton(
|
||||||
|
child: const Text('Unlock'),
|
||||||
|
onPressed: () {
|
||||||
|
widget.onSubmit(_password, _remember);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -32,6 +32,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
|
|||||||
|
|
||||||
final state = ref.watch(oathStateProvider(widget.device.path));
|
final state = ref.watch(oathStateProvider(widget.device.path));
|
||||||
final hasKey = state?.hasKey ?? false;
|
final hasKey = state?.hasKey ?? false;
|
||||||
|
final remembered = state?.remembered ?? false;
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('Manage password'),
|
title: const Text('Manage password'),
|
||||||
@ -41,8 +42,48 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
|
|||||||
if (hasKey)
|
if (hasKey)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
|
if (remembered)
|
||||||
|
// TODO: This is temporary, to be able to forget a password.
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: const [
|
||||||
|
Text(
|
||||||
|
'You password is remembered by the app.',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
OutlinedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await ref
|
||||||
|
.read(oathStateProvider(widget.device.path)
|
||||||
|
.notifier)
|
||||||
|
.forgetPassword();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Password forgotten'),
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('Forget'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
const Text(
|
const Text(
|
||||||
'Enter your current password to change it. If you don\'t know your password, you\'ll need to reset the YubiKey, thne create a new password.'),
|
'Enter your current password to change it. If you don\'t know your password, you\'ll need to reset the YubiKey, then create a new password.'),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 765ccf63d9ccc972858d71730b9712514a9b0e0d
|
Subproject commit f90e4d6f59e8acc4399e9ff587f8c821456d069c
|
Loading…
Reference in New Issue
Block a user