Skip to content
Merged
12 changes: 12 additions & 0 deletions FlowCrypt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,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 */; };
Expand Down Expand Up @@ -490,6 +491,7 @@
5ADEDCBD23A4363700EC495E /* KeyDetailInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailInfoViewController.swift; sourceTree = "<group>"; };
5ADEDCBF23A43B0800EC495E /* KeyDetailInfoViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailInfoViewDecorator.swift; sourceTree = "<group>"; };
5ADEDCC123A43C6800EC495E /* KeyTextCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyTextCellNode.swift; sourceTree = "<group>"; };
601EEE30272B19D200FE445B /* CheckAuthScopesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckAuthScopesViewController.swift; sourceTree = "<group>"; };
949ED9412303E3B400530579 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = "<group>"; };
9F003D6025E1B4ED00EB38C0 /* TrashFolderProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrashFolderProvider.swift; sourceTree = "<group>"; };
9F003D6C25EA8F3200EB38C0 /* UserAccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAccountService.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1053,6 +1055,14 @@
path = "Key Details";
sourceTree = "<group>";
};
601EEE32272B1A5800FE445B /* CheckAuthScopes */ = {
isa = PBXGroup;
children = (
601EEE30272B19D200FE445B /* CheckAuthScopesViewController.swift */,
);
path = CheckAuthScopes;
sourceTree = "<group>";
};
9F0C3C1F23191F2000299985 /* Services */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1604,6 +1614,7 @@
C132B9C51EC2DCAB00763715 /* Controllers */ = {
isa = PBXGroup;
children = (
601EEE32272B1A5800FE445B /* CheckAuthScopes */,
04B472911ECE29F600B8266F /* SideMenu */,
D2FF6969243115FE007182F0 /* SetupImap */,
32DCA8D5AF0A43354CC7F58B /* SignIn */,
Expand Down Expand Up @@ -2600,6 +2611,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 */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// 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]
private let globalRouter: GlobalRouterType

init(
missingScopes: [GoogleScope],
globalRouter: GlobalRouterType = GlobalRouter()
) {
self.missingScopes = missingScopes
self.globalRouter = globalRouter
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
guard let self = self else { return }
self.globalRouter.signIn(with: .gmailLogin(self))
}
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
)
}
}
14 changes: 14 additions & 0 deletions FlowCrypt/Functionality/Services/GlobalRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: -
Expand All @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions FlowCrypt/Functionality/Services/GoogleUserService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum GoogleUserServiceError: Error {
case serviceError(Error)
case parsingError(Error)
case inconsistentState(String)
case userNotAllowedAllNeededScopes(missingScopes: [GoogleScope])
}

struct GoogleUser: Codable {
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions FlowCrypt/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"open" = "Open";
"settings" = "Settings";
"go_back" = "Go back";
"continue" = "Continue";

// EMAIL
"email_removed" = "Email moved to Trash";
Expand Down Expand Up @@ -244,6 +245,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 %@ 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";
Expand Down
8 changes: 6 additions & 2 deletions FlowCryptUI/Cell Nodes/TextCellNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import AsyncDisplayKit
import FlowCryptCommon
import UIKit

public final class TextCellNode: CellNode {
public struct Input {
Expand All @@ -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
}
}

Expand All @@ -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)
}
Expand Down