core: minor refactor + add test for password change (#10291)

This commit is contained in:
Abdullah Atta
2026-08-27 09:55:24 +05:00
committed by GitHub
parent 725df6f6f7
commit 4c702af1ca
2 changed files with 97 additions and 64 deletions

View File

@@ -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");

View File

@@ -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}`,
{