Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

core, iOS: support for self-destruct password #2412

Merged
merged 4 commits into from May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/ios/Shared/ContentView.swift
Expand Up @@ -192,7 +192,7 @@ struct ContentView: View {
private func justAuthenticate() {
userAuthorized = false
let laMode = privacyLocalAuthModeDefault.get()
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason")) { laResult in
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason"), selfDestruct: true) { laResult in
logger.debug("authenticate callback: \(String(describing: laResult))")
switch (laResult) {
case .success:
Expand Down
16 changes: 10 additions & 6 deletions apps/ios/Shared/Model/NtfManager.swift
Expand Up @@ -42,7 +42,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
changeActiveUser(userId, viewPwd: nil)
}
if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact,
let chatId = content.userInfo["chatId"] as? String {
let chatId = content.userInfo["chatId"] as? String {
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
Task { await acceptContactRequest(contactRequest) }
} else {
Expand Down Expand Up @@ -107,8 +107,8 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
// in another chat
return recent ? [.banner, .list] : [.sound, .banner, .list]
}
// this notification is deliverd from the notifications server
// when the app is in foreground it does not need to be shown
// this notification is deliverd from the notifications server
// when the app is in foreground it does not need to be shown
case ntfCategoryCheckMessage: return []
case ntfCategoryCallInvitation: return []
default: return [.sound, .banner, .list]
Expand Down Expand Up @@ -247,8 +247,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
}
}

func removeNotifications(_ ids : [String]){
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids)
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids)
func removeAllNotifications() async {
let nc = UNUserNotificationCenter.current()
let settings = await nc.notificationSettings()
if settings.authorizationStatus == .authorized {
nc.removeAllPendingNotificationRequests()
nc.removeAllDeliveredNotifications()
}
}
}
2 changes: 1 addition & 1 deletion apps/ios/Shared/Model/SimpleXAPI.swift
Expand Up @@ -125,7 +125,7 @@ func apiGetActiveUser() throws -> User? {
}
}

func apiCreateActiveUser(_ p: Profile) throws -> User {
func apiCreateActiveUser(_ p: Profile?) throws -> User {
let r = chatSendCmdSync(.createActiveUser(profile: p))
if case let .activeUser(user) = r { return user }
throw r
Expand Down
22 changes: 15 additions & 7 deletions apps/ios/Shared/Views/Database/DatabaseView.swift
Expand Up @@ -317,10 +317,7 @@ struct DatabaseView: View {
private func stopChat() {
Task {
do {
try await apiStopChat()
ChatReceiver.shared.stop()
await MainActor.run { m.chatRunning = false }
appStateGroupDefault.set(.stopped)
try await stopChatAsync()
} catch let error {
await MainActor.run {
runChat = true
Expand Down Expand Up @@ -374,9 +371,7 @@ struct DatabaseView: View {
progressIndicator = true
Task {
do {
try await apiDeleteStorage()
_ = kcDatabasePassword.remove()
storeDBPassphraseGroupDefault.set(true)
try await deleteChatAsync()
await operationEnded(.chatDeleted)
appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
} catch let error {
Expand Down Expand Up @@ -468,6 +463,19 @@ struct DatabaseView: View {
}
}

func stopChatAsync() async throws {
try await apiStopChat()
ChatReceiver.shared.stop()
await MainActor.run { ChatModel.shared.chatRunning = false }
appStateGroupDefault.set(.stopped)
}

func deleteChatAsync() async throws {
try await apiDeleteStorage()
_ = kcDatabasePassword.remove()
storeDBPassphraseGroupDefault.set(true)
}

struct DatabaseView_Previews: PreviewProvider {
static var previews: some View {
DatabaseView(showSettings: Binding.constant(false), chatItemTTL: .none)
Expand Down
13 changes: 10 additions & 3 deletions apps/ios/Shared/Views/Helpers/LocalAuthenticationUtils.swift
Expand Up @@ -30,19 +30,26 @@ struct LocalAuthRequest {
var title: LocalizedStringKey? // if title is null, reason is shown
var reason: String
var password: String
var selfDestruct: Bool
var completed: (LAResult) -> Void

static var sample = LocalAuthRequest(title: "Enter Passcode", reason: "Authenticate", password: "", completed: { _ in })
static var sample = LocalAuthRequest(title: "Enter Passcode", reason: "Authenticate", password: "", selfDestruct: false, completed: { _ in })
}

func authenticate(title: LocalizedStringKey? = nil, reason: String, completed: @escaping (LAResult) -> Void) {
func authenticate(title: LocalizedStringKey? = nil, reason: String, selfDestruct: Bool = false, completed: @escaping (LAResult) -> Void) {
logger.debug("authenticate")
switch privacyLocalAuthModeDefault.get() {
case .system: systemAuthenticate(reason, completed)
case .passcode:
if let password = kcAppPassword.get() {
DispatchQueue.main.async {
ChatModel.shared.laRequest = LocalAuthRequest(title: title, reason: reason, password: password, completed: completed)
ChatModel.shared.laRequest = LocalAuthRequest(
title: title,
reason: reason,
password: password,
selfDestruct: selfDestruct && UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT),
completed: completed
)
}
} else {
completed(.unavailable(authError: NSLocalizedString("No app password", comment: "Authentication unavailable")))
Expand Down
43 changes: 43 additions & 0 deletions apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
Expand Up @@ -7,6 +7,7 @@
//

import SwiftUI
import SimpleXChat

struct LocalAuthView: View {
@EnvironmentObject var m: ChatModel
Expand All @@ -15,6 +16,13 @@ struct LocalAuthView: View {

var body: some View {
PasscodeView(passcode: $password, title: authRequest.title ?? "Enter Passcode", reason: authRequest.reason, submitLabel: "Submit") {
if let sdPassword = kcSelfDestructPassword.get(), authRequest.selfDestruct && password == sdPassword {
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"))
Expand All @@ -25,6 +33,41 @@ struct LocalAuthView: View {
authRequest.completed(.failed(authError: NSLocalizedString("Authentication cancelled", comment: "PIN entry")))
}
}

private func deleteStorageAndRestart(_ password: String, completed: @escaping (LAResult) -> Void) {
Task {
do {
try await stopChatAsync()
try await deleteChatAsync()
_ = kcAppPassword.set(password)
_ = kcSelfDestructPassword.remove()
await NtfManager.shared.removeAllNotifications()
let displayName = UserDefaults.standard.string(forKey: DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME)
UserDefaults.standard.removeObject(forKey: DEFAULT_LA_SELF_DESTRUCT)
UserDefaults.standard.removeObject(forKey: DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME)
await MainActor.run {
m.chatDbChanged = true
m.chatInitialized = false
}
resetChatCtrl()
try initializeChat(start: true)
m.chatDbChanged = false
appStateGroupDefault.set(.active)
if m.currentUser != nil { return }
var profile: Profile? = nil
if let displayName = displayName, displayName != "" {
profile = Profile(displayName: displayName, fullName: "")
}
m.currentUser = try apiCreateActiveUser(profile)
onboardingStageDefault.set(.onboardingComplete)
m.onboardingStage = .onboardingComplete
try startChat()
completed(.success)
} catch {
completed(.failed(authError: NSLocalizedString("Incorrect passcode", comment: "PIN entry")))
}
}
}
}

struct LocalAuthView_Previews: PreviewProvider {
Expand Down
9 changes: 6 additions & 3 deletions apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift
Expand Up @@ -10,6 +10,9 @@ import SwiftUI
import SimpleXChat

struct SetAppPasscodeView: View {
var passcodeKeychain: KeyChainItem = kcAppPassword
var title: LocalizedStringKey = "New Passcode"
var reason: String?
var submit: () -> Void
var cancel: () -> Void
@Environment(\.dismiss) var dismiss: DismissAction
Expand All @@ -27,7 +30,7 @@ struct SetAppPasscodeView: View {
submitEnabled: { pwd in pwd == enteredPassword }
) {
if passcode == enteredPassword {
if kcAppPassword.set(passcode) {
if passcodeKeychain.set(passcode) {
enteredPassword = ""
passcode = ""
dismiss()
Expand All @@ -38,7 +41,7 @@ struct SetAppPasscodeView: View {
}
}
} else {
setPasswordView(title: "New Passcode", submitLabel: "Save") {
setPasswordView(title: title, submitLabel: "Save") {
enteredPassword = passcode
passcode = ""
confirming = true
Expand All @@ -51,7 +54,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, submitLabel: submitLabel, submitEnabled: submitEnabled, submit: submit) {
PasscodeView(passcode: $passcode, title: title, reason: reason, submitLabel: submitLabel, submitEnabled: submitEnabled, submit: submit) {
dismiss()
cancel()
}
Expand Down
2 changes: 2 additions & 0 deletions apps/ios/Shared/Views/Onboarding/CreateProfile.swift
Expand Up @@ -129,6 +129,8 @@ struct CreateProfile: View {
m.onboardingStage = .step3_CreateSimpleXAddress
}
} else {
onboardingStageDefault.set(.onboardingComplete)
m.onboardingStage = .onboardingComplete
dismiss()
m.users = try listUsers()
try getUserChatData()
Expand Down
1 change: 1 addition & 0 deletions apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift
Expand Up @@ -13,6 +13,7 @@ struct IncognitoHelp: View {
VStack(alignment: .leading) {
Text("Incognito mode")
.font(.largeTitle)
.bold()
.padding(.vertical)
ScrollView {
VStack(alignment: .leading) {
Expand Down