mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 01:58:51 +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 () => {
|
test("empty new password throws 'New password is required'", async () => {
|
||||||
await databaseTest().then(async (db) => {
|
await databaseTest().then(async (db) => {
|
||||||
await setupLoggedInUser(db, "oldpassword");
|
await setupLoggedInUser(db, "oldpassword");
|
||||||
|
|||||||
@@ -675,15 +675,77 @@ class UserManager {
|
|||||||
encryptionKey?: SerializedKey;
|
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 token = await this.tokenManager.getAccessToken();
|
||||||
const user = await this.getUser();
|
const user = await this.getUser();
|
||||||
if (!token || !user) throw new Error("You are not logged in.");
|
if (!token || !user) throw new Error("You are not logged in.");
|
||||||
|
|
||||||
const { email, salt } = user;
|
const { email, salt } = user;
|
||||||
|
|
||||||
const { new_password, old_password } = data;
|
|
||||||
if (old_password && !(await this.verifyPassword(old_password)))
|
if (old_password && !(await this.verifyPassword(old_password)))
|
||||||
throw new Error("Incorrect old password.");
|
throw new Error("Incorrect old password.");
|
||||||
|
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
|
||||||
|
// proceeding
|
||||||
|
await this.verifyEncryptionKey(data.encryptionKey);
|
||||||
|
|
||||||
|
const updateUserPayload: Partial<User> = {};
|
||||||
|
const newMasterKey = await this.db
|
||||||
|
.storage()
|
||||||
|
.generateCryptoKey(new_password, salt);
|
||||||
|
|
||||||
|
if (user.attachmentsKey) {
|
||||||
|
updateUserPayload.attachmentsKey = await this.keyManager.rewrapKey(
|
||||||
|
user.attachmentsKey,
|
||||||
|
data.encryptionKey,
|
||||||
|
newMasterKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (user.monographPasswordsKey) {
|
||||||
|
updateUserPayload.monographPasswordsKey = await this.keyManager.rewrapKey(
|
||||||
|
user.monographPasswordsKey,
|
||||||
|
data.encryptionKey,
|
||||||
|
newMasterKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (user.inboxKeys) {
|
||||||
|
updateUserPayload.inboxKeys = await this.keyManager.rewrapKey(
|
||||||
|
user.inboxKeys,
|
||||||
|
data.encryptionKey,
|
||||||
|
newMasterKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.legacyDataEncryptionKey)
|
||||||
|
updateUserPayload.legacyDataEncryptionKey =
|
||||||
|
await this.keyManager.rewrapKey(
|
||||||
|
user.legacyDataEncryptionKey,
|
||||||
|
data.encryptionKey,
|
||||||
|
newMasterKey
|
||||||
|
);
|
||||||
|
if (user.dataEncryptionKey)
|
||||||
|
updateUserPayload.dataEncryptionKey = await this.keyManager.rewrapKey(
|
||||||
|
user.dataEncryptionKey,
|
||||||
|
data.encryptionKey,
|
||||||
|
newMasterKey
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!user.legacyDataEncryptionKey && !user.dataEncryptionKey) {
|
||||||
|
updateUserPayload.dataEncryptionKey = await this.keyManager.wrapKey(
|
||||||
|
await this.db.crypto().generateRandomKey(),
|
||||||
|
newMasterKey
|
||||||
|
);
|
||||||
|
updateUserPayload.legacyDataEncryptionKey = await this.keyManager.wrapKey(
|
||||||
|
data.encryptionKey,
|
||||||
|
newMasterKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const oldPassword = old_password
|
const oldPassword = old_password
|
||||||
? // we don't lowercase email here to allow user accounts with
|
? // we don't lowercase email here to allow user accounts with
|
||||||
@@ -694,69 +756,6 @@ class UserManager {
|
|||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!new_password) throw new Error("New password is required.");
|
|
||||||
|
|
||||||
if (oldPassword && !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
|
|
||||||
// proceeding
|
|
||||||
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,
|
|
||||||
data.encryptionKey,
|
|
||||||
newMasterKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (user.monographPasswordsKey) {
|
|
||||||
updateUserPayload.monographPasswordsKey =
|
|
||||||
await this.keyManager.rewrapKey(
|
|
||||||
user.monographPasswordsKey,
|
|
||||||
data.encryptionKey,
|
|
||||||
newMasterKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (user.inboxKeys) {
|
|
||||||
updateUserPayload.inboxKeys = await this.keyManager.rewrapKey(
|
|
||||||
user.inboxKeys,
|
|
||||||
data.encryptionKey,
|
|
||||||
newMasterKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.legacyDataEncryptionKey)
|
|
||||||
updateUserPayload.legacyDataEncryptionKey =
|
|
||||||
await this.keyManager.rewrapKey(
|
|
||||||
user.legacyDataEncryptionKey,
|
|
||||||
data.encryptionKey,
|
|
||||||
newMasterKey
|
|
||||||
);
|
|
||||||
|
|
||||||
if (user.dataEncryptionKey)
|
|
||||||
updateUserPayload.dataEncryptionKey = await this.keyManager.rewrapKey(
|
|
||||||
user.dataEncryptionKey,
|
|
||||||
data.encryptionKey,
|
|
||||||
newMasterKey
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!user.legacyDataEncryptionKey && !user.dataEncryptionKey) {
|
|
||||||
updateUserPayload.dataEncryptionKey = await this.keyManager.wrapKey(
|
|
||||||
await this.db.crypto().generateRandomKey(),
|
|
||||||
newMasterKey
|
|
||||||
);
|
|
||||||
updateUserPayload.legacyDataEncryptionKey =
|
|
||||||
await this.keyManager.wrapKey(data.encryptionKey, newMasterKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await http.patch.json(
|
await http.patch.json(
|
||||||
`${constants.API_HOST}/users/password/${type}`,
|
`${constants.API_HOST}/users/password/${type}`,
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user