WIP: desktop password login and auth flow

This commit is contained in:
julian 2022-11-10 12:40:16 -06:00
parent f2bef21853
commit a50520b37f
5 changed files with 148 additions and 126 deletions

View file

@ -43,23 +43,23 @@ class DB {
static bool _initialized = false;
late final Box<dynamic> _boxAddressBook;
late final Box<String> _boxDebugInfo;
late final Box<NodeModel> _boxNodeModels;
late final Box<NodeModel> _boxPrimaryNodes;
late final Box<dynamic> _boxAllWalletsData;
late final Box<NotificationModel> _boxNotifications;
late final Box<NotificationModel> _boxWatchedTransactions;
late final Box<NotificationModel> _boxWatchedTrades;
late final Box<ExchangeTransaction> _boxTrades;
late final Box<Trade> _boxTradesV2;
late final Box<String> _boxTradeNotes;
late final Box<String> _boxFavoriteWallets;
late final Box<xmr.WalletInfo> _walletInfoSource;
late final Box<dynamic> _boxPrefs;
late final Box<TradeWalletLookup> _boxTradeLookup;
late final Box<dynamic> _boxDBInfo;
late final Box<String> _boxDesktopData;
Box<dynamic>? _boxAddressBook;
Box<String>? _boxDebugInfo;
Box<NodeModel>? _boxNodeModels;
Box<NodeModel>? _boxPrimaryNodes;
Box<dynamic>? _boxAllWalletsData;
Box<NotificationModel>? _boxNotifications;
Box<NotificationModel>? _boxWatchedTransactions;
Box<NotificationModel>? _boxWatchedTrades;
Box<ExchangeTransaction>? _boxTrades;
Box<Trade>? _boxTradesV2;
Box<String>? _boxTradeNotes;
Box<String>? _boxFavoriteWallets;
Box<xmr.WalletInfo>? _walletInfoSource;
Box<dynamic>? _boxPrefs;
Box<TradeWalletLookup>? _boxTradeLookup;
Box<dynamic>? _boxDBInfo;
Box<String>? _boxDesktopData;
final Map<String, Box<dynamic>> _walletBoxes = {};
@ -68,7 +68,7 @@ class DB {
final Map<Coin, Box<dynamic>> _usedSerialsCacheBoxes = {};
// exposed for monero
Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource;
Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource!;
// mutex for stack backup
final mutex = Mutex();
@ -124,6 +124,12 @@ class DB {
_boxAllWalletsData = await Hive.openBox<dynamic>(boxNameAllWalletsData);
}
if (Hive.isBoxOpen(boxNameDesktopData)) {
_boxDesktopData = Hive.box<String>(boxNameDesktopData);
} else {
_boxDesktopData = await Hive.openBox<String>(boxNameDesktopData);
}
_boxNotifications =
await Hive.openBox<NotificationModel>(boxNameNotifications);
_boxWatchedTransactions =
@ -147,11 +153,11 @@ class DB {
_initialized = true;
try {
if (_boxPrefs.get("familiarity") == null) {
await _boxPrefs.put("familiarity", 0);
if (_boxPrefs!.get("familiarity") == null) {
await _boxPrefs!.put("familiarity", 0);
}
int count = _boxPrefs.get("familiarity") as int;
await _boxPrefs.put("familiarity", count + 1);
int count = _boxPrefs!.get("familiarity") as int;
await _boxPrefs!.put("familiarity", count + 1);
Constants.exchangeForExperiencedUsers(count + 1);
} catch (e, s) {
print("$e $s");
@ -160,7 +166,7 @@ class DB {
}
Future<void> _loadWalletBoxes() async {
final names = _boxAllWalletsData.get("names") as Map? ?? {};
final names = _boxAllWalletsData!.get("names") as Map? ?? {};
names.removeWhere((name, dyn) {
final jsonObject = Map<String, dynamic>.from(dyn as Map);
try {

View file

@ -48,19 +48,16 @@ import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/notifications_service.dart';
import 'package:stackwallet/services/trade_service.dart';
import 'package:stackwallet/services/wallets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/db_version_migration.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/theme/color_theme.dart';
import 'package:stackwallet/utilities/theme/dark_colors.dart';
import 'package:stackwallet/utilities/theme/light_colors.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:window_size/window_size.dart';
final openedFromSWBFileStringStateProvider =
@ -221,8 +218,8 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
static const platform = MethodChannel("STACK_WALLET_RESTORE");
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
late final Wallets _wallets;
late final Prefs _prefs;
// late final Wallets _wallets;
// late final Prefs _prefs;
late final NotificationsService _notificationsService;
late final NodeService _nodeService;
late final TradesService _tradesService;
@ -232,6 +229,16 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
bool didLoad = false;
bool _desktopHasPassword = false;
Future<void> loadShared() async {
await DB.instance.init();
await ref.read(prefsChangeNotifierProvider).init();
if (Util.isDesktop) {
_desktopHasPassword =
await ref.read(storageCryptoHandlerProvider).hasPassword();
}
}
Future<void> load() async {
try {
if (didLoad) {
@ -239,19 +246,15 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
}
didLoad = true;
await DB.instance.init();
await _prefs.init();
if (Util.isDesktop) {
_desktopHasPassword =
await ref.read(storageCryptoHandlerProvider).hasPassword();
if (!Util.isDesktop) {
await loadShared();
}
_notificationsService = ref.read(notificationsProvider);
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
_tradesService = ref.read(tradesServiceProvider);
NotificationApi.prefs = _prefs;
NotificationApi.prefs = ref.read(prefsChangeNotifierProvider);
NotificationApi.notificationsService = _notificationsService;
unawaited(ref.read(baseCurrenciesProvider).update());
@ -260,23 +263,25 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
await _notificationsService.init(
nodeService: _nodeService,
tradesService: _tradesService,
prefs: _prefs,
prefs: ref.read(prefsChangeNotifierProvider),
);
ref.read(priceAnd24hChangeNotifierProvider).start(true);
await _wallets.load(_prefs);
await ref
.read(walletsChangeNotifierProvider)
.load(ref.read(prefsChangeNotifierProvider));
loadingCompleter.complete();
// TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet
// unawaited(_nodeService.updateCommunityNodes());
// run without awaiting
if (Constants.enableExchange &&
_prefs.externalCalls &&
await _prefs.isExternalCallsSet()) {
ref.read(prefsChangeNotifierProvider).externalCalls &&
await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) {
unawaited(ExchangeDataLoadingService().loadAll(ref));
}
if (_prefs.isAutoBackupEnabled) {
switch (_prefs.backupFrequencyType) {
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) {
switch (ref.read(prefsChangeNotifierProvider).backupFrequencyType) {
case BackupFrequencyType.everyTenMinutes:
ref.read(autoSWBServiceProvider).startPeriodicBackupTimer(
duration: const Duration(minutes: 10));
@ -316,9 +321,6 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
.read(localeServiceChangeNotifierProvider.notifier)
.loadLocale(notify: false);
_prefs = ref.read(prefsChangeNotifierProvider);
_wallets = ref.read(walletsChangeNotifierProvider);
WidgetsBinding.instance.addPostFrameCallback((_) async {
ref.read(colorThemeProvider.state).state =
StackColors.fromStackColorTheme(
@ -423,7 +425,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
}
Future<void> goToRestoreSWB(String encrypted) async {
if (!_prefs.hasPin) {
if (!ref.read(prefsChangeNotifierProvider).hasPin) {
await Navigator.of(navigatorKey.currentContext!)
.pushNamed(CreatePinView.routeName, arguments: true)
.then((value) {
@ -569,57 +571,70 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
),
),
home: ConditionalParent(
condition: Util.isDesktop,
builder: (child) {
return child;
},
child: FutureBuilder(
future: load(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// FlutterNativeSplash.remove();
if (Util.isDesktop &&
(_wallets.hasWallets || _desktopHasPassword)) {
String? startupWalletId;
if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) {
startupWalletId =
ref.read(prefsChangeNotifierProvider).startupWalletId;
home: Util.isDesktop
? FutureBuilder(
future: loadShared(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (_desktopHasPassword) {
String? startupWalletId;
if (ref
.read(prefsChangeNotifierProvider)
.gotoWalletOnStartup) {
startupWalletId =
ref.read(prefsChangeNotifierProvider).startupWalletId;
}
return DesktopLoginView(
startupWalletId: startupWalletId,
load: load,
);
} else {
return const IntroView();
}
} else {
return const LoadingView();
}
},
)
: FutureBuilder(
future: load(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// FlutterNativeSplash.remove();
if (ref.read(walletsChangeNotifierProvider).hasWallets ||
ref.read(prefsChangeNotifierProvider).hasPin) {
// return HomeView();
return DesktopLoginView(startupWalletId: startupWalletId);
} else if (!Util.isDesktop &&
(_wallets.hasWallets || _prefs.hasPin)) {
// return HomeView();
String? startupWalletId;
if (ref
.read(prefsChangeNotifierProvider)
.gotoWalletOnStartup) {
startupWalletId =
ref.read(prefsChangeNotifierProvider).startupWalletId;
}
String? startupWalletId;
if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) {
startupWalletId =
ref.read(prefsChangeNotifierProvider).startupWalletId;
return LockscreenView(
isInitialAppLogin: true,
routeOnSuccess: HomeView.routeName,
routeOnSuccessArguments: startupWalletId,
biometricsAuthenticationTitle: "Unlock Stack",
biometricsLocalizedReason:
"Unlock your stack wallet using biometrics",
biometricsCancelButtonString: "Cancel",
);
} else {
return const IntroView();
}
} else {
// CURRENTLY DISABLED as cannot be animated
// technically not needed as FlutterNativeSplash will overlay
// anything returned here until the future completes but
// FutureBuilder requires you to return something
return const LoadingView();
}
return LockscreenView(
isInitialAppLogin: true,
routeOnSuccess: HomeView.routeName,
routeOnSuccessArguments: startupWalletId,
biometricsAuthenticationTitle: "Unlock Stack",
biometricsLocalizedReason:
"Unlock your stack wallet using biometrics",
biometricsCancelButtonString: "Cancel",
);
} else {
return const IntroView();
}
} else {
// CURRENTLY DISABLED as cannot be animated
// technically not needed as FlutterNativeSplash will overlay
// anything returned here until the future completes but
// FutureBuilder requires you to return something
return const LoadingView();
}
},
),
),
},
),
);
}
}

View file

@ -21,11 +21,13 @@ class DesktopLoginView extends ConsumerStatefulWidget {
const DesktopLoginView({
Key? key,
this.startupWalletId,
this.load,
}) : super(key: key);
static const String routeName = "/desktopLogin";
final String? startupWalletId;
final Future<void> Function()? load;
@override
ConsumerState<DesktopLoginView> createState() => _DesktopLoginViewState();
@ -39,6 +41,32 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
bool hidePassword = true;
bool _continueEnabled = false;
Future<void> login() async {
try {
await ref
.read(storageCryptoHandlerProvider)
.initFromExisting(passwordController.text);
await widget.load?.call();
// if no errors passphrase is correct
if (mounted) {
unawaited(
Navigator.of(context).pushNamedAndRemoveUntil(
DesktopHomeView.routeName,
(route) => false,
),
);
}
} catch (e) {
await showFloatingFlushBar(
type: FlushBarType.warning,
message: e.toString(),
context: context,
);
}
}
@override
void initState() {
passwordController = TextEditingController();
@ -159,29 +187,7 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
PrimaryButton(
label: "Continue",
enabled: _continueEnabled,
onPressed: () async {
try {
await ref
.read(storageCryptoHandlerProvider)
.initFromExisting(passwordController.text);
// if no errors passphrase is correct
if (mounted) {
unawaited(
Navigator.of(context).pushNamedAndRemoveUntil(
DesktopHomeView.routeName,
(route) => false,
),
);
}
} catch (e) {
await showFloatingFlushBar(
type: FlushBarType.warning,
message: e.toString(),
context: context,
);
}
},
onPressed: login,
),
const SizedBox(
height: 60,

View file

@ -5,13 +5,13 @@ import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stack_wallet_backup/stack_wallet_backup.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
@ -35,13 +35,8 @@ import 'package:zxcvbn/zxcvbn.dart';
class CreateAutoBackup extends ConsumerStatefulWidget {
const CreateAutoBackup({
Key? key,
this.secureStore = const SecureStorageWrapper(
FlutterSecureStorage(),
),
}) : super(key: key);
final FlutterSecureStorageInterface secureStore;
@override
ConsumerState<CreateAutoBackup> createState() => _CreateAutoBackup();
}
@ -51,7 +46,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
late final TextEditingController passphraseController;
late final TextEditingController passphraseRepeatController;
late final FlutterSecureStorageInterface secureStore;
late final SecureStorageInterface secureStore;
late final StackFileSystem stackFileSystem;
late final FocusNode passphraseFocusNode;
@ -85,7 +80,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
@override
void initState() {
secureStore = widget.secureStore;
secureStore = ref.read(secureStoreProvider);
stackFileSystem = StackFileSystem();
fileLocationController = TextEditingController();
@ -686,7 +681,9 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
final String fileToSave =
createAutoBackupFilename(pathToSave, now);
final backup = await SWB.createStackWalletJSON();
final backup = await SWB.createStackWalletJSON(
secureStorage: secureStore,
);
bool result = await SWB.encryptStackWalletWithADK(
fileToSave,

View file

@ -89,12 +89,10 @@ class DPS {
}
Future<bool> hasPassword() async {
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
final keyBlob = DB.instance.get<String>(
boxName: DB.boxNameDesktopData,
key: _kKeyBlobKey,
);
await box.close();
return keyBlob != null;
}
}