Skip to content

Commit 865775e

Browse files
committed
wip
1 parent b2dc719 commit 865775e

File tree

11 files changed

+318
-147
lines changed

11 files changed

+318
-147
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: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,16 +337,15 @@ class AccountCubit extends Cubit<AccountState> {
337337
final newEmailHashed = await hashString(newEmailAddress, EMAIL_ID_SALT);
338338

339339
final protectedValue = ProtectedValue.fromString(password);
340-
final key = protectedValue.hash;
341-
final passKeyConfirmation = await derivePassKey(user.email!, key);
342340

343-
await _userRepo.changeEmailAddress(user, newEmailAddress, newEmailHashed, passKeyConfirmation);
341+
await _userRepo.changeEmailAddress(user, newEmailAddress, newEmailHashed, protectedValue);
344342
final prefs = await SharedPreferences.getInstance();
345343
await prefs.setString('user.current.email', newEmailAddress);
346344
if (user.id?.isNotEmpty ?? false) {
347345
await prefs.setString('user.authMaterialUserIdMap.${user.emailHashed}', user.id!);
348346
}
349347
final newUser = await User.fromEmail(newEmailAddress);
348+
//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.
350349
//TODO: verify that we can only get here if Vault is already locked. Throw exception earlier if we can detect that state?
351350
emit(AccountChosen(newUser));
352351
//await BlocProvider.of<AccountCubit>(context).forgetUser(vc.signout);
@@ -356,7 +355,12 @@ class AccountCubit extends Cubit<AccountState> {
356355
l.w('Unable to changeEmailAddress due to a 403.');
357356
emit(AccountEmailChangeRequested(user,
358357
'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.'));
358+
} on FormatException {
359+
// Local validation
360+
l.i('Unable to changeEmailAddress due to FormatException.');
361+
emit(AccountEmailChangeRequested(user, 'Please enter the correct password for your existing Kee Vault account.'));
359362
} on KeeInvalidRequestException {
363+
// Local validation should mean this is unlikely to happen outside of malicious acts
360364
l.i('Unable to changeEmailAddress due to 400 response.');
361365
emit(AccountEmailChangeRequested(user,
362366
'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.'));

lib/generated/intl/messages_en.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,30 @@ class MessageLookup extends MessageLookupByLibrary {
139139
"cancelExportOrImport": m3,
140140
"changeCancelSubscription": MessageLookupByLibrary.simpleMessage(
141141
"Change or Cancel Subscription"),
142+
"changeEmail":
143+
MessageLookupByLibrary.simpleMessage("Change email address"),
144+
"changeEmailInfo1": MessageLookupByLibrary.simpleMessage(
145+
"Your email address has a crucial role in the advanced security protections Kee Vault offers. Changing it securely is a far more complex task than for most of the places you might wish to change it. We are happy to finally offer this feature to you but please read the information carefully and don\'t proceed when you are in a rush."),
146+
"changeEmailInfo2": MessageLookupByLibrary.simpleMessage(
147+
"Your new email address will:"),
148+
"changeEmailInfo2a":
149+
MessageLookupByLibrary.simpleMessage("become your new sign-in ID"),
150+
"changeEmailInfo2b": MessageLookupByLibrary.simpleMessage(
151+
"need to be verified to confirm it belongs to you"),
152+
"changeEmailInfo3":
153+
MessageLookupByLibrary.simpleMessage("We recommend that you:"),
154+
"changeEmailInfo3a": MessageLookupByLibrary.simpleMessage(
155+
"1) Click the Cancel button below, sign in to Kee Vault again, Export your Vault to a KDBX file and store it somewhere safe as a backup."),
156+
"changeEmailInfo3b": MessageLookupByLibrary.simpleMessage(
157+
"2) Double check you enter the correct email address - you will need to type it exactly to sign in to your account in a moment."),
158+
"changeEmailInfo3c": MessageLookupByLibrary.simpleMessage(
159+
"3) Copy/paste what you have entered in the email address box and store somewhere like a note on your phone."),
160+
"changeEmailInfo4": MessageLookupByLibrary.simpleMessage(
161+
"If you make a mistake, you should be able to regain access to your Vault but in some cases you may need to create a new Kee Vault subscription and import from your previously exported KDBX file - this can result in additional hassle and costs since your current subscription would not automatically end."),
162+
"changeEmailInfo5": MessageLookupByLibrary.simpleMessage(
163+
"Your password will remain the same throughout the process. If you want to change that too, we first recommend signing in on multiple devices using your new email address and waiting at least an hour."),
164+
"changeEmailInfo6": MessageLookupByLibrary.simpleMessage(
165+
"I have read the above warnings, mitigated the risks and wish to continue"),
142166
"changeEmailPrefs":
143167
MessageLookupByLibrary.simpleMessage("Change email preferences"),
144168
"changePassword":
@@ -161,6 +185,8 @@ class MessageLookup extends MessageLookupByLibrary {
161185
"Colours allow you to categorise your entries in a more simplistic and visual way than Groups or Labels."),
162186
"confirmItsYou": MessageLookupByLibrary.simpleMessage(
163187
"To access your passwords please confirm it\'s you"),
188+
"continueSigningIn":
189+
MessageLookupByLibrary.simpleMessage("Continue signing in"),
164190
"copySecret": MessageLookupByLibrary.simpleMessage("Copy Secret"),
165191
"createNewEntry":
166192
MessageLookupByLibrary.simpleMessage("Create new entry"),
@@ -175,6 +201,8 @@ class MessageLookup extends MessageLookupByLibrary {
175201
MessageLookupByLibrary.simpleMessage("Create secure password"),
176202
"createVault": MessageLookupByLibrary.simpleMessage("Create my Vault"),
177203
"creating": MessageLookupByLibrary.simpleMessage("Creating"),
204+
"currentEmailAddress":
205+
MessageLookupByLibrary.simpleMessage("Current email address"),
178206
"currentPassword":
179207
MessageLookupByLibrary.simpleMessage("Current Password"),
180208
"currentPasswordNotCorrect": MessageLookupByLibrary.simpleMessage(
@@ -419,6 +447,8 @@ class MessageLookup extends MessageLookupByLibrary {
419447
"The URL must match the hostname (domain and subdomains) and port."),
420448
"move": MessageLookupByLibrary.simpleMessage("Move"),
421449
"name": MessageLookupByLibrary.simpleMessage("name"),
450+
"newEmailAddress":
451+
MessageLookupByLibrary.simpleMessage("New email address"),
422452
"newGroup": MessageLookupByLibrary.simpleMessage("New Group"),
423453
"newPassword": MessageLookupByLibrary.simpleMessage("New password"),
424454
"newPasswordRepeat": MessageLookupByLibrary.simpleMessage(
@@ -427,6 +457,9 @@ class MessageLookup extends MessageLookupByLibrary {
427457
"newUser": MessageLookupByLibrary.simpleMessage("New User"),
428458
"noEntriesCreateNewInstruction": MessageLookupByLibrary.simpleMessage(
429459
"You have no password entries yet. Create one using the + button below. If you have passwords already stored in the standard KDBX (KeePass) format you can import them."),
460+
"noLongerHaveAccessToUnverifiedEmail":
461+
MessageLookupByLibrary.simpleMessage(
462+
"No longer have access to your email address?"),
430463
"notSignedIn": MessageLookupByLibrary.simpleMessage("Not signed in"),
431464
"notes": MessageLookupByLibrary.simpleMessage("Notes"),
432465
"occasionalNotifications": MessageLookupByLibrary.simpleMessage(

lib/generated/l10n.dart

Lines changed: 160 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/l10n/intl_en.arb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,5 +377,22 @@
377377
"resendVerification": "Resend the verification link",
378378
"signInAgainWhenVerified": "When your email address is successfully verified you can click the button below to continue your sign-in.",
379379
"expiredWhileSignedIn": "Your subscription has just expired. Please click \"Sign out\" from the main menu below, then sign-in and follow the instructions. Your data is still available at the moment so don't panic.",
380-
"openInBrowser": "Open in browser"
380+
"openInBrowser": "Open in browser",
381+
"continueSigningIn": "Continue signing in",
382+
"noLongerHaveAccessToUnverifiedEmail": "No longer have access to your email address?",
383+
"changeEmail": "Change email address",
384+
"changeEmailInfo1": "Your email address has a crucial role in the advanced security protections Kee Vault offers. Changing it securely is a far more complex task than for most of the places you might wish to change it. We are happy to finally offer this feature to you but please read the information carefully and don't proceed when you are in a rush.",
385+
"changeEmailInfo2": "Your new email address will:",
386+
"changeEmailInfo2a": "become your new sign-in ID",
387+
"changeEmailInfo2b": "need to be verified to confirm it belongs to you",
388+
"changeEmailInfo3": "We recommend that you:",
389+
"changeEmailInfo3a": "1) Click the Cancel button below, sign in to Kee Vault again, Export your Vault to a KDBX file and store it somewhere safe as a backup.",
390+
"changeEmailInfo3b": "2) Double check you enter the correct email address - you will need to type it exactly to sign in to your account in a moment.",
391+
"changeEmailInfo3c": "3) Copy/paste what you have entered in the email address box and store somewhere like a note on your phone.",
392+
"changeEmailInfo4": "If you make a mistake, you should be able to regain access to your Vault but in some cases you may need to create a new Kee Vault subscription and import from your previously exported KDBX file - this can result in additional hassle and costs since your current subscription would not automatically end.",
393+
"changeEmailInfo5": "Your password will remain the same throughout the process. If you want to change that too, we first recommend signing in on multiple devices using your new email address and waiting at least an hour.",
394+
"changeEmailInfo6": "I have read the above warnings, mitigated the risks and wish to continue",
395+
"currentEmailAddress": "Current email address",
396+
"newEmailAddress": "New email address"
397+
381398
}

lib/user_repository.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ class UserRepository {
5656
return user;
5757
}
5858

59-
Future<void> changeEmailAddress(User user, String newEmailAddress, String newEmailHashed, String newPassKey) async {
60-
await userService.changeEmailAddress(user, newEmailAddress, newEmailHashed, newPassKey);
59+
Future<void> changeEmailAddress(
60+
User user, String newEmailAddress, String newEmailHashed, ProtectedValue password) async {
61+
await userService.changeEmailAddress(user, newEmailAddress, newEmailHashed, password);
6162
return;
6263
}
6364

lib/vault_backend/user_service.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:convert';
22
import 'dart:typed_data';
33
import 'package:convert/convert.dart';
4+
import 'package:kdbx/kdbx.dart';
45
import 'package:keevault/logging/logger.dart';
56
import 'package:keevault/vault_backend/exceptions.dart';
67
import 'package:keevault/vault_backend/login_parameters.dart';
@@ -107,7 +108,6 @@ class UserService {
107108
Future<User> createAccount(User user, int marketingEmailStatus, int subscriptionSource) async {
108109
final hexSalt = generateSalt();
109110
user.salt = hex2base64(hexSalt);
110-
//TODO: Verify that changing from user.id to user.emailHashed has expected effect (nothing since we just defaulted the id to emailHashed anyway and the server will tell us the final random user ID after registration succeeds)
111111
final privateKey = derivePrivateKey(hexSalt, user.emailHashed!, user.passKey!);
112112
final verifier = deriveVerifier(privateKey);
113113
final response = await _service.postRequest<String>('register', {
@@ -207,13 +207,22 @@ class UserService {
207207

208208
// We make no changes to the User model since we will sign the user out and ask them to
209209
// sign in again, partly so that we can ensure they have verified their new email address.
210-
Future<void> changeEmailAddress(User user, String newEmailAddress, String newEmailHashed, String newPassKey) async {
210+
Future<void> changeEmailAddress(
211+
User user, String newEmailAddress, String newEmailHashed, ProtectedValue password) async {
212+
final key = password.hash;
213+
final oldPassKey = await derivePassKey(user.email!, key);
214+
final newPassKey = await derivePassKey(newEmailAddress, key);
215+
216+
if (user.passKey != oldPassKey) {
217+
throw FormatException('Password does not match');
218+
}
219+
211220
final newHexSalt = generateSalt();
212221
final newSalt = hex2base64(newHexSalt);
213222
final newPrivateKey = derivePrivateKey(newHexSalt, newEmailHashed, newPassKey);
214223
final newVerifier = deriveVerifier(newPrivateKey);
215224

216-
final oldPrivateKey = derivePrivateKey(user.salt!, user.emailHashed!, user.passKey!);
225+
final oldPrivateKey = derivePrivateKey(user.salt!, user.emailHashed!, oldPassKey);
217226
final oldVerifier = deriveVerifier(oldPrivateKey);
218227
final oldVerifierHashed = await hashBytes(Uint8List.fromList(hex.decode(oldVerifier)));
219228

0 commit comments

Comments
 (0)