core: add support for fallback hashing

on ios some users using passwords with multi byte characters
generates the wrong hash. This commit adds support for fallback
to allow those users to continue to login once the fix has been applied.
This commit is contained in:
Abdullah Atta
2024-12-11 14:49:26 +05:00
committed by Ammar Ahmed
parent 565b0382f9
commit 4b9a95ded7
3 changed files with 65 additions and 19 deletions

View File

@@ -136,17 +136,39 @@ class UserManager {
hashedPassword = await this.db.storage().hash(password, email); hashedPassword = await this.db.storage().hash(password, email);
} }
try { try {
let usesFallback = false;
await this.tokenManager.saveToken( await this.tokenManager.saveToken(
await http.post( await http
`${constants.AUTH_HOST}${ENDPOINTS.token}`, .post(
{ `${constants.AUTH_HOST}${ENDPOINTS.token}`,
grant_type: "mfa_password", {
client_id: "notesnook", grant_type: "mfa_password",
scope: "notesnook.sync offline_access IdentityServerApi", client_id: "notesnook",
password: hashedPassword scope: "notesnook.sync offline_access IdentityServerApi",
}, password: hashedPassword
token.access_token },
) token.access_token
)
.catch(async (e) => {
if (e instanceof Error && e.message === "invalid_grant") {
hashedPassword = await this.db
.storage()
.hash(password, email, { usesFallback: true });
if (hashedPassword === null) return Promise.reject(e);
usesFallback = true;
return await http.post(
`${constants.AUTH_HOST}${ENDPOINTS.token}`,
{
grant_type: "mfa_password",
client_id: "notesnook",
scope: "notesnook.sync offline_access IdentityServerApi",
password: hashedPassword
},
token.access_token
);
}
return Promise.reject(e);
})
); );
const user = await this.fetchUser(); const user = await this.fetchUser();
@@ -157,10 +179,18 @@ class UserManager {
await this.db.syncer.devices.register(); await this.db.syncer.devices.register();
} }
await this.db.storage().deriveCryptoKey({ if (usesFallback) {
password, await this.db.storage().deriveCryptoKeyFallback({
salt: user.salt password,
}); salt: user.salt
});
} else {
await this.db.storage().deriveCryptoKey({
password,
salt: user.salt
});
}
await this.db.kv().write("usesFallbackPWHash", usesFallback);
EV.publish(EVENTS.userLoggedIn, user); EV.publish(EVENTS.userLoggedIn, user);
} catch (e) { } catch (e) {
await this.tokenManager.saveToken(token); await this.tokenManager.saveToken(token);
@@ -301,7 +331,11 @@ class UserManager {
await http.post( await http.post(
`${constants.API_HOST}${ENDPOINTS.deleteUser}`, `${constants.API_HOST}${ENDPOINTS.deleteUser}`,
{ password: await this.db.storage().hash(password, user.email) }, {
password: await this.db.storage().hash(password, user.email, {
usesFallback: await this.db.kv().read("usesFallbackPWHash")
})
},
token token
); );
await this.logout(false, "Account deleted."); await this.logout(false, "Account deleted.");
@@ -450,7 +484,9 @@ class UserManager {
{ {
type: "change_email", type: "change_email",
new_email: newEmail, new_email: newEmail,
password: await this.db.storage().hash(password, email), password: await this.db.storage().hash(password, email, {
usesFallback: await this.db.kv().read("usesFallbackPWHash")
}),
verification_code: code verification_code: code
}, },
token token
@@ -529,7 +565,9 @@ class UserManager {
} }
if (old_password) if (old_password)
old_password = await this.db.storage().hash(old_password, email); old_password = await this.db.storage().hash(old_password, email, {
usesFallback: await this.db.kv().read("usesFallbackPWHash")
});
if (new_password) if (new_password)
new_password = await this.db.storage().hash(new_password, email); new_password = await this.db.storage().hash(new_password, email);

View File

@@ -30,6 +30,7 @@ interface KV {
deviceId: string; deviceId: string;
lastBackupTime: number; lastBackupTime: number;
fullOfflineMode: boolean; fullOfflineMode: boolean;
usesFallbackPWHash: boolean;
} }
export const KEYS: (keyof KV)[] = [ export const KEYS: (keyof KV)[] = [
@@ -40,7 +41,8 @@ export const KEYS: (keyof KV)[] = [
"monographs", "monographs",
"deviceId", "deviceId",
"lastBackupTime", "lastBackupTime",
"fullOfflineMode" "fullOfflineMode",
"usesFallbackPWHash"
]; ];
export class KVStorage { export class KVStorage {

View File

@@ -58,10 +58,16 @@ export interface IStorage {
items: Cipher<"base64">[] items: Cipher<"base64">[]
): Promise<string[]>; ): Promise<string[]>;
deriveCryptoKey(credentials: SerializedKey): Promise<void>; deriveCryptoKey(credentials: SerializedKey): Promise<void>;
hash(password: string, email: string): Promise<string>; hash(
password: string,
email: string,
options?: { usesFallback?: boolean }
): Promise<string>;
getCryptoKey(): Promise<string | undefined>; getCryptoKey(): Promise<string | undefined>;
generateCryptoKey(password: string, salt?: string): Promise<SerializedKey>; generateCryptoKey(password: string, salt?: string): Promise<SerializedKey>;
deriveCryptoKeyFallback(credentials: SerializedKey): Promise<void>;
// async generateRandomKey() { // async generateRandomKey() {
// const passwordBytes = randomBytes(124); // const passwordBytes = randomBytes(124);
// const password = passwordBytes.toString("base64"); // const password = passwordBytes.toString("base64");