mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
core: minor refactor + add test for password change (#10291)
This commit is contained in:
@@ -97,6 +97,40 @@ describe("UserManager._updatePassword (change)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("new master key decrypts dataEncryptionKey and old master key throws (regression)", async () => {
|
||||
await databaseTest().then(async (db) => {
|
||||
const { salt } = await setupLoggedInUser(db, "oldpassword");
|
||||
|
||||
// Simulate a full password change with old -> new passwords.
|
||||
const result = await db.user.changePassword("oldpassword", "newpassword");
|
||||
expect(result).toBe(true);
|
||||
|
||||
const user = await db.user.getUser();
|
||||
|
||||
// The stored master key must now be the one derived from the NEW password.
|
||||
const newMasterKey = await db.user.getMasterKey();
|
||||
expect(newMasterKey).toBeDefined();
|
||||
expect(newMasterKey!.key).toBe(
|
||||
(await db.storage().generateCryptoKey("newpassword", salt)).key
|
||||
);
|
||||
|
||||
// (a) The NEW master key must decrypt the rewrapped dataEncryptionKey.
|
||||
await expect(
|
||||
db.storage().decrypt(newMasterKey!, user!.dataEncryptionKey!)
|
||||
).resolves.toBeDefined();
|
||||
|
||||
// (b) The OLD master key must NOT be able to decrypt it anymore.
|
||||
// We derive it without persisting it to the key store.
|
||||
const oldMasterKey = await db.storage().generateCryptoKey(
|
||||
"oldpassword",
|
||||
salt
|
||||
);
|
||||
await expect(
|
||||
db.storage().decrypt(oldMasterKey, user!.dataEncryptionKey!)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
test("empty new password throws 'New password is required'", async () => {
|
||||
await databaseTest().then(async (db) => {
|
||||
await setupLoggedInUser(db, "oldpassword");
|
||||
|
||||
@@ -675,29 +675,20 @@ class UserManager {
|
||||
encryptionKey?: SerializedKey;
|
||||
}
|
||||
) {
|
||||
const { new_password, old_password } = data;
|
||||
if (!new_password) throw new Error("New password is required.");
|
||||
|
||||
const token = await this.tokenManager.getAccessToken();
|
||||
const user = await this.getUser();
|
||||
if (!token || !user) throw new Error("You are not logged in.");
|
||||
|
||||
const { email, salt } = user;
|
||||
|
||||
const { new_password, old_password } = data;
|
||||
if (old_password && !(await this.verifyPassword(old_password)))
|
||||
throw new Error("Incorrect old password.");
|
||||
|
||||
const oldPassword = old_password
|
||||
? // we don't lowercase email here to allow user accounts with
|
||||
// mixed cased emails to change their passwords. Once that is done,
|
||||
// we will lowercase the email in the backend.
|
||||
await this.db.storage().hash(old_password, email, {
|
||||
usesFallback: await this.usesFallbackPWHash(old_password)
|
||||
})
|
||||
: null;
|
||||
|
||||
if (!new_password) throw new Error("New password is required.");
|
||||
|
||||
if (oldPassword && !data.encryptionKey)
|
||||
if (old_password && !data.encryptionKey)
|
||||
data.encryptionKey = await this.getMasterKey();
|
||||
|
||||
if (!data.encryptionKey) throw new Error("Encryption key is required.");
|
||||
|
||||
// we must be 100% sure that the provided encryption key is valid before
|
||||
@@ -705,10 +696,10 @@ class UserManager {
|
||||
await this.verifyEncryptionKey(data.encryptionKey);
|
||||
|
||||
const updateUserPayload: Partial<User> = {};
|
||||
if (data.encryptionKey) {
|
||||
const newMasterKey = await this.db
|
||||
.storage()
|
||||
.generateCryptoKey(new_password, salt);
|
||||
|
||||
if (user.attachmentsKey) {
|
||||
updateUserPayload.attachmentsKey = await this.keyManager.rewrapKey(
|
||||
user.attachmentsKey,
|
||||
@@ -717,8 +708,7 @@ class UserManager {
|
||||
);
|
||||
}
|
||||
if (user.monographPasswordsKey) {
|
||||
updateUserPayload.monographPasswordsKey =
|
||||
await this.keyManager.rewrapKey(
|
||||
updateUserPayload.monographPasswordsKey = await this.keyManager.rewrapKey(
|
||||
user.monographPasswordsKey,
|
||||
data.encryptionKey,
|
||||
newMasterKey
|
||||
@@ -739,7 +729,6 @@ class UserManager {
|
||||
data.encryptionKey,
|
||||
newMasterKey
|
||||
);
|
||||
|
||||
if (user.dataEncryptionKey)
|
||||
updateUserPayload.dataEncryptionKey = await this.keyManager.rewrapKey(
|
||||
user.dataEncryptionKey,
|
||||
@@ -752,11 +741,21 @@ class UserManager {
|
||||
await this.db.crypto().generateRandomKey(),
|
||||
newMasterKey
|
||||
);
|
||||
updateUserPayload.legacyDataEncryptionKey =
|
||||
await this.keyManager.wrapKey(data.encryptionKey, newMasterKey);
|
||||
}
|
||||
updateUserPayload.legacyDataEncryptionKey = await this.keyManager.wrapKey(
|
||||
data.encryptionKey,
|
||||
newMasterKey
|
||||
);
|
||||
}
|
||||
|
||||
const oldPassword = old_password
|
||||
? // we don't lowercase email here to allow user accounts with
|
||||
// mixed cased emails to change their passwords. Once that is done,
|
||||
// we will lowercase the email in the backend.
|
||||
await this.db.storage().hash(old_password, email, {
|
||||
usesFallback: await this.usesFallbackPWHash(old_password)
|
||||
})
|
||||
: null;
|
||||
|
||||
await http.patch.json(
|
||||
`${constants.API_HOST}/users/password/${type}`,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user