Skip to content

Commit 88729cd

Browse files
committed
Add change email and password features
1 parent de63c83 commit 88729cd

25 files changed

+1407
-293
lines changed

assets/Demo.kdbx

256 Bytes
Binary file not shown.

assets/Demo.zip

22.4 KB
Binary file not shown.

lib/cubit/account_cubit.dart

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:keevault/vault_backend/user.dart';
1010
import 'package:meta/meta.dart';
1111
import 'package:shared_preferences/shared_preferences.dart';
1212
import '../logging/logger.dart';
13+
import '../vault_backend/utils.dart';
1314

1415
part 'account_state.dart';
1516

@@ -325,6 +326,93 @@ class AccountCubit extends Cubit<AccountState> {
325326
}
326327
}
327328

329+
void startEmailChange() {
330+
emit(AccountEmailChangeRequested(currentUser, null));
331+
}
332+
333+
Future<bool> changePassword(ProtectedValue password, Future<bool> Function(User user) onChangeStarted) async {
334+
if (currentUser.emailHashed == null) throw KeeInvalidStateException();
335+
if (currentUser.email?.isEmpty ?? true) throw KeeInvalidStateException();
336+
if (currentUser.salt?.isEmpty ?? true) throw KeeInvalidStateException();
337+
338+
final key = password.hash;
339+
final newPassKey = await derivePassKey(currentUser.email!, key);
340+
341+
try {
342+
await _userRepo.changePasswordStart(currentUser, newPassKey);
343+
344+
final success = await onChangeStarted(currentUser);
345+
346+
if (!success) {
347+
throw VaultPasswordChangeException('Password change aborted.');
348+
}
349+
350+
await _userRepo.changePasswordFinish(currentUser, newPassKey);
351+
return true;
352+
} on KeeLoginFailedMITMException {
353+
rethrow;
354+
} on KeeLoginRequiredException {
355+
l.w('Unable to change password due to a 403.');
356+
throw VaultPasswordChangeException(
357+
'Due to an authentication problem, we were unable to change your password. Probably it has been too long since you last signed in. Please sign out and then sign in again with your previous password and try again when you have enough time to complete the operation within 10 minutes.');
358+
} on KeeNotFoundException {
359+
l.i('Unable to change password due to 404 response.');
360+
throw VaultPasswordChangeException('We cannot find your account. Have you recently deleted it?');
361+
} on KeeServiceTransportException catch (e) {
362+
l.w('Unable to change password due to a transport error. Cannot be sure if the request was successful or not. Details: $e');
363+
throw VaultPasswordChangeException(
364+
'Due to a network failure, we cannot say whether your request succeeded or not. If possible, try signing in to a different device with your new password to find out if the change took effect. If unsure if it worked, sign in with your previous password next time and try again when you have a more stable network connection.');
365+
}
366+
}
367+
368+
Future<bool> changeEmailAddress(String password, String newEmailAddress) async {
369+
l.d('starting the changeEmailAddress procedure');
370+
User user = currentUser;
371+
try {
372+
final newEmailHashed = await hashString(newEmailAddress, EMAIL_ID_SALT);
373+
374+
final protectedValue = ProtectedValue.fromString(password);
375+
376+
await _userRepo.changeEmailAddress(user, newEmailAddress, newEmailHashed, protectedValue);
377+
final prefs = await SharedPreferences.getInstance();
378+
await prefs.setString('user.current.email', newEmailAddress);
379+
if (user.id?.isNotEmpty ?? false) {
380+
await prefs.setString('user.authMaterialUserIdMap.${user.emailHashed}', user.id!);
381+
}
382+
final newUser = await User.fromEmail(newEmailAddress);
383+
emit(AccountChosen(newUser));
384+
await signout();
385+
return true;
386+
} on KeeLoginFailedMITMException {
387+
rethrow;
388+
} on KeeLoginRequiredException {
389+
l.w('Unable to changeEmailAddress due to a 403.');
390+
emit(AccountEmailChangeRequested(user,
391+
'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.'));
392+
} on FormatException {
393+
// Local validation
394+
l.i('Unable to changeEmailAddress due to FormatException.');
395+
emit(AccountEmailChangeRequested(user, 'Please enter the correct password for your Kee Vault account.'));
396+
} on KeeInvalidRequestException {
397+
// Local validation should mean this is unlikely to happen outside of malicious acts
398+
l.i('Unable to changeEmailAddress due to 400 response.');
399+
emit(AccountEmailChangeRequested(user,
400+
'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.'));
401+
} on KeeServerConflictException {
402+
l.i('Unable to changeEmailAddress due to 409 response.');
403+
emit(AccountEmailChangeRequested(user,
404+
'Sorry, that email address is already associated with a different Kee Vault account (or is reserved due to earlier use by a deleted account). Try signing in to that account, and consider importing your exported KDBX file from this account if you wish to transfer your data to the other account. If you have access to the requested email address but are unable to remember your password, you could use the account reset feature to delete the contents of the other account and assign it a new password that you will remember.'));
405+
} on KeeNotFoundException {
406+
l.i('Unable to changeEmailAddress due to 404 response.');
407+
emit(AccountEmailChangeRequested(user, 'We cannot find your account. Have you recently deleted it?'));
408+
} on KeeServiceTransportException catch (e) {
409+
l.w('Unable to changeEmailAddress due to a transport error. Cannot be sure if the request was successful or not. Details: $e');
410+
emit(AccountEmailChangeRequested(user,
411+
'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.'));
412+
}
413+
return false;
414+
}
415+
328416
Future<void> signout() async {
329417
final AccountState currentState = state;
330418
if (currentState is AccountChosen && (currentState.user.email?.isNotEmpty ?? false)) {

lib/cubit/account_state.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ class AccountEmailNotVerified extends AccountAuthenticated {
6060
const AccountEmailNotVerified(super.user);
6161
}
6262

63+
class AccountEmailChangeRequested extends AccountAuthenticated {
64+
final String? error;
65+
const AccountEmailChangeRequested(super.user, this.error);
66+
}
67+
6368
class AccountTrialRestartStarted extends AccountExpired {
6469
const AccountTrialRestartStarted(super.user, super.trialAvailable);
6570
}

0 commit comments

Comments
 (0)