mobile: fix backup failed on iOS

This commit is contained in:
Ammar Ahmed
2024-02-14 10:27:58 +05:00
committed by Abdullah Atta
parent 6a2734acff
commit c8ccd31fe0
10 changed files with 496 additions and 457 deletions

View File

@@ -6,7 +6,7 @@
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.0.1-beta",
"version": "3.0.2-beta",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -42,8 +42,11 @@ export const CipherStorage = new MMKVLoader()
const IOS_KEYCHAIN_ACCESS_GROUP = "group.org.streetwriters.notesnook";
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
const KEYCHAIN_SERVER_DBKEY = "notesnook:db";
const NOTESNOOK_APPLOCK_KEY_SALT = "notesnook_applock_key_salt";
const NOTESNOOK_DB_KEY_SALT = "notesnook_database_key";
const NOTESNOOK_APPLOCK_KEY_SALT = "3dqclWbOYllfk9kk";
const NOTESNOOK_DB_KEY_SALT = "2rcgSprDmRvZ1AAa";
const NOTESNOOK_USER_KEY_SALT = "7qO4qeoM6PbsAJ0Q";
const DB_KEY_CIPHER = "databaseKeyCipher";
const USER_KEY_CIPHER = "userKeyCipher";
const APPLOCK_CIPHER = "applockCipher";
@@ -167,54 +170,65 @@ export async function getDatabaseKey(appLockPassword) {
);
DatabaseLogger.info("Getting database key from cipher");
DB_KEY = databaseKey;
return databaseKey;
}
const hasKey = await Keychain.hasInternetCredentials(KEYCHAIN_SERVER_DBKEY);
if (hasKey) {
let credentials = await Keychain.getInternetCredentials(
if (!DB_KEY) {
const hasKey = await Keychain.hasInternetCredentials(
KEYCHAIN_SERVER_DBKEY
);
if (hasKey) {
let credentials = await Keychain.getInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
KEYSTORE_CONFIG
);
DatabaseLogger.info("Getting database key from Keychain");
DB_KEY = credentials.password;
}
}
if (!DB_KEY) {
DatabaseLogger.info("Generating new database key");
const password = generatePassword();
const derivedDatabaseKey = await Sodium.deriveKey(
password,
NOTESNOOK_DB_KEY_SALT
);
DB_KEY = derivedDatabaseKey.key;
await Keychain.setInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
DB_KEY,
KEYSTORE_CONFIG
);
DatabaseLogger.info("Getting database key from Keychain");
DB_KEY = credentials.password;
return credentials.password;
}
DatabaseLogger.info("Generating new database key");
const password = generatePassword();
const derivedDatabaseKey = await Sodium.deriveKey(
password,
NOTESNOOK_DB_KEY_SALT
);
await Keychain.setInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
derivedDatabaseKey.key,
KEYSTORE_CONFIG
);
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook",
KEYSTORE_CONFIG
);
if (userKeyCredentials) {
const userKeyCipher = await encrypt(
{
key: derivedDatabaseKey.key
},
userKeyCredentials.password
if (await Keychain.hasInternetCredentials("notesnook")) {
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook",
KEYSTORE_CONFIG
);
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
await Keychain.resetInternetCredentials("notesnook");
if (userKeyCredentials) {
const userKeyCipher = await encrypt(
{
key: DB_KEY,
salt: NOTESNOOK_USER_KEY_SALT
},
userKeyCredentials.password
);
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
await Keychain.resetInternetCredentials("notesnook");
}
DatabaseLogger.info("Migrated user credentials to cipher storage");
}
DB_KEY = derivedDatabaseKey.key;
return derivedDatabaseKey.key;
return DB_KEY;
} catch (e) {
console.log(e, "error");
DatabaseLogger.error(e);
return null;
}
@@ -225,11 +239,12 @@ export async function deriveCryptoKey(data) {
let credentials = await Sodium.deriveKey(data.password, data.salt);
const userKeyCipher = await encrypt(
{
key: await getDatabaseKey()
key: await getDatabaseKey(),
salt: NOTESNOOK_USER_KEY_SALT
},
credentials.key
);
DatabaseLogger.info("User key stored: ", !!userKeyCipher, credentials);
DatabaseLogger.info("User key stored: ", !!userKeyCipher);
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
@@ -259,6 +274,7 @@ export async function getCryptoKey(_name) {
return key;
} catch (e) {
console.log("getCryptoKey", e);
DatabaseLogger.error(e);
}
}
@@ -301,6 +317,9 @@ export async function decrypt(password, data) {
return undefined;
let _data = { ...data };
_data.output = "plain";
if (!password.salt) password.salt = data.salt;
return await Sodium.decrypt(password, _data);
}
@@ -313,6 +332,11 @@ export async function decryptMulti(password, data) {
d.output = "plain";
return d;
});
if (data.length && !password.salt) {
password.salt = data[0].salt;
}
return await Sodium.decryptMulti(password, data);
}

View File

@@ -99,6 +99,7 @@ function ReorderableList<T extends { id: string }>({
onPress={() => {
const _hiddenItems = hiddenItemsState.slice();
const index = _hiddenItems.indexOf(info.item.id);
if (index === -1) {
_hiddenItems.push(info.item?.id);
} else {

View File

@@ -96,17 +96,13 @@ export default function Migrate() {
});
setLoading(true);
await sleep(1);
const backupSaved = await BackupService.run(false, "local");
if (!backupSaved) {
ToastManager.show({
heading: "Migration failed",
message: "You must download a backup of your data before migrating.",
context: "local",
type: "error"
});
const { error } = await BackupService.run(false, "local");
if (error) {
ToastManager.error(error, "Backup failed");
setLoading(false);
return;
}
await db.migrations?.migrate();
useUserStore.setState({
disableAppLockRequests: false

View File

@@ -160,6 +160,7 @@ export const SideMenu = React.memo(
dragging: false
});
}}
color={colors.primary.icon}
customStyle={{
width: 35,
height: 35

View File

@@ -148,10 +148,18 @@ async function updateNextBackupTime() {
lastBackupDate: Date.now()
});
}
/**
*
* @param {boolean} progress
* @param {string} context
* @returns {Promise<{path?: string, error?: Error}}>
*/
async function run(progress, context) {
let androidBackupDirectory = await checkBackupDirExists(false, context);
if (!androidBackupDirectory) return;
if (!androidBackupDirectory)
return {
error: new Error("Backup directory not selected")
};
if (progress) {
presentSheet({
@@ -216,7 +224,11 @@ async function run(progress, context) {
let showBackupCompleteSheet =
progress && SettingsService.get().showBackupCompleteSheet;
if (context) return path;
if (context)
return {
path: path
};
await sleep(300);
if (showBackupCompleteSheet) {
@@ -232,12 +244,17 @@ async function run(progress, context) {
context: "global"
});
return path;
return {
path: path
};
} catch (e) {
ToastManager.error(e, "Backup failed", context || "global");
DatabaseLogger.error("Backup failed", e);
await sleep(300);
progress && eSendEvent(eCloseSheet);
ToastManager.error(e, "Backup failed!");
return null;
return {
error: e
};
}
}

View File

@@ -946,6 +946,6 @@ SPEC CHECKSUMS:
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
Yoga: 1d6727ed193122f6adaf435c3de1a768326ff83b
PODFILE CHECKSUM: c859ca7e037c52f80a95e3483c6ee99525c62cd9
PODFILE CHECKSUM: 2b8b28a341b202bf3ca5f231b75bb05893486ed8
COCOAPODS: 1.14.2

File diff suppressed because it is too large Load Diff

View File

@@ -977,7 +977,7 @@
},
"../web": {
"name": "@notesnook/web",
"version": "3.0.1-beta",
"version": "3.0.2-beta",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -191,7 +191,7 @@ const DataMappers: Partial<Record<ItemType, (row: any) => void>> = {
(row.key.startsWith("groupOptions") ||
row.key.startsWith("toolbarConfig") ||
row.key.startsWith("sideBarOrder") ||
row.key.startsWith("sideBarHidenItems"))
row.key.startsWith("sideBarHiddenItems"))
)
row.value = JSON.parse(row.value);
},