From cf6a92e22eee267480cc409f7cce960fb9974d4e Mon Sep 17 00:00:00 2001 From: Personal Date: Tue, 12 Oct 2021 23:20:14 +0300 Subject: [PATCH 1/4] Catching 403 status code on gmail login with no access; --- .../Setup/SetupInitialViewController.swift | 59 ++++++++++++++++++- .../Backup Provider/Gmail+Backup.swift | 4 ++ .../Gmail/GmailServiceError.swift | 13 ++++ .../Resources/en.lproj/Localizable.strings | 2 + 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 47f456153..5b5d243c8 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -24,7 +24,7 @@ final class SetupInitialViewController: TableNodeViewController { } private enum State { - case idle, decidingIfEKMshouldBeUsed, fetchingKeysFromEKM, searchingKeyBackupsInInbox, noKeyBackupsInInbox, error(Error) + case idle, decidingIfEKMshouldBeUsed, fetchingKeysFromEKM, searchingKeyBackupsInInbox, noKeyBackupsInInbox, gmailUnauthorised, error(Error) var numberOfRows: Int { switch self { @@ -34,13 +34,17 @@ final class SetupInitialViewController: TableNodeViewController { // title, loading case .searchingKeyBackupsInInbox: return 2 - case .error: + case .error, .gmailUnauthorised: return 3 case .noKeyBackupsInInbox: return Parts.allCases.count } } } + + private struct Constants { + static let unauthorizedAPICode = 403 + } private var state = State.idle { didSet { handleNewState() } @@ -110,7 +114,7 @@ extension SetupInitialViewController { decideIfEKMshouldBeUsed() case .fetchingKeysFromEKM: fetchKeysFromEKM() - case .error, .idle, .noKeyBackupsInInbox: + case .error, .idle, .noKeyBackupsInInbox, .gmailUnauthorised: break } node.reloadData() @@ -139,6 +143,12 @@ extension SetupInitialViewController { } private func handle(error: Error) { + if let gmailServiceError = error as? GmailServiceError, + let gmailError = gmailServiceError.underlyingError, + (gmailError as NSError).code == Constants.unauthorizedAPICode { + state = .gmailUnauthorised + return + } handleCommon(error: error) state = .error(error) } @@ -208,6 +218,8 @@ extension SetupInitialViewController: ASTableDelegate, ASTableDataSource { return self.errorStateNode(for: indexPath, error: error) case .noKeyBackupsInInbox: return self.noKeysStateNode(for: indexPath) + case .gmailUnauthorised: + return self.unauthStateNode(for: indexPath) } } } @@ -307,6 +319,34 @@ extension SetupInitialViewController { return ASCellNode() } } + + private func unauthStateNode(for indexPath: IndexPath) -> ASCellNode { + switch indexPath.row { + case 0: + return SetupTitleNode( + SetupTitleNode.Input( + title: self.decorator.title(for: .setup), + insets: self.decorator.insets.titleInset, + backgroundColor: .backgroundColor + ) + ) + case 1: + return TextCellNode( + input: .init( + backgroundColor: .backgroundColor, + title: "gmail_service_no_access_to_account_message".localized, + withSpinner: false, + size: CGSize(width: 200, height: 200) + ) + ) + case 2: + return ButtonCellNode(input: .signInAgain) { [weak self] in + self?.router.signOut() + } + default: + return ASCellNode() + } + } } // MARK: - Navigation @@ -338,3 +378,16 @@ extension SetupInitialViewController { } } } + +// MARK: - Buttons input +private extension ButtonCellNode.Input { + static var signInAgain: ButtonCellNode.Input { + return .init( + title: "sign_in_again" + .localized + .attributed(.bold(16), color: .white, alignment: .center), + insets: UIEdgeInsets(top: 16, left: 24, bottom: 8, right: 24), + color: .main + ) + } +} diff --git a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift index 56e1c246a..be2e62413 100644 --- a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift +++ b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift @@ -34,6 +34,10 @@ extension GmailService: BackupProvider { logger.logVerbose("downloaded \(attachments.count) attachments that contain \(data.count / 1024)kB of data") resolve(data) } catch { + if error is GmailServiceError { + reject(error) + return + } reject(GmailServiceError.missedBackupQuery(error)) } } diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift index 5c4038fc7..e776f384d 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift @@ -41,3 +41,16 @@ extension GmailServiceError: LocalizedError { } } } + +extension GmailServiceError { + var underlyingError: Error? { + switch self { + case .failedToParseData, .messageEncode, .missedMessagePayload, .missedMessageInfo: + return nil + case .providerError(let error): + return error + case .missedBackupQuery(let error): + return error + } + } +} diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 8fdd4d298..a3f9800c7 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -70,6 +70,7 @@ "folder_add_account" = "Add account"; // Sign in +"sign_in_again" = "Sign In again"; "sign_in_privacy" = "Privacy"; "sign_in_terms" = "Terms"; "sign_in_security" = "Security"; @@ -225,6 +226,7 @@ "gmail_service_missing_message_info_error_message" = "Missing message info: %@"; "gmail_service_provider_error_error_message" = "Gmail provider error: %@"; "gmail_service_missing_back_query_error_message" = "Missed backup query: %@"; +"gmail_service_no_access_to_account_message" = "There is no access of read, compose, send and delete emails. Please allow FlowCrypt app to make these actions."; // Files picking "files_picking_select_input_source_title" = "Please select input source"; From 97085872b323de95caa52acb57a0118524cd276a Mon Sep 17 00:00:00 2001 From: Personal Date: Wed, 27 Oct 2021 00:17:47 +0300 Subject: [PATCH 2/4] Changed texts, fixed PR comments --- .../Controllers/Setup/SetupInitialViewController.swift | 5 +++-- FlowCrypt/Resources/en.lproj/Localizable.strings | 3 ++- FlowCryptUI/Cell Nodes/TextCellNode.swift | 8 ++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 97c83df97..745f597e0 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -337,7 +337,8 @@ extension SetupInitialViewController { backgroundColor: .backgroundColor, title: "gmail_service_no_access_to_account_message".localized, withSpinner: false, - size: CGSize(width: 200, height: 200) + size: CGSize(width: 200, height: 200), + alignment: .center ) ) case 2: @@ -384,7 +385,7 @@ extension SetupInitialViewController { private extension ButtonCellNode.Input { static var signInAgain: ButtonCellNode.Input { return .init( - title: "sign_in_again" + title: "continue" .localized .attributed(.bold(16), color: .white, alignment: .center), insets: UIEdgeInsets(top: 16, left: 24, bottom: 8, right: 24), diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 2bae8e113..f993419b4 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -9,6 +9,7 @@ "open" = "Open"; "settings" = "Settings"; "go_back" = "Go back"; +"continue" = "Continue"; // EMAIL "email_removed" = "Email moved to Trash"; @@ -243,7 +244,7 @@ "gmail_service_missing_message_info_error_message" = "Missing message info: %@"; "gmail_service_provider_error_error_message" = "Gmail provider error: %@"; "gmail_service_missing_back_query_error_message" = "Missed backup query: %@"; -"gmail_service_no_access_to_account_message" = "There is no access of read, compose, send and delete emails. Please allow FlowCrypt app to make these actions."; +"gmail_service_no_access_to_account_message" = "Google login successful.\n Next, connect your inbox. Be sure to tick Gmail and Contacts permission on the next screen. The grant is stored locally on your device, never shared with anyone."; // Files picking "files_picking_select_input_source_title" = "Please select input source"; diff --git a/FlowCryptUI/Cell Nodes/TextCellNode.swift b/FlowCryptUI/Cell Nodes/TextCellNode.swift index 8e53dffaa..b289c64bb 100644 --- a/FlowCryptUI/Cell Nodes/TextCellNode.swift +++ b/FlowCryptUI/Cell Nodes/TextCellNode.swift @@ -8,6 +8,7 @@ import AsyncDisplayKit import FlowCryptCommon +import UIKit public final class TextCellNode: CellNode { public struct Input { @@ -16,19 +17,22 @@ public final class TextCellNode: CellNode { let withSpinner: Bool let size: CGSize let insets: UIEdgeInsets + let alignment: NSTextAlignment? public init( backgroundColor: UIColor, title: String, withSpinner: Bool, size: CGSize, - insets: UIEdgeInsets = .zero + insets: UIEdgeInsets = .zero, + alignment: NSTextAlignment? = nil ) { self.backgroundColor = backgroundColor self.title = title self.withSpinner = withSpinner self.size = size self.insets = insets + self.alignment = alignment } } @@ -44,7 +48,7 @@ public final class TextCellNode: CellNode { insets = input.insets super.init() addSubnode(textNode) - textNode.attributedText = NSAttributedString.text(from: input.title, style: .medium(16), color: .lightGray) + textNode.attributedText = NSAttributedString.text(from: input.title, style: .medium(16), color: .lightGray, alignment: input.alignment) if input.withSpinner { addSubnode(spinner) } From e4e4cbf9e5fad7834ffeafd76d97e6c06e3ee00c Mon Sep 17 00:00:00 2001 From: Evgenii Kievsky Date: Thu, 28 Oct 2021 22:22:26 +0300 Subject: [PATCH 3/4] Handled missing scopes for Google auth; --- FlowCrypt.xcodeproj/project.pbxproj | 12 ++ .../CheckAuthScopesViewController.swift | 119 ++++++++++++++++++ .../Setup/SetupInitialViewController.swift | 60 +-------- .../Backup Provider/Gmail+Backup.swift | 3 - .../Gmail/GmailServiceError.swift | 13 -- .../Functionality/Services/GlobalRouter.swift | 14 +++ .../Services/GoogleUserService.swift | 14 +++ .../Resources/en.lproj/Localizable.strings | 3 +- 8 files changed, 163 insertions(+), 75 deletions(-) create mode 100644 FlowCrypt/Controllers/CheckAuthScopes/CheckAuthScopesViewController.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 80f5678ff..054656ba8 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -91,6 +91,7 @@ 5ADEDCBC23A4329000EC495E /* PublicKeyDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADEDCBB23A4329000EC495E /* PublicKeyDetailViewController.swift */; }; 5ADEDCBE23A4363700EC495E /* KeyDetailInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADEDCBD23A4363700EC495E /* KeyDetailInfoViewController.swift */; }; 5ADEDCC023A43B0800EC495E /* KeyDetailInfoViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADEDCBF23A43B0800EC495E /* KeyDetailInfoViewDecorator.swift */; }; + 601EEE31272B19D200FE445B /* CheckAuthScopesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 601EEE30272B19D200FE445B /* CheckAuthScopesViewController.swift */; }; 7F72537A0C44D3CE670F0EFD /* Pods_FlowCryptUIApplication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3382C015A576728FA08BA310 /* Pods_FlowCryptUIApplication.framework */; }; 949ED9422303E3B400530579 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 949ED9412303E3B400530579 /* Colors.xcassets */; }; 9F003D6125E1B4ED00EB38C0 /* TrashFolderProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F003D6025E1B4ED00EB38C0 /* TrashFolderProvider.swift */; }; @@ -487,6 +488,7 @@ 5ADEDCBD23A4363700EC495E /* KeyDetailInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailInfoViewController.swift; sourceTree = ""; }; 5ADEDCBF23A43B0800EC495E /* KeyDetailInfoViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailInfoViewDecorator.swift; sourceTree = ""; }; 5ADEDCC123A43C6800EC495E /* KeyTextCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyTextCellNode.swift; sourceTree = ""; }; + 601EEE30272B19D200FE445B /* CheckAuthScopesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckAuthScopesViewController.swift; sourceTree = ""; }; 949ED9412303E3B400530579 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 9F003D6025E1B4ED00EB38C0 /* TrashFolderProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrashFolderProvider.swift; sourceTree = ""; }; 9F003D6C25EA8F3200EB38C0 /* UserAccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAccountService.swift; sourceTree = ""; }; @@ -1042,6 +1044,14 @@ path = "Key Details"; sourceTree = ""; }; + 601EEE32272B1A5800FE445B /* CheckAuthScopes */ = { + isa = PBXGroup; + children = ( + 601EEE30272B19D200FE445B /* CheckAuthScopesViewController.swift */, + ); + path = CheckAuthScopes; + sourceTree = ""; + }; 9F0C3C1F23191F2000299985 /* Services */ = { isa = PBXGroup; children = ( @@ -1595,6 +1605,7 @@ C132B9C51EC2DCAB00763715 /* Controllers */ = { isa = PBXGroup; children = ( + 601EEE32272B1A5800FE445B /* CheckAuthScopes */, 04B472911ECE29F600B8266F /* SideMenu */, D2FF6969243115FE007182F0 /* SetupImap */, 32DCA8D5AF0A43354CC7F58B /* SignIn */, @@ -2588,6 +2599,7 @@ 5A39F43F239EE7D2001F4607 /* SegmentedViewController.swift in Sources */, 21489B7A267CB4DF00BDE4AC /* ClientConfigurationObject.swift in Sources */, 5A5C234B23A042520015E705 /* WebViewController.swift in Sources */, + 601EEE31272B19D200FE445B /* CheckAuthScopesViewController.swift in Sources */, 9F5C2A7E257E64D500DE9B4B /* MessageOperationsProvider.swift in Sources */, 9FC7EB76266EB67B00F3BF5D /* EncryptedStorageProtocols.swift in Sources */, 32DCACF9C6FC4B9330C9B362 /* Imap+send.swift in Sources */, diff --git a/FlowCrypt/Controllers/CheckAuthScopes/CheckAuthScopesViewController.swift b/FlowCrypt/Controllers/CheckAuthScopes/CheckAuthScopesViewController.swift new file mode 100644 index 000000000..9c29ff5bb --- /dev/null +++ b/FlowCrypt/Controllers/CheckAuthScopes/CheckAuthScopesViewController.swift @@ -0,0 +1,119 @@ +// +// CheckAuthScopesViewController.swift +// FlowCrypt +// +// Created by Yevhen Kyivskyi on 28.10.2021 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import AsyncDisplayKit +import FlowCryptCommon +import FlowCryptUI + +private extension GoogleScope { + var title: String { + switch self { + case .userInfo: return "User Info" + case .mail: return "Gmail" + case .contacts: return "Contacts" + } + } +} + +class CheckAuthScopesViewController: TableNodeViewController { + + private let missingScopes: [GoogleScope] + + init(missingScopes: [GoogleScope]) { + self.missingScopes = missingScopes + super.init(node: TableNode()) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + var errorMessage: String { + let scopesMessage = missingScopes.map { $0.title }.joined(separator: ", ") + return "gmail_service_no_access_to_account_message".localizeWithArguments(scopesMessage) + } +} + +// MARK: - ASTableDelegate, ASTableDataSource +extension CheckAuthScopesViewController: ASTableDelegate, ASTableDataSource { + func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { + return 3 + } + + func tableNode(_ node: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { + return { [weak self] in + guard let self = self else { return ASCellNode() } + return self.unauthStateNode(for: indexPath) + } + } +} + +// MARK: - UI +extension CheckAuthScopesViewController { + private func setupUI() { + node.delegate = self + node.dataSource = self + + title = "FlowCrypt" + } + private func unauthStateNode(for indexPath: IndexPath) -> ASCellNode { + switch indexPath.row { + case 0: + return SetupTitleNode( + SetupTitleNode.Input( + title: "setup_title" + .localized + .attributed( + .bold(35), + color: .mainTextColor, + alignment: .center + ), + insets: UIEdgeInsets( + top: 64, left: 16, + bottom: 64, right: 16 + ), + backgroundColor: .backgroundColor + ) + ) + case 1: + return TextCellNode( + input: .init( + backgroundColor: .backgroundColor, + title: errorMessage, + withSpinner: false, + size: CGSize(width: 200, height: 200), + alignment: .center + ) + ) + case 2: + return ButtonCellNode(input: .signInAgain) { [weak self] in + self?.navigationController?.popViewController(animated: true) + } + default: + return ASCellNode() + } + } +} + +private extension ButtonCellNode.Input { + static var signInAgain: ButtonCellNode.Input { + return .init( + title: "continue" + .localized + .attributed(.bold(16), color: .white, alignment: .center), + insets: UIEdgeInsets(top: 16, left: 24, bottom: 8, right: 24), + color: .main + ) + } +} diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 745f597e0..e57a43ce9 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -24,7 +24,7 @@ final class SetupInitialViewController: TableNodeViewController { } private enum State { - case idle, decidingIfEKMshouldBeUsed, fetchingKeysFromEKM, searchingKeyBackupsInInbox, noKeyBackupsInInbox, gmailUnauthorised, error(Error) + case idle, decidingIfEKMshouldBeUsed, fetchingKeysFromEKM, searchingKeyBackupsInInbox, noKeyBackupsInInbox, error(Error) var numberOfRows: Int { switch self { @@ -34,17 +34,13 @@ final class SetupInitialViewController: TableNodeViewController { // title, loading case .searchingKeyBackupsInInbox: return 2 - case .error, .gmailUnauthorised: + case .error: return 3 case .noKeyBackupsInInbox: return Parts.allCases.count } } } - - private struct Constants { - static let unauthorizedAPICode = 403 - } private var state = State.idle { didSet { handleNewState() } @@ -113,7 +109,7 @@ extension SetupInitialViewController { decideIfEKMshouldBeUsed() case .fetchingKeysFromEKM: fetchKeysFromEKM() - case .error, .idle, .noKeyBackupsInInbox, .gmailUnauthorised: + case .error, .idle, .noKeyBackupsInInbox: break } node.reloadData() @@ -143,12 +139,6 @@ extension SetupInitialViewController { } private func handle(error: Error) { - if let gmailServiceError = error as? GmailServiceError, - let gmailError = gmailServiceError.underlyingError, - (gmailError as NSError).code == Constants.unauthorizedAPICode { - state = .gmailUnauthorised - return - } handleCommon(error: error) state = .error(error) } @@ -219,8 +209,6 @@ extension SetupInitialViewController: ASTableDelegate, ASTableDataSource { return self.errorStateNode(for: indexPath, error: error) case .noKeyBackupsInInbox: return self.noKeysStateNode(for: indexPath) - case .gmailUnauthorised: - return self.unauthStateNode(for: indexPath) } } } @@ -320,35 +308,6 @@ extension SetupInitialViewController { return ASCellNode() } } - - private func unauthStateNode(for indexPath: IndexPath) -> ASCellNode { - switch indexPath.row { - case 0: - return SetupTitleNode( - SetupTitleNode.Input( - title: self.decorator.title(for: .setup), - insets: self.decorator.insets.titleInset, - backgroundColor: .backgroundColor - ) - ) - case 1: - return TextCellNode( - input: .init( - backgroundColor: .backgroundColor, - title: "gmail_service_no_access_to_account_message".localized, - withSpinner: false, - size: CGSize(width: 200, height: 200), - alignment: .center - ) - ) - case 2: - return ButtonCellNode(input: .signInAgain) { [weak self] in - self?.router.signOut() - } - default: - return ASCellNode() - } - } } // MARK: - Navigation @@ -381,19 +340,6 @@ extension SetupInitialViewController { } } -// MARK: - Buttons input -private extension ButtonCellNode.Input { - static var signInAgain: ButtonCellNode.Input { - return .init( - title: "continue" - .localized - .attributed(.bold(16), color: .white, alignment: .center), - insets: UIEdgeInsets(top: 16, left: 24, bottom: 8, right: 24), - color: .main - ) - } -} - // TODO temporary solution for background execution problem private actor ServiceActor { private let backupService: BackupServiceType diff --git a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift index ce4755d9b..a11faad39 100644 --- a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift +++ b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift @@ -33,9 +33,6 @@ extension GmailService: BackupProvider { logger.logVerbose("downloaded \(attachments.count) attachments that contain \(data.count / 1024)kB of data") return data } catch { - if error is GmailServiceError { - throw error - } throw GmailServiceError.missedBackupQuery(error) } } diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift index e776f384d..5c4038fc7 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift @@ -41,16 +41,3 @@ extension GmailServiceError: LocalizedError { } } } - -extension GmailServiceError { - var underlyingError: Error? { - switch self { - case .failedToParseData, .messageEncode, .missedMessagePayload, .missedMessageInfo: - return nil - case .providerError(let error): - return error - case .missedBackupQuery(let error): - return error - } - } -} diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index 1268ac263..1fcd302fa 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -86,6 +86,18 @@ extension GlobalRouter { AppStartup().initializeApp(window: window, session: session) } } + + private func handleGmailError(_ error: Error) { + logger.logInfo("gmail login failed with error \(error.localizedDescription)") + if let gmailUserError = error as? GoogleUserServiceError, + case .userNotAllowedAllNeededScopes(let missingScopes) = gmailUserError { + DispatchQueue.main.async { + let topNavigation = (self.keyWindow.rootViewController as? UINavigationController) + let checkAuthViewControlelr = CheckAuthScopesViewController(missingScopes: missingScopes) + topNavigation?.pushViewController(checkAuthViewControlelr, animated: true) + } + } + } } // MARK: - @@ -99,6 +111,8 @@ extension GlobalRouter { .then(on: .main) { [weak self] session in self?.userAccountService.startSessionFor(user: session) self?.proceed(with: session) + }.catch { [weak self] error in + self?.handleGmailError(error) } case .other(let session): userAccountService.startSessionFor(user: session) diff --git a/FlowCrypt/Functionality/Services/GoogleUserService.swift b/FlowCrypt/Functionality/Services/GoogleUserService.swift index 8a05f81e8..77e6c9e3c 100644 --- a/FlowCrypt/Functionality/Services/GoogleUserService.swift +++ b/FlowCrypt/Functionality/Services/GoogleUserService.swift @@ -25,6 +25,7 @@ enum GoogleUserServiceError: Error { case serviceError(Error) case parsingError(Error) case inconsistentState(String) + case userNotAllowedAllNeededScopes(missingScopes: [GoogleScope]) } struct GoogleUser: Codable { @@ -88,6 +89,12 @@ extension GoogleUserService: UserServiceType { presenting: viewController ) { authState, error in if let authState = authState { + let missingScopes = self.checkMissingScopes(authState.scope) + if !missingScopes.isEmpty { + reject(GoogleUserServiceError.userNotAllowedAllNeededScopes(missingScopes: missingScopes)) + return + } + let authorization = GTMAppAuthFetcherAuthorization(authState: authState) guard let email = authorization.userEmail else { reject(GoogleUserServiceError.inconsistentState("Missed email")) @@ -199,6 +206,13 @@ extension GoogleUserService { logger.logError("Authorization error during fetching user info") } } + + private func checkMissingScopes(_ scope: String?) -> [GoogleScope] { + guard let scope = scope else { + return GoogleScope.allCases + } + return GoogleScope.allCases.filter { !scope.contains($0.value) } + } } // MARK: - OIDAuthStateChangeDelegate diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index f993419b4..428a98085 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -77,7 +77,6 @@ "folder_add_account" = "Add account"; // Sign in -"sign_in_again" = "Sign In again"; "sign_in_privacy" = "Privacy"; "sign_in_terms" = "Terms"; "sign_in_security" = "Security"; @@ -244,7 +243,7 @@ "gmail_service_missing_message_info_error_message" = "Missing message info: %@"; "gmail_service_provider_error_error_message" = "Gmail provider error: %@"; "gmail_service_missing_back_query_error_message" = "Missed backup query: %@"; -"gmail_service_no_access_to_account_message" = "Google login successful.\n Next, connect your inbox. Be sure to tick Gmail and Contacts permission on the next screen. The grant is stored locally on your device, never shared with anyone."; +"gmail_service_no_access_to_account_message" = "Google login successful.\n Next, connect your inbox. Be sure to tick %@ permission on the next screen. The grant is stored locally on your device, never shared with anyone."; // Files picking "files_picking_select_input_source_title" = "Please select input source"; From 320c41943fdec464f32c3d3d1815ee5a7c22f925 Mon Sep 17 00:00:00 2001 From: Evgenii Kievsky Date: Thu, 28 Oct 2021 22:46:18 +0300 Subject: [PATCH 4/4] Allowed user to login with Google if scopes were wrong; --- .../CheckAuthScopesViewController.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/FlowCrypt/Controllers/CheckAuthScopes/CheckAuthScopesViewController.swift b/FlowCrypt/Controllers/CheckAuthScopes/CheckAuthScopesViewController.swift index 9c29ff5bb..4e2957417 100644 --- a/FlowCrypt/Controllers/CheckAuthScopes/CheckAuthScopesViewController.swift +++ b/FlowCrypt/Controllers/CheckAuthScopes/CheckAuthScopesViewController.swift @@ -23,9 +23,14 @@ private extension GoogleScope { class CheckAuthScopesViewController: TableNodeViewController { private let missingScopes: [GoogleScope] + private let globalRouter: GlobalRouterType - init(missingScopes: [GoogleScope]) { + init( + missingScopes: [GoogleScope], + globalRouter: GlobalRouterType = GlobalRouter() + ) { self.missingScopes = missingScopes + self.globalRouter = globalRouter super.init(node: TableNode()) } @@ -98,7 +103,8 @@ extension CheckAuthScopesViewController { ) case 2: return ButtonCellNode(input: .signInAgain) { [weak self] in - self?.navigationController?.popViewController(animated: true) + guard let self = self else { return } + self.globalRouter.signIn(with: .gmailLogin(self)) } default: return ASCellNode()