From 1cc6567d6fbddb58bfc7189300494bfe2ae8d5d1 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 3 May 2025 18:08:22 +0200 Subject: [PATCH] Refactor storeEncryptedDatabase method to separate metadata set (#771) --- apps/mobile-app/context/DbContext.tsx | 8 +- .../ios/AliasVault.xcodeproj/project.pbxproj | 75 ++++++++++++++++--- .../RCTNativeVaultManager.h | 4 - .../RCTNativeVaultManager.mm | 8 +- .../ios/NativeVaultManager/VaultManager.swift | 15 +++- .../ios/VaultStoreKit/VaultStoreKit.swift | 16 ++-- .../VaultStoreKitTests.swift | 40 ++++++++-- apps/mobile-app/specs/NativeVaultManager.ts | 3 +- apps/mobile-app/utils/SqliteClient.tsx | 21 +++++- 9 files changed, 152 insertions(+), 38 deletions(-) diff --git a/apps/mobile-app/context/DbContext.tsx b/apps/mobile-app/context/DbContext.tsx index 5db29923c..79a2ff196 100644 --- a/apps/mobile-app/context/DbContext.tsx +++ b/apps/mobile-app/context/DbContext.tsx @@ -49,11 +49,9 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children } vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber, }; - // Initialize the SQLite client with both database and metadata - await sqliteClient.storeEncryptedDatabase( - vaultResponse.vault.blob, - metadata - ); + // Store the encrypted database and metadata (metadata is stored in plain text in UserDefaults) + await sqliteClient.storeEncryptedDatabase(vaultResponse.vault.blob); + await sqliteClient.storeMetadata(JSON.stringify(metadata)); // Initialize the database in the native module await unlockVault(); diff --git a/apps/mobile-app/ios/AliasVault.xcodeproj/project.pbxproj b/apps/mobile-app/ios/AliasVault.xcodeproj/project.pbxproj index 49a79b507..6af0ecd05 100644 --- a/apps/mobile-app/ios/AliasVault.xcodeproj/project.pbxproj +++ b/apps/mobile-app/ios/AliasVault.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 70; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -208,7 +208,7 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, @@ -218,11 +218,62 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKit; sourceTree = ""; }; - CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKitTests; sourceTree = ""; }; - CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUI; sourceTree = ""; }; - CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultModels; sourceTree = ""; }; - CEE909812DA548C7008D568F /* Autofill */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Autofill; sourceTree = ""; }; + CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = VaultStoreKit; + sourceTree = ""; + }; + CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = VaultStoreKitTests; + sourceTree = ""; + }; + CEE4816B2DBE8AC800F4A367 /* VaultUI */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = VaultUI; + sourceTree = ""; + }; + CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = VaultModels; + sourceTree = ""; + }; + CEE909812DA548C7008D568F /* Autofill */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */, + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = Autofill; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1204,7 +1255,10 @@ LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -1259,7 +1313,10 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.h b/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.h index ee08fc567..eeb987534 100644 --- a/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.h +++ b/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.h @@ -12,10 +12,6 @@ NS_ASSUME_NONNULL_BEGIN @interface RCTNativeVaultManager : NSObject -- (void)getEncryptedDatabase:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; -- (void)getCurrentVaultRevisionNumber:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; -- (void)setCurrentVaultRevisionNumber:(double)revisionNumber resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; - @end NS_ASSUME_NONNULL_END diff --git a/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.mm b/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.mm index fb4871565..e8c70ea9d 100644 --- a/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.mm +++ b/apps/mobile-app/ios/NativeVaultManager/RCTNativeVaultManager.mm @@ -96,8 +96,12 @@ [vaultManager setAutoLockTimeout:timeout resolver:resolve rejecter:reject]; } -- (void)storeDatabase:(NSString *)base64EncryptedDb metadata:(NSString *)metadata resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { - [vaultManager storeDatabase:base64EncryptedDb metadata:metadata resolver:resolve rejecter:reject]; +- (void)storeDatabase:(NSString *)base64EncryptedDb resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [vaultManager storeDatabase:base64EncryptedDb resolver:resolve rejecter:reject]; +} + +- (void)storeMetadata:(NSString *)metadata resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [vaultManager storeMetadata:metadata resolver:resolve rejecter:reject]; } - (void)storeEncryptionKey:(NSString *)base64EncryptionKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { diff --git a/apps/mobile-app/ios/NativeVaultManager/VaultManager.swift b/apps/mobile-app/ios/NativeVaultManager/VaultManager.swift index 27d4117f2..f8d039f3c 100644 --- a/apps/mobile-app/ios/NativeVaultManager/VaultManager.swift +++ b/apps/mobile-app/ios/NativeVaultManager/VaultManager.swift @@ -19,17 +19,28 @@ public class VaultManager: NSObject { @objc func storeDatabase(_ base64EncryptedDb: String, - metadata: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { do { - try vaultStore.storeEncryptedDatabase(base64EncryptedDb, metadata: metadata) + try vaultStore.storeEncryptedDatabase(base64EncryptedDb) resolve(nil) } catch { reject("DB_ERROR", "Failed to store database: \(error.localizedDescription)", error) } } + @objc + func storeMetadata(_ metadata: String, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock) { + do { + try vaultStore.storeMetadata(metadata) + resolve(nil) + } catch { + reject("METADATA_ERROR", "Failed to store metadata: \(error.localizedDescription)", error) + } + } + @objc func setAuthMethods(_ authMethods: [String], resolver resolve: @escaping RCTPromiseResolveBlock, diff --git a/apps/mobile-app/ios/VaultStoreKit/VaultStoreKit.swift b/apps/mobile-app/ios/VaultStoreKit/VaultStoreKit.swift index f8efb5e49..f9e146f7d 100644 --- a/apps/mobile-app/ios/VaultStoreKit/VaultStoreKit.swift +++ b/apps/mobile-app/ios/VaultStoreKit/VaultStoreKit.swift @@ -237,14 +237,15 @@ public class VaultStore { } // Store the encrypted database (base64 encoded) in the app's documents directory - // and metadata in UserDefaults - // TODO: refactor metadata save and retrieve calls to separate calls for better clarity throughout - // full call chain? - public func storeEncryptedDatabase(_ base64EncryptedDb: String, metadata: String) throws { - // Store the encrypted database (base64 encoded) in the app's documents directory + public func storeEncryptedDatabase(_ base64EncryptedDb: String) throws { try base64EncryptedDb.write(to: getEncryptedDbPath(), atomically: true, encoding: .utf8) + } - // Store metadata in UserDefaults + // Store metadata in UserDefaults + // Metadata is stored in plain text in UserDefaults. The metadata consists of the following: + // - public and private email domains + // - vault revision number + public func storeMetadata(_ metadata: String) throws { userDefaults.set(metadata, forKey: vaultMetadataKey) userDefaults.synchronize() } @@ -742,7 +743,8 @@ public class VaultStore { let encryptedBase64String = encryptedBase64Data.base64EncodedString() // Persist the encrypted base64 string. - try storeEncryptedDatabase(encryptedBase64String, metadata: getVaultMetadata()!) + try storeEncryptedDatabase(encryptedBase64String) + try storeMetadata(getVaultMetadata()!) // Cleanup try FileManager.default.removeItem(at: tempDbPath) diff --git a/apps/mobile-app/ios/VaultStoreKitTests/VaultStoreKitTests.swift b/apps/mobile-app/ios/VaultStoreKitTests/VaultStoreKitTests.swift index 48084da2d..fa41b8b34 100644 --- a/apps/mobile-app/ios/VaultStoreKitTests/VaultStoreKitTests.swift +++ b/apps/mobile-app/ios/VaultStoreKitTests/VaultStoreKitTests.swift @@ -29,8 +29,19 @@ final class VaultStoreKitTests: XCTestCase { // Load and store the encrypted database let encryptedDb = try loadTestDatabase() - // TODO: get metadata via vault generation and pass it here so we have all info we need for all tests. - try vaultStore.storeEncryptedDatabase(encryptedDb, metadata: "") + + // Store the database + try vaultStore.storeEncryptedDatabase(encryptedDb) + + // Store metadata with example domains + let metadata = """ + { + "publicEmailDomains": ["spamok.com", "spamok.nl"], + "privateEmailDomains": ["aliasvault.net", "main.aliasvault.net"], + "vaultRevisionNumber": 1 + } + """ + try vaultStore.storeMetadata(metadata) // Unlock the vault (which loads, decrypts and opens the database in memory) try vaultStore.unlockVault() @@ -45,8 +56,17 @@ final class VaultStoreKitTests: XCTestCase { // Load and store the encrypted database let encryptedDb = try loadTestDatabase() - // TODO: get metadata via vault generation and pass it here so we have all info we need for all tests. - try vaultStore.storeEncryptedDatabase(encryptedDb, metadata: "") + try vaultStore.storeEncryptedDatabase(encryptedDb) + + // Store metadata with example domains + let metadata = """ + { + "publicEmailDomains": ["spamok.com", "spamok.nl"], + "privateEmailDomains": ["aliasvault.net", "main.aliasvault.net"], + "vaultRevisionNumber": 1 + } + """ + try vaultStore.storeMetadata(metadata) // Unlock the vault (which loads, decrypts and opens the database in memory) try vaultStore.unlockVault() @@ -75,7 +95,17 @@ final class VaultStoreKitTests: XCTestCase { // Load and store the encrypted database let encryptedDb = try loadTestDatabase() - try vaultStore.storeEncryptedDatabase(encryptedDb, metadata: "") + try vaultStore.storeEncryptedDatabase(encryptedDb) + + // Store metadata with example domains + let metadata = """ + { + "publicEmailDomains": ["spamok.com", "spamok.nl"], + "privateEmailDomains": ["aliasvault.net", "main.aliasvault.net"], + "vaultRevisionNumber": 1 + } + """ + try vaultStore.storeMetadata(metadata) // Unlock the vault (which loads, decrypts and opens the database in memory) try vaultStore.unlockVault() diff --git a/apps/mobile-app/specs/NativeVaultManager.ts b/apps/mobile-app/specs/NativeVaultManager.ts index 036405c5e..5ed17047b 100644 --- a/apps/mobile-app/specs/NativeVaultManager.ts +++ b/apps/mobile-app/specs/NativeVaultManager.ts @@ -13,7 +13,8 @@ export interface Spec extends TurboModule { unlockVault(): Promise; // Database operations - storeDatabase(base64EncryptedDb: string, metadata: string): Promise; + storeDatabase(base64EncryptedDb: string): Promise; + storeMetadata(metadata: string): Promise; setAuthMethods(authMethods: string[]): Promise; storeEncryptionKey(base64EncryptionKey: string): Promise; getEncryptedDatabase(): Promise; diff --git a/apps/mobile-app/utils/SqliteClient.tsx b/apps/mobile-app/utils/SqliteClient.tsx index fd5af10db..3e7e271a5 100644 --- a/apps/mobile-app/utils/SqliteClient.tsx +++ b/apps/mobile-app/utils/SqliteClient.tsx @@ -16,16 +16,31 @@ class SqliteClient { /** * Store the encrypted database via the native code implementation. */ - async storeEncryptedDatabase(base64EncryptedDb: string, metadata: VaultMetadata): Promise { + async storeEncryptedDatabase(base64EncryptedDb: string): Promise { try { - const metadataJson = JSON.stringify(metadata); - await NativeVaultManager.storeDatabase(base64EncryptedDb, metadataJson); + await NativeVaultManager.storeDatabase(base64EncryptedDb); } catch (error) { console.error('Error initializing SQLite database:', error); throw error; } } + /** + * Store the vault metadata via the native code implementation. + * + * Metadata is stored in plain text in UserDefaults. The metadata consists of the following: + * - public and private email domains + * - vault revision number + */ + async storeMetadata(metadata: string): Promise { + try { + await NativeVaultManager.storeMetadata(metadata); + } catch (error) { + console.error('Error storing vault metadata:', error); + throw error; + } + } + /** * Retrieve the vault metadata from native storage * @returns The parsed VaultMetadata object