mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-03-30 16:25:57 +00:00
ios: additional db encryption UX (#1031)
* ios: additional db encryption UX * typo Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> * fixes Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
9eb244f9c1
commit
06835ee3fc
@@ -35,9 +35,9 @@ enum DatabaseEncryptionAlert: Identifiable {
|
||||
|
||||
struct DatabaseEncryptionView: View {
|
||||
@EnvironmentObject private var m: ChatModel
|
||||
@Binding var useKeychain: Bool
|
||||
@State private var alert: DatabaseEncryptionAlert? = nil
|
||||
@State private var progressIndicator = false
|
||||
@State private var useKeychain = storeDBPassphraseGroupDefault.get()
|
||||
@State private var useKeychainToggle = storeDBPassphraseGroupDefault.get()
|
||||
@State private var initialRandomDBPassphrase = initialRandomDBPassphraseGroupDefault.get()
|
||||
@State private var storedKey = getDatabaseKey() != nil
|
||||
@@ -58,7 +58,7 @@ struct DatabaseEncryptionView: View {
|
||||
private func databaseEncryptionView() -> some View {
|
||||
List {
|
||||
Section {
|
||||
settingsRow("key") {
|
||||
settingsRow(storedKey ? "key.fill" : "key", color: storedKey ? .green : .secondary) {
|
||||
Toggle("Save passphrase in Keychain", isOn: $useKeychainToggle)
|
||||
.onChange(of: useKeychainToggle) { _ in
|
||||
if useKeychainToggle {
|
||||
@@ -224,7 +224,7 @@ struct DatabaseEncryptionView: View {
|
||||
case .currentPassphraseError:
|
||||
return Alert(
|
||||
title: Text("Wrong passsphrase!"),
|
||||
message: Text("Please enter correct current passphrase")
|
||||
message: Text("Please enter correct current passphrase.")
|
||||
)
|
||||
case let .error(title, error):
|
||||
return Alert(title: Text(title), message: Text("\(error)"))
|
||||
@@ -254,6 +254,7 @@ struct DatabaseKeyField: View {
|
||||
var placeholder: LocalizedStringKey
|
||||
var valid: Bool
|
||||
var showStrength = false
|
||||
var onSubmit: () -> Void = {}
|
||||
@State private var showKey = false
|
||||
|
||||
var body: some View {
|
||||
@@ -272,6 +273,7 @@ struct DatabaseKeyField: View {
|
||||
.autocapitalization(.none)
|
||||
.submitLabel(.done)
|
||||
.padding(.leading, 36)
|
||||
.onSubmit(onSubmit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,6 +340,6 @@ func validKey(_ s: String) -> Bool {
|
||||
|
||||
struct DatabaseEncryptionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DatabaseEncryptionView()
|
||||
DatabaseEncryptionView(useKeychain: Binding.constant(true))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import SimpleXChat
|
||||
|
||||
struct DatabaseErrorView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var status: DBMigrationResult
|
||||
@State var status: DBMigrationResult
|
||||
@State private var dbKey = ""
|
||||
@State private var storedDBKey = getDatabaseKey()
|
||||
@State private var useKeychain = storeDBPassphraseGroupDefault.get()
|
||||
@@ -23,17 +23,18 @@ struct DatabaseErrorView: View {
|
||||
if useKeychain && storedDBKey != nil && storedDBKey != "" {
|
||||
Text("Wrong database passphrase").font(.title)
|
||||
Text("Database passphrase is different from saved in the keychain.")
|
||||
DatabaseKeyField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey))
|
||||
databaseKeyField(onSubmit: saveAndRunChat)
|
||||
saveAndOpenButton()
|
||||
Spacer()
|
||||
Text("File: \(dbFile)")
|
||||
} else {
|
||||
Text("Encrypted database").font(.title)
|
||||
Text("Database passphrase is required to open chat.")
|
||||
DatabaseKeyField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey))
|
||||
if useKeychain {
|
||||
databaseKeyField(onSubmit: saveAndRunChat)
|
||||
saveAndOpenButton()
|
||||
} else {
|
||||
databaseKeyField(onSubmit: runChat)
|
||||
openChatButton()
|
||||
}
|
||||
Spacer()
|
||||
@@ -59,29 +60,62 @@ struct DatabaseErrorView: View {
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.frame(maxHeight: .infinity) }
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
|
||||
private func databaseKeyField(onSubmit: @escaping () -> Void) -> some View {
|
||||
DatabaseKeyField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey), onSubmit: onSubmit)
|
||||
}
|
||||
|
||||
private func saveAndOpenButton() -> some View {
|
||||
Button("Save passphrase and open chat") {
|
||||
if setDatabaseKey(dbKey) {
|
||||
storeDBPassphraseGroupDefault.set(true)
|
||||
initialRandomDBPassphraseGroupDefault.set(false)
|
||||
}
|
||||
do {
|
||||
try initializeChat(start: m.v3DBMigration.startChat, dbKey: dbKey)
|
||||
} catch let error {
|
||||
logger.error("initializeChat \(responseError(error))")
|
||||
}
|
||||
saveAndRunChat()
|
||||
}
|
||||
}
|
||||
|
||||
private func openChatButton() -> some View {
|
||||
Button("Open chat") {
|
||||
do {
|
||||
try initializeChat(start: m.v3DBMigration.startChat, dbKey: dbKey)
|
||||
} catch let error {
|
||||
logger.error("initializeChat \(responseError(error))")
|
||||
runChat()
|
||||
}
|
||||
}
|
||||
|
||||
private func saveAndRunChat() {
|
||||
if setDatabaseKey(dbKey) {
|
||||
storeDBPassphraseGroupDefault.set(true)
|
||||
initialRandomDBPassphraseGroupDefault.set(false)
|
||||
}
|
||||
runChat()
|
||||
}
|
||||
|
||||
private func runChat() {
|
||||
do {
|
||||
try initializeChat(start: m.v3DBMigration.startChat, dbKey: dbKey)
|
||||
if let s = m.chatDbStatus {
|
||||
status = s
|
||||
let am = AlertManager.shared
|
||||
switch s {
|
||||
case .errorNotADatabase:
|
||||
am.showAlertMsg(
|
||||
title: "Wrong passphrase!",
|
||||
message: "Enter correct passphrase."
|
||||
)
|
||||
case .errorKeychain:
|
||||
am.showAlertMsg(title: "Keychain error")
|
||||
case let .error(_, error):
|
||||
am.showAlert(Alert(
|
||||
title: Text("Database error"),
|
||||
message: Text(error)
|
||||
))
|
||||
case let .unknown(error):
|
||||
am.showAlert(Alert(
|
||||
title: Text("Unknown error"),
|
||||
message: Text(error)
|
||||
))
|
||||
case .ok: ()
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("initializeChat \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ struct DatabaseView: View {
|
||||
@AppStorage(DEFAULT_CHAT_ARCHIVE_TIME) private var chatArchiveTime: Double = 0
|
||||
@State private var dbContainer = dbContainerGroupDefault.get()
|
||||
@State private var legacyDatabase = hasLegacyDatabase()
|
||||
@State private var useKeychain = storeDBPassphraseGroupDefault.get()
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -86,9 +87,9 @@ struct DatabaseView: View {
|
||||
Section {
|
||||
let unencrypted = m.chatDbEncrypted == false
|
||||
let color: Color = unencrypted ? .orange : .secondary
|
||||
settingsRow(unencrypted ? "lock.open" : "lock", color: color) {
|
||||
settingsRow(unencrypted ? "lock.open" : useKeychain ? "key" : "lock", color: color) {
|
||||
NavigationLink {
|
||||
DatabaseEncryptionView()
|
||||
DatabaseEncryptionView(useKeychain: $useKeychain)
|
||||
.navigationTitle("Database passphrase")
|
||||
} label: {
|
||||
Text("Database passphrase")
|
||||
@@ -168,7 +169,7 @@ struct DatabaseView: View {
|
||||
title: Text("Stop chat?"),
|
||||
message: Text("Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped."),
|
||||
primaryButton: .destructive(Text("Stop")) {
|
||||
stopChat()
|
||||
authStopChat()
|
||||
},
|
||||
secondaryButton: .cancel {
|
||||
withAnimation { runChat = true }
|
||||
@@ -226,6 +227,20 @@ struct DatabaseView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func authStopChat() {
|
||||
if UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) {
|
||||
authenticate(reason: NSLocalizedString("Stop SimpleX", comment: "authentication reason")) { laResult in
|
||||
switch laResult {
|
||||
case .success: stopChat()
|
||||
case .unavailable: stopChat()
|
||||
case .failed: withAnimation { runChat = true }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stopChat()
|
||||
}
|
||||
}
|
||||
|
||||
private func stopChat() {
|
||||
Task {
|
||||
do {
|
||||
|
||||
@@ -17,8 +17,28 @@ struct TerminalView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@State var composeState: ComposeState = ComposeState()
|
||||
@FocusState private var keyboardVisible: Bool
|
||||
@State var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
|
||||
|
||||
var body: some View {
|
||||
if authorized {
|
||||
terminalView()
|
||||
} else {
|
||||
Button(action: runAuth) { Label("Unlock", systemImage: "lock") }
|
||||
.onAppear(perform: runAuth)
|
||||
}
|
||||
}
|
||||
|
||||
private func runAuth() {
|
||||
authenticate(reason: NSLocalizedString("Open chat console", comment: "authentication reason")) { laResult in
|
||||
switch laResult {
|
||||
case .success: authorized = true
|
||||
case .unavailable: authorized = true
|
||||
case .failed: authorized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func terminalView() -> some View {
|
||||
VStack {
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView {
|
||||
|
||||
@@ -2546,7 +2546,7 @@ chatCommandP =
|
||||
"/_db delete" $> APIDeleteStorage,
|
||||
"/_db encryption " *> (APIStorageEncryption <$> jsonP),
|
||||
"/db encrypt " *> (APIStorageEncryption . DBEncryptionConfig "" <$> dbKeyP),
|
||||
"/db password " *> (APIStorageEncryption <$> (DBEncryptionConfig <$> dbKeyP <* A.space <*> dbKeyP)),
|
||||
"/db key " *> (APIStorageEncryption <$> (DBEncryptionConfig <$> dbKeyP <* A.space <*> dbKeyP)),
|
||||
"/db decrypt " *> (APIStorageEncryption . (`DBEncryptionConfig` "") <$> dbKeyP),
|
||||
"/_get chats" *> (APIGetChats <$> (" pcc=on" $> True <|> " pcc=off" $> False <|> pure False)),
|
||||
"/_get chat " *> (APIGetChat <$> chatRefP <* A.space <*> chatPaginationP <*> optional searchP),
|
||||
|
||||
@@ -2788,9 +2788,9 @@ testDatabaseEncryption = withTmpFiles $ do
|
||||
testChatWorking alice bob
|
||||
alice ##> "/_stop"
|
||||
alice <## "chat stopped"
|
||||
alice ##> "/db password wrongkey nextkey"
|
||||
alice ##> "/db key wrongkey nextkey"
|
||||
alice <## "error encrypting database: wrong passphrase or invalid database file"
|
||||
alice ##> "/db password mykey nextkey"
|
||||
alice ##> "/db key mykey nextkey"
|
||||
alice <## "ok"
|
||||
alice ##> "/_db encryption {\"currentKey\":\"nextkey\",\"newKey\":\"anotherkey\"}"
|
||||
alice <## "ok"
|
||||
|
||||
Reference in New Issue
Block a user