ios: self destruct improvements
This commit is contained in:
parent
767522e701
commit
51a16cfdc2
@ -61,22 +61,25 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
if !showSettings, let la = chatModel.laRequest {
|
if !showSettings, let la = chatModel.laRequest {
|
||||||
LocalAuthView(authRequest: la)
|
LocalAuthView(authRequest: la)
|
||||||
} else if showSetPasscode {
|
} else {
|
||||||
SetAppPasscodeView {
|
if showSetPasscode {
|
||||||
chatModel.contentViewAccessAuthenticated = true
|
SetAppPasscodeView {
|
||||||
prefPerformLA = true
|
chatModel.contentViewAccessAuthenticated = true
|
||||||
showSetPasscode = false
|
prefPerformLA = true
|
||||||
privacyLocalAuthModeDefault.set(.passcode)
|
showSetPasscode = false
|
||||||
alertManager.showAlert(laTurnedOnAlert())
|
privacyLocalAuthModeDefault.set(.passcode)
|
||||||
} cancel: {
|
alertManager.showAlert(laTurnedOnAlert())
|
||||||
prefPerformLA = false
|
} cancel: {
|
||||||
showSetPasscode = false
|
prefPerformLA = false
|
||||||
alertManager.showAlert(laPasscodeNotSetAlert())
|
showSetPasscode = false
|
||||||
|
alertManager.showAlert(laPasscodeNotSetAlert())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if chatModel.chatDbStatus == nil {
|
||||||
|
initializationView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if chatModel.chatDbStatus == nil {
|
|
||||||
initializationView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! }
|
.alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! }
|
||||||
.sheet(isPresented: $showSettings) {
|
.sheet(isPresented: $showSettings) {
|
||||||
|
@ -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
|
let m = ChatModel.shared
|
||||||
if (!m.chatInitialized) {
|
if (!m.chatInitialized) {
|
||||||
m.v3DBMigration = v3DBMigrationDefault.get()
|
m.v3DBMigration = v3DBMigrationDefault.get()
|
||||||
|
@ -484,6 +484,24 @@ func deleteChatAsync() async throws {
|
|||||||
try await apiDeleteStorage()
|
try await apiDeleteStorage()
|
||||||
_ = kcDatabasePassword.remove()
|
_ = kcDatabasePassword.remove()
|
||||||
storeDBPassphraseGroupDefault.set(true)
|
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 {
|
struct DatabaseView_Previews: PreviewProvider {
|
||||||
|
@ -13,19 +13,28 @@ struct LocalAuthView: View {
|
|||||||
@EnvironmentObject var m: ChatModel
|
@EnvironmentObject var m: ChatModel
|
||||||
var authRequest: LocalAuthRequest
|
var authRequest: LocalAuthRequest
|
||||||
@State private var password = ""
|
@State private var password = ""
|
||||||
|
@State private var allowToReact = true
|
||||||
|
|
||||||
var body: some View {
|
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 {
|
if let sdPassword = kcSelfDestructPassword.get(), authRequest.selfDestruct && password == sdPassword {
|
||||||
|
allowToReact = false
|
||||||
deleteStorageAndRestart(sdPassword) { r in
|
deleteStorageAndRestart(sdPassword) { r in
|
||||||
m.laRequest = nil
|
m.laRequest = nil
|
||||||
authRequest.completed(r)
|
authRequest.completed(r)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let r: LAResult = password == authRequest.password
|
let r: LAResult
|
||||||
? .success
|
if password == authRequest.password {
|
||||||
: .failed(authError: NSLocalizedString("Incorrect passcode", comment: "PIN entry"))
|
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
|
m.laRequest = nil
|
||||||
authRequest.completed(r)
|
authRequest.completed(r)
|
||||||
} cancel: {
|
} cancel: {
|
||||||
@ -37,8 +46,23 @@ struct LocalAuthView: View {
|
|||||||
private func deleteStorageAndRestart(_ password: String, completed: @escaping (LAResult) -> Void) {
|
private func deleteStorageAndRestart(_ password: String, completed: @escaping (LAResult) -> Void) {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
try await stopChatAsync()
|
if m.chatRunning == true {
|
||||||
try await deleteChatAsync()
|
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)
|
_ = kcAppPassword.set(password)
|
||||||
_ = kcSelfDestructPassword.remove()
|
_ = kcSelfDestructPassword.remove()
|
||||||
await NtfManager.shared.removeAllNotifications()
|
await NtfManager.shared.removeAllNotifications()
|
||||||
@ -53,7 +77,7 @@ struct LocalAuthView: View {
|
|||||||
try initializeChat(start: true)
|
try initializeChat(start: true)
|
||||||
m.chatDbChanged = false
|
m.chatDbChanged = false
|
||||||
AppChatState.shared.set(.active)
|
AppChatState.shared.set(.active)
|
||||||
if m.currentUser != nil { return }
|
if m.currentUser != nil || !m.chatInitialized { return }
|
||||||
var profile: Profile? = nil
|
var profile: Profile? = nil
|
||||||
if let displayName = displayName, displayName != "" {
|
if let displayName = displayName, displayName != "" {
|
||||||
profile = Profile(displayName: displayName, fullName: "")
|
profile = Profile(displayName: displayName, fullName: "")
|
||||||
|
@ -14,6 +14,8 @@ struct PasscodeView: View {
|
|||||||
var reason: String? = nil
|
var reason: String? = nil
|
||||||
var submitLabel: LocalizedStringKey
|
var submitLabel: LocalizedStringKey
|
||||||
var submitEnabled: ((String) -> Bool)?
|
var submitEnabled: ((String) -> Bool)?
|
||||||
|
@Binding var buttonsEnabled: Bool
|
||||||
|
|
||||||
var submit: () -> Void
|
var submit: () -> Void
|
||||||
var cancel: () -> Void
|
var cancel: () -> Void
|
||||||
|
|
||||||
@ -70,11 +72,11 @@ struct PasscodeView: View {
|
|||||||
@ViewBuilder private func buttonsView() -> some View {
|
@ViewBuilder private func buttonsView() -> some View {
|
||||||
Button(action: cancel) {
|
Button(action: cancel) {
|
||||||
Label("Cancel", systemImage: "multiply")
|
Label("Cancel", systemImage: "multiply")
|
||||||
}
|
}.disabled(!buttonsEnabled)
|
||||||
Button(action: submit) {
|
Button(action: submit) {
|
||||||
Label(submitLabel, systemImage: "checkmark")
|
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",
|
title: "Enter Passcode",
|
||||||
reason: "Unlock app",
|
reason: "Unlock app",
|
||||||
submitLabel: "Submit",
|
submitLabel: "Submit",
|
||||||
|
buttonsEnabled: Binding.constant(true),
|
||||||
submit: {},
|
submit: {},
|
||||||
cancel: {}
|
cancel: {}
|
||||||
)
|
)
|
||||||
|
@ -41,7 +41,10 @@ struct SetAppPasscodeView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
enteredPassword = passcode
|
||||||
passcode = ""
|
passcode = ""
|
||||||
confirming = true
|
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 {
|
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()
|
dismiss()
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ public let kcAppPassword = KeyChainItem(forKey: APP_PASSWORD_ITEM)
|
|||||||
public let kcSelfDestructPassword = KeyChainItem(forKey: SELF_DESTRUCT_PASSWORD_ITEM)
|
public let kcSelfDestructPassword = KeyChainItem(forKey: SELF_DESTRUCT_PASSWORD_ITEM)
|
||||||
|
|
||||||
public struct KeyChainItem {
|
public struct KeyChainItem {
|
||||||
var forKey: String
|
public var forKey: String
|
||||||
|
|
||||||
public func get() -> String? {
|
public func get() -> String? {
|
||||||
getItemString(forKey: forKey)
|
getItemString(forKey: forKey)
|
||||||
|
Loading…
Reference in New Issue
Block a user