From 84ad0e03d08cfe463bb3490c6de453e59d9c627c Mon Sep 17 00:00:00 2001 From: Andrii Sievrikov Date: Sat, 4 Feb 2023 23:11:36 -0500 Subject: [PATCH] Add local authentication when accessing private key --- damus.xcodeproj/project.pbxproj | 2 ++ damus/Views/ConfigView.swift | 51 +++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 2706ccf5..a0037202 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -1494,6 +1494,7 @@ INFOPLIST_FILE = damus/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Damus; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key"; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -1535,6 +1536,7 @@ INFOPLIST_FILE = damus/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Damus; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key"; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift index 8b23c478..7c6d352b 100644 --- a/damus/Views/ConfigView.swift +++ b/damus/Views/ConfigView.swift @@ -7,6 +7,7 @@ import AVFoundation import Kingfisher import SwiftUI +import LocalAuthentication enum RemoteImagePolicy: String, CaseIterable { case everyone @@ -34,6 +35,7 @@ struct ConfigView: View { @State var confirm_logout: Bool = false @State var confirm_delete_account: Bool = false @State var show_privkey: Bool = false + @State var has_authenticated_locally: Bool = false @State var show_libretranslate_api_key: Bool = false @State var privkey: String @State var privkey_copied: Bool = false @@ -50,13 +52,45 @@ struct ConfigView: View { _settings = ObservedObject(initialValue: state.settings) } + func authenticateLocally(completion: @escaping (Bool) -> Void) { + // Need to authenticate only once while ConfigView is presented + guard !has_authenticated_locally else { + completion(true) + return + } + let context = LAContext() + if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) { + context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: NSLocalizedString("Local authentication to access private key", comment: "Face ID usage description shown when trying to access private key")) { success, error in + DispatchQueue.main.async { + has_authenticated_locally = success + completion(success) + } + } + } else { + // If there's no authentication set up on the device, let the user copy the key without it + has_authenticated_locally = true + completion(true) + } + } + // TODO: (jb55) could be more general but not gonna worry about it atm func CopyButton(is_pk: Bool) -> some View { return Button(action: { - UIPasteboard.general.string = is_pk ? self.state.keypair.pubkey_bech32 : self.privkey - self.privkey_copied = !is_pk - self.pubkey_copied = is_pk - generator.impactOccurred() + let copyKey = { + UIPasteboard.general.string = is_pk ? self.state.keypair.pubkey_bech32 : self.privkey + self.privkey_copied = !is_pk + self.pubkey_copied = is_pk + generator.impactOccurred() + } + if has_authenticated_locally { + copyKey() + } else { + authenticateLocally { success in + if success { + copyKey() + } + } + } }) { let copied = is_pk ? self.pubkey_copied : self.privkey_copied Image(systemName: copied ? "checkmark.circle" : "doc.on.doc") @@ -78,7 +112,7 @@ struct ConfigView: View { if let sec = state.keypair.privkey_bech32 { Section(NSLocalizedString("Secret Account Login Key", comment: "Section title for user's secret account login key.")) { HStack { - if show_privkey == false { + if show_privkey == false || !has_authenticated_locally { SecureField(NSLocalizedString("Private Key", comment: "Title of the secure field that holds the user's private key."), text: $privkey) .disabled(true) } else { @@ -90,6 +124,13 @@ struct ConfigView: View { } Toggle(NSLocalizedString("Show", comment: "Toggle to show or hide user's secret account login key."), isOn: $show_privkey) + .onChange(of: show_privkey) { newValue in + if newValue { + authenticateLocally { success in + show_privkey = success + } + } + } } }