From 3bab218a62d20c4a91cfa7f2f96e35d29566cc6c Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Mon, 8 May 2023 23:31:40 +0100
Subject: [PATCH 1/4] core, iOS: support for self-destruct password
---
apps/ios/Shared/ContentView.swift | 2 +-
apps/ios/Shared/Model/SimpleXAPI.swift | 2 +-
.../Shared/Views/Database/DatabaseView.swift | 22 ++-
.../Helpers/LocalAuthenticationUtils.swift | 13 +-
.../Views/LocalAuth/LocalAuthView.swift | 42 ++++++
.../Views/LocalAuth/SetAppPasscodeView.swift | 9 +-
.../Views/UserSettings/IncognitoHelp.swift | 1 +
.../Views/UserSettings/PrivacySettings.swift | 130 +++++++++++++++++-
.../Views/UserSettings/SettingsView.swift | 6 +-
.../cs.xcloc/Localized Contents/cs.xliff | 4 +-
.../de.xcloc/Localized Contents/de.xliff | 4 +-
.../el.xcloc/Localized Contents/el.xliff | 4 +-
.../en.xcloc/Localized Contents/en.xliff | 6 +-
.../es.xcloc/Localized Contents/es.xliff | 4 +-
.../fr.xcloc/Localized Contents/fr.xliff | 4 +-
.../he.xcloc/Localized Contents/he.xliff | 4 +-
.../it.xcloc/Localized Contents/it.xliff | 4 +-
.../nl.xcloc/Localized Contents/nl.xliff | 4 +-
.../pl.xcloc/Localized Contents/pl.xliff | 4 +-
.../pt.xcloc/Localized Contents/pt.xliff | 4 +-
.../ru.xcloc/Localized Contents/ru.xliff | 4 +-
.../Localized Contents/zh-Hans.xliff | 4 +-
.../Localized Contents/zh-Hant.xliff | 4 +-
apps/ios/SimpleXChat/APITypes.swift | 8 +-
apps/ios/SimpleXChat/KeyChain.swift | 3 +
apps/ios/de.lproj/Localizable.strings | 2 +-
apps/ios/es.lproj/Localizable.strings | 2 +-
apps/ios/fr.lproj/Localizable.strings | 2 +-
apps/ios/it.lproj/Localizable.strings | 2 +-
apps/ios/nl.lproj/Localizable.strings | 2 +-
apps/ios/pl.lproj/Localizable.strings | 2 +-
apps/ios/ru.lproj/Localizable.strings | 2 +-
apps/ios/zh-Hans.lproj/Localizable.strings | 2 +-
src/Simplex/Chat.hs | 39 +++---
src/Simplex/Chat/Controller.hs | 2 +-
src/Simplex/Chat/Store.hs | 7 +-
src/Simplex/Chat/Types.hs | 7 +
tests/ChatClient.hs | 4 +-
tests/ChatTests/Groups.hs | 6 +-
tests/ChatTests/Profiles.hs | 4 +-
40 files changed, 289 insertions(+), 92 deletions(-)
diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift
index 910922d704..8edc66df43 100644
--- a/apps/ios/Shared/ContentView.swift
+++ b/apps/ios/Shared/ContentView.swift
@@ -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:
diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift
index 55109fd166..f616a9929c 100644
--- a/apps/ios/Shared/Model/SimpleXAPI.swift
+++ b/apps/ios/Shared/Model/SimpleXAPI.swift
@@ -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
diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift
index cf926e61a7..15efd5955d 100644
--- a/apps/ios/Shared/Views/Database/DatabaseView.swift
+++ b/apps/ios/Shared/Views/Database/DatabaseView.swift
@@ -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
@@ -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 {
@@ -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)
diff --git a/apps/ios/Shared/Views/Helpers/LocalAuthenticationUtils.swift b/apps/ios/Shared/Views/Helpers/LocalAuthenticationUtils.swift
index e06ae61acf..d9b1bfed34 100644
--- a/apps/ios/Shared/Views/Helpers/LocalAuthenticationUtils.swift
+++ b/apps/ios/Shared/Views/Helpers/LocalAuthenticationUtils.swift
@@ -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")))
diff --git a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
index 5cf74bafdd..4d8d758377 100644
--- a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
+++ b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
@@ -7,6 +7,7 @@
//
import SwiftUI
+import SimpleXChat
struct LocalAuthView: View {
@EnvironmentObject var m: ChatModel
@@ -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"))
@@ -25,6 +33,40 @@ 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()
+ 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 {
diff --git a/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift b/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift
index 5b95ab0cc5..76cd3e279a 100644
--- a/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift
+++ b/apps/ios/Shared/Views/LocalAuth/SetAppPasscodeView.swift
@@ -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
@@ -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()
@@ -38,7 +41,7 @@ struct SetAppPasscodeView: View {
}
}
} else {
- setPasswordView(title: "New Passcode", submitLabel: "Save") {
+ setPasswordView(title: title, submitLabel: "Save") {
enteredPassword = passcode
passcode = ""
confirming = true
@@ -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()
}
diff --git a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift
index 92f0f8c201..3652dec054 100644
--- a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift
+++ b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift
@@ -13,6 +13,7 @@ struct IncognitoHelp: View {
VStack(alignment: .leading) {
Text("Incognito mode")
.font(.largeTitle)
+ .bold()
.padding(.vertical)
ScrollView {
VStack(alignment: .leading) {
diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
index dd21ea26c0..fbf5364ee3 100644
--- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
@@ -102,9 +102,13 @@ struct SimplexLockView: View {
@AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false
@State private var laMode: LAMode = privacyLocalAuthModeDefault.get()
@AppStorage(DEFAULT_LA_LOCK_DELAY) private var laLockDelay = 30
- @State var performLA: Bool = UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
+ @State private var performLA: Bool = UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
+ @State private var selfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT)
+ @State private var currentSelfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT)
+ @AppStorage(DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME) private var selfDestructDisplayName = ""
@State private var performLAToggleReset = false
@State private var performLAModeReset = false
+ @State private var performLASelfDestructReset = false
@State private var showPasswordAction: PasswordAction? = nil
@State private var showChangePassword = false
@State var laAlert: LASettingViewAlert? = nil
@@ -124,7 +128,10 @@ struct SimplexLockView: View {
enum PasswordAction: Identifiable {
case enableAuth
case toggleMode
- case changePassword
+ case changePasscode
+ case enableSelfDestruct
+ case changeSelfDestructPasscode
+ case selfDestructInfo
var id: Self { self }
}
@@ -159,12 +166,34 @@ struct SimplexLockView: View {
}
}
if showChangePassword && laMode == .passcode {
- Button("Change Passcode") {
+ Button("Change passcode") {
changeLAPassword()
}
}
}
}
+
+ if performLA && laMode == .passcode {
+ Section("Self-destruct passcode") {
+ Toggle(isOn: $selfDestruct) {
+ HStack(spacing: 6) {
+ Text("Enable self-destruct")
+ Image(systemName: "info.circle")
+ .foregroundColor(.accentColor)
+ .font(.system(size: 14))
+ }
+ .onTapGesture {
+ showPasswordAction = .selfDestructInfo
+ }
+ }
+ if selfDestruct {
+ TextField("New display name", text: $selfDestructDisplayName)
+ Button("Change self-destruct passcode") {
+ changeSelfDestructPassword()
+ }
+ }
+ }
+ }
}
}
.onChange(of: performLA) { performLAToggle in
@@ -192,6 +221,13 @@ struct SimplexLockView: View {
updateLAMode()
}
}
+ .onChange(of: selfDestruct) { _ in
+ if performLASelfDestructReset {
+ performLASelfDestructReset = false
+ } else if prefPerformLA {
+ toggleSelfDestruct()
+ }
+ }
.alert(item: $laAlert) { alertItem in
switch alertItem {
case .laTurnedOnAlert: return laTurnedOnAlert()
@@ -223,12 +259,27 @@ struct SimplexLockView: View {
} cancel: {
revertLAMode()
}
- case .changePassword:
+ case .changePasscode:
SetAppPasscodeView {
showLAAlert(.laPasscodeChangedAlert)
} cancel: {
showLAAlert(.laPasscodeNotChangedAlert)
}
+ case .enableSelfDestruct:
+ SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, title: "Set passcode", reason: NSLocalizedString("Enable self-destruct passcode", comment: "set passcode view")) {
+ updateSelfDestruct()
+ showLAAlert(.laPasscodeSetAlert) // change
+ } cancel: {
+ revertSelfDestruct()
+ }
+ case .changeSelfDestructPasscode:
+ SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, reason: NSLocalizedString("Change self-destruct passcode", comment: "set passcode view")) {
+ showLAAlert(.laPasscodeChangedAlert) // change
+ } cancel: {
+ showLAAlert(.laPasscodeNotChangedAlert) // change
+ }
+ case .selfDestructInfo:
+ selfDestructInfoView()
}
}
.onAppear {
@@ -239,6 +290,30 @@ struct SimplexLockView: View {
}
}
+ private func selfDestructInfoView() -> some View {
+ VStack(alignment: .leading) {
+ Text("Self-desctruct")
+ .font(.largeTitle)
+ .bold()
+ .padding(.vertical)
+ ScrollView {
+ VStack(alignment: .leading) {
+ Group {
+ Text("If you enter your self-desctruct passcode while opening the app:")
+ VStack(spacing: 8) {
+ textListItem("1.", "All app data is deleted.")
+ textListItem("2.", "App passcode is replaced with self-desctruct passcode.")
+ textListItem("3.", "An empty chat profile with the provided name is created, and the app opens as usual.")
+ }
+ }
+ .padding(.bottom)
+ }
+ }
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ }
+
private func showLAAlert(_ a: LASettingViewAlert) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
laAlert = a
@@ -276,11 +351,39 @@ struct SimplexLockView: View {
}
}
+ private func toggleSelfDestruct() {
+ authenticate(reason: NSLocalizedString("Change self-destruct mode", comment: "authentication reason")) { laResult in
+ switch laResult {
+ case .failed:
+ revertSelfDestruct()
+ laAlert = .laFailedAlert
+ case .success:
+ if selfDestruct {
+ showPasswordAction = .enableSelfDestruct
+ } else {
+ resetSelfDestruct()
+ }
+ case .unavailable:
+ disableUnavailableLA()
+ }
+ }
+ }
+
private func changeLAPassword() {
authenticate(title: "Current Passcode", reason: NSLocalizedString("Change passcode", comment: "authentication reason")) { laResult in
switch laResult {
case .failed: laAlert = .laFailedAlert
- case .success: showPasswordAction = .changePassword
+ case .success: showPasswordAction = .changePasscode
+ case .unavailable: disableUnavailableLA()
+ }
+ }
+ }
+
+ private func changeSelfDestructPassword() {
+ authenticate(reason: NSLocalizedString("Change self-destruct passcode", comment: "authentication reason")) { laResult in
+ switch laResult {
+ case .failed: laAlert = .laFailedAlert
+ case .success: showPasswordAction = .changeSelfDestructPasscode
case .unavailable: disableUnavailableLA()
}
}
@@ -329,6 +432,7 @@ struct SimplexLockView: View {
_ = kcAppPassword.remove()
laLockDelay = 30
showChangePassword = false
+ resetSelfDestruct()
}
private func resetLAEnabled(_ onOff: Bool) {
@@ -347,6 +451,22 @@ struct SimplexLockView: View {
privacyLocalAuthModeDefault.set(laMode)
}
+ private func resetSelfDestruct() {
+ _ = kcSelfDestructPassword.remove()
+ selfDestruct = false
+ updateSelfDestruct()
+ }
+
+ private func revertSelfDestruct() {
+ performLASelfDestructReset = true
+ withAnimation { selfDestruct = currentSelfDestruct }
+ }
+
+ private func updateSelfDestruct() {
+ UserDefaults.standard.set(selfDestruct, forKey: DEFAULT_LA_SELF_DESTRUCT)
+ currentSelfDestruct = selfDestruct
+ }
+
private func passcodeAlert(_ title: LocalizedStringKey) -> Alert {
mkAlert(title: title, message: "Please remember or store it securely - there is no way to recover a lost passcode!")
}
diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
index 5f5c1334b6..714de5b2b2 100644
--- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift
+++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
@@ -21,6 +21,8 @@ let DEFAULT_LA_NOTICE_SHOWN = "localAuthenticationNoticeShown"
let DEFAULT_PERFORM_LA = "performLocalAuthentication"
let DEFAULT_LA_MODE = "localAuthenticationMode"
let DEFAULT_LA_LOCK_DELAY = "localAuthenticationLockDelay"
+let DEFAULT_LA_SELF_DESTRUCT = "localAuthenticationSelfDestruct"
+let DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME = "localAuthenticationSelfDestructDisplayName"
let DEFAULT_NOTIFICATION_ALERT_SHOWN = "notificationAlertShown"
let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay"
let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers"
@@ -53,6 +55,7 @@ let appDefaults: [String: Any] = [
DEFAULT_PERFORM_LA: false,
DEFAULT_LA_MODE: LAMode.system.rawValue,
DEFAULT_LA_LOCK_DELAY: 30,
+ DEFAULT_LA_SELF_DESTRUCT: false,
DEFAULT_NOTIFICATION_ALERT_SHOWN: false,
DEFAULT_WEBRTC_POLICY_RELAY: true,
DEFAULT_CALL_KIT_CALLS_IN_RECENTS: false,
@@ -298,9 +301,8 @@ struct SettingsView: View {
.frame(maxWidth: 24, maxHeight: 24, alignment: .center)
.foregroundColor(chatModel.incognito ? Color.indigo : .secondary)
Toggle(isOn: $chatModel.incognito) {
- HStack {
+ HStack(spacing: 6) {
Text("Incognito")
- Spacer().frame(width: 4)
Image(systemName: "info.circle")
.foregroundColor(.accentColor)
.font(.system(size: 14))
diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
index 52d88f00c6..faf4fb391d 100644
--- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
+++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
@@ -730,8 +730,8 @@ Dostupné ve verzi 5.1
Změnit
No comment provided by engineer.
-
-
+
+
Změna hesla
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
index b735149017..d90797dce3 100644
--- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
+++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
@@ -730,8 +730,8 @@ Verfügbar ab v5.1
Ändern
No comment provided by engineer.
-
-
+
+
Passwort ändern
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff
index ce52791721..8759953c90 100644
--- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff
+++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff
@@ -569,8 +569,8 @@ Available in v5.1
No comment provided by engineer.
-
-
+
+
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
index 66db8126e2..341f77da69 100644
--- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
+++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
@@ -739,9 +739,9 @@ Available in v5.1
Change
No comment provided by engineer.
-
-
- Change Passcode
+
+
+ Change passcode
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
index 6e20eade0d..dfd5c780bc 100644
--- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
+++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
@@ -730,8 +730,8 @@ Disponible en v5.1
Cambiar
No comment provided by engineer.
-
-
+
+
Cambiar el código de acceso
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
index cf17e8a993..63cace370b 100644
--- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
+++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
@@ -730,8 +730,8 @@ Disponible dans la v5.1
Changer
No comment provided by engineer.
-
-
+
+
Modifier le code d'accès
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff
index 74495a0d22..1665c5220e 100644
--- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff
+++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff
@@ -569,8 +569,8 @@ Available in v5.1
No comment provided by engineer.
-
-
+
+
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
index 0433429359..7b3534d026 100644
--- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
+++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
@@ -730,8 +730,8 @@ Disponibile nella v5.1
Cambia
No comment provided by engineer.
-
-
+
+
Cambia codice di accesso
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
index 11c0de1dc7..6b1f2f2e21 100644
--- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
+++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
@@ -730,8 +730,8 @@ Beschikbaar in v5.1
Veranderen
No comment provided by engineer.
-
-
+
+
Toegangscode wijzigen
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
index f711d0c08a..dce3f7b509 100644
--- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
+++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
@@ -730,8 +730,8 @@ Dostępny w v5.1
Zmień
No comment provided by engineer.
-
-
+
+
Zmień kod dostępu
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff
index e5a11e3a13..d016a4649e 100644
--- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff
+++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff
@@ -569,8 +569,8 @@ Available in v5.1
No comment provided by engineer.
-
-
+
+
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
index 576f6a2378..74995c5233 100644
--- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
+++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
@@ -730,8 +730,8 @@ Available in v5.1
Поменять
No comment provided by engineer.
-
-
+
+
Изменить код доступа
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
index af58442ef9..5d8dbf1776 100644
--- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
+++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
@@ -730,8 +730,8 @@ Available in v5.1
更改
No comment provided by engineer.
-
-
+
+
更改密码
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff
index 36cbc3076c..d25b9a4c06 100644
--- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff
+++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff
@@ -4207,8 +4207,8 @@ SimpleX servers cannot see your profile.
已取消認證
PIN entry
-
-
+
+
修改密碼
No comment provided by engineer.
diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift
index 53ae7e8849..561bbc48d0 100644
--- a/apps/ios/SimpleXChat/APITypes.swift
+++ b/apps/ios/SimpleXChat/APITypes.swift
@@ -14,7 +14,7 @@ let jsonEncoder = getJSONEncoder()
public enum ChatCommand {
case showActiveUser
- case createActiveUser(profile: Profile)
+ case createActiveUser(profile: Profile?)
case listUsers
case apiSetActiveUser(userId: Int64, viewPwd: String?)
case apiHideUser(userId: Int64, viewPwd: String)
@@ -110,7 +110,11 @@ public enum ChatCommand {
get {
switch self {
case .showActiveUser: return "/u"
- case let .createActiveUser(profile): return "/create user \(profile.displayName) \(profile.fullName)"
+ case let .createActiveUser(profile):
+ if let profile = profile {
+ return "/create user \(profile.displayName) \(profile.fullName)"
+ }
+ return "/create user"
case .listUsers: return "/users"
case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))"
case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))"
diff --git a/apps/ios/SimpleXChat/KeyChain.swift b/apps/ios/SimpleXChat/KeyChain.swift
index 91d94146a0..50a5946462 100644
--- a/apps/ios/SimpleXChat/KeyChain.swift
+++ b/apps/ios/SimpleXChat/KeyChain.swift
@@ -13,11 +13,14 @@ private let ACCESS_POLICY: CFString = kSecAttrAccessibleAfterFirstUnlockThisDevi
private let ACCESS_GROUP: String = "5NN7GUYB6T.chat.simplex.app"
private let DATABASE_PASSWORD_ITEM: String = "databasePassword"
private let APP_PASSWORD_ITEM: String = "appPassword"
+private let SELF_DESTRUCT_PASSWORD_ITEM: String = "selfDestructPassword"
public let kcDatabasePassword = KeyChainItem(forKey: DATABASE_PASSWORD_ITEM)
public let kcAppPassword = KeyChainItem(forKey: APP_PASSWORD_ITEM)
+public let kcSelfDestructPassword = KeyChainItem(forKey: SELF_DESTRUCT_PASSWORD_ITEM)
+
public struct KeyChainItem {
var forKey: String
diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings
index 0674ca7f1b..5721b2bd6d 100644
--- a/apps/ios/de.lproj/Localizable.strings
+++ b/apps/ios/de.lproj/Localizable.strings
@@ -477,7 +477,7 @@
"Change passcode" = "Passwort ändern";
/* No comment provided by engineer. */
-"Change Passcode" = "Passwort ändern";
+"Change passcode" = "Passwort ändern";
/* No comment provided by engineer. */
"Change receiving address" = "Wechseln der Empfängeradresse";
diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings
index ebad345675..520da70c7b 100644
--- a/apps/ios/es.lproj/Localizable.strings
+++ b/apps/ios/es.lproj/Localizable.strings
@@ -477,7 +477,7 @@
"Change passcode" = "Cambiar el código de acceso";
/* No comment provided by engineer. */
-"Change Passcode" = "Cambiar el código de acceso";
+"Change passcode" = "Cambiar el código de acceso";
/* No comment provided by engineer. */
"Change receiving address" = "Cambiar servidor de recepción";
diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings
index a4e25219b9..d5c0ade54e 100644
--- a/apps/ios/fr.lproj/Localizable.strings
+++ b/apps/ios/fr.lproj/Localizable.strings
@@ -477,7 +477,7 @@
"Change passcode" = "Modifier le code d'accès";
/* No comment provided by engineer. */
-"Change Passcode" = "Modifier le code d'accès";
+"Change passcode" = "Modifier le code d'accès";
/* No comment provided by engineer. */
"Change receiving address" = "Changer d'adresse de réception";
diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings
index 26e61d858a..0ccf4153c8 100644
--- a/apps/ios/it.lproj/Localizable.strings
+++ b/apps/ios/it.lproj/Localizable.strings
@@ -477,7 +477,7 @@
"Change passcode" = "Cambia codice di accesso";
/* No comment provided by engineer. */
-"Change Passcode" = "Cambia codice di accesso";
+"Change passcode" = "Cambia codice di accesso";
/* No comment provided by engineer. */
"Change receiving address" = "Cambia indirizzo di ricezione";
diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings
index b3a809f714..c6232ffb12 100644
--- a/apps/ios/nl.lproj/Localizable.strings
+++ b/apps/ios/nl.lproj/Localizable.strings
@@ -477,7 +477,7 @@
"Change passcode" = "Toegangscode wijzigen";
/* No comment provided by engineer. */
-"Change Passcode" = "Toegangscode wijzigen";
+"Change passcode" = "Toegangscode wijzigen";
/* No comment provided by engineer. */
"Change receiving address" = "Ontvangst adres wijzigen";
diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings
index 15233d206b..8fec32c91e 100644
--- a/apps/ios/pl.lproj/Localizable.strings
+++ b/apps/ios/pl.lproj/Localizable.strings
@@ -477,7 +477,7 @@
"Change passcode" = "Zmień pin";
/* No comment provided by engineer. */
-"Change Passcode" = "Zmień kod dostępu";
+"Change passcode" = "Zmień kod dostępu";
/* No comment provided by engineer. */
"Change receiving address" = "Zmień adres odbioru";
diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings
index 9e19a5bd04..fd8b15e672 100644
--- a/apps/ios/ru.lproj/Localizable.strings
+++ b/apps/ios/ru.lproj/Localizable.strings
@@ -477,7 +477,7 @@
"Change passcode" = "Изменить код доступа";
/* No comment provided by engineer. */
-"Change Passcode" = "Изменить код доступа";
+"Change passcode" = "Изменить код доступа";
/* No comment provided by engineer. */
"Change receiving address" = "Поменять адрес получения";
diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings
index 2499415b9b..25a5d98b85 100644
--- a/apps/ios/zh-Hans.lproj/Localizable.strings
+++ b/apps/ios/zh-Hans.lproj/Localizable.strings
@@ -477,7 +477,7 @@
"Change passcode" = "更改密码";
/* No comment provided by engineer. */
-"Change Passcode" = "更改密码";
+"Change passcode" = "更改密码";
/* No comment provided by engineer. */
"Change receiving address" = "更改接收地址";
diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs
index 497be8f6cb..e049efcc1c 100644
--- a/src/Simplex/Chat.hs
+++ b/src/Simplex/Chat.hs
@@ -83,6 +83,7 @@ import Simplex.Messaging.Util
import System.Exit (exitFailure, exitSuccess)
import System.FilePath (combine, splitExtensions, takeFileName, (>))
import System.IO (Handle, IOMode (..), SeekMode (..), hFlush, openFile, stdout)
+import System.Random (randomRIO)
import Text.Read (readMaybe)
import UnliftIO.Async
import UnliftIO.Concurrent (forkFinally, forkIO, mkWeakThreadId, threadDelay)
@@ -318,7 +319,8 @@ toView event = do
processChatCommand :: forall m. ChatMonad m => ChatCommand -> m ChatResponse
processChatCommand = \case
ShowActiveUser -> withUser' $ pure . CRActiveUser
- CreateActiveUser p@Profile {displayName} sameServers -> do
+ CreateActiveUser NewUser {profile, sameServers, pastTimestamp} -> do
+ p@Profile {displayName} <- liftIO $ maybe generateRandomProfile pure profile
u <- asks currentUser
(smp, smpServers) <- chooseServers SPSMP
(xftp, xftpServers) <- chooseServers SPXFTP
@@ -329,7 +331,8 @@ processChatCommand = \case
when (any (\User {localDisplayName = n} -> n == displayName) users) $
throwChatError $ CEUserExists displayName
withAgent (\a -> createUser a smp xftp)
- user <- withStore $ \db -> createUserRecord db (AgentUserId auId) p True
+ ts <- liftIO $ getCurrentTime >>= if pastTimestamp then coupleDaysAgo else pure
+ user <- withStore $ \db -> createUserRecordAt db (AgentUserId auId) p True ts
storeServers user smpServers
storeServers user xftpServers
setActive ActiveNone
@@ -351,6 +354,8 @@ processChatCommand = \case
storeServers user servers =
unless (null servers) $
withStore $ \db -> overwriteProtocolServers db user servers
+ coupleDaysAgo t = (`addUTCTime` t) . fromInteger . (+ (2 * day)) <$> randomRIO (0, day)
+ day = 86400
ListUsers -> CRUsersList <$> withStore' getUsersInfo
APISetActiveUser userId' viewPwd_ -> withUser $ \user -> do
user' <- privateGetUser userId'
@@ -4584,12 +4589,8 @@ chatCommandP =
choice
[ "/mute " *> ((`ShowMessages` False) <$> chatNameP),
"/unmute " *> ((`ShowMessages` True) <$> chatNameP),
- "/create user"
- *> ( do
- sameSmp <- (A.space *> "same_smp=" *> onOffP) <|> pure False
- uProfile <- A.space *> userProfile
- pure $ CreateActiveUser uProfile sameSmp
- ),
+ "/_create user " *> (CreateActiveUser <$> jsonP),
+ "/create user " *> (CreateActiveUser <$> newUserP),
"/users" $> ListUsers,
"/_user " *> (APISetActiveUser <$> A.decimal <*> optional (A.space *> jsonP)),
("/user " <|> "/u ") *> (SetActiveUser <$> displayName <*> optional (A.space *> pwdP)),
@@ -4784,7 +4785,7 @@ chatCommandP =
("/welcome" <|> "/w") $> Welcome,
"/profile_image " *> (UpdateProfileImage . Just . ImageData <$> imageP),
"/profile_image" $> UpdateProfileImage Nothing,
- ("/profile " <|> "/p ") *> (uncurry UpdateProfile <$> userNames),
+ ("/profile " <|> "/p ") *> (uncurry UpdateProfile <$> profileNames),
("/profile" <|> "/p") $> ShowProfile,
"/set voice #" *> (SetGroupFeature (AGF SGFVoice) <$> displayName <*> (A.space *> strP)),
"/set voice @" *> (SetContactFeature (ACF SCFVoice) <$> displayName <*> optional (A.space *> strP)),
@@ -4823,23 +4824,19 @@ chatCommandP =
refChar c = c > ' ' && c /= '#' && c /= '@'
liveMessageP = " live=" *> onOffP <|> pure False
onOffP = ("on" $> True) <|> ("off" $> False)
- userNames = do
- cName <- displayName
- fullName <- fullNameP cName
- pure (cName, fullName)
- userProfile = do
- (cName, fullName) <- userNames
- pure Profile {displayName = cName, fullName, image = Nothing, contactLink = Nothing, preferences = Nothing}
+ profileNames = (,) <$> displayName <*> fullNameP
+ newUserP = do
+ sameServers <- "same_smp=" *> onOffP <* A.space <|> pure False
+ (cName, fullName) <- profileNames
+ let profile = Just Profile {displayName = cName, fullName, image = Nothing, contactLink = Nothing, preferences = Nothing}
+ pure NewUser {profile, sameServers, pastTimestamp = False}
jsonP :: J.FromJSON a => Parser a
jsonP = J.eitherDecodeStrict' <$?> A.takeByteString
groupProfile = do
- gName <- displayName
- fullName <- fullNameP gName
+ (gName, fullName) <- profileNames
let groupPreferences = Just (emptyGroupPrefs :: GroupPreferences) {directMessages = Just DirectMessagesGroupPreference {enable = FEOn}}
pure GroupProfile {displayName = gName, fullName, description = Nothing, image = Nothing, groupPreferences}
- fullNameP name = do
- n <- (A.space *> A.takeByteString) <|> pure ""
- pure $ if B.null n then name else safeDecodeUtf8 n
+ fullNameP = A.space *> textP <|> pure ""
textP = safeDecodeUtf8 <$> A.takeByteString
pwdP = jsonP <|> (UserPwd . safeDecodeUtf8 <$> A.takeTill (== ' '))
msgTextP = jsonP <|> textP
diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs
index 5537524945..88f62b6791 100644
--- a/src/Simplex/Chat/Controller.hs
+++ b/src/Simplex/Chat/Controller.hs
@@ -180,7 +180,7 @@ instance ToJSON HelpSection where
data ChatCommand
= ShowActiveUser
- | CreateActiveUser Profile Bool
+ | CreateActiveUser NewUser
| ListUsers
| APISetActiveUser UserId (Maybe UserPwd)
| SetActiveUser UserName (Maybe UserPwd)
diff --git a/src/Simplex/Chat/Store.hs b/src/Simplex/Chat/Store.hs
index e2aa28ad2c..d670c52dc2 100644
--- a/src/Simplex/Chat/Store.hs
+++ b/src/Simplex/Chat/Store.hs
@@ -27,6 +27,7 @@ module Simplex.Chat.Store
chatStoreFile,
agentStoreFile,
createUserRecord,
+ createUserRecordAt,
getUsersInfo,
getUsers,
setActiveUser,
@@ -490,9 +491,11 @@ insertedRowId :: DB.Connection -> IO Int64
insertedRowId db = fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()"
createUserRecord :: DB.Connection -> AgentUserId -> Profile -> Bool -> ExceptT StoreError IO User
-createUserRecord db (AgentUserId auId) Profile {displayName, fullName, image, preferences = userPreferences} activeUser =
+createUserRecord db auId p activeUser = createUserRecordAt db auId p activeUser =<< liftIO getCurrentTime
+
+createUserRecordAt :: DB.Connection -> AgentUserId -> Profile -> Bool -> UTCTime -> ExceptT StoreError IO User
+createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, preferences = userPreferences} activeUser currentTs =
checkConstraint SEDuplicateName . liftIO $ do
- currentTs <- getCurrentTime
when activeUser $ DB.execute_ db "UPDATE users SET active_user = 0"
DB.execute
db
diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs
index de89599f2d..0999998861 100644
--- a/src/Simplex/Chat/Types.hs
+++ b/src/Simplex/Chat/Types.hs
@@ -120,6 +120,13 @@ instance ToJSON User where
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
+data NewUser = NewUser
+ { profile :: Maybe Profile,
+ sameServers :: Bool,
+ pastTimestamp :: Bool
+ }
+ deriving (Show, Generic, FromJSON)
+
newtype B64UrlByteString = B64UrlByteString ByteString
deriving (Eq, Show)
diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs
index 097f6aed80..fc0fe8fad3 100644
--- a/tests/ChatClient.hs
+++ b/tests/ChatClient.hs
@@ -239,8 +239,8 @@ getTermLine cc =
5000000 `timeout` atomically (readTQueue $ termQ cc) >>= \case
Just s -> do
-- uncomment 2 lines below to echo virtual terminal
- -- name <- userName cc
- -- putStrLn $ name <> ": " <> s
+ name <- userName cc
+ putStrLn $ name <> ": " <> s
pure s
_ -> error "no output for 5 seconds"
diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs
index 32e4ff05bd..79880a67e8 100644
--- a/tests/ChatTests/Groups.hs
+++ b/tests/ChatTests/Groups.hs
@@ -457,7 +457,7 @@ testGroupSameName =
alice <## "group #team is created"
alice <## "to add members use /a team or /create link #team"
alice ##> "/g team"
- alice <## "group #team_1 (team) is created"
+ alice <## "group #team_1 is created"
alice <## "to add members use /a team_1 or /create link #team_1"
testGroupDeleteWhenInvited :: HasCallStack => FilePath -> IO ()
@@ -518,7 +518,7 @@ testGroupReAddInvited =
concurrentlyN_
[ alice <## "invitation to join the group #team sent to bob",
do
- bob <## "#team_1 (team): alice invites you to join the group as admin"
+ bob <## "#team_1: alice invites you to join the group as admin"
bob <## "use /j team_1 to accept"
]
@@ -678,7 +678,7 @@ testGroupRemoveAdd =
]
alice ##> "/a team bob"
alice <## "invitation to join the group #team sent to bob"
- bob <## "#team_1 (team): alice invites you to join the group as admin"
+ bob <## "#team_1: alice invites you to join the group as admin"
bob <## "use /j team_1 to accept"
bob ##> "/j team_1"
concurrentlyN_
diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs
index a5dc083f71..d48a5e93a3 100644
--- a/tests/ChatTests/Profiles.hs
+++ b/tests/ChatTests/Profiles.hs
@@ -1147,7 +1147,7 @@ testUpdateGroupPrefs =
alice #$> ("/_get chat #1 count=100", chat, [(0, "connected")])
threadDelay 500000
bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected")])
- alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"team\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"on\"}, \"directMessages\": {\"enable\": \"on\"}}}"
+ alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"on\"}, \"directMessages\": {\"enable\": \"on\"}}}"
alice <## "updated group preferences:"
alice <## "Full deletion: on"
alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on")])
@@ -1343,7 +1343,7 @@ testEnableTimedMessagesGroup =
\alice bob -> do
createGroup2 "team" alice bob
threadDelay 1000000
- alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"team\", \"groupPreferences\": {\"timedMessages\": {\"enable\": \"on\", \"ttl\": 1}, \"directMessages\": {\"enable\": \"on\"}}}"
+ alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"\", \"groupPreferences\": {\"timedMessages\": {\"enable\": \"on\", \"ttl\": 1}, \"directMessages\": {\"enable\": \"on\"}}}"
alice <## "updated group preferences:"
alice <## "Disappearing messages: on (1 sec)"
bob <## "alice updated group #team:"
From da70eb7b681a3dd1bd4b308e9fca626cad438a2c Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Mon, 8 May 2023 23:35:29 +0100
Subject: [PATCH 2/4] disable test logging
---
tests/ChatClient.hs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs
index fc0fe8fad3..097f6aed80 100644
--- a/tests/ChatClient.hs
+++ b/tests/ChatClient.hs
@@ -239,8 +239,8 @@ getTermLine cc =
5000000 `timeout` atomically (readTQueue $ termQ cc) >>= \case
Just s -> do
-- uncomment 2 lines below to echo virtual terminal
- name <- userName cc
- putStrLn $ name <> ": " <> s
+ -- name <- userName cc
+ -- putStrLn $ name <> ": " <> s
pure s
_ -> error "no output for 5 seconds"
From b1c567a2a601a4493773faec641dd309805f5357 Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Tue, 9 May 2023 08:47:04 +0100
Subject: [PATCH 3/4] core: fix tests, iOS: remove notifications on removal
---
apps/ios/Shared/Model/NtfManager.swift | 16 ++++++++++------
.../Shared/Views/LocalAuth/LocalAuthView.swift | 1 +
.../Shared/Views/Onboarding/CreateProfile.swift | 2 ++
tests/ChatTests/Groups.hs | 14 +++++++-------
tests/ChatTests/Profiles.hs | 6 +++---
5 files changed, 23 insertions(+), 16 deletions(-)
diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift
index 4a4511eae6..ad41703c91 100644
--- a/apps/ios/Shared/Model/NtfManager.swift
+++ b/apps/ios/Shared/Model/NtfManager.swift
@@ -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 {
@@ -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]
@@ -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()
+ }
}
}
diff --git a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
index 4d8d758377..8200a2c0f5 100644
--- a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
+++ b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
@@ -41,6 +41,7 @@ struct LocalAuthView: View {
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)
diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
index 316eb2b78b..0066022098 100644
--- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
+++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
@@ -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()
diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs
index 79880a67e8..e2cf4e2e61 100644
--- a/tests/ChatTests/Groups.hs
+++ b/tests/ChatTests/Groups.hs
@@ -1058,13 +1058,13 @@ testGroupLiveMessage =
alice <## "message history:"
alice .<## ": hello 2"
alice .<## ":"
- bobItemId <- lastItemId bob
- bob ##> ("/_get item info " <> bobItemId)
- bob <##. "sent at: "
- bob <##. "received at: "
- bob <## "message history:"
- bob .<## ": hello 2"
- bob .<## ":"
+ -- bobItemId <- lastItemId bob
+ -- bob ##> ("/_get item info " <> bobItemId)
+ -- bob <##. "sent at: "
+ -- bob <##. "received at: "
+ -- bob <## "message history:"
+ -- bob .<## ": hello 2"
+ -- bob .<## ":"
testUpdateGroupProfile :: HasCallStack => FilePath -> IO ()
testUpdateGroupProfile =
diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs
index d48a5e93a3..6bbd4b1447 100644
--- a/tests/ChatTests/Profiles.hs
+++ b/tests/ChatTests/Profiles.hs
@@ -1156,7 +1156,7 @@ testUpdateGroupPrefs =
bob <## "Full deletion: on"
threadDelay 500000
bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Full deletion: on")])
- alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"team\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"off\"}, \"directMessages\": {\"enable\": \"on\"}}}"
+ alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"off\"}, \"directMessages\": {\"enable\": \"on\"}}}"
alice <## "updated group preferences:"
alice <## "Full deletion: off"
alice <## "Voice messages: off"
@@ -1167,7 +1167,7 @@ testUpdateGroupPrefs =
bob <## "Voice messages: off"
threadDelay 500000
bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Full deletion: on"), (0, "Full deletion: off"), (0, "Voice messages: off")])
- -- alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"team\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"on\"}}}"
+ -- alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"on\"}}}"
alice ##> "/set voice #team on"
alice <## "updated group preferences:"
alice <## "Voice messages: on"
@@ -1178,7 +1178,7 @@ testUpdateGroupPrefs =
threadDelay 500000
bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Full deletion: on"), (0, "Full deletion: off"), (0, "Voice messages: off"), (0, "Voice messages: on")])
threadDelay 500000
- alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"team\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"on\"}, \"directMessages\": {\"enable\": \"on\"}}}"
+ alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"on\"}, \"directMessages\": {\"enable\": \"on\"}}}"
-- no update
threadDelay 500000
alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")])
From 33edad0276b54c676a7ffb9c10848afcf6e649f3 Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Tue, 9 May 2023 08:59:02 +0100
Subject: [PATCH 4/4] change alerts
---
.../Views/UserSettings/PrivacySettings.swift | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
index fbf5364ee3..a6f12cb275 100644
--- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
@@ -120,6 +120,8 @@ struct SimplexLockView: View {
case laUnavailableTurningOffAlert
case laPasscodeSetAlert
case laPasscodeChangedAlert
+ case laSeldDestructPasscodeSetAlert
+ case laSeldDestructPasscodeChangedAlert
case laPasscodeNotChangedAlert
var id: Self { self }
@@ -236,6 +238,8 @@ struct SimplexLockView: View {
case .laUnavailableTurningOffAlert: return laUnavailableTurningOffAlert()
case .laPasscodeSetAlert: return passcodeAlert("Passcode set!")
case .laPasscodeChangedAlert: return passcodeAlert("Passcode changed!")
+ case .laSeldDestructPasscodeSetAlert: return selfDestructPasscodeAlert("Self-destruct passcode enabled!")
+ case .laSeldDestructPasscodeChangedAlert: return selfDestructPasscodeAlert("Self-destruct passcode changed!")
case .laPasscodeNotChangedAlert: return mkAlert(title: "Passcode not changed!")
}
}
@@ -268,15 +272,15 @@ struct SimplexLockView: View {
case .enableSelfDestruct:
SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, title: "Set passcode", reason: NSLocalizedString("Enable self-destruct passcode", comment: "set passcode view")) {
updateSelfDestruct()
- showLAAlert(.laPasscodeSetAlert) // change
+ showLAAlert(.laSeldDestructPasscodeSetAlert)
} cancel: {
revertSelfDestruct()
}
case .changeSelfDestructPasscode:
SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, reason: NSLocalizedString("Change self-destruct passcode", comment: "set passcode view")) {
- showLAAlert(.laPasscodeChangedAlert) // change
+ showLAAlert(.laSeldDestructPasscodeChangedAlert)
} cancel: {
- showLAAlert(.laPasscodeNotChangedAlert) // change
+ showLAAlert(.laPasscodeNotChangedAlert)
}
case .selfDestructInfo:
selfDestructInfoView()
@@ -470,6 +474,10 @@ struct SimplexLockView: View {
private func passcodeAlert(_ title: LocalizedStringKey) -> Alert {
mkAlert(title: title, message: "Please remember or store it securely - there is no way to recover a lost passcode!")
}
+
+ private func selfDestructPasscodeAlert(_ title: LocalizedStringKey) -> Alert {
+ mkAlert(title: title, message: "If you enter this passcode when opening the app, all app data will be irreversibly removed!")
+ }
}
struct PrivacySettings_Previews: PreviewProvider {