diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index d7b9fef21..8037447cf 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -61,22 +61,25 @@ struct ContentView: View { } if !showSettings, let la = chatModel.laRequest { LocalAuthView(authRequest: la) - } else if showSetPasscode { - SetAppPasscodeView { - chatModel.contentViewAccessAuthenticated = true - prefPerformLA = true - showSetPasscode = false - privacyLocalAuthModeDefault.set(.passcode) - alertManager.showAlert(laTurnedOnAlert()) - } cancel: { - prefPerformLA = false - showSetPasscode = false - alertManager.showAlert(laPasscodeNotSetAlert()) + } else { + if showSetPasscode { + SetAppPasscodeView { + chatModel.contentViewAccessAuthenticated = true + prefPerformLA = true + showSetPasscode = false + privacyLocalAuthModeDefault.set(.passcode) + alertManager.showAlert(laTurnedOnAlert()) + } cancel: { + prefPerformLA = false + showSetPasscode = false + alertManager.showAlert(laPasscodeNotSetAlert()) + } + } else { + if chatModel.chatDbStatus == nil { + initializationView() + } } } - if chatModel.chatDbStatus == nil { - initializationView() - } } .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } .sheet(isPresented: $showSettings) { diff --git a/apps/ios/Shared/Model/SuspendChat.swift b/apps/ios/Shared/Model/SuspendChat.swift index 965e567aa..92e857352 100644 --- a/apps/ios/Shared/Model/SuspendChat.swift +++ b/apps/ios/Shared/Model/SuspendChat.swift @@ -101,7 +101,9 @@ func activateChat(appState: AppState = .active) { } } -func initChatAndMigrate(refreshInvitations: Bool = true) { +func initChatAndMigrate(ignoreSelfDestruct: Bool = false, refreshInvitations: Bool = true) { + if !ignoreSelfDestruct && kcSelfDestructPassword.get() != nil { return } + let m = ChatModel.shared if (!m.chatInitialized) { m.v3DBMigration = v3DBMigrationDefault.get() diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index 72515a1fa..229c4f04d 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -484,6 +484,24 @@ func deleteChatAsync() async throws { try await apiDeleteStorage() _ = kcDatabasePassword.remove() storeDBPassphraseGroupDefault.set(true) + deleteChatDatabaseFiles() +} + +func deleteChatDatabaseFiles() { + do { + try FileManager.default.removeItem(atPath: getAppDatabasePath().path + "_chat.db") + try FileManager.default.removeItem(atPath: getAppDatabasePath().path + "_agent.db") + } catch let error { + logger.error("Failed to delete all databases: \(error)") + } + try? FileManager.default.removeItem(atPath: getAppDatabasePath().path + "_chat.db.bak") + try? FileManager.default.removeItem(atPath: getAppDatabasePath().path + "_agent.db.bak") + try? FileManager.default.removeItem(at: getTempFilesDirectory()) + try? FileManager.default.createDirectory(at: getTempFilesDirectory(), withIntermediateDirectories: true) + + deleteAppFiles() + _ = kcDatabasePassword.remove() + storeDBPassphraseGroupDefault.set(true) } struct DatabaseView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift index bdb5b03e8..872e0354e 100644 --- a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift +++ b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift @@ -13,19 +13,28 @@ struct LocalAuthView: View { @EnvironmentObject var m: ChatModel var authRequest: LocalAuthRequest @State private var password = "" + @State private var allowToReact = true var body: some View { - PasscodeView(passcode: $password, title: authRequest.title ?? "Enter Passcode", reason: authRequest.reason, submitLabel: "Submit") { + PasscodeView(passcode: $password, title: authRequest.title ?? "Enter Passcode", reason: authRequest.reason, submitLabel: "Submit", + buttonsEnabled: $allowToReact) { if let sdPassword = kcSelfDestructPassword.get(), authRequest.selfDestruct && password == sdPassword { + allowToReact = false deleteStorageAndRestart(sdPassword) { r in m.laRequest = nil authRequest.completed(r) } return } - let r: LAResult = password == authRequest.password - ? .success - : .failed(authError: NSLocalizedString("Incorrect passcode", comment: "PIN entry")) + let r: LAResult + if password == authRequest.password { + if authRequest.selfDestruct && kcSelfDestructPassword.get() != nil && !m.chatInitialized { + initChatAndMigrate(ignoreSelfDestruct: true) + } + r = .success + } else { + r = .failed(authError: NSLocalizedString("Incorrect passcode", comment: "PIN entry")) + } m.laRequest = nil authRequest.completed(r) } cancel: { @@ -37,8 +46,23 @@ struct LocalAuthView: View { private func deleteStorageAndRestart(_ password: String, completed: @escaping (LAResult) -> Void) { Task { do { - try await stopChatAsync() - try await deleteChatAsync() + if m.chatRunning == true { + try await stopChatAsync() + } + if m.chatInitialized { + /** + * The following sequence can bring a user here: + * the user opened the app, entered app passcode, went to background, returned back, entered self-destruct code. + * In this case database should be closed to prevent possible situation when OS can deny database removal command + * */ + chatCloseStore() + } + deleteChatDatabaseFiles() + // Clear sensitive data on screen just in case ModalManager will fail to prevent hiding its modals while database encrypts itself + m.chatId = nil + m.reversedChatItems = [] + m.chats = [] + m.users = [] _ = kcAppPassword.set(password) _ = kcSelfDestructPassword.remove() await NtfManager.shared.removeAllNotifications() @@ -53,7 +77,7 @@ struct LocalAuthView: View { try initializeChat(start: true) m.chatDbChanged = false AppChatState.shared.set(.active) - if m.currentUser != nil { return } + if m.currentUser != nil || !m.chatInitialized { return } var profile: Profile? = nil if let displayName = displayName, displayName != "" { profile = Profile(displayName: displayName, fullName: "") diff --git a/apps/ios/Shared/Views/LocalAuth/PasscodeView.swift b/apps/ios/Shared/Views/LocalAuth/PasscodeView.swift index c73ded2d2..9e0d7f38b 100644 --- a/apps/ios/Shared/Views/LocalAuth/PasscodeView.swift +++ b/apps/ios/Shared/Views/LocalAuth/PasscodeView.swift @@ -14,6 +14,8 @@ struct PasscodeView: View { var reason: String? = nil var submitLabel: LocalizedStringKey var submitEnabled: ((String) -> Bool)? + @Binding var buttonsEnabled: Bool + var submit: () -> Void var cancel: () -> Void @@ -70,11 +72,11 @@ struct PasscodeView: View { @ViewBuilder private func buttonsView() -> some View { Button(action: cancel) { Label("Cancel", systemImage: "multiply") - } + }.disabled(!buttonsEnabled) Button(action: submit) { Label(submitLabel, systemImage: "checkmark") } - .disabled(submitEnabled?(passcode) == false || passcode.count < 4) + .disabled(submitEnabled?(passcode) == false || passcode.count < 4 || !buttonsEnabled) } } @@ -85,6 +87,7 @@ struct PasscodeViewView_Previews: PreviewProvider { title: "Enter Passcode", reason: "Unlock app", submitLabel: "Submit", + buttonsEnabled: Binding.constant(true), submit: {}, cancel: {} ) diff --git a/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift b/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift index 76cd3e279..365dcdb32 100644 --- a/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift +++ b/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift @@ -41,7 +41,10 @@ struct SetAppPasscodeView: View { } } } else { - setPasswordView(title: title, submitLabel: "Save") { + setPasswordView(title: title, + submitLabel: "Save", + // Do not allow to set app passcode == selfDestruct passcode + submitEnabled: { pwd in pwd != (passcodeKeychain.forKey == kcSelfDestructPassword.forKey ? kcAppPassword : kcSelfDestructPassword).get() }) { enteredPassword = passcode passcode = "" confirming = true @@ -54,7 +57,7 @@ struct SetAppPasscodeView: View { } private func setPasswordView(title: LocalizedStringKey, submitLabel: LocalizedStringKey, submitEnabled: (((String) -> Bool))? = nil, submit: @escaping () -> Void) -> some View { - PasscodeView(passcode: $passcode, title: title, reason: reason, submitLabel: submitLabel, submitEnabled: submitEnabled, submit: submit) { + PasscodeView(passcode: $passcode, title: title, reason: reason, submitLabel: submitLabel, submitEnabled: submitEnabled, buttonsEnabled: Binding.constant(true), submit: submit) { dismiss() cancel() } diff --git a/apps/ios/SimpleXChat/KeyChain.swift b/apps/ios/SimpleXChat/KeyChain.swift index 50a594646..7b7fddffb 100644 --- a/apps/ios/SimpleXChat/KeyChain.swift +++ b/apps/ios/SimpleXChat/KeyChain.swift @@ -22,7 +22,7 @@ public let kcAppPassword = KeyChainItem(forKey: APP_PASSWORD_ITEM) public let kcSelfDestructPassword = KeyChainItem(forKey: SELF_DESTRUCT_PASSWORD_ITEM) public struct KeyChainItem { - var forKey: String + public var forKey: String public func get() -> String? { getItemString(forKey: forKey)