damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 03931ef70ef9318a347974a4ed6117e0fc83725b
parent 3284832eb05c163cb74072cae19087eff5e35e57
Author: Bryan Montz <bryanmontz@me.com>
Date:   Fri, 28 Apr 2023 14:24:34 -0500

Save keys when logging in and when creating new keypair

Changelog-Added: Save keys when logging in and when creating new keypair
Closes: #1042

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Adamus/Util/CredentialHandler.swift | 48++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/LoginView.swift | 154++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mdamus/Views/SaveKeysView.swift | 5+++++
4 files changed, 134 insertions(+), 77 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -259,6 +259,7 @@ 4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; }; 50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; }; 50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; }; + 50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; }; 5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; }; 5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; }; 5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; }; @@ -681,6 +682,7 @@ 4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; }; 50088DA029E8271A008A1FDF /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; }; 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; }; + 50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; }; 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; }; 5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; }; 5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; }; @@ -1059,6 +1061,7 @@ 4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */, 4CDA128B29EB19C40006FA5A /* LocalNotification.swift */, 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */, + 50B5685229F97CB400A23243 /* CredentialHandler.swift */, ); path = Util; sourceTree = "<group>"; @@ -1761,6 +1764,7 @@ 4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */, 4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */, 4C1A9A1A29DCA17E00516EAC /* ReplyCounter.swift in Sources */, + 50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */, 643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */, 4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */, 4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */, diff --git a/damus/Util/CredentialHandler.swift b/damus/Util/CredentialHandler.swift @@ -0,0 +1,48 @@ +// +// CredentialHandler.swift +// damus +// +// Created by Bryan Montz on 4/26/23. +// + +import Foundation +import AuthenticationServices + +final class CredentialHandler: NSObject, ASAuthorizationControllerDelegate { + + func check_credentials() { + let requests: [ASAuthorizationRequest] = [ASAuthorizationPasswordProvider().createRequest()] + let authorizationController = ASAuthorizationController(authorizationRequests: requests) + authorizationController.delegate = self + authorizationController.performRequests() + } + + func save_credential(pubkey: String, privkey: String) { + SecAddSharedWebCredential("damus.io" as CFString, pubkey as CFString, privkey as CFString, { error in + if let error { + print("⚠️ An error occurred while saving credentials: \(error)") + } + }) + } + + // MARK: - ASAuthorizationControllerDelegate + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + guard let cred = authorization.credential as? ASPasswordCredential, + let parsedKey = parse_key(cred.password) else { + return + } + + Task { + switch parsedKey { + case .pub, .priv: + try? await process_login(parsedKey, is_pubkey: false) + default: + break + } + } + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + print("⚠️ Warning: authentication failed with error: \(error)") + } +} diff --git a/damus/Views/LoginView.swift b/damus/Views/LoginView.swift @@ -36,6 +36,7 @@ struct LoginView: View { @State var key: String = "" @State var is_pubkey: Bool = false @State var error: String? = nil + @State private var credential_handler = CredentialHandler() func get_error(parsed_key: ParsedKey?) -> String? { if self.error != nil { @@ -43,85 +44,12 @@ struct LoginView: View { } if !key.isEmpty && parsed_key == nil { - return "Invalid key" + return LoginError.invalid_key.errorDescription } return nil } - - func process_login(_ key: ParsedKey, is_pubkey: Bool) -> Bool { - switch key { - case .priv(let priv): - do { - try save_privkey(privkey: priv) - } catch { - return false - } - - guard let pk = privkey_to_pubkey(privkey: priv) else { - return false - } - save_pubkey(pubkey: pk) - - case .pub(let pub): - do { - try clear_saved_privkey() - } catch { - return false - } - - save_pubkey(pubkey: pub) - - case .nip05(let id): - Task.init { - guard let nip05 = await get_nip05_pubkey(id: id) else { - self.error = "Could not fetch pubkey" - return - } - - // this is a weird way to login anyways - /* - var bootstrap_relays = load_bootstrap_relays(pubkey: nip05.pubkey) - for relay in nip05.relays { - if !(bootstrap_relays.contains { $0 == relay }) { - bootstrap_relays.append(relay) - } - } - */ - save_pubkey(pubkey: nip05.pubkey) - - notify(.login, ()) - } - - - case .hex(let hexstr): - if is_pubkey { - do { - try clear_saved_privkey() - } catch { - return false - } - - save_pubkey(pubkey: hexstr) - } else { - do { - try save_privkey(privkey: hexstr) - } catch { - return false - } - - guard let pk = privkey_to_pubkey(privkey: hexstr) else { - return false - } - save_pubkey(pubkey: pk) - } - } - - notify(.login, ()) - return true -} - - + var body: some View { ZStack(alignment: .top) { DamusGradient() @@ -163,14 +91,21 @@ struct LoginView: View { if let p = parsed { DamusWhiteButton(NSLocalizedString("Login", comment: "Button to log into account.")) { - if !process_login(p, is_pubkey: self.is_pubkey) { - self.error = NSLocalizedString("Invalid key", comment: "Error message indicating that an invalid account key was entered for login.") + Task { + do { + try await process_login(p, is_pubkey: is_pubkey) + } catch { + self.error = error.localizedDescription + } } } } } .padding() } + .onAppear { + credential_handler.check_credentials() + } .navigationBarBackButtonHidden(true) .navigationBarItems(leading: BackNav()) } @@ -214,6 +149,71 @@ func parse_key(_ thekey: String) -> ParsedKey? { return nil } +enum LoginError: LocalizedError { + case invalid_key + case nip05_failed + + var errorDescription: String? { + switch self { + case .invalid_key: + return NSLocalizedString("Invalid key", comment: "Error message indicating that an invalid account key was entered for login.") + case .nip05_failed: + return "Could not fetch pubkey" + } + } +} + +func process_login(_ key: ParsedKey, is_pubkey: Bool) async throws { + switch key { + case .priv(let priv): + try handle_privkey(priv) + case .pub(let pub): + try clear_saved_privkey() + save_pubkey(pubkey: pub) + + case .nip05(let id): + guard let nip05 = await get_nip05_pubkey(id: id) else { + throw LoginError.nip05_failed + } + + // this is a weird way to login anyways + /* + var bootstrap_relays = load_bootstrap_relays(pubkey: nip05.pubkey) + for relay in nip05.relays { + if !(bootstrap_relays.contains { $0 == relay }) { + bootstrap_relays.append(relay) + } + } + */ + save_pubkey(pubkey: nip05.pubkey) + + case .hex(let hexstr): + if is_pubkey { + try clear_saved_privkey() + save_pubkey(pubkey: hexstr) + } else { + try handle_privkey(hexstr) + } + } + + func handle_privkey(_ privkey: String) throws { + try save_privkey(privkey: privkey) + + guard let pk = privkey_to_pubkey(privkey: privkey) else { + throw LoginError.invalid_key + } + + if let pub = bech32_pubkey(pk), let priv = bech32_privkey(privkey) { + CredentialHandler().save_credential(pubkey: pub, privkey: priv) + } + save_pubkey(pubkey: pk) + } + + await MainActor.run { + notify(.login, ()) + } +} + struct NIP05Result: Decodable { let names: Dictionary<String, String> let relays: Dictionary<String, [String]>? diff --git a/damus/Views/SaveKeysView.swift b/damus/Views/SaveKeysView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Security struct SaveKeysView: View { let account: CreateAccountModel @@ -15,6 +16,8 @@ struct SaveKeysView: View { @State var priv_copied: Bool = false @State var loading: Bool = false @State var error: String? = nil + + @State private var credential_handler = CredentialHandler() @FocusState var pubkey_focused: Bool @FocusState var privkey_focused: Bool @@ -97,6 +100,8 @@ struct SaveKeysView: View { self.pool.register_handler(sub_id: "signup", handler: handle_event) + credential_handler.save_credential(pubkey: account.pubkey_bech32, privkey: account.privkey_bech32) + self.loading = true self.pool.connect()