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:
Evgeny Poberezkin
2022-09-08 17:36:16 +01:00
committed by GitHub
parent 9eb244f9c1
commit 06835ee3fc
6 changed files with 98 additions and 27 deletions

View File

@@ -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))
}
}

View File

@@ -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))")
}
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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),

View File

@@ -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"