Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
luckyrat committed Jan 25, 2025
1 parent 162499e commit bb346af
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 92 deletions.
32 changes: 6 additions & 26 deletions lib/cubit/account_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -378,16 +378,6 @@ class AccountCubit extends Cubit<AccountState> {
l.w('Unable to changeEmailAddress due to a transport error. Cannot be sure if the request was successful or not. Details: $e');
emit(AccountEmailChangeRequested(currentUser,
'Due to a network failure, we cannot say whether your request succeeded or not. We have left you signed in using your old email address but if the operation did eventually succeed, you may find that you are signed out soon. Please check your new email address for a verification request. It might take a moment to arrive but if it does, that suggests the process did work so just verify your new address, sign out of the app and then sign-in using the new email address. If unsure if it worked, sign in with your previous email address next time and try again when you have a more stable network connection.'));

//} on KeeMaybeOfflineException {
//TODO: confirm if can get a KeeMaybeOfflineException here
// l.i('Unable to authenticate since initial identification failed, probably due to a transport error. App should continue to work offline if user has previously stored their Vault.');
// final prefs = await SharedPreferences.getInstance();
// await prefs.setString('user.current.email', user.email!);
// if (user.id?.isNotEmpty ?? false) {
// await prefs.setString('user.authMaterialUserIdMap.${user.emailHashed}', user.id!);
// }
// emit(AccountAuthenticationBypassed(user));
}
return false;
}
Expand All @@ -407,26 +397,26 @@ class AccountCubit extends Cubit<AccountState> {
await prefs.setString('user.authMaterialUserIdMap.${user.emailHashed}', user.id!);
}
final newUser = await User.fromEmail(newEmailAddress);
//TODO: what is the below on about? maybe that we need to make sure that the vault is locked when we do this operation, thus ensuring that leaving the user signed in due to network faults, manipulation, etc. doesn't leave them with access to their vault data. but what about taking exports? shouldn't that be allowed? probably yes, as long as they are currently verified and can thus log in normally. then cliking the button to change email address will lock the vault and kick them to the screen that ultimately sends their request to this function.
//TODO: verify that we can only get here if Vault is already locked. Throw exception earlier if we can detect that state?
emit(AccountChosen(newUser));
await signout();
// await AppConfig.router.navigateTo(AppConfig.navigatorKey.currentContext!, Routes.root, clearStack: true);
//await BlocProvider.of<AccountCubit>(context).forgetUser(vc.signout);
return true;
} on KeeLoginFailedMITMException {
rethrow;
} on KeeLoginRequiredException {
l.w('Unable to changeEmailAddress due to a 403.');
emit(AccountEmailChangeRequested(user,
'Due to an authentication problem, we were unable to change your email address. Probably it has been too long since you last signed in with your previous email address. We have left you signed in using your old email address but you may find that you are signed out soon. Please sign out and then sign in again with your previous email address and try again when you have enough time to complete the operation within 10 minutes.'));
'Due to an authentication problem, we were unable to change your email address. Probably it has been too long since you last signed in with your previous email address. Please click Cancel and then sign in again with your previous email address and try this email change again when you have enough time to complete the operation within 10 minutes.'));
} on FormatException {
// Local validation
l.i('Unable to changeEmailAddress due to FormatException.');
emit(AccountEmailChangeRequested(user, 'Please enter the correct password for your existing Kee Vault account.'));
emit(AccountEmailChangeRequested(user, 'Please enter the correct password for your Kee Vault account.'));
} on KeeInvalidRequestException {
// Local validation should mean this is unlikely to happen outside of malicious acts
l.i('Unable to changeEmailAddress due to 400 response.');
emit(AccountEmailChangeRequested(user,
'Please double check that you have entered the correct password for your existing Kee Vault account. Also check that you have entered a valid email address of no more than 70 characters.'));
'Please double check that you have entered the correct password for your Kee Vault account. Also check that you have entered a valid email address of no more than 70 characters.'));
} on KeeServerConflictException {
l.i('Unable to changeEmailAddress due to 409 response.');
emit(AccountEmailChangeRequested(user,
Expand All @@ -437,17 +427,7 @@ class AccountCubit extends Cubit<AccountState> {
} on KeeServiceTransportException catch (e) {
l.w('Unable to changeEmailAddress due to a transport error. Cannot be sure if the request was successful or not. Details: $e');
emit(AccountEmailChangeRequested(user,
'Due to a network failure, we cannot say whether your request succeeded or not. We have left you signed in using your old email address but if the operation did eventually succeed, you may find that you are signed out soon. Please check your new email address for a verification request. It might take a moment to arrive but if it does, that suggests the process did work so just verify your new address, sign out of the app and then sign-in using the new email address. If unsure if it worked, sign in with your previous email address next time and try again when you have a more stable network connection.'));

//} on KeeMaybeOfflineException {
//TODO: confirm if can get a KeeMaybeOfflineException here
// l.i('Unable to authenticate since initial identification failed, probably due to a transport error. App should continue to work offline if user has previously stored their Vault.');
// final prefs = await SharedPreferences.getInstance();
// await prefs.setString('user.current.email', user.email!);
// if (user.id?.isNotEmpty ?? false) {
// await prefs.setString('user.authMaterialUserIdMap.${user.emailHashed}', user.id!);
// }
// emit(AccountAuthenticationBypassed(user));
'Due to a network failure, we cannot say whether your request succeeded or not. Please check your new email address for a verification request. It might take a moment to arrive but if it does, that suggests the process did work so just verify your new address, click Cancel below and then sign-in using the new email address. If unsure if it worked, sign in with your previous email address next time and try again when you have a more stable network connection.'));
}
return false;
}
Expand Down
146 changes: 85 additions & 61 deletions lib/widgets/account_email_change.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:keevault/cubit/account_cubit.dart';
import '../config/app.dart';
import '../config/routes.dart';
import '../cubit/vault_cubit.dart';
import '../generated/l10n.dart';
import '../logging/logger.dart';

typedef SubmitCallback = Future<void> Function(String string);

Expand All @@ -21,13 +23,20 @@ class AccountEmailChangeWidget extends StatefulWidget {

class _AccountEmailChangeWidgetState extends State<AccountEmailChangeWidget> {
Future<void> cancel() async {
// We always sign the user out. Might be nice to send them back to their Vault if that's where they came from (i.e. they have a validated and active account) but might be hard to do securely so will ignore that edge case initially.
// We always sign the user out. Might be nice to send them back to their Vault if that's
// where they came from (i.e. they have a validated and active account) but might be
// hard to do securely so will ignore that edge case initially.
final accountCubit = BlocProvider.of<AccountCubit>(context);
await accountCubit.signout();
await AppConfig.router.navigateTo(AppConfig.navigatorKey.currentContext!, Routes.root, clearStack: true);
}

Future<bool> changeEmailAddress(String password, String newEmailAddress) async {
final vaultCubit = BlocProvider.of<VaultCubit>(context);
if (vaultCubit.state is! VaultInitial) {
l.e('Vault is not in expected state so we will not proceed with the email address change. Found state: ${vaultCubit.state.runtimeType}');
return false;
}
setState(() {
changing = true;
});
Expand Down Expand Up @@ -157,7 +166,7 @@ class _AccountEmailChangeWidgetState extends State<AccountEmailChangeWidget> {
},
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 12, 0, 32),
padding: const EdgeInsets.fromLTRB(0, 12, 0, 16),
child: TextFormField(
enabled: !disableChange,
controller: _newEmailAddress,
Expand All @@ -181,7 +190,7 @@ class _AccountEmailChangeWidgetState extends State<AccountEmailChangeWidget> {
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 12, 0, 32),
padding: const EdgeInsets.fromLTRB(0, 12, 0, 16),
child: TextFormField(
enabled: !disableChange,
controller: _currentPassword,
Expand Down Expand Up @@ -214,66 +223,81 @@ class _AccountEmailChangeWidgetState extends State<AccountEmailChangeWidget> {
keyboardType: TextInputType.visiblePassword,
),
),
FilledButton(
onPressed: changing || disableChange
? null
: () async {
// final navigator = Navigator.of(context);
final sm = ScaffoldMessenger.of(context);
setState(() {
showPreviousError = false;
});
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
try {
final result =
await changeEmailAddress(submittedValuePassword!, submittedValueNewEmailAddress!);
if (result) {
sm.showSnackBar(SnackBar(
content: Text(str.emailChanged),
duration: Duration(seconds: 6),
));
await AppConfig.router
.navigateTo(AppConfig.navigatorKey.currentContext!, Routes.root, clearStack: true);
}
} finally {
if (mounted) {
setState(() {
changing = false;
});

if (state.error != null)
Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Text(
state.error!,
textAlign: TextAlign.left,
style: theme.textTheme.titleMedium?.copyWith(color: theme.colorScheme.error),
),
),

ButtonBar(
alignment: MainAxisAlignment.end,
children: [
OutlinedButton(
onPressed: changing
? null
: () async {
await cancel();
},
child: changing
? Container(
width: 24,
height: 24,
padding: const EdgeInsets.all(2.0),
child: const CircularProgressIndicator(
strokeWidth: 3,
),
)
: Text(str.alertCancel),
),
ElevatedButton(
onPressed: changing || disableChange
? null
: () async {
// final navigator = Navigator.of(context);
final sm = ScaffoldMessenger.of(context);
setState(() {
showPreviousError = false;
});
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
try {
final result =
await changeEmailAddress(submittedValuePassword!, submittedValueNewEmailAddress!);
if (result) {
sm.showSnackBar(SnackBar(
content: Text(str.emailChanged),
duration: Duration(seconds: 6),
));
await AppConfig.router.navigateTo(AppConfig.navigatorKey.currentContext!, Routes.root,
clearStack: true);
}
} finally {
if (mounted) {
setState(() {
changing = false;
});
}
}
}
}
}
},
child: changing
? Container(
width: 24,
height: 24,
padding: const EdgeInsets.all(2.0),
child: const CircularProgressIndicator(
strokeWidth: 3,
),
)
: Text(str.changeEmail),
),
OutlinedButton(
onPressed: changing
? null
: () async {
await cancel();
},
child: changing
? Container(
width: 24,
height: 24,
padding: const EdgeInsets.all(2.0),
child: const CircularProgressIndicator(
strokeWidth: 3,
),
)
: Text(str.alertCancel),
},
child: changing
? Container(
width: 24,
height: 24,
padding: const EdgeInsets.all(2.0),
child: const CircularProgressIndicator(
strokeWidth: 3,
),
)
: Text(str.changeEmail),
),
],
),
//TODO: Cancel button location
//TODO: Handle back press - cancel automatically.
]),
),
Expand Down
10 changes: 5 additions & 5 deletions lib/widgets/account_wrapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ class AccountWrapperState extends State<AccountWrapperWidget> {
}
return Text(str.vaultStatusUnknownState);
}, listenWhen: (prev, current) {
if (current is AccountAuthenticated) {
if (prev is! AccountEmailNotVerified && prev is! AccountExpired) {
return false;
}
if (current is AccountAuthenticated && prev is! AccountEmailNotVerified && prev is! AccountExpired) {
// &&
//prev is! AccountEmailChangeRequested) {
return false;
}
return true;
}, listener: (context, state) async {
Expand All @@ -148,7 +148,7 @@ class AccountWrapperState extends State<AccountWrapperWidget> {
Routes.createAccount.replaceFirst(':email', state.user.email ?? ''),
transition: TransitionType.inFromRight,
);
} else if (state is AccountAuthenticated) {
} else if (state is AccountAuthenticated && state is! AccountEmailChangeRequested) {
final vaultCubit = BlocProvider.of<VaultCubit>(context);
await vaultCubit.startup(state.user, null);
}
Expand Down

0 comments on commit bb346af

Please sign in to comment.