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;
|
||||
}
|
||||
|
||||
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
|
||||
class DeviceNode with _$DeviceNode {
|
||||
factory DeviceNode.usbYubiKey(
|
||||
List<String> path, String name, int pid, DeviceInfo info) =
|
||||
UsbYubiKeyNode;
|
||||
factory DeviceNode.nfcReader(List<String> path, String name) = NfcReaderNode;
|
||||
DevicePath path, String name, int pid, DeviceInfo info) = UsbYubiKeyNode;
|
||||
factory DeviceNode.nfcReader(DevicePath path, String name) = NfcReaderNode;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -206,7 +206,7 @@ class _$DeviceNodeTearOff {
|
||||
const _$DeviceNodeTearOff();
|
||||
|
||||
UsbYubiKeyNode usbYubiKey(
|
||||
List<String> path, String name, int pid, DeviceInfo info) {
|
||||
DevicePath path, String name, int pid, DeviceInfo info) {
|
||||
return UsbYubiKeyNode(
|
||||
path,
|
||||
name,
|
||||
@ -215,7 +215,7 @@ class _$DeviceNodeTearOff {
|
||||
);
|
||||
}
|
||||
|
||||
NfcReaderNode nfcReader(List<String> path, String name) {
|
||||
NfcReaderNode nfcReader(DevicePath path, String name) {
|
||||
return NfcReaderNode(
|
||||
path,
|
||||
name,
|
||||
@ -228,29 +228,29 @@ const $DeviceNode = _$DeviceNodeTearOff();
|
||||
|
||||
/// @nodoc
|
||||
mixin _$DeviceNode {
|
||||
List<String> get path => throw _privateConstructorUsedError;
|
||||
DevicePath get path => throw _privateConstructorUsedError;
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(
|
||||
List<String> path, String name, int pid, DeviceInfo info)
|
||||
DevicePath path, String name, int pid, DeviceInfo info)
|
||||
usbYubiKey,
|
||||
required TResult Function(List<String> path, String name) nfcReader,
|
||||
required TResult Function(DevicePath path, String name) nfcReader,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
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,
|
||||
TResult Function(List<String> path, String name)? nfcReader,
|
||||
TResult Function(DevicePath path, String name)? nfcReader,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
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,
|
||||
TResult Function(List<String> path, String name)? nfcReader,
|
||||
TResult Function(DevicePath path, String name)? nfcReader,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@ -284,7 +284,7 @@ abstract class $DeviceNodeCopyWith<$Res> {
|
||||
factory $DeviceNodeCopyWith(
|
||||
DeviceNode value, $Res Function(DeviceNode) then) =
|
||||
_$DeviceNodeCopyWithImpl<$Res>;
|
||||
$Res call({List<String> path, String name});
|
||||
$Res call({DevicePath path, String name});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -304,7 +304,7 @@ class _$DeviceNodeCopyWithImpl<$Res> implements $DeviceNodeCopyWith<$Res> {
|
||||
path: path == freezed
|
||||
? _value.path
|
||||
: path // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
as DevicePath,
|
||||
name: name == freezed
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
@ -320,7 +320,7 @@ abstract class $UsbYubiKeyNodeCopyWith<$Res>
|
||||
UsbYubiKeyNode value, $Res Function(UsbYubiKeyNode) then) =
|
||||
_$UsbYubiKeyNodeCopyWithImpl<$Res>;
|
||||
@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;
|
||||
}
|
||||
@ -346,7 +346,7 @@ class _$UsbYubiKeyNodeCopyWithImpl<$Res> extends _$DeviceNodeCopyWithImpl<$Res>
|
||||
path == freezed
|
||||
? _value.path
|
||||
: path // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
as DevicePath,
|
||||
name == freezed
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
@ -376,7 +376,7 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
||||
_$UsbYubiKeyNode(this.path, this.name, this.pid, this.info);
|
||||
|
||||
@override
|
||||
final List<String> path;
|
||||
final DevicePath path;
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
@ -417,9 +417,9 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(
|
||||
List<String> path, String name, int pid, DeviceInfo info)
|
||||
DevicePath path, String name, int pid, DeviceInfo info)
|
||||
usbYubiKey,
|
||||
required TResult Function(List<String> path, String name) nfcReader,
|
||||
required TResult Function(DevicePath path, String name) nfcReader,
|
||||
}) {
|
||||
return usbYubiKey(path, name, pid, info);
|
||||
}
|
||||
@ -427,9 +427,9 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
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,
|
||||
TResult Function(List<String> path, String name)? nfcReader,
|
||||
TResult Function(DevicePath path, String name)? nfcReader,
|
||||
}) {
|
||||
return usbYubiKey?.call(path, name, pid, info);
|
||||
}
|
||||
@ -437,9 +437,9 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
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,
|
||||
TResult Function(List<String> path, String name)? nfcReader,
|
||||
TResult Function(DevicePath path, String name)? nfcReader,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (usbYubiKey != null) {
|
||||
@ -482,11 +482,11 @@ class _$UsbYubiKeyNode implements UsbYubiKeyNode {
|
||||
|
||||
abstract class UsbYubiKeyNode implements DeviceNode {
|
||||
factory UsbYubiKeyNode(
|
||||
List<String> path, String name, int pid, DeviceInfo info) =
|
||||
DevicePath path, String name, int pid, DeviceInfo info) =
|
||||
_$UsbYubiKeyNode;
|
||||
|
||||
@override
|
||||
List<String> get path;
|
||||
DevicePath get path;
|
||||
@override
|
||||
String get name;
|
||||
int get pid;
|
||||
@ -504,7 +504,7 @@ abstract class $NfcReaderNodeCopyWith<$Res>
|
||||
NfcReaderNode value, $Res Function(NfcReaderNode) then) =
|
||||
_$NfcReaderNodeCopyWithImpl<$Res>;
|
||||
@override
|
||||
$Res call({List<String> path, String name});
|
||||
$Res call({DevicePath path, String name});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -526,7 +526,7 @@ class _$NfcReaderNodeCopyWithImpl<$Res> extends _$DeviceNodeCopyWithImpl<$Res>
|
||||
path == freezed
|
||||
? _value.path
|
||||
: path // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
as DevicePath,
|
||||
name == freezed
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
@ -541,7 +541,7 @@ class _$NfcReaderNode implements NfcReaderNode {
|
||||
_$NfcReaderNode(this.path, this.name);
|
||||
|
||||
@override
|
||||
final List<String> path;
|
||||
final DevicePath path;
|
||||
@override
|
||||
final String name;
|
||||
|
||||
@ -574,9 +574,9 @@ class _$NfcReaderNode implements NfcReaderNode {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(
|
||||
List<String> path, String name, int pid, DeviceInfo info)
|
||||
DevicePath path, String name, int pid, DeviceInfo info)
|
||||
usbYubiKey,
|
||||
required TResult Function(List<String> path, String name) nfcReader,
|
||||
required TResult Function(DevicePath path, String name) nfcReader,
|
||||
}) {
|
||||
return nfcReader(path, name);
|
||||
}
|
||||
@ -584,9 +584,9 @@ class _$NfcReaderNode implements NfcReaderNode {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
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,
|
||||
TResult Function(List<String> path, String name)? nfcReader,
|
||||
TResult Function(DevicePath path, String name)? nfcReader,
|
||||
}) {
|
||||
return nfcReader?.call(path, name);
|
||||
}
|
||||
@ -594,9 +594,9 @@ class _$NfcReaderNode implements NfcReaderNode {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
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,
|
||||
TResult Function(List<String> path, String name)? nfcReader,
|
||||
TResult Function(DevicePath path, String name)? nfcReader,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (nfcReader != null) {
|
||||
@ -638,10 +638,10 @@ class _$NfcReaderNode implements NfcReaderNode {
|
||||
}
|
||||
|
||||
abstract class NfcReaderNode implements DeviceNode {
|
||||
factory NfcReaderNode(List<String> path, String name) = _$NfcReaderNode;
|
||||
factory NfcReaderNode(DevicePath path, String name) = _$NfcReaderNode;
|
||||
|
||||
@override
|
||||
List<String> get path;
|
||||
DevicePath get path;
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
|
@ -1,14 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:yubico_authenticator/management/models.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'device_avatar.dart';
|
||||
|
||||
Function _listEquals = const ListEquality().equals;
|
||||
|
||||
class MainActionsDialog extends ConsumerWidget {
|
||||
const MainActionsDialog({Key? key}) : super(key: key);
|
||||
|
||||
@ -20,7 +17,7 @@ class MainActionsDialog extends ConsumerWidget {
|
||||
final actions = ref.watch(menuActionsProvider)(context);
|
||||
|
||||
if (currentNode != null) {
|
||||
devices.removeWhere((e) => _listEquals(e.path, currentNode.path));
|
||||
devices.removeWhere((e) => e.path == currentNode.path);
|
||||
}
|
||||
|
||||
return SimpleDialog(
|
||||
|
@ -67,7 +67,7 @@ class UsbDeviceNotifier extends StateNotifier<List<UsbYubiKeyNode>> {
|
||||
var deviceResult = await _rpc.command('get', path);
|
||||
var deviceData = deviceResult['data'];
|
||||
usbDevices.add(DeviceNode.usbYubiKey(
|
||||
path,
|
||||
DevicePath(path),
|
||||
deviceData['name'],
|
||||
deviceData['pid'],
|
||||
DeviceInfo.fromJson(deviceData['info']),
|
||||
@ -131,8 +131,8 @@ class NfcDeviceNotifier extends StateNotifier<List<NfcReaderNode>> {
|
||||
log.info('NFC state change', jsonEncode(children));
|
||||
_nfcState = newState;
|
||||
state = children.entries
|
||||
.map((e) =>
|
||||
DeviceNode.nfcReader(['nfc', e.key], e.value['name'] as String)
|
||||
.map((e) => DeviceNode.nfcReader(
|
||||
DevicePath(['nfc', e.key]), e.value['name'] as String)
|
||||
as NfcReaderNode)
|
||||
.toList();
|
||||
}
|
||||
@ -207,7 +207,7 @@ class CurrentDeviceDataNotifier extends StateNotifier<YubiKeyData?> {
|
||||
_pollTimer?.cancel();
|
||||
final node = _deviceNode!;
|
||||
try {
|
||||
var result = await _rpc.command('get', node.path);
|
||||
var result = await _rpc.command('get', node.path.segments);
|
||||
if (mounted) {
|
||||
if (result['data']['present']) {
|
||||
state = YubiKeyData(node, result['data']['name'],
|
||||
|
@ -15,13 +15,35 @@ import '../state.dart';
|
||||
final log = Logger('desktop.oath.state');
|
||||
|
||||
final _sessionProvider =
|
||||
Provider.autoDispose.family<RpcNodeSession, List<String>>(
|
||||
Provider.autoDispose.family<RpcNodeSession, DevicePath>(
|
||||
(ref, devicePath) =>
|
||||
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
|
||||
.family<OathStateNotifier, OathState?, List<String>>(
|
||||
.family<OathStateNotifier, OathState?, DevicePath>(
|
||||
(ref, devicePath) {
|
||||
final session = ref.watch(_sessionProvider(devicePath));
|
||||
final notifier = _DesktopOathStateNotifier(session, ref);
|
||||
@ -50,13 +72,15 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
|
||||
var result = await _session.command('get');
|
||||
log.config('application status', jsonEncode(result));
|
||||
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) {
|
||||
final result = await _session.command('validate', params: {'key': key});
|
||||
if (result['unlocked']) {
|
||||
if (result['success']) {
|
||||
oathState = oathState.copyWith(locked: false);
|
||||
} else {
|
||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
||||
_ref
|
||||
.read(_oathLockKeyProvider(_session.devicePath).notifier)
|
||||
.unsetKey();
|
||||
}
|
||||
}
|
||||
if (mounted) {
|
||||
@ -67,28 +91,32 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
|
||||
@override
|
||||
Future<void> reset() async {
|
||||
await _session.command('reset');
|
||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
||||
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
||||
_ref.refresh(_sessionProvider(_session.devicePath));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> unlock(String password) async {
|
||||
Future<bool> unlock(String password, {bool remember = false}) async {
|
||||
var result =
|
||||
await _session.command('derive', params: {'password': password});
|
||||
var key = result['key'];
|
||||
final status = await _session.command('validate', params: {'key': key});
|
||||
if (mounted && status['unlocked']) {
|
||||
final status = await _session
|
||||
.command('validate', params: {'key': key, 'remember': remember});
|
||||
if (mounted && status['success']) {
|
||||
log.config('applet unlocked');
|
||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
|
||||
state = state?.copyWith(locked: false);
|
||||
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
|
||||
state = state?.copyWith(
|
||||
locked: false,
|
||||
remembered: remember || state?.remembered == true,
|
||||
);
|
||||
}
|
||||
return status['unlocked'];
|
||||
return status['success'];
|
||||
}
|
||||
|
||||
Future<bool> _checkPassword(String password) async {
|
||||
var result =
|
||||
await _session.command('derive', params: {'password': password});
|
||||
return _ref.read(oathLockKeyProvider(_session.devicePath)) == result['key'];
|
||||
await _session.command('validate', params: {'password': password});
|
||||
return result['success'];
|
||||
}
|
||||
|
||||
@override
|
||||
@ -108,7 +136,7 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
|
||||
var key = result['key'];
|
||||
await _session.command('set_key', params: {'key': key});
|
||||
log.config('OATH key set');
|
||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
|
||||
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
|
||||
if (mounted) {
|
||||
state = state?.copyWith(hasKey: true);
|
||||
}
|
||||
@ -123,16 +151,25 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
|
||||
}
|
||||
}
|
||||
await _session.command('unset_key');
|
||||
_ref.read(oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
||||
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
|
||||
if (mounted) {
|
||||
state = state?.copyWith(hasKey: false, locked: false);
|
||||
}
|
||||
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
|
||||
.family<OathCredentialListNotifier, List<OathPair>?, List<String>>(
|
||||
.family<OathCredentialListNotifier, List<OathPair>?, DevicePath>(
|
||||
(ref, devicePath) {
|
||||
var notifier = _DesktopCredentialListNotifier(
|
||||
ref.watch(_sessionProvider(devicePath)),
|
||||
|
@ -5,6 +5,7 @@ import 'dart:io';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:async/async.dart';
|
||||
|
||||
import '../app/models.dart';
|
||||
import 'models.dart';
|
||||
|
||||
final log = Logger('rpc');
|
||||
@ -147,7 +148,7 @@ typedef ErrorHandler = Future<void> Function(RpcError e);
|
||||
|
||||
class RpcNodeSession {
|
||||
final RpcSession _rpc;
|
||||
final List<String> devicePath;
|
||||
final DevicePath devicePath;
|
||||
final List<String> subPath;
|
||||
final Map<String, ErrorHandler> _errorHandlers = {};
|
||||
|
||||
@ -170,7 +171,7 @@ class RpcNodeSession {
|
||||
try {
|
||||
return await _rpc.command(
|
||||
action,
|
||||
devicePath + subPath + target,
|
||||
devicePath.segments + subPath + target,
|
||||
params: params,
|
||||
signal: signal,
|
||||
);
|
||||
|
@ -55,7 +55,12 @@ class OathPair with _$OathPair {
|
||||
|
||||
@freezed
|
||||
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) =>
|
||||
_$OathStateFromJson(json);
|
||||
|
@ -652,11 +652,13 @@ OathState _$OathStateFromJson(Map<String, dynamic> json) {
|
||||
class _$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(
|
||||
deviceId,
|
||||
hasKey,
|
||||
locked,
|
||||
hasKey: hasKey,
|
||||
remembered: remembered,
|
||||
locked: locked,
|
||||
);
|
||||
}
|
||||
|
||||
@ -672,6 +674,7 @@ const $OathState = _$OathStateTearOff();
|
||||
mixin _$OathState {
|
||||
String get deviceId => throw _privateConstructorUsedError;
|
||||
bool get hasKey => throw _privateConstructorUsedError;
|
||||
bool get remembered => throw _privateConstructorUsedError;
|
||||
bool get locked => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -684,7 +687,7 @@ mixin _$OathState {
|
||||
abstract class $OathStateCopyWith<$Res> {
|
||||
factory $OathStateCopyWith(OathState value, $Res Function(OathState) then) =
|
||||
_$OathStateCopyWithImpl<$Res>;
|
||||
$Res call({String deviceId, bool hasKey, bool locked});
|
||||
$Res call({String deviceId, bool hasKey, bool remembered, bool locked});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -699,6 +702,7 @@ class _$OathStateCopyWithImpl<$Res> implements $OathStateCopyWith<$Res> {
|
||||
$Res call({
|
||||
Object? deviceId = freezed,
|
||||
Object? hasKey = freezed,
|
||||
Object? remembered = freezed,
|
||||
Object? locked = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
@ -710,6 +714,10 @@ class _$OathStateCopyWithImpl<$Res> implements $OathStateCopyWith<$Res> {
|
||||
? _value.hasKey
|
||||
: hasKey // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
remembered: remembered == freezed
|
||||
? _value.remembered
|
||||
: remembered // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
locked: locked == freezed
|
||||
? _value.locked
|
||||
: locked // ignore: cast_nullable_to_non_nullable
|
||||
@ -724,7 +732,7 @@ abstract class _$OathStateCopyWith<$Res> implements $OathStateCopyWith<$Res> {
|
||||
_OathState value, $Res Function(_OathState) then) =
|
||||
__$OathStateCopyWithImpl<$Res>;
|
||||
@override
|
||||
$Res call({String deviceId, bool hasKey, bool locked});
|
||||
$Res call({String deviceId, bool hasKey, bool remembered, bool locked});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -740,6 +748,7 @@ class __$OathStateCopyWithImpl<$Res> extends _$OathStateCopyWithImpl<$Res>
|
||||
$Res call({
|
||||
Object? deviceId = freezed,
|
||||
Object? hasKey = freezed,
|
||||
Object? remembered = freezed,
|
||||
Object? locked = freezed,
|
||||
}) {
|
||||
return _then(_OathState(
|
||||
@ -747,11 +756,15 @@ class __$OathStateCopyWithImpl<$Res> extends _$OathStateCopyWithImpl<$Res>
|
||||
? _value.deviceId
|
||||
: deviceId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
hasKey == freezed
|
||||
hasKey: hasKey == freezed
|
||||
? _value.hasKey
|
||||
: hasKey // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
locked == freezed
|
||||
remembered: remembered == freezed
|
||||
? _value.remembered
|
||||
: remembered // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
locked: locked == freezed
|
||||
? _value.locked
|
||||
: locked // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
@ -762,7 +775,8 @@ class __$OathStateCopyWithImpl<$Res> extends _$OathStateCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
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) =>
|
||||
_$$_OathStateFromJson(json);
|
||||
@ -772,11 +786,13 @@ class _$_OathState implements _OathState {
|
||||
@override
|
||||
final bool hasKey;
|
||||
@override
|
||||
final bool remembered;
|
||||
@override
|
||||
final bool locked;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'OathState(deviceId: $deviceId, hasKey: $hasKey, locked: $locked)';
|
||||
return 'OathState(deviceId: $deviceId, hasKey: $hasKey, remembered: $remembered, locked: $locked)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -786,6 +802,8 @@ class _$_OathState implements _OathState {
|
||||
other is _OathState &&
|
||||
const DeepCollectionEquality().equals(other.deviceId, deviceId) &&
|
||||
const DeepCollectionEquality().equals(other.hasKey, hasKey) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.remembered, remembered) &&
|
||||
const DeepCollectionEquality().equals(other.locked, locked));
|
||||
}
|
||||
|
||||
@ -794,6 +812,7 @@ class _$_OathState implements _OathState {
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(deviceId),
|
||||
const DeepCollectionEquality().hash(hasKey),
|
||||
const DeepCollectionEquality().hash(remembered),
|
||||
const DeepCollectionEquality().hash(locked));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@ -808,7 +827,10 @@ 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) =
|
||||
_$_OathState.fromJson;
|
||||
@ -818,6 +840,8 @@ abstract class _OathState implements OathState {
|
||||
@override
|
||||
bool get hasKey;
|
||||
@override
|
||||
bool get remembered;
|
||||
@override
|
||||
bool get locked;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
|
@ -48,14 +48,16 @@ Map<String, dynamic> _$$_OathCodeToJson(_$_OathCode instance) =>
|
||||
|
||||
_$_OathState _$$_OathStateFromJson(Map<String, dynamic> json) => _$_OathState(
|
||||
json['device_id'] as String,
|
||||
json['has_key'] as bool,
|
||||
json['locked'] as bool,
|
||||
hasKey: json['has_key'] as bool,
|
||||
remembered: json['remembered'] as bool,
|
||||
locked: json['locked'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$_OathStateToJson(_$_OathState instance) =>
|
||||
<String, dynamic>{
|
||||
'device_id': instance.deviceId,
|
||||
'has_key': instance.hasKey,
|
||||
'remembered': instance.remembered,
|
||||
'locked': instance.locked,
|
||||
};
|
||||
|
||||
|
@ -5,31 +5,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../app/models.dart';
|
||||
import '../app/state.dart';
|
||||
import '../core/state.dart';
|
||||
import 'models.dart';
|
||||
|
||||
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
|
||||
.family<OathStateNotifier, OathState?, List<String>>(
|
||||
.family<OathStateNotifier, OathState?, DevicePath>(
|
||||
(ref, devicePath) => throw UnimplementedError(),
|
||||
);
|
||||
|
||||
@ -37,13 +21,14 @@ abstract class OathStateNotifier extends StateNotifier<OathState?> {
|
||||
OathStateNotifier() : super(null);
|
||||
|
||||
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> unsetPassword(String current);
|
||||
Future<void> forgetPassword();
|
||||
}
|
||||
|
||||
final credentialListProvider = StateNotifierProvider.autoDispose
|
||||
.family<OathCredentialListNotifier, List<OathPair>?, List<String>>(
|
||||
.family<OathCredentialListNotifier, List<OathPair>?, DevicePath>(
|
||||
(ref, arg) => throw UnimplementedError(),
|
||||
);
|
||||
|
||||
|
@ -23,20 +23,13 @@ class OathScreen extends ConsumerWidget {
|
||||
}
|
||||
|
||||
if (state.locked) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
return ListView(
|
||||
children: [
|
||||
const Text('Password required'),
|
||||
TextField(
|
||||
autofocus: true,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(labelText: 'Password'),
|
||||
onSubmitted: (value) async {
|
||||
_UnlockForm(
|
||||
onSubmit: (password, remember) async {
|
||||
final result = await ref
|
||||
.read(oathStateProvider(deviceData.node.path).notifier)
|
||||
.unlock(value);
|
||||
.unlock(password, remember: remember);
|
||||
if (!result) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
@ -48,7 +41,6 @@ class OathScreen extends ConsumerWidget {
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
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 hasKey = state?.hasKey ?? false;
|
||||
final remembered = state?.remembered ?? false;
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Manage password'),
|
||||
@ -41,8 +42,48 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
|
||||
if (hasKey)
|
||||
Column(
|
||||
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(
|
||||
'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(
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 765ccf63d9ccc972858d71730b9712514a9b0e0d
|
||||
Subproject commit f90e4d6f59e8acc4399e9ff587f8c821456d069c
|
Loading…
Reference in New Issue
Block a user