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,8 +136,10 @@ class UserManager {
hashedPassword = await this.db.storage().hash(password, email);
}
try {
let usesFallback = false;
await this.tokenManager.saveToken(
await http.post(
await http
.post(
`${constants.AUTH_HOST}${ENDPOINTS.token}`,
{
grant_type: "mfa_password",
@@ -147,6 +149,26 @@ class UserManager {
},
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();
@@ -157,10 +179,18 @@ class UserManager {
await this.db.syncer.devices.register();
}
if (usesFallback) {
await this.db.storage().deriveCryptoKeyFallback({
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);
} catch (e) {
await this.tokenManager.saveToken(token);
@@ -301,7 +331,11 @@ class UserManager {
await http.post(
`${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
);
await this.logout(false, "Account deleted.");
@@ -450,7 +484,9 @@ class UserManager {
{
type: "change_email",
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
},
token
@@ -529,7 +565,9 @@ class UserManager {
}
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)
new_password = await this.db.storage().hash(new_password, email);

View File

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

View File

@@ -58,10 +58,16 @@ export interface IStorage {
items: Cipher<"base64">[]
): Promise<string[]>;
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>;
generateCryptoKey(password: string, salt?: string): Promise<SerializedKey>;
deriveCryptoKeyFallback(credentials: SerializedKey): Promise<void>;
// async generateRandomKey() {
// const passwordBytes = randomBytes(124);
// const password = passwordBytes.toString("base64");