From 2d965772e782c47a02d7c24917d209a56a80cfbb Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Fri, 20 Aug 2021 17:13:01 -0400 Subject: [PATCH 1/3] Add Keychain-related errors and throw from Keychain extension --- .../Extensions/WriteFreelyModel+Keychain.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Shared/Extensions/WriteFreelyModel+Keychain.swift b/Shared/Extensions/WriteFreelyModel+Keychain.swift index fd37506..f039e31 100644 --- a/Shared/Extensions/WriteFreelyModel+Keychain.swift +++ b/Shared/Extensions/WriteFreelyModel+Keychain.swift @@ -1,7 +1,14 @@ import Foundation extension WriteFreelyModel { - func saveTokenToKeychain(_ token: String, username: String?, server: String) { + + enum WFKeychainError: Error { + case saveToKeychainFailed + case purgeFromKeychainFailed + case fetchFromKeychainFailed + } + + func saveTokenToKeychain(_ token: String, username: String?, server: String) throws { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecValueData as String: token.data(using: .utf8)!, @@ -10,7 +17,7 @@ extension WriteFreelyModel { ] let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecDuplicateItem || status == errSecSuccess else { - fatalError("Error storing in Keychain with OSStatus: \(status)") + throw WFKeychainError.saveToKeychainFailed } } @@ -22,11 +29,11 @@ extension WriteFreelyModel { ] let status = SecItemDelete(query as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { - fatalError("Error deleting from Keychain with OSStatus: \(status)") + throw WFKeychainError.purgeFromKeychainFailed } } - func fetchTokenFromKeychain(username: String?, server: String) -> String? { + func fetchTokenFromKeychain(username: String?, server: String) throws -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: username ?? "anonymous", @@ -41,7 +48,7 @@ extension WriteFreelyModel { return nil } guard status == errSecSuccess else { - fatalError("Error fetching from Keychain with OSStatus: \(status)") + throw WFKeychainError.fetchFromKeychainFailed } guard let existingSecItem = secItem as? [String: Any], let tokenData = existingSecItem[kSecValueData as String] as? Data, @@ -50,4 +57,5 @@ extension WriteFreelyModel { } return token } + } From cfe161362139770ede1b8b1870d7fb592772a937 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Fri, 20 Aug 2021 17:13:41 -0400 Subject: [PATCH 2/3] Handle thrown Keychain errors with informational alerts --- Shared/Account/AccountModel.swift | 18 ++++++++++++ .../WriteFreelyModel+APIHandlers.swift | 13 +++++++-- Shared/Models/WriteFreelyModel.swift | 29 ++++++++++++------- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Shared/Account/AccountModel.swift b/Shared/Account/AccountModel.swift index 4dc3aba..1d91b95 100644 --- a/Shared/Account/AccountModel.swift +++ b/Shared/Account/AccountModel.swift @@ -6,6 +6,9 @@ enum AccountError: Error { case usernameNotFound case serverNotFound case invalidServerURL + case couldNotSaveTokenToKeychain + case couldNotFetchTokenFromKeychain + case couldNotDeleteTokenFromKeychain } extension AccountError: LocalizedError { @@ -31,6 +34,21 @@ extension AccountError: LocalizedError { "Please enter a valid instance domain name. It should look like \"https://example.com\" or \"write.as\".", // swiftlint:disable:this line_length comment: "" ) + case .couldNotSaveTokenToKeychain: + return NSLocalizedString( + "There was a problem trying to save your access token to the device, please try logging in again.", + comment: "" + ) + case .couldNotFetchTokenFromKeychain: + return NSLocalizedString( + "There was a problem trying to fetch your access token from the device, please try logging in again.", + comment: "" + ) + case .couldNotDeleteTokenFromKeychain: + return NSLocalizedString( + "There was a problem trying to delete your access token from the device, please try logging out again.", + comment: "" + ) } } } diff --git a/Shared/Extensions/WriteFreelyModel+APIHandlers.swift b/Shared/Extensions/WriteFreelyModel+APIHandlers.swift index 9000ace..b4d24a6 100644 --- a/Shared/Extensions/WriteFreelyModel+APIHandlers.swift +++ b/Shared/Extensions/WriteFreelyModel+APIHandlers.swift @@ -10,9 +10,16 @@ extension WriteFreelyModel { let user = try result.get() fetchUserCollections() fetchUserPosts() - saveTokenToKeychain(user.token, username: user.username, server: account.server) - DispatchQueue.main.async { - self.account.login(user) + do { + try saveTokenToKeychain(user.token, username: user.username, server: account.server) + DispatchQueue.main.async { + self.account.login(user) + } + } catch { + DispatchQueue.main.async { + self.loginErrorMessage = "There was a problem storing your access token to the Keychain." + self.isPresentingLoginErrorAlert = true + } } } catch WFError.notFound { DispatchQueue.main.async { diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index d392e02..e6c9a23 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -51,18 +51,25 @@ final class WriteFreelyModel: ObservableObject { print("Server URL not found") return } - guard let token = self.fetchTokenFromKeychain( - username: self.account.username, - server: self.account.server - ) else { - print("Could not fetch token from Keychain") - return + do { + guard let token = try self.fetchTokenFromKeychain( + username: self.account.username, + server: self.account.server + ) else { + self.loginErrorMessage = AccountError.couldNotFetchTokenFromKeychain.localizedDescription + self.isPresentingLoginErrorAlert = true + return + } + + self.account.login(WFUser(token: token, username: self.account.username)) + self.client = WFClient(for: serverURL) + self.client?.user = self.account.user + self.fetchUserCollections() + self.fetchUserPosts() + } catch { + self.loginErrorMessage = AccountError.couldNotFetchTokenFromKeychain.localizedDescription + self.isPresentingLoginErrorAlert = true } - self.account.login(WFUser(token: token, username: self.account.username)) - self.client = WFClient(for: serverURL) - self.client?.user = self.account.user - self.fetchUserCollections() - self.fetchUserPosts() } } From 184835b68c65de664856b3bfe0828d281a990b75 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Fri, 27 Aug 2021 14:22:29 -0400 Subject: [PATCH 3/3] Bump build and version number for internal TestFlight release --- WriteFreely-MultiPlatform.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj index 132d71e..ad7eae5 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj +++ b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj @@ -969,7 +969,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 612; + CURRENT_PROJECT_VERSION = 615; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; @@ -978,7 +978,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.6; + MARKETING_VERSION = 1.0.7; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely-MultiPlatform"; SDKROOT = iphoneos; @@ -993,7 +993,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 612; + CURRENT_PROJECT_VERSION = 615; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; @@ -1002,7 +1002,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.6; + MARKETING_VERSION = 1.0.7; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely-MultiPlatform"; SDKROOT = iphoneos;