From cc24e4d886f05abb3978f8609ec3d0f777409e1d Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Sun, 23 May 2021 22:10:27 +0300 Subject: [PATCH 01/26] Add tests for KeyMethods --- FlowCrypt.xcodeproj/project.pbxproj | 6 + FlowCrypt/App/Logger.swift | 3 + .../EnterPassPhraseViewController.swift | 4 +- .../Import Key/ImportKeyViewController.swift | 2 + .../Setup/SetupViewController.swift | 28 ++++- FlowCrypt/Core/Core.swift | 6 +- .../UIViewControllerExtensions.swift | 1 + .../DataManager/DataService.swift | 11 +- .../Mail Provider/Imap/Imap+session.swift | 4 +- FlowCrypt/Functionality/Pgp/KeyMethods.swift | 37 ++++-- FlowCrypt/Models/OrganisationalRule.swift | 4 +- FlowCryptTests/KeyMethodsTest.swift | 117 ++++++++++++++++++ Scripts/format.sh | 3 +- 13 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 FlowCryptTests/KeyMethodsTest.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index d6a4f69c1..a35890bd4 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -120,6 +120,8 @@ 9F953E09238310D500AEB98B /* KeyMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F953E08238310D500AEB98B /* KeyMethods.swift */; }; 9F9AAFFD2383E216000A00F1 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9AAFFC2383E216000A00F1 /* Document.swift */; }; 9F9ABC8723AC1EAA00D560E3 /* MessageContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */; }; + 9FA0157A26565B7800CBBA05 /* KeyMethodsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */; }; + 9FA0158026565B9D00CBBA05 /* KeyMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F953E08238310D500AEB98B /* KeyMethods.swift */; }; 9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA1988F253C841F008C9CF2 /* TableViewController.swift */; }; 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA9C83B264C2D75005A9670 /* MessageService.swift */; }; 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */; }; @@ -494,6 +496,7 @@ 9F95A3F623607C0900C80B64 /* SigninButtonNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigninButtonNode.swift; sourceTree = ""; }; 9F9AAFFC2383E216000A00F1 /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = ""; }; 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContext.swift; sourceTree = ""; }; + 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyMethodsTest.swift; sourceTree = ""; }; 9FA1988F253C841F008C9CF2 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 9FA9C83B264C2D75005A9670 /* MessageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageService.swift; sourceTree = ""; }; 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceErrorHandler.swift; sourceTree = ""; }; @@ -1273,6 +1276,7 @@ A3A680EF22EEF0BF00905813 /* FlowCryptTests-Bridging-Header.h */, D2A9CA44242622F800E1D898 /* GeneralConstantsTest.swift */, 9F003D9D25EA910B00EB38C0 /* LocalStorageTests.swift */, + 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */, ); path = FlowCryptTests; sourceTree = ""; @@ -2270,6 +2274,7 @@ files = ( D2D27B7A248A874C007346FA /* BigIntExtension.swift in Sources */, 9F3EF32B23B16ADE00FA0CEF /* CommonExtensions.swift in Sources */, + 9FA0157A26565B7800CBBA05 /* KeyMethodsTest.swift in Sources */, A357699622EA2BC8009242C4 /* KeyInfo.swift in Sources */, 9FC411902596229D001180A8 /* AppErr.swift in Sources */, D212D35E24C1AACF00035991 /* PrvKeyInfo.swift in Sources */, @@ -2297,6 +2302,7 @@ 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */, 9F3EF32823B15C8400FA0CEF /* ImapHelper.swift in Sources */, 9F003D9E25EA910B00EB38C0 /* LocalStorageTests.swift in Sources */, + 9FA0158026565B9D00CBBA05 /* KeyMethods.swift in Sources */, 32DCAF683D87EA6221F71335 /* SequenceExtensions.swift in Sources */, 9F589F11238C7DDC007FD759 /* User.swift in Sources */, 21F836D32652A46E00B2448C /* WKDURLsConstructorTests.swift in Sources */, diff --git a/FlowCrypt/App/Logger.swift b/FlowCrypt/App/Logger.swift index bee940c46..fd0449892 100644 --- a/FlowCrypt/App/Logger.swift +++ b/FlowCrypt/App/Logger.swift @@ -162,6 +162,9 @@ extension Logger { /// log all db migration events case migration = "Migration" + + /// Core related logs + case core = "Core" } static func nested(in type: T.Type, with logLabel: LogLabels) -> Logger { diff --git a/FlowCrypt/Controllers/ImportKey/Enter Pass Phrase/EnterPassPhraseViewController.swift b/FlowCrypt/Controllers/ImportKey/Enter Pass Phrase/EnterPassPhraseViewController.swift index 55d4a5fff..0d116fcb5 100644 --- a/FlowCrypt/Controllers/ImportKey/Enter Pass Phrase/EnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/ImportKey/Enter Pass Phrase/EnterPassPhraseViewController.swift @@ -30,7 +30,7 @@ final class EnterPassPhraseViewController: TableNodeViewController { init( decorator: EnterPassPhraseViewDecoratorType = EnterPassPhraseViewDecorator(), - keyMethods: KeyMethodsType = KeyMethods(core: .shared), + keyMethods: KeyMethodsType = KeyMethods(), keysService: KeyDataServiceType = DataService.shared, router: GlobalRouterType = GlobalRouter(), keyService: KeyServiceType = KeyService(), @@ -255,3 +255,5 @@ extension EnterPassPhraseViewController { router.proceed() } } + +// TODO: - ANTON diff --git a/FlowCrypt/Controllers/ImportKey/Import Key/ImportKeyViewController.swift b/FlowCrypt/Controllers/ImportKey/Import Key/ImportKeyViewController.swift index f69f7fe67..a0c83d6c3 100644 --- a/FlowCrypt/Controllers/ImportKey/Import Key/ImportKeyViewController.swift +++ b/FlowCrypt/Controllers/ImportKey/Import Key/ImportKeyViewController.swift @@ -208,3 +208,5 @@ extension ImportKeyViewController: UIDocumentPickerDelegate { } } } + +// TODO: - ANTON diff --git a/FlowCrypt/Controllers/Setup/SetupViewController.swift b/FlowCrypt/Controllers/Setup/SetupViewController.swift index d17896b94..1f6d55c5d 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupViewController.swift @@ -63,7 +63,7 @@ final class SetupViewController: TableNodeViewController { storage: DataServiceType & KeyDataServiceType = DataService.shared, decorator: SetupViewDecoratorType = SetupViewDecorator(), core: Core = Core.shared, - keyMethods: KeyMethodsType = KeyMethods(core: .shared), + keyMethods: KeyMethodsType = KeyMethods(), attester: AttesterApiType = AttesterApi(), backupService: BackupServiceType = BackupService.shared, user: UserId @@ -422,7 +422,7 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { } func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { - { [weak self] in + return { [weak self] in guard let self = self, let part = Parts(rawValue: indexPath.row) else { return ASCellNode() } switch part { case .title: @@ -478,3 +478,27 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { } } } + +// TODO: - ANTON + +/* + During setup + new key + when importing key + when loading from backup + creating + entering pass phrase + +the user should see two radio buttons: + + o store pass phrase locally - Default is to store. + o keep pass phrase in memory + + If the user switches it, + then we do not store pass phrase with the key (or at all). + We only keep it in memory for up to 4 hours from the moment it was stored - then it needs to be forgotten. + During those 4 hours, the key will be used for actions (eg decrypt messages). + After those 4 hours, the user will be prompted for a pass phrase with a modal / alert to re-enter it, at which point it will be again remembered for 4 hours. + + If app gets killed, pass phrase gets forgotten. + */ diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index a35900aba..472d26bbf 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -12,7 +12,11 @@ enum CoreError: Error { case value(String) } -final class Core { +protocol KeyDecrypter { + func decryptKey(armoredPrv: String, passphrase: String) throws -> CoreRes.DecryptKey +} + +final class Core: KeyDecrypter { static let shared = Core() private var jsEndpointListener: JSValue? diff --git a/FlowCrypt/Extensions/UIViewControllerExtensions.swift b/FlowCrypt/Extensions/UIViewControllerExtensions.swift index bc9c953a4..cd4464305 100644 --- a/FlowCrypt/Extensions/UIViewControllerExtensions.swift +++ b/FlowCrypt/Extensions/UIViewControllerExtensions.swift @@ -141,6 +141,7 @@ extension UIViewController { } } + // TODO: - ANTON func awaitUserPassPhraseEntry(title: String) -> Promise { Promise(on: .main) { [weak self] resolve, _ in guard let self = self else { throw AppErr.nilSelf } diff --git a/FlowCrypt/Functionality/DataManager/DataService.swift b/FlowCrypt/Functionality/DataManager/DataService.swift index e162227df..a6361d487 100644 --- a/FlowCrypt/Functionality/DataManager/DataService.swift +++ b/FlowCrypt/Functionality/DataManager/DataService.swift @@ -27,9 +27,18 @@ protocol ImapSessionProvider { func smtpSession() -> SMTPSession? } -enum SessionType { +enum SessionType: CustomStringConvertible { case google(_ email: String, name: String, token: String) case session(_ userObject: UserObject) + + var description: String { + switch self { + case let .google(email, name, _): + return "Google \(email) \(name)" + case let .session(user): + return "Session \(user.email)" + } + } } // MARK: - DataService diff --git a/FlowCrypt/Functionality/Mail Provider/Imap/Imap+session.swift b/FlowCrypt/Functionality/Mail Provider/Imap/Imap+session.swift index 821d28a8e..46bd9607c 100644 --- a/FlowCrypt/Functionality/Mail Provider/Imap/Imap+session.swift +++ b/FlowCrypt/Functionality/Mail Provider/Imap/Imap+session.swift @@ -29,14 +29,14 @@ extension Imap { logger.logInfo("Creating a new IMAP session") let newImapSession = MCOIMAPSession(session: imap) imapSess = newImapSession - //logIMAPConnection(for: imapSess!) + logIMAPConnection(for: imapSess!) } if let smtp = smtpSession { logger.logInfo("Creating a new SMTP session") let newSmtpSession = MCOSMTPSession(session: smtp) smtpSess = newSmtpSession - //logSMTPConnection(for: smtpSess!) + logSMTPConnection(for: smtpSess!) } } diff --git a/FlowCrypt/Functionality/Pgp/KeyMethods.swift b/FlowCrypt/Functionality/Pgp/KeyMethods.swift index 1d997991f..6892d6fa8 100644 --- a/FlowCrypt/Functionality/Pgp/KeyMethods.swift +++ b/FlowCrypt/Functionality/Pgp/KeyMethods.swift @@ -12,15 +12,38 @@ protocol KeyMethodsType { func filterByPassPhraseMatch(keys: [KeyDetails], passPhrase: String) -> [KeyDetails] } -struct KeyMethods: KeyMethodsType { - let core: Core +final class KeyMethods: KeyMethodsType { + + let decrypter: KeyDecrypter + + init(decrypter: KeyDecrypter = Core.shared) { + self.decrypter = decrypter + } func filterByPassPhraseMatch(keys: [KeyDetails], passPhrase: String) -> [KeyDetails] { - keys.compactMap { key -> KeyDetails? in - guard let privateKey = key.private, - let decrypted = try? self.core.decryptKey(armoredPrv: privateKey, passphrase: passPhrase), - decrypted.decryptedKey != nil - else { return nil } + let logger = Logger.nested(in: Self.self, with: .core) + + guard keys.isNotEmpty else { + logger.logInfo("Keys are empty") + return [] + } + + return keys.compactMap { key -> KeyDetails? in + guard let privateKey = key.private else { + logger.logInfo("Filtered not private key") + return nil + } + + guard let decrypted = try? self.decrypter.decryptKey(armoredPrv: privateKey, passphrase: passPhrase) else { + logger.logInfo("Filtered not decrypted key") + return nil + } + + guard decrypted.decryptedKey != nil else { + logger.logInfo("Filtered. decryptedKey = nil") + return nil + } + return key } } diff --git a/FlowCrypt/Models/OrganisationalRule.swift b/FlowCrypt/Models/OrganisationalRule.swift index 7321bd8ea..7c009be16 100644 --- a/FlowCrypt/Models/OrganisationalRule.swift +++ b/FlowCrypt/Models/OrganisationalRule.swift @@ -43,12 +43,12 @@ class OrganisationalRules { self.domainRules = domainRules self.domain = domain } - + init?(domainRules: DomainRules, email: String) { guard let recipientDomain = email.recipientDomain else { return nil } - + self.domain = recipientDomain self.domainRules = domainRules } diff --git a/FlowCryptTests/KeyMethodsTest.swift b/FlowCryptTests/KeyMethodsTest.swift new file mode 100644 index 000000000..7cac8abb9 --- /dev/null +++ b/FlowCryptTests/KeyMethodsTest.swift @@ -0,0 +1,117 @@ +// +// KeyMethodsTest.swift +// FlowCryptTests +// +// Created by Anton Kharchevskyi on 20.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import XCTest + +class KeyMethodsTest: XCTestCase { + + var sut: KeyMethods! + var decrypter: MockKeyDecrypter! + var passPhrase = "Some long frase" + + override func setUp() { + decrypter = MockKeyDecrypter() + sut = KeyMethods(decrypter: decrypter) + } + + func testEmptyParsingKey() { + let emptyKeys: [KeyDetails] = [] + let result = sut.filterByPassPhraseMatch(keys: emptyKeys, passPhrase: passPhrase) + + XCTAssertTrue(result.isEmpty) + } + + func testNoPrivateKey() { + // private part = nil + let keys = [ + KeyDetails( + public: "Public part", + private: nil, + isFullyDecrypted: false, + isFullyEncrypted: false, + ids: [], + created: 1, + users: [], + algo: nil + ), + KeyDetails( + public: "Public part2", + private: nil, + isFullyDecrypted: false, + isFullyEncrypted: false, + ids: [], + created: 1, + users: [], + algo: nil + ) + ] + let result = sut.filterByPassPhraseMatch(keys: keys, passPhrase: passPhrase) + + XCTAssertTrue(result.isEmpty) + } + + func testCantDecryptKey() { + decrypter.result = .failure(.some) + let result = sut.filterByPassPhraseMatch(keys: validKeys, passPhrase: passPhrase) + XCTAssertTrue(result.isEmpty) + } + + func testNoDecryptedKey() { + decrypter.result = .success(CoreRes.DecryptKey(decryptedKey: nil)) + let result = sut.filterByPassPhraseMatch(keys: validKeys, passPhrase: passPhrase) + XCTAssertTrue(result.isEmpty) + } + + func testSuccessDecryption() { + decrypter.result = .success(CoreRes.DecryptKey(decryptedKey: "some key")) + let result = sut.filterByPassPhraseMatch(keys: validKeys, passPhrase: passPhrase) + XCTAssertTrue(result.isNotEmpty) + } +} + +extension KeyMethodsTest { + var validKeys: [KeyDetails] {[ + KeyDetails( + public: "Public part", + private: "private 1", + isFullyDecrypted: false, + isFullyEncrypted: false, + ids: [], + created: 1, + users: [], + algo: nil + ), + KeyDetails( + public: "Public part2", + private: "private 2", + isFullyDecrypted: false, + isFullyEncrypted: false, + ids: [], + created: 1, + users: [], + algo: nil + ) + ]} +} + +class MockKeyDecrypter: KeyDecrypter { + enum MockError: Error { + case some + } + + var result: Result = .success(CoreRes.DecryptKey(decryptedKey: "decrypted")) + + func decryptKey(armoredPrv: String, passphrase: String) throws -> CoreRes.DecryptKey { + switch result { + case .success(let key): + return key + case .failure(let error): + throw error + } + } +} diff --git a/Scripts/format.sh b/Scripts/format.sh index 44475d2f5..6262b8eee 100755 --- a/Scripts/format.sh +++ b/Scripts/format.sh @@ -13,7 +13,7 @@ if which swiftformat >/dev/null; then --rules trailingSpace, blankLinesAtEndOfScope, consecutiveBlankLines, consecutiveSpaces, \ duplicateImports, initCoderUnavailable, isEmpty, leadingDelimiters, preferKeyPath, redundantBreak, \ redundantExtensionACL, redundantFileprivate, redundantGet, redundantLet, redundantLetError, \ - redundantNilInit, redundantParens, redundantPattern, redundantReturn, redundantVoidReturnType, semicolons, \ + redundantNilInit, redundantParens, redundantPattern, redundantVoidReturnType, semicolons, \ sortedImports, spaceAroundBraces, spaceAroundBrackets, spaceAroundGenerics, spaceInsideBraces, spaceInsideGenerics, \ strongifiedSelf, trailingClosures, void else @@ -23,3 +23,4 @@ else fi ################### RULES https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md +# ASCellNodeBlock - removed due to Opening Brace Spacing Violation when dealing with ASCellNodeBlock From 8863f7abe878d0b4279fd10da06e22b7353f4c6f Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Sun, 23 May 2021 22:32:47 +0300 Subject: [PATCH 02/26] Update Core tests and test scheme --- .../xcshareddata/xcschemes/FlowCrypt.xcscheme | 22 ++++--------------- FlowCrypt/Core/Core.swift | 3 ++- FlowCryptTests/FlowCryptCoreTests.swift | 11 +++++----- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme b/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme index 061c191e6..8b1a6f240 100644 --- a/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme +++ b/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme @@ -7,8 +7,8 @@ buildImplicitDependencies = "YES"> @@ -84,7 +84,8 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "NO" language = "en" - region = "GB"> + region = "GB" + codeCoverageEnabled = "YES"> - - - - - - diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index 472d26bbf..e390526fe 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -105,7 +105,7 @@ final class Core: KeyDecrypter { return try r.json.decodeJson(as: CoreRes.ZxcvbnStrengthBar.self) } - public func startInBackgroundIfNotAlreadyRunning() { + public func startInBackgroundIfNotAlreadyRunning(_ completion: (() -> Void)? = nil) { if !started { started = true DispatchQueue.global(qos: .default).async { [weak self] in @@ -127,6 +127,7 @@ final class Core: KeyDecrypter { self.context!.setObject(unsafeBitCast(cb_last_value_filler, to: AnyObject.self), forKeyedSubscript: "engine_host_cb_catcher" as (NSCopying & NSObjectProtocol)?) self.ready = true self.logger.logInfo("JsContext took \(trace.finish()) to start") + completion?() } } } diff --git a/FlowCryptTests/FlowCryptCoreTests.swift b/FlowCryptTests/FlowCryptCoreTests.swift index 8478d4c3f..7f23e42fd 100644 --- a/FlowCryptTests/FlowCryptCoreTests.swift +++ b/FlowCryptTests/FlowCryptCoreTests.swift @@ -15,11 +15,12 @@ class FlowCryptCoreTests: XCTestCase { super.setUp() // DispatchQueue.promises = .global() // this helps prevent Promise deadlocks - but currently Promises are not in use by tests core = Core.shared - core.startInBackgroundIfNotAlreadyRunning() - do { - try core.blockUntilReadyOrThrow() - } catch { - XCTFail("Core did not get ready in time") + core.startInBackgroundIfNotAlreadyRunning() { [weak self] in + do { + try self?.core.blockUntilReadyOrThrow() + } catch { + XCTFail("Core did not get ready in time") + } } } From 2123519135cac9412149f6683ad4f2f2e04eb3e5 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Sun, 23 May 2021 23:41:54 +0300 Subject: [PATCH 03/26] Move logic to CreatePrivateKeyViewController --- FlowCrypt.xcodeproj/project.pbxproj | 22 +- .../CreatePrivateKeyDecorator.swift | 36 +++ .../CreatePrivateKeyViewController.swift | 218 ++++++++++++++++++ .../EnterPassPhraseViewController.swift | 0 .../EnterPassPhraseViewDecorator.swift | 0 .../Import Key/ImportKeyViewController.swift | 0 .../Setup/SetupViewController.swift | 96 ++------ .../Setup/SetupViewDecorator.swift | 91 ++++---- .../UIViewControllerExtensions.swift | 21 -- 9 files changed, 340 insertions(+), 144 deletions(-) create mode 100644 FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift create mode 100644 FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift rename FlowCrypt/Controllers/{ImportKey => Key}/Enter Pass Phrase/EnterPassPhraseViewController.swift (100%) rename FlowCrypt/Controllers/{ImportKey => Key}/Import Key/EnterPassPhraseViewDecorator.swift (100%) rename FlowCrypt/Controllers/{ImportKey => Key}/Import Key/ImportKeyViewController.swift (100%) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index a35890bd4..106db748f 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -123,6 +123,8 @@ 9FA0157A26565B7800CBBA05 /* KeyMethodsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */; }; 9FA0158026565B9D00CBBA05 /* KeyMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F953E08238310D500AEB98B /* KeyMethods.swift */; }; 9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA1988F253C841F008C9CF2 /* TableViewController.swift */; }; + 9FA405C7265AEBA50084D133 /* CreatePrivateKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* CreatePrivateKeyViewController.swift */; }; + 9FA405CF265AEBC10084D133 /* CreatePrivateKeyDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405CE265AEBC10084D133 /* CreatePrivateKeyDecorator.swift */; }; 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA9C83B264C2D75005A9670 /* MessageService.swift */; }; 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */; }; 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */; }; @@ -498,6 +500,8 @@ 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContext.swift; sourceTree = ""; }; 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyMethodsTest.swift; sourceTree = ""; }; 9FA1988F253C841F008C9CF2 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; + 9FA405C6265AEBA40084D133 /* CreatePrivateKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePrivateKeyViewController.swift; sourceTree = ""; }; + 9FA405CE265AEBC10084D133 /* CreatePrivateKeyDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePrivateKeyDecorator.swift; sourceTree = ""; }; 9FA9C83B264C2D75005A9670 /* MessageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageService.swift; sourceTree = ""; }; 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceErrorHandler.swift; sourceTree = ""; }; 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceError.swift; sourceTree = ""; }; @@ -955,13 +959,13 @@ path = Resources; sourceTree = ""; }; - 9F268892237DC55E00428A94 /* ImportKey */ = { + 9F268892237DC55E00428A94 /* Key */ = { isa = PBXGroup; children = ( D29A0000240C133E00C1387D /* Import Key */, D29AFFFF240C133800C1387D /* Enter Pass Phrase */, ); - path = ImportKey; + path = Key; sourceTree = ""; }; 9F2A939F23363D2400014A92 /* Main */ = { @@ -1110,6 +1114,15 @@ path = "Message Provider"; sourceTree = ""; }; + 9FA405CD265AEBA90084D133 /* Create Private Key */ = { + isa = PBXGroup; + children = ( + 9FA405C6265AEBA40084D133 /* CreatePrivateKeyViewController.swift */, + 9FA405CE265AEBC10084D133 /* CreatePrivateKeyDecorator.swift */, + ); + path = "Create Private Key"; + sourceTree = ""; + }; 9FB22CB425715C860026EE64 /* Error Handling */ = { isa = PBXGroup; children = ( @@ -1353,7 +1366,7 @@ D2FF6969243115FE007182F0 /* SignIn Other */, 32DCA8D5AF0A43354CC7F58B /* SignIn */, C192421D1EC48B5600C3D251 /* Setup */, - 9F268892237DC55E00428A94 /* ImportKey */, + 9F268892237DC55E00428A94 /* Key */, D29AFFF12409301300C1387D /* Bootstrap */, C132B9D71EC30E0B00763715 /* Inbox */, 04B4728F1ECE29F600B8266F /* Compose */, @@ -1361,6 +1374,7 @@ D29AFFF02409300600C1387D /* Search */, 5A39F42E239EC32B001F4607 /* Settings */, D29A0001240C137700C1387D /* MessageList Extension */, + 9FA405CD265AEBA90084D133 /* Create Private Key */, ); path = Controllers; sourceTree = ""; @@ -2365,6 +2379,7 @@ D27B911924EFE79F002DF0A1 /* LocalContactsProvider.swift in Sources */, 9FE1B3942563F98600D6D086 /* Imap+MessagesList.swift in Sources */, 32DCA04CA0DAB79C39514782 /* CoreTypes.swift in Sources */, + 9FA405CF265AEBC10084D133 /* CreatePrivateKeyDecorator.swift in Sources */, 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */, 5ADEDCB923A42B9400EC495E /* KeyDetailViewDecorator.swift in Sources */, 5A39F437239ECC23001F4607 /* KeySettingsViewController.swift in Sources */, @@ -2432,6 +2447,7 @@ 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewControllerContainer.swift in Sources */, D227C0E3250538100070F805 /* LocalFoldersProvider.swift in Sources */, + 9FA405C7265AEBA50084D133 /* CreatePrivateKeyViewController.swift in Sources */, 9F6EE1552597399D0059BA51 /* BackupProvider.swift in Sources */, 9FEED1D2230DAD1E00700F8E /* InboxViewModel.swift in Sources */, 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */, diff --git a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift b/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift new file mode 100644 index 000000000..b7b417445 --- /dev/null +++ b/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift @@ -0,0 +1,36 @@ +// +// CreatePrivateKeyDecorator.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 23.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import FlowCryptCommon +import FlowCryptUI +import UIKit + +final class CreatePrivateKeyDecorator { + let insets = SetupViewInsets() + let textFieldStyle = SetupCommonStyle.passPhraseTextFieldStyle + + let title = "setup_title" + .localized + .attributed( + .bold(35), + color: .mainTextColor, + alignment: .center + ) + + let buttonTitle = "setup_create_key" + .localized + .attributed( + .regular(17), + color: .white, + alignment: .center + ) + + let subtitle = "setup_action_create_new_subtitle" + .localized + .attributed(.regular(17)) +} diff --git a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift b/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift new file mode 100644 index 000000000..f6122e1cd --- /dev/null +++ b/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift @@ -0,0 +1,218 @@ +// +// CreatePrivateKeyViewController.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 23.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import AsyncDisplayKit +import FlowCryptUI +import Promises + +enum CreateKeyError: Error { + // "Pass phrase strength: \(strength.word.word)\ncrack time: \(strength.time)\n\nWe recommend to use 5-6 unrelated words as your Pass Phrase.") + case weakPassPhrase(_ strength: CoreRes.ZxcvbnStrengthBar) + // Missing user email + case missedUserEmail + // Missing user name + case missedUserName + // Pass phrases don't match + case doesntMatch + // silent abort + case conformingPassPhraseError +} + +final class CreatePrivateKeyViewController: TableNodeViewController { + enum Parts: Int, CaseIterable { + case title, description, passPhrase, divider, action + } + + private let parts = Parts.allCases + private let decorator: CreatePrivateKeyDecorator + private let core: Core + private let router: GlobalRouterType + private let user: UserId + private let backupService: BackupServiceType + + init( + user: UserId, + backupService: BackupServiceType, + core: Core = .shared, + router: GlobalRouterType = GlobalRouter(), + decorator: CreatePrivateKeyDecorator = CreatePrivateKeyDecorator() + ) { + self.user = user + self.core = core + self.router = router + self.decorator = decorator + self.backupService = backupService + + super.init(node: TableNode()) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Setup + +extension CreatePrivateKeyViewController { + private func setupAccountWithGeneratedKey(with passPhrase: String) { + Promise { [weak self] in + guard let self = self else { return } + + let userId = try self.getUserId() + + try awaitPromise(self.validateAndConfirmNewPassPhraseOrReject(passPhrase: passPhrase)) + + let encryptedPrv = try self.core.generateKey(passphrase: passPhrase, variant: .curve25519, userIds: [userId]) + + try awaitPromise(self.backupService.backupToInbox(keys: [encryptedPrv.key], for: self.user)) + + try self.storePrvs(prvs: [encryptedPrv.key], passPhrase: passPhrase, source: .generated) + + let updateKey = self.attester.updateKey( + email: userId.email, + pubkey: encryptedPrv.key.public, + token: self.storage.token + ) + try awaitPromise(self.alertAndSkipOnRejection( + updateKey, + fail: "Failed to submit Public Key") + ) + let testWelcome = self.attester.testWelcome(email: userId.email, pubkey: encryptedPrv.key.public) + try awaitPromise(self.alertAndSkipOnRejection( + testWelcome, + fail: "Failed to send you welcome email") + ) + } + .then(on: .main) { [weak self] in + self?.moveToMainFlow() + } + .catch(on: .main) { [weak self] error in + guard let self = self else { return } + let isErrorHandled = self.handleCommon(error: error) + + if !isErrorHandled { + self.showAlert(error: error, message: "Could not finish setup, please try again") + } + } + } + + private func getUserId() throws -> UserId { + guard let email = DataService.shared.email, !email.isEmpty else { + throw CreateKeyError.missedUserEmail + } + guard let name = DataService.shared.email, !name.isEmpty else { + throw CreateKeyError.missedUserName + } + return UserId(email: email, name: name) + } + + private func validateAndConfirmNewPassPhraseOrReject(passPhrase: String) -> Promise { + Promise { [weak self] in + guard let self = self else { throw AppErr.nilSelf } + + let strength = try self.core.zxcvbnStrengthBar(passPhrase: passPhrase) + + guard strength.word.pass else { + throw CreateKeyError.weakPassPhrase(strength) + } + + let confirmPassPhrase = try awaitPromise(self.awaitUserPassPhraseEntry()) + + guard confirmPassPhrase != nil else { + throw CreateKeyError.conformingPassPhraseError + } + + guard confirmPassPhrase == passPhrase else { + throw CreateKeyError.doesntMatch + } + } + } + + private func awaitUserPassPhraseEntry() -> Promise { + Promise(on: .main) { [weak self] resolve, _ in + guard let self = self else { throw AppErr.nilSelf } + let alert = UIAlertController( + title: "Pass Phrase", + message: "Confirm Pass Phrase", + preferredStyle: .alert + ) + + alert.addTextField { textField in + textField.isSecureTextEntry = true + textField.accessibilityLabel = "textField" + } + + alert.addAction(UIAlertAction(title: "Cancel", style: .default) { _ in + resolve(nil) + }) + + alert.addAction(UIAlertAction(title: "OK", style: .default) { [weak alert] _ in + resolve(alert?.textFields?[0].text) + }) + + self.present(alert, animated: true, completion: nil) + } + } +} + +extension CreatePrivateKeyViewController { + private func moveToMainFlow() { + router.proceed() + } +} + +// MARK: - ASTableDelegate, ASTableDataSource + +extension CreatePrivateKeyViewController: ASTableDelegate, ASTableDataSource { + func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { + parts.count + } + + func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { + return { [weak self] in + guard let self = self, let part = Parts(rawValue: indexPath.row) else { return ASCellNode() } + switch part { + case .title: + return SetupTitleNode( + SetupTitleNode.Input( + title: self.decorator.title, + insets: self.decorator.insets.titleInset, + backgroundColor: .backgroundColor + ) + ) + case .description: + // TODO: - ANTON + // see choosing secure pass phrase + return ASCellNode() + case .passPhrase: + return TextFieldCellNode(input: self.decorator.textFieldStyle) { [weak self] action in + guard case let .didEndEditing(value) = action else { return } + + } + .then { + $0.becomeFirstResponder() + } + .onShouldReturn { [weak self] _ in + self?.view.endEditing(true) + + return true + } + case .action: + return ButtonCellNode( + title: self.decorator.buttonTitle, + insets: self.decorator.insets.buttonInsets + ) { [weak self] in + + } + case .divider: + return DividerCellNode(inset: self.decorator.insets.dividerInsets) + } + } + } +} diff --git a/FlowCrypt/Controllers/ImportKey/Enter Pass Phrase/EnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift similarity index 100% rename from FlowCrypt/Controllers/ImportKey/Enter Pass Phrase/EnterPassPhraseViewController.swift rename to FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift diff --git a/FlowCrypt/Controllers/ImportKey/Import Key/EnterPassPhraseViewDecorator.swift b/FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift similarity index 100% rename from FlowCrypt/Controllers/ImportKey/Import Key/EnterPassPhraseViewDecorator.swift rename to FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift diff --git a/FlowCrypt/Controllers/ImportKey/Import Key/ImportKeyViewController.swift b/FlowCrypt/Controllers/Key/Import Key/ImportKeyViewController.swift similarity index 100% rename from FlowCrypt/Controllers/ImportKey/Import Key/ImportKeyViewController.swift rename to FlowCrypt/Controllers/Key/Import Key/ImportKeyViewController.swift diff --git a/FlowCrypt/Controllers/Setup/SetupViewController.swift b/FlowCrypt/Controllers/Setup/SetupViewController.swift index 1f6d55c5d..714fe0b70 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupViewController.swift @@ -14,7 +14,7 @@ final class SetupViewController: TableNodeViewController { private let router: GlobalRouterType private let storage: DataServiceType & KeyDataServiceType - private let decorator: SetupViewDecoratorType + private let decorator: SetupViewDecorator private let core: Core private let keyMethods: KeyMethodsType private let attester: AttesterApiType @@ -39,8 +39,6 @@ final class SetupViewController: TableNodeViewController { case searchingBackups /// encrypted keys found case fetchedEncrypted([KeyDetails]) - /// creating new key - case createKey /// error state case error(SetupError) @@ -61,7 +59,7 @@ final class SetupViewController: TableNodeViewController { init( router: GlobalRouterType = GlobalRouter(), storage: DataServiceType & KeyDataServiceType = DataService.shared, - decorator: SetupViewDecoratorType = SetupViewDecorator(), + decorator: SetupViewDecorator = SetupViewDecorator(), core: Core = Core.shared, keyMethods: KeyMethodsType = KeyMethods(), attester: AttesterApiType = AttesterApi(), @@ -160,9 +158,6 @@ extension SetupViewController { case let .error(error): hideSpinner() handleError(with: error) - case .createKey: - hideSpinner() - handleCreateKey() } } @@ -198,10 +193,6 @@ extension SetupViewController { .becomeFirstResponder() } - private func handleCreateKey() { - reloadNodes() - } - private func reloadNodes() { let indexes = [ IndexPath(row: Parts.action.rawValue, section: 0), @@ -272,14 +263,14 @@ extension SetupViewController { title: "setup_action_import".localized, style: .default ) { [weak self] _ in - self?.handleImportKey() + self?.proceedToKeyImport() } let createNewPrivateKeyAction = UIAlertAction( title: "setup_action_create_new".localized, style: .default ) { [weak self] _ in - self?.state = .createKey + self?.proceedToCreatingNewKey() } alert.addAction(importAction) @@ -309,71 +300,28 @@ extension SetupViewController { moveToMainFlow() } - private func setupAccountWithGeneratedKey(with passPhrase: String) { - Promise { [weak self] in - guard let self = self else { return } - let userId = try self.getUserId() - try awaitPromise(self.validateAndConfirmNewPassPhraseOrReject(passPhrase: passPhrase)) - let encryptedPrv = try self.core.generateKey(passphrase: passPhrase, variant: .curve25519, userIds: [userId]) - try awaitPromise(self.backupService.backupToInbox(keys: [encryptedPrv.key], for: self.user)) - try self.storePrvs(prvs: [encryptedPrv.key], passPhrase: passPhrase, source: .generated) - - let updateKey = self.attester.updateKey( - email: userId.email, - pubkey: encryptedPrv.key.public, - token: self.storage.token - ) - try awaitPromise(self.alertAndSkipOnRejection( - updateKey, - fail: "Failed to submit Public Key") - ) - let testWelcome = self.attester.testWelcome(email: userId.email, pubkey: encryptedPrv.key.public) - try awaitPromise(self.alertAndSkipOnRejection( - testWelcome, - fail: "Failed to send you welcome email") - ) - } - .then(on: .main) { [weak self] in - self?.moveToMainFlow() - } - .catch(on: .main) { [weak self] error in - guard let self = self else { return } - let isErrorHandled = self.handleCommon(error: error) - - if !isErrorHandled { - self.showAlert(error: error, message: "Could not finish setup, please try again") - } - } + private func storePrvs(prvs: [KeyDetails], passPhrase: String, source: KeySource) throws { + storage.addKeys(keyDetails: prvs, passPhrase: passPhrase, source: source) } +} - private func validateAndConfirmNewPassPhraseOrReject(passPhrase: String) -> Promise { - Promise { - let strength = try self.core.zxcvbnStrengthBar(passPhrase: passPhrase) - guard strength.word.pass else { throw AppErr.user("Pass phrase strength: \(strength.word.word)\ncrack time: \(strength.time)\n\nWe recommend to use 5-6 unrelated words as your Pass Phrase.") } - let confirmPassPhrase = try awaitPromise(self.awaitUserPassPhraseEntry(title: "Confirm Pass Phrase")) - guard confirmPassPhrase != nil else { throw AppErr.silentAbort } - guard confirmPassPhrase == passPhrase else { throw AppErr.user("Pass phrases don't match") } - } - } +// MARK: - Navigation - private func getUserId() throws -> UserId { - guard let email = DataService.shared.email, !email.isEmpty else { throw AppErr.unexpected("Missing user email") } - guard let name = DataService.shared.email, !name.isEmpty else { throw AppErr.unexpected("Missing user name") } - return UserId(email: email, name: name) +extension SetupViewController { + private func proceedToKeyImport() { + hideSpinner() + let viewController = ImportKeyViewController() + navigationController?.pushViewController(viewController, animated: true) } - private func storePrvs(prvs: [KeyDetails], passPhrase: String, source: KeySource) throws { - storage.addKeys(keyDetails: prvs, passPhrase: passPhrase, source: source) + private func proceedToCreatingNewKey() { + hideSpinner() } } // MARK: - Events extension SetupViewController { - private func handleImportKey() { - let viewController = ImportKeyViewController() - navigationController?.pushViewController(viewController, animated: true) - } private func handleButtonPressed() { // ignore if we are still fetching keys @@ -395,8 +343,6 @@ extension SetupViewController { // https://github.com/FlowCrypt/flowcrypt-ios/issues/291 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { switch self.state { - case .createKey: - self.setupAccountWithGeneratedKey(with: passPhrase) case let .fetchedEncrypted(backups): self.recoverAccount(with: backups, and: passPhrase) default: @@ -429,7 +375,7 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { return SetupTitleNode( SetupTitleNode.Input( title: self.decorator.title, - insets: self.decorator.titleInset, + insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) ) @@ -437,7 +383,7 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { return SetupTitleNode( SetupTitleNode.Input( title: self.decorator.subtitle(for: self.state), - insets: self.decorator.subTitleInset, + insets: self.decorator.insets.subTitleInset, backgroundColor: .backgroundColor ) ) @@ -456,8 +402,8 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { } case .action: return ButtonCellNode( - title: self.decorator.buttonTitle(for: self.state), - insets: self.decorator.buttonInsets + title: self.decorator.buttonTitle, + insets: self.decorator.insets.buttonInsets ) { [weak self] in self?.handleButtonPressed() } @@ -467,13 +413,13 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { case .optionalAction: return ButtonCellNode( title: self.decorator.useAnotherAccountTitle, - insets: self.decorator.optionalButtonInsets, + insets: self.decorator.insets.optionalButtonInsets, color: .white ) { [weak self] in self?.handleOtherAccount() } case .divider: - return DividerCellNode(inset: UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24)) + return DividerCellNode(inset: self.decorator.insets.dividerInsets) } } } diff --git a/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift b/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift index 8f09f59da..e764268c8 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift +++ b/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift @@ -10,56 +10,67 @@ import FlowCryptCommon import FlowCryptUI import UIKit -protocol SetupViewDecoratorType { - var title: NSAttributedString { get } - var useAnotherAccountTitle: NSAttributedString { get } - var titleInset: UIEdgeInsets { get } - var subTitleInset: UIEdgeInsets { get } - var buttonInsets: UIEdgeInsets { get } - var optionalButtonInsets: UIEdgeInsets { get } - var textFieldStyle: TextFieldCellNode.Input { get } +struct SetupViewInsets { + let titleInset = UIEdgeInsets(top: 92, left: 16, bottom: 20, right: 16) + let subTitleInset = UIEdgeInsets(top: 0, left: 16, bottom: 60, right: 16) + let buttonInsets = UIEdgeInsets(top: 80, left: 24, bottom: 8, right: 24) + let optionalButtonInsets = UIEdgeInsets(top: 0, left: 24, bottom: 8, right: 24) + let dividerInsets = UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24) +} - func buttonTitle(for state: SetupViewController.State) -> NSAttributedString - func subtitle(for state: SetupViewController.State) -> NSAttributedString +enum SetupCommonStyle { + static let passPhraseTextFieldStyle: TextFieldCellNode.Input = TextFieldCellNode.Input( + placeholder: "setup_enter" + .localized + .attributed( + .bold(16), + color: .lightGray, + alignment: .center + ), + isSecureTextEntry: true, + textInsets: 0, + textAlignment: .center, + insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + ) } -struct SetupViewDecorator: SetupViewDecoratorType { - let textFieldStyle = SetupCommonStyle.passPhraseTextFieldStyle +struct SetupViewDecorator { + let insets = SetupViewInsets() - var title: NSAttributedString { - "setup_title".localized.attributed(.bold(35), color: .mainTextColor, alignment: .center) - } + let textFieldStyle = SetupCommonStyle.passPhraseTextFieldStyle - var useAnotherAccountTitle: NSAttributedString { - "setup_use_another".localized.attributed( - .regular(15), - color: UIColor.colorFor(darkStyle: .black, lightStyle: .blueColor), + let title = "setup_title" + .localized + .attributed( + .bold(35), + color: .mainTextColor, alignment: .center ) - } - let titleInset = UIEdgeInsets(top: 92, left: 16, bottom: 20, right: 16) - let subTitleInset = UIEdgeInsets(top: 0, left: 16, bottom: 60, right: 16) - let buttonInsets = UIEdgeInsets(top: 80, left: 24, bottom: 8, right: 24) - let optionalButtonInsets = UIEdgeInsets(top: 0, left: 24, bottom: 8, right: 24) - - func buttonTitle(for state: SetupViewController.State) -> NSAttributedString { - let title: String - switch state { - case .createKey: title = "setup_create_key" - default: title = "setup_load" - } + let buttonTitle = "setup_load" + .localized + .attributed( + .regular(17), + color: .white, + alignment: .center + ) - return title.localized.attributed(.regular(17), color: .white, alignment: .center) - } + let useAnotherAccountTitle = "setup_use_another" + .localized + .attributed( + .regular(15), + color: UIColor.colorFor( + darkStyle: .black, + lightStyle: .blueColor + ), + alignment: .center + ) func subtitle(for state: SetupViewController.State) -> NSAttributedString { let subtitle: String = { switch state { case let .fetchedEncrypted(keys): return "Found \(keys.count) key backup\(keys.count > 1 ? "s" : "")" - case .createKey: - return "setup_action_create_new_subtitle".localized default: return "setup_description".localized } @@ -68,13 +79,3 @@ struct SetupViewDecorator: SetupViewDecoratorType { return subtitle.attributed(.regular(17)) } } - -enum SetupCommonStyle { - static let passPhraseTextFieldStyle: TextFieldCellNode.Input = TextFieldCellNode.Input( - placeholder: "setup_enter".localized.attributed(.bold(16), color: .lightGray, alignment: .center), - isSecureTextEntry: true, - textInsets: 0, - textAlignment: .center, - insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) - ) -} diff --git a/FlowCrypt/Extensions/UIViewControllerExtensions.swift b/FlowCrypt/Extensions/UIViewControllerExtensions.swift index cd4464305..261229796 100644 --- a/FlowCrypt/Extensions/UIViewControllerExtensions.swift +++ b/FlowCrypt/Extensions/UIViewControllerExtensions.swift @@ -141,27 +141,6 @@ extension UIViewController { } } - // TODO: - ANTON - func awaitUserPassPhraseEntry(title: String) -> Promise { - Promise(on: .main) { [weak self] resolve, _ in - guard let self = self else { throw AppErr.nilSelf } - let alert = UIAlertController(title: "Pass Phrase", message: title, preferredStyle: .alert) - alert.addTextField { textField in - textField.isSecureTextEntry = true - textField.accessibilityLabel = "textField" - } - - alert.addAction(UIAlertAction(title: "Cancel", style: .default) { _ in - resolve(nil) - }) - - alert.addAction(UIAlertAction(title: "OK", style: .default) { [weak alert] _ in - resolve(alert?.textFields?[0].text) - }) - self.present(alert, animated: true, completion: nil) - } - } - func keyboardHeight(from notification: Notification) -> CGFloat { (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height ?? 0 } From 5685c728af3e28ae55ed6a15f9043fe0f6472c6b Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Wed, 26 May 2021 15:45:24 +0300 Subject: [PATCH 04/26] Add UI for CreatePrivateKeyViewController --- .../CreatePrivateKeyDecorator.swift | 12 +- .../CreatePrivateKeyViewController.swift | 134 ++++++++++++++---- .../BackupOptionsViewController.swift | 2 +- .../Backups Scene/BackupViewController.swift | 2 +- .../BackupSelectKeyViewController.swift | 2 +- .../Setup/SetupViewController.swift | 21 ++- .../Backup Services/BackupService.swift | 28 ++-- .../Resources/en.lproj/Localizable.strings | 5 + 8 files changed, 148 insertions(+), 58 deletions(-) diff --git a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift b/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift index b7b417445..fcdf892b9 100644 --- a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift +++ b/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift @@ -22,7 +22,7 @@ final class CreatePrivateKeyDecorator { alignment: .center ) - let buttonTitle = "setup_create_key" + let buttonTitle = "create_pass_phrase_set_title" .localized .attributed( .regular(17), @@ -30,7 +30,15 @@ final class CreatePrivateKeyDecorator { alignment: .center ) - let subtitle = "setup_action_create_new_subtitle" + let subtitle = "create_pass_phrase_description" .localized .attributed(.regular(17)) + + let optionalDescription = "Lost pass phrase cannot be recovered" + .localized + .attributed( + .regular(16), + color: .lightGray, + alignment: .center + ) } diff --git a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift b/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift index f6122e1cd..98dd36caa 100644 --- a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift +++ b/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift @@ -25,7 +25,7 @@ enum CreateKeyError: Error { final class CreatePrivateKeyViewController: TableNodeViewController { enum Parts: Int, CaseIterable { - case title, description, passPhrase, divider, action + case title, description, passPhrase, divider, action, subtitle } private let parts = Parts.allCases @@ -34,19 +34,25 @@ final class CreatePrivateKeyViewController: TableNodeViewController { private let router: GlobalRouterType private let user: UserId private let backupService: BackupServiceType + private let storage: DataServiceType & KeyDataServiceType + private let attester: AttesterApiType init( user: UserId, - backupService: BackupServiceType, + backupService: BackupServiceType = BackupService(), core: Core = .shared, router: GlobalRouterType = GlobalRouter(), - decorator: CreatePrivateKeyDecorator = CreatePrivateKeyDecorator() + decorator: CreatePrivateKeyDecorator = CreatePrivateKeyDecorator(), + storage: DataServiceType & KeyDataServiceType = DataService.shared, + attester: AttesterApiType = AttesterApi() ) { self.user = user self.core = core self.router = router self.decorator = decorator self.backupService = backupService + self.storage = storage + self.attester = attester super.init(node: TableNode()) } @@ -55,6 +61,48 @@ final class CreatePrivateKeyViewController: TableNodeViewController { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } +} + +// MARK: - UI + +extension CreatePrivateKeyViewController { + private func setupUI() { + node.delegate = self + node.dataSource = self + observeKeyboardNotifications() + } + + // TODO: - ANTON - Unify this logic for all controllers + // swiftlint:disable discarded_notification_center_observer + private func observeKeyboardNotifications() { + NotificationCenter.default.addObserver( + forName: UIResponder.keyboardWillShowNotification, + object: nil, + queue: .main + ) { [weak self] notification in + guard let self = self else { return } + self.adjustForKeyboard(height: self.keyboardHeight(from: notification)) + } + + NotificationCenter.default.addObserver( + forName: UIResponder.keyboardWillHideNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.adjustForKeyboard(height: 0) + } + } + + private func adjustForKeyboard(height: CGFloat) { + let insets = UIEdgeInsets(top: 0, left: 0, bottom: height + 5, right: 0) + node.contentInset = insets + node.scrollToRow(at: IndexPath(item: Parts.passPhrase.rawValue, section: 0), at: .middle, animated: true) + } } // MARK: - Setup @@ -63,17 +111,17 @@ extension CreatePrivateKeyViewController { private func setupAccountWithGeneratedKey(with passPhrase: String) { Promise { [weak self] in guard let self = self else { return } - + let userId = try self.getUserId() - + try awaitPromise(self.validateAndConfirmNewPassPhraseOrReject(passPhrase: passPhrase)) - + let encryptedPrv = try self.core.generateKey(passphrase: passPhrase, variant: .curve25519, userIds: [userId]) - + try awaitPromise(self.backupService.backupToInbox(keys: [encryptedPrv.key], for: self.user)) - - try self.storePrvs(prvs: [encryptedPrv.key], passPhrase: passPhrase, source: .generated) - + + self.storage.addKeys(keyDetails: [encryptedPrv.key], passPhrase: passPhrase, source: .generated) + let updateKey = self.attester.updateKey( email: userId.email, pubkey: encryptedPrv.key.public, @@ -95,13 +143,13 @@ extension CreatePrivateKeyViewController { .catch(on: .main) { [weak self] error in guard let self = self else { return } let isErrorHandled = self.handleCommon(error: error) - + if !isErrorHandled { self.showAlert(error: error, message: "Could not finish setup, please try again") } } } - + private func getUserId() throws -> UserId { guard let email = DataService.shared.email, !email.isEmpty else { throw CreateKeyError.missedUserEmail @@ -111,29 +159,29 @@ extension CreatePrivateKeyViewController { } return UserId(email: email, name: name) } - + private func validateAndConfirmNewPassPhraseOrReject(passPhrase: String) -> Promise { Promise { [weak self] in guard let self = self else { throw AppErr.nilSelf } - + let strength = try self.core.zxcvbnStrengthBar(passPhrase: passPhrase) - + guard strength.word.pass else { throw CreateKeyError.weakPassPhrase(strength) } - + let confirmPassPhrase = try awaitPromise(self.awaitUserPassPhraseEntry()) - + guard confirmPassPhrase != nil else { throw CreateKeyError.conformingPassPhraseError } - + guard confirmPassPhrase == passPhrase else { throw CreateKeyError.doesntMatch } } } - + private func awaitUserPassPhraseEntry() -> Promise { Promise(on: .main) { [weak self] resolve, _ in guard let self = self else { throw AppErr.nilSelf } @@ -142,7 +190,7 @@ extension CreatePrivateKeyViewController { message: "Confirm Pass Phrase", preferredStyle: .alert ) - + alert.addTextField { textField in textField.isSecureTextEntry = true textField.accessibilityLabel = "textField" @@ -155,7 +203,7 @@ extension CreatePrivateKeyViewController { alert.addAction(UIAlertAction(title: "OK", style: .default) { [weak alert] _ in resolve(alert?.textFields?[0].text) }) - + self.present(alert, animated: true, completion: nil) } } @@ -165,6 +213,11 @@ extension CreatePrivateKeyViewController { private func moveToMainFlow() { router.proceed() } + + private func showChoosingOptions() { + // + showToast("Not implemented yet") + } } // MARK: - ASTableDelegate, ASTableDataSource @@ -187,32 +240,53 @@ extension CreatePrivateKeyViewController: ASTableDelegate, ASTableDataSource { ) ) case .description: - // TODO: - ANTON - // see choosing secure pass phrase - return ASCellNode() + return SetupTitleNode( + SetupTitleNode.Input( + title: self.decorator.subtitle, + insets: self.decorator.insets.subTitleInset, + backgroundColor: .backgroundColor + ) + ) case .passPhrase: return TextFieldCellNode(input: self.decorator.textFieldStyle) { [weak self] action in guard case let .didEndEditing(value) = action else { return } - - } - .then { - $0.becomeFirstResponder() } .onShouldReturn { [weak self] _ in self?.view.endEditing(true) - + return true } + .then { + $0.becomeFirstResponder() + } case .action: return ButtonCellNode( title: self.decorator.buttonTitle, insets: self.decorator.insets.buttonInsets ) { [weak self] in - } + case .subtitle: + return SetupTitleNode( + SetupTitleNode.Input( + title: self.decorator.optionalDescription, + insets: .side(8), + backgroundColor: .backgroundColor + ) + ) case .divider: return DividerCellNode(inset: self.decorator.insets.dividerInsets) } } } + + func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { + guard let part = Parts(rawValue: indexPath.row) else { return } + + switch part { + case .description: + showChoosingOptions() + default: + break + } + } } diff --git a/FlowCrypt/Controllers/Settings/Backup/Backups Option Scene/BackupOptionsViewController.swift b/FlowCrypt/Controllers/Settings/Backup/Backups Option Scene/BackupOptionsViewController.swift index 2a0d64d3f..7b78a92ea 100644 --- a/FlowCrypt/Controllers/Settings/Backup/Backups Option Scene/BackupOptionsViewController.swift +++ b/FlowCrypt/Controllers/Settings/Backup/Backups Option Scene/BackupOptionsViewController.swift @@ -36,7 +36,7 @@ final class BackupOptionsViewController: ASDKViewController { init( decorator: BackupOptionsViewDecoratorType = BackupOptionsViewDecorator(), - backupService: BackupServiceType = BackupService.shared, + backupService: BackupServiceType = BackupService(), backups: [KeyDetails], userId: UserId ) { diff --git a/FlowCrypt/Controllers/Settings/Backup/Backups Scene/BackupViewController.swift b/FlowCrypt/Controllers/Settings/Backup/Backups Scene/BackupViewController.swift index a32368891..5c3278ea3 100644 --- a/FlowCrypt/Controllers/Settings/Backup/Backups Scene/BackupViewController.swift +++ b/FlowCrypt/Controllers/Settings/Backup/Backups Scene/BackupViewController.swift @@ -41,7 +41,7 @@ final class BackupViewController: ASDKViewController { init( decorator: BackupViewDecoratorType = BackupViewDecorator(), - backupProvider: BackupServiceType = BackupService.shared, + backupProvider: BackupServiceType = BackupService(), userId: UserId ) { self.decorator = decorator diff --git a/FlowCrypt/Controllers/Settings/Backup/Backups Seleckt Key Scene/BackupSelectKeyViewController.swift b/FlowCrypt/Controllers/Settings/Backup/Backups Seleckt Key Scene/BackupSelectKeyViewController.swift index ad5eef482..f6a31314b 100644 --- a/FlowCrypt/Controllers/Settings/Backup/Backups Seleckt Key Scene/BackupSelectKeyViewController.swift +++ b/FlowCrypt/Controllers/Settings/Backup/Backups Seleckt Key Scene/BackupSelectKeyViewController.swift @@ -19,7 +19,7 @@ final class BackupSelectKeyViewController: ASDKViewController { init( decorator: BackupSelectKeyDecoratorType = BackupSelectKeyDecorator(), - backupService: BackupServiceType = BackupService.shared, + backupService: BackupServiceType = BackupService(), selectedOption: BackupOption, backups: [KeyDetails], userId: UserId diff --git a/FlowCrypt/Controllers/Setup/SetupViewController.swift b/FlowCrypt/Controllers/Setup/SetupViewController.swift index 714fe0b70..83b65ce0e 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupViewController.swift @@ -17,7 +17,6 @@ final class SetupViewController: TableNodeViewController { private let decorator: SetupViewDecorator private let core: Core private let keyMethods: KeyMethodsType - private let attester: AttesterApiType private let backupService: BackupServiceType private let user: UserId @@ -62,8 +61,7 @@ final class SetupViewController: TableNodeViewController { decorator: SetupViewDecorator = SetupViewDecorator(), core: Core = Core.shared, keyMethods: KeyMethodsType = KeyMethods(), - attester: AttesterApiType = AttesterApi(), - backupService: BackupServiceType = BackupService.shared, + backupService: BackupServiceType = BackupService(), user: UserId ) { self.router = router @@ -71,7 +69,6 @@ final class SetupViewController: TableNodeViewController { self.decorator = decorator self.core = core self.keyMethods = keyMethods - self.attester = attester self.backupService = backupService self.user = user @@ -292,16 +289,9 @@ extension SetupViewController { return } - do { - try storePrvs(prvs: matchingKeyBackups, passPhrase: passPhrase, source: .backup) - } catch { - fatalError() - } - moveToMainFlow() - } + storage.addKeys(keyDetails: matchingKeyBackups, passPhrase: passPhrase, source: .backup) - private func storePrvs(prvs: [KeyDetails], passPhrase: String, source: KeySource) throws { - storage.addKeys(keyDetails: prvs, passPhrase: passPhrase, source: source) + moveToMainFlow() } } @@ -316,6 +306,8 @@ extension SetupViewController { private func proceedToCreatingNewKey() { hideSpinner() + let viewController = CreatePrivateKeyViewController(user: user) + navigationController?.pushViewController(viewController, animated: true) } } @@ -448,3 +440,6 @@ the user should see two radio buttons: If app gets killed, pass phrase gets forgotten. */ + +// TODO: - ANTON +// - make intermediate screen to distinguish which flow to show (setup/create) diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift index 2d789521a..979180736 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift @@ -19,22 +19,28 @@ protocol BackupServiceType { } // MARK: - BackupService -struct BackupService { - static let shared: BackupService = BackupService( - backupProvider: MailProvider.shared.backupProvider, - core: Core.shared, - messageSender: MailProvider.shared.messageSender - ) - +final class BackupService { let backupProvider: BackupProvider let core: Core let messageSender: MessageGateway + + init( + backupProvider: BackupProvider = MailProvider.shared.backupProvider, + core: Core = .shared, + messageSender: MessageGateway = MailProvider.shared.messageSender + ) { + self.backupProvider = backupProvider + self.core = core + self.messageSender = messageSender + } } // MARK: - BackupServiceType extension BackupService: BackupServiceType { func fetchBackups(for userId: UserId) -> Promise<[KeyDetails]> { - Promise<[KeyDetails]> { resolve, reject in + Promise<[KeyDetails]> { [weak self] resolve, reject in + guard let self = self else { throw AppErr.nilSelf } + let backupData = try awaitPromise(self.backupProvider.searchBackups(for: userId.email)) do { @@ -48,7 +54,9 @@ extension BackupService: BackupServiceType { } func backupToInbox(keys: [KeyDetails], for userId: UserId) -> Promise { - Promise { () -> Void in + Promise { [weak self] () -> Void in + guard let self = self else { throw AppErr.nilSelf } + let isFullyEncryptedKeys = keys.map(\.isFullyDecrypted).contains(false) guard isFullyEncryptedKeys else { @@ -74,7 +82,7 @@ extension BackupService: BackupServiceType { atts: attachments ) let backupEmail = try self.core.composeEmail(msg: message, fmt: .plain, pubKeys: nil) - try awaitPromise(messageSender.sendMail(mime: backupEmail.mimeEncoded)) + try awaitPromise(self.messageSender.sendMail(mime: backupEmail.mimeEncoded)) } } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 2b8723717..38e16e84b 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -99,6 +99,11 @@ "import_key_add_new" = "Keys imported: %@"; "import_key_add_update" = "Keys updated: %@"; +// Key Create +"create_pass_phrase_description" = "See choosing secure pass phrase ?"; +"create_pass_phrase_set_title" = "Set pass phrase"; +"create_pass_phrase_lost" = "Lost pass phrase cannot be recovered"; + // Search "search_title" = "Search"; "search_placeholder" = "Search"; From d047a84d18f14df14ce0bb907855792165227bad Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Wed, 26 May 2021 23:11:26 +0300 Subject: [PATCH 05/26] wip separate setup logic into different controllers --- FlowCrypt.xcodeproj/project.pbxproj | 14 +- FlowCrypt/App/Logger.swift | 3 + .../CreatePrivateKeyDecorator.swift | 44 ----- .../CreatePrivateKeyDecorator.swift | 12 ++ .../EnterPassPhraseViewController.swift | 2 +- .../Setup/SetupInitialViewController.swift | 160 ++++++++++++++++++ .../SetupKeyViewController.swift} | 34 ++-- .../Setup/SetupViewController.swift | 127 +++----------- .../Setup/SetupViewDecorator.swift | 100 +++++++---- 9 files changed, 284 insertions(+), 212 deletions(-) delete mode 100644 FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift create mode 100644 FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift create mode 100644 FlowCrypt/Controllers/Setup/SetupInitialViewController.swift rename FlowCrypt/Controllers/{Create Private Key/CreatePrivateKeyViewController.swift => Setup/SetupKeyViewController.swift} (90%) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 106db748f..63733e6b8 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -86,6 +86,7 @@ 9F3EF32B23B16ADE00FA0CEF /* CommonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0C3C2723194E8500299985 /* CommonExtensions.swift */; }; 9F3EF32F23B172D300FA0CEF /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EF32E23B172D300FA0CEF /* SearchViewController.swift */; }; 9F3EF33123B1785600FA0CEF /* MsgListViewConroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EF33023B1785600FA0CEF /* MsgListViewConroller.swift */; }; + 9F4163B8265ED61C00106194 /* SetupInitialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */; }; 9F41FA28253B75F4003B970D /* BackupSelectKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41FA27253B75F4003B970D /* BackupSelectKeyViewController.swift */; }; 9F41FA2F253B7624003B970D /* BackupSelectKeyDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41FA2E253B7624003B970D /* BackupSelectKeyDecorator.swift */; }; 9F4300CC2571045B00791CFB /* InboxViewControllerContainerDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4300CB2571045B00791CFB /* InboxViewControllerContainerDecorator.swift */; }; @@ -123,7 +124,7 @@ 9FA0157A26565B7800CBBA05 /* KeyMethodsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */; }; 9FA0158026565B9D00CBBA05 /* KeyMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F953E08238310D500AEB98B /* KeyMethods.swift */; }; 9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA1988F253C841F008C9CF2 /* TableViewController.swift */; }; - 9FA405C7265AEBA50084D133 /* CreatePrivateKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* CreatePrivateKeyViewController.swift */; }; + 9FA405C7265AEBA50084D133 /* SetupKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */; }; 9FA405CF265AEBC10084D133 /* CreatePrivateKeyDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405CE265AEBC10084D133 /* CreatePrivateKeyDecorator.swift */; }; 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA9C83B264C2D75005A9670 /* MessageService.swift */; }; 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */; }; @@ -448,6 +449,7 @@ 9F3EF32923B15C9500FA0CEF /* ImapHelperTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImapHelperTest.swift; sourceTree = ""; }; 9F3EF32E23B172D300FA0CEF /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; 9F3EF33023B1785600FA0CEF /* MsgListViewConroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgListViewConroller.swift; sourceTree = ""; }; + 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupInitialViewController.swift; sourceTree = ""; }; 9F41FA27253B75F4003B970D /* BackupSelectKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSelectKeyViewController.swift; sourceTree = ""; }; 9F41FA2E253B7624003B970D /* BackupSelectKeyDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSelectKeyDecorator.swift; sourceTree = ""; }; 9F4300CB2571045B00791CFB /* InboxViewControllerContainerDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxViewControllerContainerDecorator.swift; sourceTree = ""; }; @@ -500,7 +502,7 @@ 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContext.swift; sourceTree = ""; }; 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyMethodsTest.swift; sourceTree = ""; }; 9FA1988F253C841F008C9CF2 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; - 9FA405C6265AEBA40084D133 /* CreatePrivateKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePrivateKeyViewController.swift; sourceTree = ""; }; + 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupKeyViewController.swift; sourceTree = ""; }; 9FA405CE265AEBC10084D133 /* CreatePrivateKeyDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePrivateKeyDecorator.swift; sourceTree = ""; }; 9FA9C83B264C2D75005A9670 /* MessageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageService.swift; sourceTree = ""; }; 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceErrorHandler.swift; sourceTree = ""; }; @@ -962,6 +964,7 @@ 9F268892237DC55E00428A94 /* Key */ = { isa = PBXGroup; children = ( + 9FA405CD265AEBA90084D133 /* Create Private Key */, D29A0000240C133E00C1387D /* Import Key */, D29AFFFF240C133800C1387D /* Enter Pass Phrase */, ); @@ -1117,7 +1120,6 @@ 9FA405CD265AEBA90084D133 /* Create Private Key */ = { isa = PBXGroup; children = ( - 9FA405C6265AEBA40084D133 /* CreatePrivateKeyViewController.swift */, 9FA405CE265AEBC10084D133 /* CreatePrivateKeyDecorator.swift */, ); path = "Create Private Key"; @@ -1374,7 +1376,6 @@ D29AFFF02409300600C1387D /* Search */, 5A39F42E239EC32B001F4607 /* Settings */, D29A0001240C137700C1387D /* MessageList Extension */, - 9FA405CD265AEBA90084D133 /* Create Private Key */, ); path = Controllers; sourceTree = ""; @@ -1406,7 +1407,9 @@ C192421D1EC48B5600C3D251 /* Setup */ = { isa = PBXGroup; children = ( + 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */, C192421E1EC48B6900C3D251 /* SetupViewController.swift */, + 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */, 9F17976C2368EEBD002BF770 /* SetupViewDecorator.swift */, ); path = Setup; @@ -2346,6 +2349,7 @@ 9F0C3C2623194E0A00299985 /* FolderViewModel.swift in Sources */, 21CE25E62650070300ADFF4B /* WKDURLsConstructor.swift in Sources */, 9FC411212595EA12001180A8 /* MessageSearchProvider.swift in Sources */, + 9F4163B8265ED61C00106194 /* SetupInitialViewController.swift in Sources */, D27B911D24EFE806002DF0A1 /* ContactObject.swift in Sources */, 9FDF3654235A218E00614596 /* main.swift in Sources */, D2E26F6C24F25B1F00612AF1 /* KeyAlgo.swift in Sources */, @@ -2447,7 +2451,7 @@ 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewControllerContainer.swift in Sources */, D227C0E3250538100070F805 /* LocalFoldersProvider.swift in Sources */, - 9FA405C7265AEBA50084D133 /* CreatePrivateKeyViewController.swift in Sources */, + 9FA405C7265AEBA50084D133 /* SetupKeyViewController.swift in Sources */, 9F6EE1552597399D0059BA51 /* BackupProvider.swift in Sources */, 9FEED1D2230DAD1E00700F8E /* InboxViewModel.swift in Sources */, 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */, diff --git a/FlowCrypt/App/Logger.swift b/FlowCrypt/App/Logger.swift index fd0449892..9776c198a 100644 --- a/FlowCrypt/App/Logger.swift +++ b/FlowCrypt/App/Logger.swift @@ -165,6 +165,9 @@ extension Logger { /// Core related logs case core = "Core" + + /// Setup Flow logs + case setup = "Setup" } static func nested(in type: T.Type, with logLabel: LogLabels) -> Logger { diff --git a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift b/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift deleted file mode 100644 index fcdf892b9..000000000 --- a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyDecorator.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// CreatePrivateKeyDecorator.swift -// FlowCrypt -// -// Created by Anton Kharchevskyi on 23.05.2021. -// Copyright © 2021 FlowCrypt Limited. All rights reserved. -// - -import FlowCryptCommon -import FlowCryptUI -import UIKit - -final class CreatePrivateKeyDecorator { - let insets = SetupViewInsets() - let textFieldStyle = SetupCommonStyle.passPhraseTextFieldStyle - - let title = "setup_title" - .localized - .attributed( - .bold(35), - color: .mainTextColor, - alignment: .center - ) - - let buttonTitle = "create_pass_phrase_set_title" - .localized - .attributed( - .regular(17), - color: .white, - alignment: .center - ) - - let subtitle = "create_pass_phrase_description" - .localized - .attributed(.regular(17)) - - let optionalDescription = "Lost pass phrase cannot be recovered" - .localized - .attributed( - .regular(16), - color: .lightGray, - alignment: .center - ) -} diff --git a/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift b/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift new file mode 100644 index 000000000..b025dac83 --- /dev/null +++ b/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift @@ -0,0 +1,12 @@ +// +// CreatePrivateKeyDecorator.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 23.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import FlowCryptCommon +import FlowCryptUI +import UIKit + diff --git a/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift index 0d116fcb5..ee731e41e 100644 --- a/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift @@ -119,7 +119,7 @@ extension EnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { } func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { - { [weak self] in + return { [weak self] in guard let self = self, let part = Parts(rawValue: indexPath.row) else { return ASCellNode() } switch part { case .title: diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift new file mode 100644 index 000000000..9aa0b040d --- /dev/null +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -0,0 +1,160 @@ +// +// SetupInitialViewController.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 26.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import AsyncDisplayKit +import FlowCryptUI +import Promises + +struct SetupInitialViewDecorator { + let insets = SetupViewInsets() +} + +// TODO: - ANTON +// - make intermediate screen to distinguish which flow to show (with backups/without backups) +// - searching will search keys +// - result will show createKey, importKey, anotherAccount +final class SetupInitialViewController: TableNodeViewController { + private enum Parts: Int, CaseIterable { + case title, createKey, importKey, anotherAccount + } + + private enum State { + case idle, searching, noKeyBackups + } + + private var state = State.idle { + didSet { + node.reloadData() + } + } + + private let backupService: BackupServiceType + private let user: UserId + private let router: GlobalRouterType + private let decorator: SetupViewDecorator + + private lazy var logger = Logger.nested(in: Self.self, with: .setup) + + init( + user: UserId, + backupService: BackupServiceType = BackupService(), + router: GlobalRouterType = GlobalRouter(), + decorator: SetupViewDecorator = SetupViewDecorator() + ) { + self.user = user + self.backupService = backupService + self.router = router + self.decorator = decorator + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + + showSpinner() + searchBackups() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + state = .searching + } + + private func setupUI() { + node.delegate = self + node.dataSource = self + } +} + +extension SetupInitialViewController { + private func searchBackups() { + logger.logInfo("[Setup] searching for backups in inbox") + + backupService.fetchBackups(for: user) + .then(on: .main) { [weak self] keys in + self?.proceedToSetupWith(keys: keys) + } + .catch(on: .main) { [weak self] error in + self?.handleCommon(error: error) + } + } + + private func handleOtherAccount() { + router.signOut() + } + + private func proceedToSetupWith(keys: [KeyDetails]) { + logger.logInfo("Done searching for backups in inbox") + + let viewController: UIViewController + if keys.isEmpty { + logger.logInfo("No key backups found in inbox") + state = .noKeyBackups + } else { + logger.logInfo("\(keys.count) key backups found in inbox") + let viewController = SetupViewController(fetchedEncryptedKeys: keys, user: user) + navigationController?.pushViewController(viewController, animated: true) + } + } +} + +extension SetupInitialViewController: ASTableDelegate, ASTableDataSource { + func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { + switch state { + case .idle, .searching: + return 1 + case .noKeyBackups: + return Parts.allCases.count + } + } + + func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { + return { [weak self] in + guard let self = self else { return ASCellNode() } + + guard case .noKeyBackups = self.state else { + // TODO: - ANTON return spinner node + return ASCellNode() + } + + guard let part = Parts(rawValue: indexPath.row) else { return ASCellNode() } + + switch part { + case .title: + return SetupTitleNode( + SetupTitleNode.Input( + title: self.decorator., + insets: self.decorator.insets.titleInset, + backgroundColor: .backgroundColor + ) + ) + case .createKey: + return ButtonCellNode( + title: self.decorator.buttonTitle, + insets: self.decorator.insets.buttonInsets + ) { [weak self] in + + } + case .anotherAccount: + return ButtonCellNode( + title: self.decorator.useAnotherAccountTitle, + insets: self.decorator.insets.optionalButtonInsets, + color: .white + ) { [weak self] in + self?.handleOtherAccount() + } + case .importKey: + + } + } + } +} diff --git a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift similarity index 90% rename from FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift rename to FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index 98dd36caa..d6586ac6f 100644 --- a/FlowCrypt/Controllers/Create Private Key/CreatePrivateKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -23,13 +23,13 @@ enum CreateKeyError: Error { case conformingPassPhraseError } -final class CreatePrivateKeyViewController: TableNodeViewController { +final class SetupKeyViewController: TableNodeViewController { enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, action, subtitle } private let parts = Parts.allCases - private let decorator: CreatePrivateKeyDecorator + private let decorator: SetupViewDecorator private let core: Core private let router: GlobalRouterType private let user: UserId @@ -42,7 +42,7 @@ final class CreatePrivateKeyViewController: TableNodeViewController { backupService: BackupServiceType = BackupService(), core: Core = .shared, router: GlobalRouterType = GlobalRouter(), - decorator: CreatePrivateKeyDecorator = CreatePrivateKeyDecorator(), + decorator: SetupViewDecorator = SetupViewDecorator(), storage: DataServiceType & KeyDataServiceType = DataService.shared, attester: AttesterApiType = AttesterApi() ) { @@ -70,7 +70,7 @@ final class CreatePrivateKeyViewController: TableNodeViewController { // MARK: - UI -extension CreatePrivateKeyViewController { +extension SetupKeyViewController { private func setupUI() { node.delegate = self node.dataSource = self @@ -107,7 +107,7 @@ extension CreatePrivateKeyViewController { // MARK: - Setup -extension CreatePrivateKeyViewController { +extension SetupKeyViewController { private func setupAccountWithGeneratedKey(with passPhrase: String) { Promise { [weak self] in guard let self = self else { return } @@ -209,7 +209,7 @@ extension CreatePrivateKeyViewController { } } -extension CreatePrivateKeyViewController { +extension SetupKeyViewController { private func moveToMainFlow() { router.proceed() } @@ -222,7 +222,7 @@ extension CreatePrivateKeyViewController { // MARK: - ASTableDelegate, ASTableDataSource -extension CreatePrivateKeyViewController: ASTableDelegate, ASTableDataSource { +extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { parts.count } @@ -234,7 +234,7 @@ extension CreatePrivateKeyViewController: ASTableDelegate, ASTableDataSource { case .title: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.title, + title: self.decorator.setupTitle, insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) @@ -242,13 +242,13 @@ extension CreatePrivateKeyViewController: ASTableDelegate, ASTableDataSource { case .description: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.subtitle, + title: self.decorator.subtitle(for: .choosingPassPhrase), insets: self.decorator.insets.subTitleInset, backgroundColor: .backgroundColor ) ) case .passPhrase: - return TextFieldCellNode(input: self.decorator.textFieldStyle) { [weak self] action in + return TextFieldCellNode(input: self.decorator.passPhraseTextFieldStyle) { [weak self] action in guard case let .didEndEditing(value) = action else { return } } .onShouldReturn { [weak self] _ in @@ -261,14 +261,14 @@ extension CreatePrivateKeyViewController: ASTableDelegate, ASTableDataSource { } case .action: return ButtonCellNode( - title: self.decorator.buttonTitle, + title: self.decorator.buttonTitle(for: .setPassPhrase), insets: self.decorator.insets.buttonInsets ) { [weak self] in } case .subtitle: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.optionalDescription, + title: self.decorator.passPhraseLostDescription, insets: .side(8), backgroundColor: .backgroundColor ) @@ -280,13 +280,7 @@ extension CreatePrivateKeyViewController: ASTableDelegate, ASTableDataSource { } func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { - guard let part = Parts(rawValue: indexPath.row) else { return } - - switch part { - case .description: - showChoosingOptions() - default: - break - } + guard let part = Parts(rawValue: indexPath.row), case .description = part else { return } + showChoosingOptions() } } diff --git a/FlowCrypt/Controllers/Setup/SetupViewController.swift b/FlowCrypt/Controllers/Setup/SetupViewController.swift index 83b65ce0e..55282e812 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupViewController.swift @@ -17,10 +17,10 @@ final class SetupViewController: TableNodeViewController { private let decorator: SetupViewDecorator private let core: Core private let keyMethods: KeyMethodsType - private let backupService: BackupServiceType private let user: UserId private var passPhrase: String? + private lazy var logger = Logger.nested(in: Self.self, with: .setup) enum SetupError: Error { /// fetched keys error @@ -30,46 +30,24 @@ final class SetupViewController: TableNodeViewController { /// no backups found while searching case noBackups } - - enum State { - /// initial state - case idle - /// start searching backups - case searchingBackups - /// encrypted keys found - case fetchedEncrypted([KeyDetails]) - /// error state - case error(SetupError) - - var isSearchingBackups: Bool { - guard case .searchingBackups = self else { - return false - } - return true - } - } - - private var state: State = .idle { - didSet { - handle(newState: state) - } - } + + private let fetchedEncryptedKeys: [KeyDetails] init( + fetchedEncryptedKeys: [KeyDetails], router: GlobalRouterType = GlobalRouter(), storage: DataServiceType & KeyDataServiceType = DataService.shared, decorator: SetupViewDecorator = SetupViewDecorator(), core: Core = Core.shared, keyMethods: KeyMethodsType = KeyMethods(), - backupService: BackupServiceType = BackupService(), user: UserId ) { + self.fetchedEncryptedKeys = fetchedEncryptedKeys self.router = router self.storage = storage self.decorator = decorator self.core = core self.keyMethods = keyMethods - self.backupService = backupService self.user = user super.init(node: TableNode()) @@ -82,23 +60,14 @@ final class SetupViewController: TableNodeViewController { override func viewDidLoad() { super.viewDidLoad() - setupUI() + processBackupsFetchResult() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated: animated) } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - state = .searchingBackups - } - - deinit { - NotificationCenter.default.removeObserver(self) - } } // MARK: - Setup @@ -108,8 +77,6 @@ extension SetupViewController { node.delegate = self node.dataSource = self observeKeyboardNotifications() - - state = .idle } // swiftlint:disable discarded_notification_center_observer @@ -139,71 +106,32 @@ extension SetupViewController { } } -// MARK: - State Handling +// MARK: - Key processing extension SetupViewController { - private func handle(newState: State) { - switch newState { - case .idle: - node.reloadData() - case .searchingBackups: - showSpinner() - searchBackups() - case let .fetchedEncrypted(details): - handleBackupsFetchResult(with: details) - hideSpinner() - case let .error(error): - hideSpinner() - handleError(with: error) - } - } - - private func searchBackups() { - Logger.logInfo("[Setup] searching for backups in inbox") - backupService.fetchBackups(for: user) - .then(on: .main) { [weak self] keys in - Logger.logInfo("[Setup] done searching for backups in inbox") - guard keys.isNotEmpty else { - Logger.logInfo("[Setup] no key backups found in inbox") - self?.state = .error(.noBackups) - return - } - Logger.logInfo("[Setup] \(keys.count) key backups found in inbox") - self?.state = .fetchedEncrypted(keys) - } - .catch(on: .main) { [weak self] error in - self?.handleCommon(error: error) - } - } - - private func handleBackupsFetchResult(with keys: [KeyDetails]) { - guard keys.isNotEmpty else { - state = .error(.emptyFetchedKeys) + private func processBackupsFetchResult() { + guard fetchedEncryptedKeys.isNotEmpty else { + // TODO: - ANTON - double check + handleError(with: .emptyFetchedKeys) return } - reloadNodes() + node.reloadData() node.visibleNodes .compactMap { $0 as? TextFieldCellNode } .first? .becomeFirstResponder() } - - private func reloadNodes() { - let indexes = [ - IndexPath(row: Parts.action.rawValue, section: 0), - IndexPath(row: Parts.description.rawValue, section: 0) - ] - node.reloadRows(at: indexes, with: .fade) - } } // MARK: - Error Handling extension SetupViewController { private func handleError(with error: SetupError) { - Logger.logWarning("[Setup] handling error during setup: \(error)") + hideSpinner() + logger.logWarning("handling error during setup: \(error)") + switch error { case .emptyFetchedKeys: let user = DataService.shared.email ?? "unknown_title".localized @@ -230,8 +158,7 @@ extension SetupViewController { title: "Retry", style: .default ) { [weak self] _ in - self?.state = .idle - self?.state = .searchingBackups + // TODO: - ANTON - rework retry logic - proceed to searching } alert.addAction(useOtherAccountAction) @@ -306,7 +233,7 @@ extension SetupViewController { private func proceedToCreatingNewKey() { hideSpinner() - let viewController = CreatePrivateKeyViewController(user: user) + let viewController = SetupKeyViewController(user: user) navigationController?.pushViewController(viewController, animated: true) } } @@ -316,11 +243,6 @@ extension SetupViewController { extension SetupViewController { private func handleButtonPressed() { - // ignore if we are still fetching keys - guard !state.isSearchingBackups else { - return - } - view.endEditing(true) guard let passPhrase = passPhrase else { return } @@ -333,13 +255,9 @@ extension SetupViewController { // TODO: - fix for spinner // https://github.com/FlowCrypt/flowcrypt-ios/issues/291 - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - switch self.state { - case let .fetchedEncrypted(backups): - self.recoverAccount(with: backups, and: passPhrase) - default: - assertionFailure("Not proper state for the screen") - } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + guard let self = self else { return } + self.recoverAccount(with: self.fetchedEncryptedKeys, and: passPhrase) } } @@ -366,7 +284,7 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { case .title: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.title, + title: self.decorator.setupTitle, insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) @@ -440,6 +358,3 @@ the user should see two radio buttons: If app gets killed, pass phrase gets forgotten. */ - -// TODO: - ANTON -// - make intermediate screen to distinguish which flow to show (setup/create) diff --git a/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift b/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift index e764268c8..a3c1716af 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift +++ b/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift @@ -18,28 +18,10 @@ struct SetupViewInsets { let dividerInsets = UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24) } -enum SetupCommonStyle { - static let passPhraseTextFieldStyle: TextFieldCellNode.Input = TextFieldCellNode.Input( - placeholder: "setup_enter" - .localized - .attributed( - .bold(16), - color: .lightGray, - alignment: .center - ), - isSecureTextEntry: true, - textInsets: 0, - textAlignment: .center, - insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) - ) -} - struct SetupViewDecorator { let insets = SetupViewInsets() - let textFieldStyle = SetupCommonStyle.passPhraseTextFieldStyle - - let title = "setup_title" + let setupTitle = "setup_title" .localized .attributed( .bold(35), @@ -47,14 +29,6 @@ struct SetupViewDecorator { alignment: .center ) - let buttonTitle = "setup_load" - .localized - .attributed( - .regular(17), - color: .white, - alignment: .center - ) - let useAnotherAccountTitle = "setup_use_another" .localized .attributed( @@ -65,17 +39,71 @@ struct SetupViewDecorator { ), alignment: .center ) + + let passPhraseLostDescription = "create_pass_phrase_lost" + .localized + .attributed( + .regular(16), + color: .lightGray, + alignment: .center + ) + + // MARK: TextField + let passPhraseTextFieldStyle: TextFieldCellNode.Input = TextFieldCellNode.Input( + placeholder: "setup_enter" + .localized + .attributed( + .bold(16), + color: .lightGray, + alignment: .center + ), + isSecureTextEntry: true, + textInsets: 0, + textAlignment: .center, + insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + ) + + // MARK: Subtitle + enum SubtitleType { + case common, fetchedKeys(Int), choosingPassPhrase + } - func subtitle(for state: SetupViewController.State) -> NSAttributedString { - let subtitle: String = { - switch state { - case let .fetchedEncrypted(keys): - return "Found \(keys.count) key backup\(keys.count > 1 ? "s" : "")" - default: - return "setup_description".localized - } - }() + func subtitle(for subtitleType: SubtitleType) -> NSAttributedString { + let subtitle: String + + switch subtitleType { + case let .fetchedKeys(count): + subtitle = "Found \(count) key backup\(count > 1 ? "s" : "")" + case .common: + subtitle = "setup_description".localized + case .choosingPassPhrase: + subtitle = "create_pass_phrase_description".localized + } return subtitle.attributed(.regular(17)) } + + // MARK: Button + enum ButtonAction { + case loadAccount, setPassPhrase + } + + func buttonTitle(for action: ButtonAction) -> NSAttributedString { + let buttonTitle: String + + switch action { + case .loadAccount: + buttonTitle = "setup_load" + case .setPassPhrase: + buttonTitle = "create_pass_phrase_set_title" + } + + return buttonTitle + .localized + .attributed( + .regular(17), + color: .white, + alignment: .center + ) + } } From 19bf16569d47ad7a141d44ee6406393ceb701345 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Mon, 31 May 2021 23:17:15 +0300 Subject: [PATCH 06/26] Finalize SetupInitialViewController. Add BackupServiceMock --- FlowCrypt.xcodeproj/project.pbxproj | 48 +++- FlowCrypt/App/Logger.swift | 2 +- FlowCrypt/Common UI/CommonNodes.swift | 31 +++ .../InboxViewControllerContainer.swift | 2 +- ...nboxViewControllerContainerDecorator.swift | 9 - .../Inbox/InboxViewController.swift | 23 +- .../Inbox/InboxViewDecorator.swift | 8 +- .../CreatePrivateKeyDecorator.swift | 1 - .../EnterPassPhraseViewController.swift | 8 +- .../EnterPassPhraseViewDecorator.swift | 21 +- .../Import Key/ImportKeyViewController.swift | 6 +- .../Setup/SetupInitialViewController.swift | 216 ++++++++++++------ .../Setup/SetupKeyViewController.swift | 2 +- .../Setup/SetupViewController.swift | 25 +- .../Setup/SetupViewDecorator.swift | 55 +++-- .../UIViewControllerExtensions.swift | 4 - .../Functionality/Services/AppStartup.swift | 18 +- .../Backup Services/BackupService.swift | 10 - .../Backup Services/BackupServiceType.swift | 20 ++ .../Resources/en.lproj/Localizable.strings | 2 + .../PGP}/KeyMethodsTest.swift | 4 - .../Backup Services/BackupServiceMock.swift | 29 +++ FlowCryptTests/PromiseExtensions.swift | 29 +++ FlowCryptUI/Cell Nodes/ButtonCellNode.swift | 28 +++ FlowCryptUI/Cell Nodes/TextCellNode.swift | 19 +- FlowCryptUI/Nodes/TableNode.swift | 23 ++ 26 files changed, 432 insertions(+), 211 deletions(-) create mode 100644 FlowCrypt/Common UI/CommonNodes.swift create mode 100644 FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift rename FlowCryptTests/{ => Functionallity/PGP}/KeyMethodsTest.swift (98%) create mode 100644 FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift create mode 100644 FlowCryptTests/PromiseExtensions.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 63733e6b8..73a60409f 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -87,6 +87,13 @@ 9F3EF32F23B172D300FA0CEF /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EF32E23B172D300FA0CEF /* SearchViewController.swift */; }; 9F3EF33123B1785600FA0CEF /* MsgListViewConroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EF33023B1785600FA0CEF /* MsgListViewConroller.swift */; }; 9F4163B8265ED61C00106194 /* SetupInitialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */; }; + 9F4163E6266520B600106194 /* CommonNodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163E5266520B600106194 /* CommonNodes.swift */; }; + 9F4164102665754A00106194 /* PromiseExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41640F2665754A00106194 /* PromiseExtensions.swift */; }; + 9F416421266575AE00106194 /* PromiseExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41640F2665754A00106194 /* PromiseExtensions.swift */; }; + 9F416428266575DC00106194 /* BackupServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F416427266575DC00106194 /* BackupServiceType.swift */; }; + 9F4164382665768D00106194 /* BackupServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163EC266574CB00106194 /* BackupServiceMock.swift */; }; + 9F41643E266576C200106194 /* BackupServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163EC266574CB00106194 /* BackupServiceMock.swift */; }; + 9F416444266576DD00106194 /* BackupServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F416427266575DC00106194 /* BackupServiceType.swift */; }; 9F41FA28253B75F4003B970D /* BackupSelectKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41FA27253B75F4003B970D /* BackupSelectKeyViewController.swift */; }; 9F41FA2F253B7624003B970D /* BackupSelectKeyDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41FA2E253B7624003B970D /* BackupSelectKeyDecorator.swift */; }; 9F4300CC2571045B00791CFB /* InboxViewControllerContainerDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4300CB2571045B00791CFB /* InboxViewControllerContainerDecorator.swift */; }; @@ -450,6 +457,10 @@ 9F3EF32E23B172D300FA0CEF /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; 9F3EF33023B1785600FA0CEF /* MsgListViewConroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgListViewConroller.swift; sourceTree = ""; }; 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupInitialViewController.swift; sourceTree = ""; }; + 9F4163E5266520B600106194 /* CommonNodes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonNodes.swift; sourceTree = ""; }; + 9F4163EC266574CB00106194 /* BackupServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceMock.swift; sourceTree = ""; }; + 9F41640F2665754A00106194 /* PromiseExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseExtensions.swift; sourceTree = ""; }; + 9F416427266575DC00106194 /* BackupServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceType.swift; sourceTree = ""; }; 9F41FA27253B75F4003B970D /* BackupSelectKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSelectKeyViewController.swift; sourceTree = ""; }; 9F41FA2E253B7624003B970D /* BackupSelectKeyDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSelectKeyDecorator.swift; sourceTree = ""; }; 9F4300CB2571045B00791CFB /* InboxViewControllerContainerDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxViewControllerContainerDecorator.swift; sourceTree = ""; }; @@ -764,7 +775,9 @@ 21F836A62652A1B700B2448C /* Functionallity */ = { isa = PBXGroup; children = ( + 9F4164162665757700106194 /* PGP */, 21F836A72652A1CD00B2448C /* WKDURLs */, + 9F4163F3266574CF00106194 /* Services */, ); path = Functionallity; sourceTree = ""; @@ -1004,11 +1017,36 @@ path = Menu; sourceTree = ""; }; + 9F4163F3266574CF00106194 /* Services */ = { + isa = PBXGroup; + children = ( + 9F4163FE2665750500106194 /* Backup Services */, + ); + path = Services; + sourceTree = ""; + }; + 9F4163FE2665750500106194 /* Backup Services */ = { + isa = PBXGroup; + children = ( + 9F4163EC266574CB00106194 /* BackupServiceMock.swift */, + ); + path = "Backup Services"; + sourceTree = ""; + }; + 9F4164162665757700106194 /* PGP */ = { + isa = PBXGroup; + children = ( + 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */, + ); + path = PGP; + sourceTree = ""; + }; 9F41FA1C25372C2D003B970D /* Backup Services */ = { isa = PBXGroup; children = ( D20D3C742520AB9A00D4AA9A /* BackupService.swift */, 9FB22CEF25715D960026EE64 /* BackupServiceError.swift */, + 9F416427266575DC00106194 /* BackupServiceType.swift */, ); path = "Backup Services"; sourceTree = ""; @@ -1196,6 +1234,7 @@ isa = PBXGroup; children = ( 5A5C234923A0422C0015E705 /* View Controllers */, + 9F4163E5266520B600106194 /* CommonNodes.swift */, ); path = "Common UI"; sourceTree = ""; @@ -1291,7 +1330,7 @@ A3A680EF22EEF0BF00905813 /* FlowCryptTests-Bridging-Header.h */, D2A9CA44242622F800E1D898 /* GeneralConstantsTest.swift */, 9F003D9D25EA910B00EB38C0 /* LocalStorageTests.swift */, - 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */, + 9F41640F2665754A00106194 /* PromiseExtensions.swift */, ); path = FlowCryptTests; sourceTree = ""; @@ -2298,7 +2337,9 @@ 9F1D5769263B540100477938 /* Logger.swift in Sources */, A3DAD5FE22E4574B00F2C4CD /* FlowCryptCoreTests.swift in Sources */, 21EA3B2326565B5D00691848 /* DomainRulesTests.swift in Sources */, + 9F4164102665754A00106194 /* PromiseExtensions.swift in Sources */, 21F836CC2652A38700B2448C /* ZBase32EncodingTests.swift in Sources */, + 9F416444266576DD00106194 /* BackupServiceType.swift in Sources */, D2A9CA45242622F800E1D898 /* GeneralConstantsTest.swift in Sources */, A3DAD60B22E458C300F2C4CD /* DataExtensions.swift in Sources */, 21EA3B532656611C00691848 /* OrganisationalRule.swift in Sources */, @@ -2314,6 +2355,7 @@ D212D36524C1AC4800035991 /* KeyId.swift in Sources */, 9F003DBC25EA92D000EB38C0 /* LogOutHandler.swift in Sources */, 21F836A02652A19A00B2448C /* WKDURLsConstructor.swift in Sources */, + 9F4164382665768D00106194 /* BackupServiceMock.swift in Sources */, 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */, 32DCAD6360C9EFF4FDD8EF6F /* DispatchTimeExtension.swift in Sources */, 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */, @@ -2335,6 +2377,7 @@ D2A9CA3D242619EC00E1D898 /* SignInViewDecorator.swift in Sources */, 9F4300CC2571045B00791CFB /* InboxViewControllerContainerDecorator.swift in Sources */, 9FB22CE425715D3E0026EE64 /* GmailServiceErrorHandler.swift in Sources */, + 9F4163E6266520B600106194 /* CommonNodes.swift in Sources */, 04B472951ECE29F600B8266F /* MyMenuViewController.swift in Sources */, C132B9B41EC2DBD800763715 /* AppDelegate.swift in Sources */, 04B472961ECE29F600B8266F /* SideMenuNavigationController.swift in Sources */, @@ -2375,6 +2418,7 @@ D274724424FD1932006BA6EF /* FolderObject.swift in Sources */, 9FE1B3802563F85400D6D086 /* MessagesListProvider.swift in Sources */, 32DCA1B95DDC04D671F662F8 /* URLSessionExtension.swift in Sources */, + 9F41643E266576C200106194 /* BackupServiceMock.swift in Sources */, D2E26F7424F2705B00612AF1 /* ContactDetailDecorator.swift in Sources */, 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */, 9FB22CF025715D960026EE64 /* BackupServiceError.swift in Sources */, @@ -2386,6 +2430,7 @@ 9FA405CF265AEBC10084D133 /* CreatePrivateKeyDecorator.swift in Sources */, 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */, 5ADEDCB923A42B9400EC495E /* KeyDetailViewDecorator.swift in Sources */, + 9F416428266575DC00106194 /* BackupServiceType.swift in Sources */, 5A39F437239ECC23001F4607 /* KeySettingsViewController.swift in Sources */, 9FF0673325520DE400FCC9E6 /* GmailService+send.swift in Sources */, D20D3C892520B67C00D4AA9A /* BackupOptionsViewController.swift in Sources */, @@ -2420,6 +2465,7 @@ D274724124F97C5C006BA6EF /* CacheService.swift in Sources */, A3B7C31923F576BA0022D628 /* AppStartup.swift in Sources */, 9F31AB8E23298BCF00CF87EA /* Imap+folders.swift in Sources */, + 9F416421266575AE00106194 /* PromiseExtensions.swift in Sources */, D2891AC224C59EFA008918E3 /* KeyService.swift in Sources */, 9F56BD3823438C7000A7371A /* DateFormattingExtensions.swift in Sources */, D269E02724103A20000495C3 /* ComposeViewControllerInput.swift in Sources */, diff --git a/FlowCrypt/App/Logger.swift b/FlowCrypt/App/Logger.swift index 9776c198a..0bb11aea2 100644 --- a/FlowCrypt/App/Logger.swift +++ b/FlowCrypt/App/Logger.swift @@ -165,7 +165,7 @@ extension Logger { /// Core related logs case core = "Core" - + /// Setup Flow logs case setup = "Setup" } diff --git a/FlowCrypt/Common UI/CommonNodes.swift b/FlowCrypt/Common UI/CommonNodes.swift new file mode 100644 index 000000000..51063775a --- /dev/null +++ b/FlowCrypt/Common UI/CommonNodes.swift @@ -0,0 +1,31 @@ +// +// CommonNodes.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 31.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import FlowCryptUI +import UIKit + +extension TextCellNode.Input { + static func loading(with size: CGSize) -> TextCellNode.Input { + .init( + backgroundColor: .backgroundColor, + title: "loading_title".localized + "...", + withSpinner: true, + size: size + ) + } +} + +extension ButtonCellNode.Input { + static let retry: ButtonCellNode.Input = .init( + title: "retry_title" + .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/Inbox/Container/InboxViewControllerContainer.swift b/FlowCrypt/Controllers/Inbox/Container/InboxViewControllerContainer.swift index 223d0b114..cf1ba02fa 100644 --- a/FlowCrypt/Controllers/Inbox/Container/InboxViewControllerContainer.swift +++ b/FlowCrypt/Controllers/Inbox/Container/InboxViewControllerContainer.swift @@ -147,7 +147,7 @@ extension InboxViewControllerContainer: ASTableDelegate, ASTableDataSource { switch self.state { case .loading: return TextCellNode( - input: self.decorator.loadingInput(with: size) + input: .loading(with: size) ) case .error(let error): return TextCellNode( diff --git a/FlowCrypt/Controllers/Inbox/Container/InboxViewControllerContainerDecorator.swift b/FlowCrypt/Controllers/Inbox/Container/InboxViewControllerContainerDecorator.swift index 479e4ff4e..29e10d695 100644 --- a/FlowCrypt/Controllers/Inbox/Container/InboxViewControllerContainerDecorator.swift +++ b/FlowCrypt/Controllers/Inbox/Container/InboxViewControllerContainerDecorator.swift @@ -31,13 +31,4 @@ struct InboxViewControllerContainerDecorator { func retryActionTitle() -> NSAttributedString { "retry_title".localized.attributed(color: .white) } - - func loadingInput(with size: CGSize) -> TextCellNode.Input { - TextCellNode.Input( - backgroundColor: .backgroundColor, - title: "loading_title".localized, - withSpinner: true, - size: size - ) - } } diff --git a/FlowCrypt/Controllers/Inbox/InboxViewController.swift b/FlowCrypt/Controllers/Inbox/InboxViewController.swift index 82c35a15f..fdfd07784 100644 --- a/FlowCrypt/Controllers/Inbox/InboxViewController.swift +++ b/FlowCrypt/Controllers/Inbox/InboxViewController.swift @@ -50,7 +50,7 @@ final class InboxViewController: ASDKViewController { private var state: State = .idle private let messageProvider: MessagesListProvider - private let decorator: InboxViewDecoratorType + private let decorator: InboxViewDecorator private let refreshControl = UIRefreshControl() private let tableNode: ASTableNode private lazy var composeButton = ComposeButtonNode { [weak self] in @@ -63,7 +63,7 @@ final class InboxViewController: ASDKViewController { init( _ viewModel: InboxViewModel, messageProvider: MessagesListProvider = MailProvider.shared.messageListProvider, - decorator: InboxViewDecoratorType = InboxViewDecorator() + decorator: InboxViewDecorator = InboxViewDecorator() ) { self.viewModel = viewModel self.messageProvider = messageProvider @@ -318,17 +318,7 @@ extension InboxViewController: ASTableDataSource, ASTableDelegate { } func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { - let height = tableNode.frame.size.height - - (navigationController?.navigationBar.frame.size.height ?? 0.0) - - safeAreaWindowInsets.top - - safeAreaWindowInsets.bottom - - let size = CGSize( - width: tableNode.frame.size.width, - height: max(height, 0) - ) - - return cellNode(for: indexPath, and: size) + cellNode(for: indexPath, and: visibleSize(for: tableNode)) } func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { @@ -356,12 +346,7 @@ extension InboxViewController { case .fetching: guard let message = self.messages[safe: indexPath.row] else { return TextCellNode( - input: TextCellNode.Input( - backgroundColor: .backgroundColor, - title: "Loading ...", - withSpinner: true, - size: CGSize(width: 44, height: 44) - ) + input: .loading(with: CGSize(width: 44, height: 44)) ) } return InboxCellNode(message: InboxCellNode.Input(message)) diff --git a/FlowCrypt/Controllers/Inbox/InboxViewDecorator.swift b/FlowCrypt/Controllers/Inbox/InboxViewDecorator.swift index b1bcc792c..ca2e266dd 100644 --- a/FlowCrypt/Controllers/Inbox/InboxViewDecorator.swift +++ b/FlowCrypt/Controllers/Inbox/InboxViewDecorator.swift @@ -36,13 +36,7 @@ extension InboxCellNode.Input { } } -protocol InboxViewDecoratorType { - func initialNodeInput(for size: CGSize) -> TextCellNode.Input - - func emptyStateNodeInput(for size: CGSize, title: String) -> TextCellNode.Input -} - -struct InboxViewDecorator: InboxViewDecoratorType { +struct InboxViewDecorator { func emptyStateNodeInput(for size: CGSize, title: String) -> TextCellNode.Input { TextCellNode.Input( backgroundColor: .backgroundColor, diff --git a/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift b/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift index b025dac83..830f85a6d 100644 --- a/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift +++ b/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift @@ -9,4 +9,3 @@ import FlowCryptCommon import FlowCryptUI import UIKit - diff --git a/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift index ee731e41e..2ece15f91 100644 --- a/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift @@ -18,7 +18,7 @@ final class EnterPassPhraseViewController: TableNodeViewController { } } - private let decorator: EnterPassPhraseViewDecoratorType + private let decorator: EnterPassPhraseViewDecorator private let email: String private let fetchedKeys: [KeyDetails] private let keyMethods: KeyMethodsType @@ -29,7 +29,7 @@ final class EnterPassPhraseViewController: TableNodeViewController { private var passPhrase: String? init( - decorator: EnterPassPhraseViewDecoratorType = EnterPassPhraseViewDecorator(), + decorator: EnterPassPhraseViewDecorator = EnterPassPhraseViewDecorator(), keyMethods: KeyMethodsType = KeyMethods(), keysService: KeyDataServiceType = DataService.shared, router: GlobalRouterType = GlobalRouter(), @@ -139,7 +139,7 @@ extension EnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { ) ) case .passPhrase: - return TextFieldCellNode(input: self.decorator.passPhraseTextFieldStyle) { [weak self] action in + return TextFieldCellNode(input: .passPhraseTextFieldStyle) { [weak self] action in guard case let .didEndEditing(text) = action else { return } self?.passPhrase = text } @@ -256,4 +256,4 @@ extension EnterPassPhraseViewController { } } -// TODO: - ANTON +// TODO: - ANTON - add radio button diff --git a/FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift b/FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift index 40f028ae1..ece79338c 100644 --- a/FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift +++ b/FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift @@ -9,24 +9,7 @@ import FlowCryptUI import UIKit -protocol EnterPassPhraseViewDecoratorType { - var sceneTitle: String { get } - var title: NSAttributedString { get } - var fileImportTitle: NSAttributedString { get } - var pasteBoardTitle: NSAttributedString { get } - var titleInsets: UIEdgeInsets { get } - var subTitleInset: UIEdgeInsets { get } - var buttonInsets: UIEdgeInsets { get } - var subtitleStyle: (String) -> NSAttributedString { get } - - var passPhraseTitle: NSAttributedString { get } - var passPhraseContine: NSAttributedString { get } - var passPhraseChooseAnother: NSAttributedString { get } - var passPhraseInsets: UIEdgeInsets { get } - var passPhraseTextFieldStyle: TextFieldCellNode.Input { get } -} - -struct EnterPassPhraseViewDecorator: EnterPassPhraseViewDecoratorType { +struct EnterPassPhraseViewDecorator { let sceneTitle = "import_key_title".localized var title: NSAttributedString { @@ -67,6 +50,4 @@ struct EnterPassPhraseViewDecorator: EnterPassPhraseViewDecoratorType { private func attributed(subTitle: String, color: UIColor = .white) -> NSAttributedString { subTitle.localized.attributed(.regular(17), color: color, alignment: .center) } - - let passPhraseTextFieldStyle = SetupCommonStyle.passPhraseTextFieldStyle } diff --git a/FlowCrypt/Controllers/Key/Import Key/ImportKeyViewController.swift b/FlowCrypt/Controllers/Key/Import Key/ImportKeyViewController.swift index a0c83d6c3..089493e9c 100644 --- a/FlowCrypt/Controllers/Key/Import Key/ImportKeyViewController.swift +++ b/FlowCrypt/Controllers/Key/Import Key/ImportKeyViewController.swift @@ -19,7 +19,7 @@ final class ImportKeyViewController: TableNodeViewController { } } - private let decorator: EnterPassPhraseViewDecoratorType + private let decorator: EnterPassPhraseViewDecorator private let pasteboard: UIPasteboard private let dataService: DataServiceType private let core: Core @@ -29,7 +29,7 @@ final class ImportKeyViewController: TableNodeViewController { } init( - decorator: EnterPassPhraseViewDecoratorType = EnterPassPhraseViewDecorator(), + decorator: EnterPassPhraseViewDecorator = EnterPassPhraseViewDecorator(), pasteboard: UIPasteboard = UIPasteboard.general, core: Core = Core.shared, dataService: DataServiceType = DataService.shared @@ -209,4 +209,4 @@ extension ImportKeyViewController: UIDocumentPickerDelegate { } } -// TODO: - ANTON +// TODO: - ANTON - add radio button diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 9aa0b040d..d03f0518f 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -8,41 +8,45 @@ import AsyncDisplayKit import FlowCryptUI -import Promises -struct SetupInitialViewDecorator { - let insets = SetupViewInsets() -} - -// TODO: - ANTON -// - make intermediate screen to distinguish which flow to show (with backups/without backups) -// - searching will search keys -// - result will show createKey, importKey, anotherAccount final class SetupInitialViewController: TableNodeViewController { private enum Parts: Int, CaseIterable { case title, createKey, importKey, anotherAccount } - + private enum State { - case idle, searching, noKeyBackups + case idle, searching, noKeyBackups, error(Error) + + var numberOfRows: Int { + switch self { + // title + case .idle: + return 1 + // title, loading + case .searching: + return 2 + case .error: + return 3 + case .noKeyBackups: + return Parts.allCases.count + } + } } - + private var state = State.idle { - didSet { - node.reloadData() - } + didSet { handleNewState() } } - + private let backupService: BackupServiceType private let user: UserId private let router: GlobalRouterType private let decorator: SetupViewDecorator - + private lazy var logger = Logger.nested(in: Self.self, with: .setup) - + init( user: UserId, - backupService: BackupServiceType = BackupService(), + backupService: BackupServiceType = BackupServiceMock(), router: GlobalRouterType = GlobalRouter(), decorator: SetupViewDecorator = SetupViewDecorator() ) { @@ -50,25 +54,30 @@ final class SetupInitialViewController: TableNodeViewController { self.backupService = backupService self.router = router self.decorator = decorator + + super.init(node: TableNode()) } - + + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { super.viewDidLoad() setupUI() - - showSpinner() - searchBackups() } - + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(true, animated: animated) + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) state = .searching } - + private func setupUI() { node.delegate = self node.dataSource = self @@ -76,26 +85,35 @@ final class SetupInitialViewController: TableNodeViewController { } extension SetupInitialViewController { + private func handleNewState() { + switch state { + case .searching: + searchBackups() + default: + break + } + node.reloadData() + } + private func searchBackups() { logger.logInfo("[Setup] searching for backups in inbox") - + backupService.fetchBackups(for: user) .then(on: .main) { [weak self] keys in self?.proceedToSetupWith(keys: keys) } .catch(on: .main) { [weak self] error in - self?.handleCommon(error: error) + self?.handle(error: error) } } - + private func handleOtherAccount() { router.signOut() } - + private func proceedToSetupWith(keys: [KeyDetails]) { - logger.logInfo("Done searching for backups in inbox") - - let viewController: UIViewController + logger.logInfo("Finish searching for backups in inbox") + if keys.isEmpty { logger.logInfo("No key backups found in inbox") state = .noKeyBackups @@ -105,56 +123,112 @@ extension SetupInitialViewController { navigationController?.pushViewController(viewController, animated: true) } } + + private func handle(error: Error) { + handleCommon(error: error) + state = .error(error) + } } extension SetupInitialViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { - switch state { - case .idle, .searching: - return 1 - case .noKeyBackups: - return Parts.allCases.count - } + state.numberOfRows } - func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { + func tableNode(_ node: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { return { [weak self] in guard let self = self else { return ASCellNode() } - - guard case .noKeyBackups = self.state else { - // TODO: - ANTON return spinner node + + switch self.state { + case .idle: return ASCellNode() + case .searching: + return self.searchStateNode(for: indexPath) + case .error(let error): + return self.errorStateNode(for: indexPath, error: error) + case .noKeyBackups: + return self.noKeysStateNode(for: indexPath) + } + } + } +} + +extension SetupInitialViewController { + private func searchStateNode(for indexPath: IndexPath) -> ASCellNode { + switch indexPath.row { + case 0: + return SetupTitleNode( + SetupTitleNode.Input( + title: self.decorator.setupTitle, + insets: self.decorator.insets.titleInset, + backgroundColor: .backgroundColor + ) + ) + default: + return TextCellNode(input: .loading(with: CGSize(width: 40, height: 40))) + } + } + + private func noKeysStateNode(for indexPath: IndexPath) -> ASCellNode { + guard let part = Parts(rawValue: indexPath.row) else { return ASCellNode() } + + switch part { + case .title: + return SetupTitleNode( + SetupTitleNode.Input( + title: self.decorator.setupTitle, + insets: self.decorator.insets.titleInset, + backgroundColor: .backgroundColor + ) + ) + case .createKey: + return ButtonCellNode( + title: self.decorator.buttonTitle(for: .createKey), + insets: self.decorator.insets.buttonInsets + ) { [weak self] in } - - guard let part = Parts(rawValue: indexPath.row) else { return ASCellNode() } - - switch part { - case .title: - return SetupTitleNode( - SetupTitleNode.Input( - title: self.decorator., - insets: self.decorator.insets.titleInset, - backgroundColor: .backgroundColor - ) + case .importKey: + return ButtonCellNode( + title: self.decorator.buttonTitle(for: .importKey), + insets: self.decorator.insets.buttonInsets + ) { [weak self] in + } + case .anotherAccount: + return ButtonCellNode( + title: self.decorator.useAnotherAccountTitle, + insets: self.decorator.insets.optionalButtonInsets, + color: .white + ) { [weak self] in + self?.handleOtherAccount() + } + } + } + + private func errorStateNode(for indexPath: IndexPath, error: Error) -> ASCellNode { + switch indexPath.row { + case 0: + return SetupTitleNode( + SetupTitleNode.Input( + title: self.decorator.setupTitle, + insets: self.decorator.insets.titleInset, + backgroundColor: .backgroundColor + ) + ) + case 1: + return TextCellNode( + input: .init( + backgroundColor: .backgroundColor, + title: error.localizedDescription, + withSpinner: false, + size: CGSize(width: 200, height: 200) ) - case .createKey: - return ButtonCellNode( - title: self.decorator.buttonTitle, - insets: self.decorator.insets.buttonInsets - ) { [weak self] in - - } - case .anotherAccount: - return ButtonCellNode( - title: self.decorator.useAnotherAccountTitle, - insets: self.decorator.insets.optionalButtonInsets, - color: .white - ) { [weak self] in - self?.handleOtherAccount() - } - case .importKey: - + ) + case 2: + return ButtonCellNode(input: .retry) { [weak self] in + self?.state = .searching } + default: + return ASCellNode() } } } diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index d6586ac6f..9ac775d77 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -248,7 +248,7 @@ extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { ) ) case .passPhrase: - return TextFieldCellNode(input: self.decorator.passPhraseTextFieldStyle) { [weak self] action in + return TextFieldCellNode(input: .passPhraseTextFieldStyle) { [weak self] action in guard case let .didEndEditing(value) = action else { return } } .onShouldReturn { [weak self] _ in diff --git a/FlowCrypt/Controllers/Setup/SetupViewController.swift b/FlowCrypt/Controllers/Setup/SetupViewController.swift index 55282e812..ebdab0f8e 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupViewController.swift @@ -30,7 +30,7 @@ final class SetupViewController: TableNodeViewController { /// no backups found while searching case noBackups } - + private let fetchedEncryptedKeys: [KeyDetails] init( @@ -42,6 +42,9 @@ final class SetupViewController: TableNodeViewController { keyMethods: KeyMethodsType = KeyMethods(), user: UserId ) { + if fetchedEncryptedKeys.isEmpty { + assertionFailure("Should be handled in SetupInitialViewController") + } self.fetchedEncryptedKeys = fetchedEncryptedKeys self.router = router self.storage = storage @@ -110,12 +113,6 @@ extension SetupViewController { extension SetupViewController { private func processBackupsFetchResult() { - guard fetchedEncryptedKeys.isNotEmpty else { - // TODO: - ANTON - double check - handleError(with: .emptyFetchedKeys) - return - } - node.reloadData() node.visibleNodes @@ -131,7 +128,7 @@ extension SetupViewController { private func handleError(with error: SetupError) { hideSpinner() logger.logWarning("handling error during setup: \(error)") - + switch error { case .emptyFetchedKeys: let user = DataService.shared.email ?? "unknown_title".localized @@ -227,7 +224,8 @@ extension SetupViewController { extension SetupViewController { private func proceedToKeyImport() { hideSpinner() - let viewController = ImportKeyViewController() + // TODO: - ANTON - check proceedToKeyImport + let viewController = UIViewController() navigationController?.pushViewController(viewController, animated: true) } @@ -292,13 +290,15 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { case .description: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.subtitle(for: self.state), + // TODO: - ANTON - check text + title: self.decorator.subtitle(for: .choosingPassPhrase), insets: self.decorator.insets.subTitleInset, backgroundColor: .backgroundColor ) ) case .passPhrase: - return TextFieldCellNode(input: self.decorator.textFieldStyle) { [weak self] action in + // TODO: - ANTON - check text + return TextFieldCellNode(input: .passPhraseTextFieldStyle) { [weak self] action in guard case let .didEndEditing(value) = action else { return } self?.passPhrase = value } @@ -312,7 +312,8 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { } case .action: return ButtonCellNode( - title: self.decorator.buttonTitle, + // TODO: - ANTON - check text + title: self.decorator.buttonTitle(for: .loadAccount), insets: self.decorator.insets.buttonInsets ) { [weak self] in self?.handleButtonPressed() diff --git a/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift b/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift index a3c1716af..9913f3e31 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift +++ b/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift @@ -11,9 +11,9 @@ import FlowCryptUI import UIKit struct SetupViewInsets { - let titleInset = UIEdgeInsets(top: 92, left: 16, bottom: 20, right: 16) + let titleInset = UIEdgeInsets(top: 92, left: 16, bottom: 100, right: 16) let subTitleInset = UIEdgeInsets(top: 0, left: 16, bottom: 60, right: 16) - let buttonInsets = UIEdgeInsets(top: 80, left: 24, bottom: 8, right: 24) + let buttonInsets = UIEdgeInsets(top: 8, left: 24, bottom: 8, right: 24) let optionalButtonInsets = UIEdgeInsets(top: 0, left: 24, bottom: 8, right: 24) let dividerInsets = UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24) } @@ -39,7 +39,7 @@ struct SetupViewDecorator { ), alignment: .center ) - + let passPhraseLostDescription = "create_pass_phrase_lost" .localized .attributed( @@ -47,22 +47,7 @@ struct SetupViewDecorator { color: .lightGray, alignment: .center ) - - // MARK: TextField - let passPhraseTextFieldStyle: TextFieldCellNode.Input = TextFieldCellNode.Input( - placeholder: "setup_enter" - .localized - .attributed( - .bold(16), - color: .lightGray, - alignment: .center - ), - isSecureTextEntry: true, - textInsets: 0, - textAlignment: .center, - insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) - ) - + // MARK: Subtitle enum SubtitleType { case common, fetchedKeys(Int), choosingPassPhrase @@ -70,7 +55,7 @@ struct SetupViewDecorator { func subtitle(for subtitleType: SubtitleType) -> NSAttributedString { let subtitle: String - + switch subtitleType { case let .fetchedKeys(count): subtitle = "Found \(count) key backup\(count > 1 ? "s" : "")" @@ -82,22 +67,26 @@ struct SetupViewDecorator { return subtitle.attributed(.regular(17)) } - + // MARK: Button enum ButtonAction { - case loadAccount, setPassPhrase + case createKey, importKey, loadAccount, setPassPhrase } - + func buttonTitle(for action: ButtonAction) -> NSAttributedString { let buttonTitle: String - + switch action { + case .createKey: + buttonTitle = "setup_initial_create_key" + case .importKey: + buttonTitle = "setup_initial_import_key" case .loadAccount: buttonTitle = "setup_load" case .setPassPhrase: buttonTitle = "create_pass_phrase_set_title" } - + return buttonTitle .localized .attributed( @@ -107,3 +96,19 @@ struct SetupViewDecorator { ) } } + +extension TextFieldCellNode.Input { + static let passPhraseTextFieldStyle: TextFieldCellNode.Input = TextFieldCellNode.Input( + placeholder: "setup_enter" + .localized + .attributed( + .bold(16), + color: .lightGray, + alignment: .center + ), + isSecureTextEntry: true, + textInsets: 0, + textAlignment: .center, + insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + ) +} diff --git a/FlowCrypt/Extensions/UIViewControllerExtensions.swift b/FlowCrypt/Extensions/UIViewControllerExtensions.swift index 261229796..e925ac996 100644 --- a/FlowCrypt/Extensions/UIViewControllerExtensions.swift +++ b/FlowCrypt/Extensions/UIViewControllerExtensions.swift @@ -65,10 +65,6 @@ extension UIViewController { } extension UIViewController { - var safeAreaWindowInsets: UIEdgeInsets { - UIApplication.shared.keyWindow?.safeAreaInsets ?? .zero - } - var statusBarHeight: CGFloat { UIApplication.shared.statusBarFrame.height } diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index cee8e4ce2..49856136b 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -54,15 +54,17 @@ struct AppStartup { return } - switch entryPoint { - case .mainFlow: - window.rootViewController = SideMenuNavigationController() - case .signIn: - window.rootViewController = MainNavigationController(rootViewController: SignInViewController()) - case .setupFlow(let userId): - let setupViewController = SetupViewController(user: userId) +// switch entryPoint { +// case .mainFlow: +// window.rootViewController = SideMenuNavigationController() +// case .signIn: +// window.rootViewController = MainNavigationController(rootViewController: SignInViewController()) +// case .setupFlow(let userId): + // TODO: - ANTON +// let setupViewController = SetupInitialViewController(user: userId) + let setupViewController = SetupInitialViewController(user: .init(email: "flow.test.anton@gmail.com", name: "Name")) window.rootViewController = MainNavigationController(rootViewController: setupViewController) - } +// } } private func entryPointForUser(session: SessionType?) -> EntryPoint? { diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift index 979180736..89457ce4d 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift @@ -9,16 +9,6 @@ import Promises import UIKit -protocol BackupServiceType { - /// get all existed backups - func fetchBackups(for userId: UserId) -> Promise<[KeyDetails]> - /// backup keys to user inbox - func backupToInbox(keys: [KeyDetails], for userId: UserId) -> Promise - /// show activity sheet to save keys as file - func backupAsFile(keys: [KeyDetails], for viewController: UIViewController) -} - -// MARK: - BackupService final class BackupService { let backupProvider: BackupProvider let core: Core diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift new file mode 100644 index 000000000..0dab95762 --- /dev/null +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift @@ -0,0 +1,20 @@ +// +// BackupServiceType.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 31.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation +import Promises + +// TODO: - ANTON - remove from FlowCrypt target +protocol BackupServiceType { + /// get all existed backups + func fetchBackups(for userId: UserId) -> Promise<[KeyDetails]> + /// backup keys to user inbox + func backupToInbox(keys: [KeyDetails], for userId: UserId) -> Promise + /// show activity sheet to save keys as file + func backupAsFile(keys: [KeyDetails], for viewController: UIViewController) +} diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 38e16e84b..cff6189e0 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -86,6 +86,8 @@ "setup_wrong_pass_phrase_retry" = "Wrong pass phrase, please try again"; "setup_backup_email" = "This email contains a key backup. It will help you access your encrypted messages from other computers (along with your pass phrase). You can safely leave it in your inbox or archive it.\n\nThe key below is protected with pass phrase that only you know. You should make sure to note your pass phrase down.\n\nDO NOT DELETE THIS EMAIL. Write us at human@flowcrypt.com so that we can help."; "setup_create_key" = "Create"; +"setup_initial_create_key" = "Create a new key"; +"setup_initial_import_key" = "Import my key"; // Key Import "import_key_title" = "Import Key"; diff --git a/FlowCryptTests/KeyMethodsTest.swift b/FlowCryptTests/Functionallity/PGP/KeyMethodsTest.swift similarity index 98% rename from FlowCryptTests/KeyMethodsTest.swift rename to FlowCryptTests/Functionallity/PGP/KeyMethodsTest.swift index 7cac8abb9..b362e5af1 100644 --- a/FlowCryptTests/KeyMethodsTest.swift +++ b/FlowCryptTests/Functionallity/PGP/KeyMethodsTest.swift @@ -100,10 +100,6 @@ extension KeyMethodsTest { } class MockKeyDecrypter: KeyDecrypter { - enum MockError: Error { - case some - } - var result: Result = .success(CoreRes.DecryptKey(decryptedKey: "decrypted")) func decryptKey(armoredPrv: String, passphrase: String) throws -> CoreRes.DecryptKey { diff --git a/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift b/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift new file mode 100644 index 000000000..fc87f1ba7 --- /dev/null +++ b/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift @@ -0,0 +1,29 @@ +// +// BackupServiceMock.swift +// FlowCryptTests +// +// Created by Anton Kharchevskyi on 31.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation +import Promises +//@testable import FlowCrypt + +// TODO: - ANTON - remove from FlowCrypt target +final class BackupServiceMock: BackupServiceType { + var fetchBackupsResult: Result<[KeyDetails], Error> = .failure(MockError.some) + func fetchBackups(for userId: UserId) -> Promise<[KeyDetails]> { + .resolveAfter(with: fetchBackupsResult) + } + + var backupToInboxResult: Result = .success(()) + func backupToInbox(keys: [KeyDetails], for userId: UserId) -> Promise { + .resolveAfter(with: backupToInboxResult) + } + + var isBackupAsFile = false + func backupAsFile(keys: [KeyDetails], for viewController: UIViewController) { + isBackupAsFile = true + } +} diff --git a/FlowCryptTests/PromiseExtensions.swift b/FlowCryptTests/PromiseExtensions.swift new file mode 100644 index 000000000..f4a7dce81 --- /dev/null +++ b/FlowCryptTests/PromiseExtensions.swift @@ -0,0 +1,29 @@ +// +// PromiseExtensions.swift +// FlowCryptTests +// +// Created by Anton Kharchevskyi on 31.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation +import Promises + +extension Promise { + static func resolveAfter(timeout: TimeInterval = 5, with result: Result) -> Promise { + Promise { resolve, reject in + DispatchQueue.main.asyncAfter(deadline: .now() + timeout) { + switch result { + case .success(let value): + resolve(value) + case .failure(let error): + reject(error) + } + } + } + } +} + +enum MockError: Error { + case some +} diff --git a/FlowCryptUI/Cell Nodes/ButtonCellNode.swift b/FlowCryptUI/Cell Nodes/ButtonCellNode.swift index 489341551..5456a444f 100644 --- a/FlowCryptUI/Cell Nodes/ButtonCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ButtonCellNode.swift @@ -9,6 +9,22 @@ import AsyncDisplayKit public final class ButtonCellNode: CellNode { + public struct Input { + let title: NSAttributedString + let insets: UIEdgeInsets + let color: UIColor? + + public init( + title: NSAttributedString, + insets: UIEdgeInsets, + color: UIColor? + ) { + self.title = title + self.insets = insets + self.color = color + } + } + private var onTap: (() -> Void)? public lazy var button = ButtonNode { [weak self] in self?.onTap?() @@ -25,7 +41,19 @@ public final class ButtonCellNode: CellNode { .withAlphaComponent(alpha) } } + + public init(input: Input, action: (() -> Void)?) { + onTap = action + self.insets = input.insets + buttonColor = input.color + super.init() + button.cornerRadius = 5 + button.backgroundColor = input.color ?? .main + button.style.preferredSize.height = 50 + button.setAttributedTitle(input.title, for: .normal) + } + @available(*, deprecated, message: "Deprecated. Use init(input: Input)") public init(title: NSAttributedString, insets: UIEdgeInsets, color: UIColor? = nil, action: (() -> Void)?) { onTap = action self.insets = insets diff --git a/FlowCryptUI/Cell Nodes/TextCellNode.swift b/FlowCryptUI/Cell Nodes/TextCellNode.swift index 4291c9b73..40c752611 100644 --- a/FlowCryptUI/Cell Nodes/TextCellNode.swift +++ b/FlowCryptUI/Cell Nodes/TextCellNode.swift @@ -30,7 +30,7 @@ public final class TextCellNode: CellNode { } private let spinner = SpinnerNode() - private let text = ASTextNode2() + private let textNode = ASTextNode2() private let size: CGSize private let withSpinner: Bool @@ -38,8 +38,8 @@ public final class TextCellNode: CellNode { withSpinner = input.withSpinner size = input.size super.init() - addSubnode(text) - text.attributedText = NSAttributedString.text(from: input.title, style: .medium(16), color: .lightGray) + addSubnode(textNode) + textNode.attributedText = NSAttributedString.text(from: input.title, style: .medium(16), color: .lightGray) if input.withSpinner { addSubnode(spinner) } @@ -48,13 +48,15 @@ public final class TextCellNode: CellNode { public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { let spec = ASStackLayoutSpec( - direction: .horizontal, + direction: .vertical, spacing: 16, justifyContent: .center, alignItems: .center, - children: withSpinner ? [text, spinner] : [text] + children: withSpinner + ? [textNode, spinner] + : [textNode] ) - spec.style.preferredSize = size + return spec } } @@ -67,10 +69,7 @@ final class SpinnerNode: ASDisplayNode { override init() { super.init() setViewBlock { - switch UITraitCollection.current.userInterfaceStyle { - case .dark: return UIActivityIndicatorView(style: .white) - default: return UIActivityIndicatorView(style: .gray) - } + UIActivityIndicatorView(style: .medium) } style.preferredSize = CGSize(width: 20.0, height: 20.0) } diff --git a/FlowCryptUI/Nodes/TableNode.swift b/FlowCryptUI/Nodes/TableNode.swift index 416df34fa..9c46f56c9 100644 --- a/FlowCryptUI/Nodes/TableNode.swift +++ b/FlowCryptUI/Nodes/TableNode.swift @@ -38,3 +38,26 @@ public final class TableNode: ASTableNode { } } } + +public extension UIViewController { + var safeAreaWindowInsets: UIEdgeInsets { + UIApplication.shared.keyWindow?.safeAreaInsets ?? .zero + } +} + +public extension UIViewController { + /// should be called on a main thread + func visibleSize(for tableNode: ASTableNode) -> CGSize { + let height = tableNode.frame.size.height + - (navigationController?.navigationBar.frame.size.height ?? 0.0) + - safeAreaWindowInsets.top + - safeAreaWindowInsets.bottom + + let size = CGSize( + width: tableNode.frame.size.width, + height: max(height, 0) + ) + + return size + } +} From 7307565dc36527a04c0e5cb12760b8eaa5501b72 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Wed, 2 Jun 2021 00:11:04 +0300 Subject: [PATCH 07/26] UI updates. Add radio buttons to SetupKeyViewController --- FlowCrypt.xcodeproj/project.pbxproj | 82 +++------- FlowCrypt/Common UI/CommonNodes.swift | 31 ---- FlowCrypt/Common UI/CommonNodesInputs.swift | 75 +++++++++ .../CreatePrivateKeyDecorator.swift | 11 -- .../EnterPassPhraseViewDecorator.swift | 53 ------- .../BackupOptionsViewDecorator.swift | 19 +-- .../Key List/KeySettingsViewController.swift | 3 +- ...swift => SetupBackupsViewController.swift} | 149 ++---------------- .../SetupEnterPassPhraseViewController.swift} | 31 ++-- .../SetupImportKeyViewController.swift} | 34 ++-- .../Setup/SetupInitialViewController.swift | 107 ++++++++----- .../Setup/SetupKeyViewController.swift | 33 +++- .../Setup/SetupViewDecorator.swift | 83 +++++++--- .../Functionality/Services/AppStartup.swift | 27 ++-- .../Backup Services/BackupServiceType.swift | 1 - .../Resources/en.lproj/Localizable.strings | 3 + .../Backup Services/BackupServiceMock.swift | 8 +- FlowCryptUI/Cell Nodes/ButtonCellNode.swift | 2 +- FlowCryptUI/Cell Nodes/CheckBoxTextNode.swift | 11 -- 19 files changed, 326 insertions(+), 437 deletions(-) delete mode 100644 FlowCrypt/Common UI/CommonNodes.swift create mode 100644 FlowCrypt/Common UI/CommonNodesInputs.swift delete mode 100644 FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift delete mode 100644 FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift rename FlowCrypt/Controllers/Setup/{SetupViewController.swift => SetupBackupsViewController.swift} (64%) rename FlowCrypt/Controllers/{Key/Enter Pass Phrase/EnterPassPhraseViewController.swift => Setup/SetupEnterPassPhraseViewController.swift} (89%) rename FlowCrypt/Controllers/{Key/Import Key/ImportKeyViewController.swift => Setup/SetupImportKeyViewController.swift} (86%) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 73a60409f..5e07b1bf3 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -73,8 +73,7 @@ 9F228BA923C67587005D2CB6 /* UserCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F228BA823C67587005D2CB6 /* UserCredentials.swift */; }; 9F228BAA23C67729005D2CB6 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAEFF16F5D91A35791730 /* DataExtensions.swift */; }; 9F23EA50237217140017DFED /* ComposeViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23EA4F237217140017DFED /* ComposeViewDecorator.swift */; }; - 9F268891237DC55600428A94 /* ImportKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268890237DC55600428A94 /* ImportKeyViewController.swift */; }; - 9F268894237DD98900428A94 /* EnterPassPhraseViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268893237DD98900428A94 /* EnterPassPhraseViewDecorator.swift */; }; + 9F268891237DC55600428A94 /* SetupImportKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268890237DC55600428A94 /* SetupImportKeyViewController.swift */; }; 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB8B23298B3F00CF87EA /* Imap+retry.swift */; }; 9F31AB8E23298BCF00CF87EA /* Imap+folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB8D23298BCF00CF87EA /* Imap+folders.swift */; }; 9F31AB91232993F500CF87EA /* Imap+session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB90232993F500CF87EA /* Imap+session.swift */; }; @@ -87,13 +86,10 @@ 9F3EF32F23B172D300FA0CEF /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EF32E23B172D300FA0CEF /* SearchViewController.swift */; }; 9F3EF33123B1785600FA0CEF /* MsgListViewConroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EF33023B1785600FA0CEF /* MsgListViewConroller.swift */; }; 9F4163B8265ED61C00106194 /* SetupInitialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */; }; - 9F4163E6266520B600106194 /* CommonNodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163E5266520B600106194 /* CommonNodes.swift */; }; + 9F4163E6266520B600106194 /* CommonNodesInputs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163E5266520B600106194 /* CommonNodesInputs.swift */; }; 9F4164102665754A00106194 /* PromiseExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41640F2665754A00106194 /* PromiseExtensions.swift */; }; 9F416421266575AE00106194 /* PromiseExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41640F2665754A00106194 /* PromiseExtensions.swift */; }; 9F416428266575DC00106194 /* BackupServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F416427266575DC00106194 /* BackupServiceType.swift */; }; - 9F4164382665768D00106194 /* BackupServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163EC266574CB00106194 /* BackupServiceMock.swift */; }; - 9F41643E266576C200106194 /* BackupServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163EC266574CB00106194 /* BackupServiceMock.swift */; }; - 9F416444266576DD00106194 /* BackupServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F416427266575DC00106194 /* BackupServiceType.swift */; }; 9F41FA28253B75F4003B970D /* BackupSelectKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41FA27253B75F4003B970D /* BackupSelectKeyViewController.swift */; }; 9F41FA2F253B7624003B970D /* BackupSelectKeyDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F41FA2E253B7624003B970D /* BackupSelectKeyDecorator.swift */; }; 9F4300CC2571045B00791CFB /* InboxViewControllerContainerDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4300CB2571045B00791CFB /* InboxViewControllerContainerDecorator.swift */; }; @@ -114,6 +110,8 @@ 9F6EE17B2598F9FA0059BA51 /* Gmail+Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6EE17A2598F9FA0059BA51 /* Gmail+Backup.swift */; }; 9F6EE18C25A8AF970059BA51 /* GeneralConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C132B9CA1EC2DE6400763715 /* GeneralConstants.swift */; }; 9F716308234FC73E0031645E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9F71630A234FC73E0031645E /* Localizable.strings */; }; + 9F7920E32666D28400DA3D80 /* BackupServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163EC266574CB00106194 /* BackupServiceMock.swift */; }; + 9F7920EE2666D32500DA3D80 /* BackupServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F416427266575DC00106194 /* BackupServiceType.swift */; }; 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA0C3D34A69851A238E87 /* Core.swift */; }; 9F7DE8C6232029D000F10B3E /* CoreTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAC732B988D9704658812 /* CoreTypes.swift */; }; 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCACB22E895C2500A99350 /* CoreHost.swift */; }; @@ -132,7 +130,6 @@ 9FA0158026565B9D00CBBA05 /* KeyMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F953E08238310D500AEB98B /* KeyMethods.swift */; }; 9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA1988F253C841F008C9CF2 /* TableViewController.swift */; }; 9FA405C7265AEBA50084D133 /* SetupKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */; }; - 9FA405CF265AEBC10084D133 /* CreatePrivateKeyDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405CE265AEBC10084D133 /* CreatePrivateKeyDecorator.swift */; }; 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA9C83B264C2D75005A9670 /* MessageService.swift */; }; 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */; }; 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */; }; @@ -147,7 +144,7 @@ 9FC411352595EA94001180A8 /* Imap+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC411342595EA94001180A8 /* Imap+Search.swift */; }; 9FC4114C25961CEA001180A8 /* MailServiceProviderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC4114B25961CEA001180A8 /* MailServiceProviderType.swift */; }; 9FC411902596229D001180A8 /* AppErr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA4B11D4531B3B04D01D1 /* AppErr.swift */; }; - 9FD364862381EFCB00657302 /* EnterPassPhraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD364852381EFCB00657302 /* EnterPassPhraseViewController.swift */; }; + 9FD364862381EFCB00657302 /* SetupEnterPassPhraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */; }; 9FDF364D235A1CCD00614596 /* SignInTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF364C235A1CCD00614596 /* SignInTest.swift */; }; 9FDF3650235A1D3F00614596 /* UITestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF364F235A1D3F00614596 /* UITestHelper.swift */; }; 9FDF3652235A1EDE00614596 /* XCUIApplicationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF3651235A1EDE00614596 /* XCUIApplicationBuilder.swift */; }; @@ -177,7 +174,7 @@ C132B9BE1EC2DBD800763715 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C132B9BC1EC2DBD800763715 /* LaunchScreen.storyboard */; }; C132B9CB1EC2DE6400763715 /* GeneralConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C132B9CA1EC2DE6400763715 /* GeneralConstants.swift */; }; C132B9D91EC30E1D00763715 /* InboxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C132B9D81EC30E1D00763715 /* InboxViewController.swift */; }; - C192421F1EC48B6900C3D251 /* SetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C192421E1EC48B6900C3D251 /* SetupViewController.swift */; }; + C192421F1EC48B6900C3D251 /* SetupBackupsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C192421E1EC48B6900C3D251 /* SetupBackupsViewController.swift */; }; CB5986DFBB2B4FF4AD555821 /* Pods_FlowCryptUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FA3FBD561C4CAD27852EB0B /* Pods_FlowCryptUITests.framework */; }; D204DBA223FB35700083B9D6 /* FlowCryptUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D204DBA023FB35700083B9D6 /* FlowCryptUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; D20D3C672520AB1000D4AA9A /* BackupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20D3C662520AB1000D4AA9A /* BackupViewController.swift */; }; @@ -444,8 +441,7 @@ 9F228BA823C67587005D2CB6 /* UserCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCredentials.swift; sourceTree = ""; }; 9F23EA4D237216FA0017DFED /* TextViewCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewCellNode.swift; sourceTree = ""; }; 9F23EA4F237217140017DFED /* ComposeViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewDecorator.swift; sourceTree = ""; }; - 9F268890237DC55600428A94 /* ImportKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportKeyViewController.swift; sourceTree = ""; }; - 9F268893237DD98900428A94 /* EnterPassPhraseViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterPassPhraseViewDecorator.swift; sourceTree = ""; }; + 9F268890237DC55600428A94 /* SetupImportKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupImportKeyViewController.swift; sourceTree = ""; }; 9F31AB8B23298B3F00CF87EA /* Imap+retry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+retry.swift"; sourceTree = ""; }; 9F31AB8D23298BCF00CF87EA /* Imap+folders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+folders.swift"; sourceTree = ""; }; 9F31AB90232993F500CF87EA /* Imap+session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+session.swift"; sourceTree = ""; }; @@ -457,7 +453,7 @@ 9F3EF32E23B172D300FA0CEF /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; 9F3EF33023B1785600FA0CEF /* MsgListViewConroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgListViewConroller.swift; sourceTree = ""; }; 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupInitialViewController.swift; sourceTree = ""; }; - 9F4163E5266520B600106194 /* CommonNodes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonNodes.swift; sourceTree = ""; }; + 9F4163E5266520B600106194 /* CommonNodesInputs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonNodesInputs.swift; sourceTree = ""; }; 9F4163EC266574CB00106194 /* BackupServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceMock.swift; sourceTree = ""; }; 9F41640F2665754A00106194 /* PromiseExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseExtensions.swift; sourceTree = ""; }; 9F416427266575DC00106194 /* BackupServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceType.swift; sourceTree = ""; }; @@ -514,7 +510,6 @@ 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyMethodsTest.swift; sourceTree = ""; }; 9FA1988F253C841F008C9CF2 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupKeyViewController.swift; sourceTree = ""; }; - 9FA405CE265AEBC10084D133 /* CreatePrivateKeyDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePrivateKeyDecorator.swift; sourceTree = ""; }; 9FA9C83B264C2D75005A9670 /* MessageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageService.swift; sourceTree = ""; }; 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceErrorHandler.swift; sourceTree = ""; }; 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceError.swift; sourceTree = ""; }; @@ -531,7 +526,7 @@ 9FD22A19230FD781005067A6 /* NavigationBarItemsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarItemsView.swift; sourceTree = ""; }; 9FD22A1B230FE7D0005067A6 /* Then.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Then.swift; sourceTree = ""; }; 9FD22A1E230FEFC6005067A6 /* NavigationBarActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarActionButton.swift; sourceTree = ""; }; - 9FD364852381EFCB00657302 /* EnterPassPhraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterPassPhraseViewController.swift; sourceTree = ""; }; + 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupEnterPassPhraseViewController.swift; sourceTree = ""; }; 9FDF3637235A0B3100614596 /* InfoCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoCellNode.swift; sourceTree = ""; }; 9FDF3639235A0B3B00614596 /* HeaderNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderNode.swift; sourceTree = ""; }; 9FDF3641235A1B0100614596 /* FlowCryptUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowCryptUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -577,7 +572,7 @@ C132B9CA1EC2DE6400763715 /* GeneralConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralConstants.swift; sourceTree = ""; }; C132B9D81EC30E1D00763715 /* InboxViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InboxViewController.swift; sourceTree = ""; }; C132B9DF1EC333AA00763715 /* FlowCrypt-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FlowCrypt-Bridging-Header.h"; sourceTree = ""; }; - C192421E1EC48B6900C3D251 /* SetupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupViewController.swift; sourceTree = ""; }; + C192421E1EC48B6900C3D251 /* SetupBackupsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupBackupsViewController.swift; sourceTree = ""; }; D204DB9E23FB35700083B9D6 /* FlowCryptUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FlowCryptUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D204DBA023FB35700083B9D6 /* FlowCryptUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FlowCryptUI.h; sourceTree = ""; }; D204DBA123FB35700083B9D6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -974,16 +969,6 @@ path = Resources; sourceTree = ""; }; - 9F268892237DC55E00428A94 /* Key */ = { - isa = PBXGroup; - children = ( - 9FA405CD265AEBA90084D133 /* Create Private Key */, - D29A0000240C133E00C1387D /* Import Key */, - D29AFFFF240C133800C1387D /* Enter Pass Phrase */, - ); - path = Key; - sourceTree = ""; - }; 9F2A939F23363D2400014A92 /* Main */ = { isa = PBXGroup; children = ( @@ -1155,14 +1140,6 @@ path = "Message Provider"; sourceTree = ""; }; - 9FA405CD265AEBA90084D133 /* Create Private Key */ = { - isa = PBXGroup; - children = ( - 9FA405CE265AEBC10084D133 /* CreatePrivateKeyDecorator.swift */, - ); - path = "Create Private Key"; - sourceTree = ""; - }; 9FB22CB425715C860026EE64 /* Error Handling */ = { isa = PBXGroup; children = ( @@ -1234,7 +1211,7 @@ isa = PBXGroup; children = ( 5A5C234923A0422C0015E705 /* View Controllers */, - 9F4163E5266520B600106194 /* CommonNodes.swift */, + 9F4163E5266520B600106194 /* CommonNodesInputs.swift */, ); path = "Common UI"; sourceTree = ""; @@ -1407,7 +1384,6 @@ D2FF6969243115FE007182F0 /* SignIn Other */, 32DCA8D5AF0A43354CC7F58B /* SignIn */, C192421D1EC48B5600C3D251 /* Setup */, - 9F268892237DC55E00428A94 /* Key */, D29AFFF12409301300C1387D /* Bootstrap */, C132B9D71EC30E0B00763715 /* Inbox */, 04B4728F1ECE29F600B8266F /* Compose */, @@ -1447,7 +1423,9 @@ isa = PBXGroup; children = ( 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */, - C192421E1EC48B6900C3D251 /* SetupViewController.swift */, + 9F268890237DC55600428A94 /* SetupImportKeyViewController.swift */, + C192421E1EC48B6900C3D251 /* SetupBackupsViewController.swift */, + 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */, 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */, 9F17976C2368EEBD002BF770 /* SetupViewDecorator.swift */, ); @@ -1583,15 +1561,6 @@ path = Views; sourceTree = ""; }; - D29A0000240C133E00C1387D /* Import Key */ = { - isa = PBXGroup; - children = ( - 9F268890237DC55600428A94 /* ImportKeyViewController.swift */, - 9F268893237DD98900428A94 /* EnterPassPhraseViewDecorator.swift */, - ); - path = "Import Key"; - sourceTree = ""; - }; D29A0001240C137700C1387D /* MessageList Extension */ = { isa = PBXGroup; children = ( @@ -1653,14 +1622,6 @@ path = Bootstrap; sourceTree = ""; }; - D29AFFFF240C133800C1387D /* Enter Pass Phrase */ = { - isa = PBXGroup; - children = ( - 9FD364852381EFCB00657302 /* EnterPassPhraseViewController.swift */, - ); - path = "Enter Pass Phrase"; - sourceTree = ""; - }; D2A1D3B823FD64AB00D626D6 /* FlowCryptUIApplication */ = { isa = PBXGroup; children = ( @@ -2339,7 +2300,7 @@ 21EA3B2326565B5D00691848 /* DomainRulesTests.swift in Sources */, 9F4164102665754A00106194 /* PromiseExtensions.swift in Sources */, 21F836CC2652A38700B2448C /* ZBase32EncodingTests.swift in Sources */, - 9F416444266576DD00106194 /* BackupServiceType.swift in Sources */, + 9F7920EE2666D32500DA3D80 /* BackupServiceType.swift in Sources */, D2A9CA45242622F800E1D898 /* GeneralConstantsTest.swift in Sources */, A3DAD60B22E458C300F2C4CD /* DataExtensions.swift in Sources */, 21EA3B532656611C00691848 /* OrganisationalRule.swift in Sources */, @@ -2355,7 +2316,7 @@ D212D36524C1AC4800035991 /* KeyId.swift in Sources */, 9F003DBC25EA92D000EB38C0 /* LogOutHandler.swift in Sources */, 21F836A02652A19A00B2448C /* WKDURLsConstructor.swift in Sources */, - 9F4164382665768D00106194 /* BackupServiceMock.swift in Sources */, + 9F7920E32666D28400DA3D80 /* BackupServiceMock.swift in Sources */, 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */, 32DCAD6360C9EFF4FDD8EF6F /* DispatchTimeExtension.swift in Sources */, 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */, @@ -2377,7 +2338,7 @@ D2A9CA3D242619EC00E1D898 /* SignInViewDecorator.swift in Sources */, 9F4300CC2571045B00791CFB /* InboxViewControllerContainerDecorator.swift in Sources */, 9FB22CE425715D3E0026EE64 /* GmailServiceErrorHandler.swift in Sources */, - 9F4163E6266520B600106194 /* CommonNodes.swift in Sources */, + 9F4163E6266520B600106194 /* CommonNodesInputs.swift in Sources */, 04B472951ECE29F600B8266F /* MyMenuViewController.swift in Sources */, C132B9B41EC2DBD800763715 /* AppDelegate.swift in Sources */, 04B472961ECE29F600B8266F /* SideMenuNavigationController.swift in Sources */, @@ -2388,7 +2349,7 @@ C132B9CB1EC2DE6400763715 /* GeneralConstants.swift in Sources */, 5ADEDCBE23A4363700EC495E /* KeyDetailInfoViewController.swift in Sources */, D20D3C752520AB9A00D4AA9A /* BackupService.swift in Sources */, - C192421F1EC48B6900C3D251 /* SetupViewController.swift in Sources */, + C192421F1EC48B6900C3D251 /* SetupBackupsViewController.swift in Sources */, 9F0C3C2623194E0A00299985 /* FolderViewModel.swift in Sources */, 21CE25E62650070300ADFF4B /* WKDURLsConstructor.swift in Sources */, 9FC411212595EA12001180A8 /* MessageSearchProvider.swift in Sources */, @@ -2418,7 +2379,6 @@ D274724424FD1932006BA6EF /* FolderObject.swift in Sources */, 9FE1B3802563F85400D6D086 /* MessagesListProvider.swift in Sources */, 32DCA1B95DDC04D671F662F8 /* URLSessionExtension.swift in Sources */, - 9F41643E266576C200106194 /* BackupServiceMock.swift in Sources */, D2E26F7424F2705B00612AF1 /* ContactDetailDecorator.swift in Sources */, 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */, 9FB22CF025715D960026EE64 /* BackupServiceError.swift in Sources */, @@ -2427,7 +2387,6 @@ D27B911924EFE79F002DF0A1 /* LocalContactsProvider.swift in Sources */, 9FE1B3942563F98600D6D086 /* Imap+MessagesList.swift in Sources */, 32DCA04CA0DAB79C39514782 /* CoreTypes.swift in Sources */, - 9FA405CF265AEBC10084D133 /* CreatePrivateKeyDecorator.swift in Sources */, 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */, 5ADEDCB923A42B9400EC495E /* KeyDetailViewDecorator.swift in Sources */, 9F416428266575DC00106194 /* BackupServiceType.swift in Sources */, @@ -2442,7 +2401,7 @@ 9F589F0D238C7A9B007FD759 /* LocalStorage.swift in Sources */, D2FC1C0624D82C9F003B949D /* ContactsService.swift in Sources */, 9FB22D0425715DF00026EE64 /* KeyServiceError.swift in Sources */, - 9FD364862381EFCB00657302 /* EnterPassPhraseViewController.swift in Sources */, + 9FD364862381EFCB00657302 /* SetupEnterPassPhraseViewController.swift in Sources */, D212D35D24C1AACF00035991 /* PrvKeyInfo.swift in Sources */, 9F23EA50237217140017DFED /* ComposeViewDecorator.swift in Sources */, D21574B724376852006B094F /* ConnectionType.swift in Sources */, @@ -2476,7 +2435,6 @@ 9FE1B3A02565B0CE00D6D086 /* Message.swift in Sources */, D27177492424D73000BDA9A9 /* InboxViewDecorator.swift in Sources */, D2F41371243CC76F0066AFB5 /* SessionObject.swift in Sources */, - 9F268894237DD98900428A94 /* EnterPassPhraseViewDecorator.swift in Sources */, 9F9362192573D10E0009912F /* Imap+Message.swift in Sources */, 32DCA9C61ABB3234649B374E /* CoreHost.swift in Sources */, 9F41FA28253B75F4003B970D /* BackupSelectKeyViewController.swift in Sources */, @@ -2512,7 +2470,7 @@ 9F003DB625EA92BC00EB38C0 /* LogOutHandler.swift in Sources */, 9F0C3C122316DDA500299985 /* DataService.swift in Sources */, D20D3C6E2520AB3900D4AA9A /* BackupViewDecorator.swift in Sources */, - 9F268891237DC55600428A94 /* ImportKeyViewController.swift in Sources */, + 9F268891237DC55600428A94 /* SetupImportKeyViewController.swift in Sources */, D2E26F7024F266F300612AF1 /* ContactDetailViewController.swift in Sources */, 9FDF3656235A22DA00614596 /* AppReset.swift in Sources */, 5A39F43F239EE7D2001F4607 /* SegmentedViewController.swift in Sources */, diff --git a/FlowCrypt/Common UI/CommonNodes.swift b/FlowCrypt/Common UI/CommonNodes.swift deleted file mode 100644 index 51063775a..000000000 --- a/FlowCrypt/Common UI/CommonNodes.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// CommonNodes.swift -// FlowCrypt -// -// Created by Anton Kharchevskyi on 31.05.2021. -// Copyright © 2021 FlowCrypt Limited. All rights reserved. -// - -import FlowCryptUI -import UIKit - -extension TextCellNode.Input { - static func loading(with size: CGSize) -> TextCellNode.Input { - .init( - backgroundColor: .backgroundColor, - title: "loading_title".localized + "...", - withSpinner: true, - size: size - ) - } -} - -extension ButtonCellNode.Input { - static let retry: ButtonCellNode.Input = .init( - title: "retry_title" - .localized - .attributed(.bold(16), color: .white, alignment: .center), - insets: UIEdgeInsets(top: 16, left: 24, bottom: 8, right: 24), - color: .main - ) -} diff --git a/FlowCrypt/Common UI/CommonNodesInputs.swift b/FlowCrypt/Common UI/CommonNodesInputs.swift new file mode 100644 index 000000000..67569f88b --- /dev/null +++ b/FlowCrypt/Common UI/CommonNodesInputs.swift @@ -0,0 +1,75 @@ +// +// CommonNodes.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 31.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import FlowCryptUI +import UIKit + +extension TextCellNode.Input { + static func loading(with size: CGSize) -> TextCellNode.Input { + .init( + backgroundColor: .backgroundColor, + title: "loading_title".localized + "...", + withSpinner: true, + size: size + ) + } +} + +extension ButtonCellNode.Input { + static let retry: ButtonCellNode.Input = .init( + title: "retry_title" + .localized + .attributed(.bold(16), color: .white, alignment: .center), + insets: UIEdgeInsets(top: 16, left: 24, bottom: 8, right: 24), + color: .main + ) + + static let chooseAnotherAccount: ButtonCellNode.Input = .init( + title: "setup_use_another" + .localized + .attributed( + .regular(15), + color: UIColor.colorFor( + darkStyle: .black, + lightStyle: .blueColor + ), + alignment: .center + ), + insets: .side(8), + color: .backgroundColor + ) +} + +extension CheckBoxTextNode.Input { + static func common(with text: String, isSelected: Bool) -> CheckBoxTextNode.Input { + let attributedTitle = text + .attributed(.bold(14), color: .textColor, alignment: .center) + + let checkboxColor: UIColor = isSelected + ? .main + : .lightGray + + return CheckBoxTextNode.Input( + title: attributedTitle, + insets: .init(top: 8, left: 16, bottom: 8, right: 16), + preferredSize: CGSize(width: 30, height: 30), + checkBoxInput: CheckBoxNode.Input( + color: checkboxColor, + strokeWidth: 2 + ) + ) + } + + static func passPhraseLocally(isSelected: Bool) -> CheckBoxTextNode.Input { + Self.common(with: "setup_save_pass_locally".localized, isSelected: isSelected) + } + + static func passPhraseMemory(isSelected: Bool) -> CheckBoxTextNode.Input { + Self.common(with: "setup_save_pass_in_memory".localized, isSelected: isSelected) + } +} diff --git a/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift b/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift deleted file mode 100644 index 830f85a6d..000000000 --- a/FlowCrypt/Controllers/Key/Create Private Key/CreatePrivateKeyDecorator.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// CreatePrivateKeyDecorator.swift -// FlowCrypt -// -// Created by Anton Kharchevskyi on 23.05.2021. -// Copyright © 2021 FlowCrypt Limited. All rights reserved. -// - -import FlowCryptCommon -import FlowCryptUI -import UIKit diff --git a/FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift b/FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift deleted file mode 100644 index ece79338c..000000000 --- a/FlowCrypt/Controllers/Key/Import Key/EnterPassPhraseViewDecorator.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// ImportKeyDecorator.swift -// FlowCrypt -// -// Created by Anton Kharchevskyi on 14.11.2019. -// Copyright © 2019 FlowCrypt Limited. All rights reserved. -// - -import FlowCryptUI -import UIKit - -struct EnterPassPhraseViewDecorator { - let sceneTitle = "import_key_title".localized - - var title: NSAttributedString { - attributed(title: "import_key_description") - } - - var passPhraseTitle: NSAttributedString { - attributed(title: "import_key_description") - } - - var fileImportTitle: NSAttributedString { - attributed(subTitle: "import_key_file") - } - - var pasteBoardTitle: NSAttributedString { - attributed(subTitle: "import_key_paste") - } - - var passPhraseContine: NSAttributedString { - attributed(subTitle: "import_key_continue") - } - - var passPhraseChooseAnother: NSAttributedString { - attributed(subTitle: "import_key_choose", color: UIColor.white.withAlphaComponent(0.9)) - } - - let buttonInsets = UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16) - let passPhraseInsets = UIEdgeInsets(top: 32, left: 16, bottom: 0, right: 16) - let titleInsets = UIEdgeInsets(top: 100, left: 16, bottom: 30, right: 16) - let subTitleInset = UIEdgeInsets(top: 8, left: 16, bottom: 16, right: 16) - var subtitleStyle: (String) -> NSAttributedString { { $0.attributed(.regular(17), alignment: .center) } - } - - private func attributed(title: String) -> NSAttributedString { - title.localized.attributed(.bold(35), alignment: .center) - } - - private func attributed(subTitle: String, color: UIColor = .white) -> NSAttributedString { - subTitle.localized.attributed(.regular(17), color: color, alignment: .center) - } -} diff --git a/FlowCrypt/Controllers/Settings/Backup/Backups Option Scene/BackupOptionsViewDecorator.swift b/FlowCrypt/Controllers/Settings/Backup/Backups Option Scene/BackupOptionsViewDecorator.swift index 262277646..5291dc5f8 100644 --- a/FlowCrypt/Controllers/Settings/Backup/Backups Option Scene/BackupOptionsViewDecorator.swift +++ b/FlowCrypt/Controllers/Settings/Backup/Backups Option Scene/BackupOptionsViewDecorator.swift @@ -54,22 +54,9 @@ struct BackupOptionsViewDecorator: BackupOptionsViewDecoratorType { default: title = "" } - let attributedTitle = title - .localized - .attributed(.bold(14), color: .textColor, alignment: .center) - - let checkboxColor: UIColor = isSelected - ? .main - : .lightGray - - return CheckBoxTextNode.Input( - title: attributedTitle, - insets: .side(16), - preferredSize: CGSize(width: 30, height: 30), - checkBoxInput: CheckBoxNode.Input( - color: checkboxColor, - strokeWidth: 2 - ) + return CheckBoxTextNode.Input.common( + with: title.localized, + isSelected: isSelected ) } } diff --git a/FlowCrypt/Controllers/Settings/KeySettings/Key List/KeySettingsViewController.swift b/FlowCrypt/Controllers/Settings/KeySettings/Key List/KeySettingsViewController.swift index 872d26de1..aba6ee78f 100644 --- a/FlowCrypt/Controllers/Settings/KeySettings/Key List/KeySettingsViewController.swift +++ b/FlowCrypt/Controllers/Settings/KeySettings/Key List/KeySettingsViewController.swift @@ -63,9 +63,10 @@ extension KeySettingsViewController { extension KeySettingsViewController { @objc private func handleAddButtonTap() { - navigationController?.pushViewController(ImportKeyViewController(), animated: true) + navigationController?.pushViewController(SetupImportKeyViewController(), animated: true) } } + extension KeySettingsViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { keys.count diff --git a/FlowCrypt/Controllers/Setup/SetupViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift similarity index 64% rename from FlowCrypt/Controllers/Setup/SetupViewController.swift rename to FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index ebdab0f8e..4d894b9cc 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -6,8 +6,7 @@ import AsyncDisplayKit import FlowCryptUI import Promises -// swiftlint:disable line_length -final class SetupViewController: TableNodeViewController { +final class SetupBackupsViewController: TableNodeViewController { private enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, action, optionalAction } @@ -18,21 +17,11 @@ final class SetupViewController: TableNodeViewController { private let core: Core private let keyMethods: KeyMethodsType private let user: UserId + private let fetchedEncryptedKeys: [KeyDetails] private var passPhrase: String? private lazy var logger = Logger.nested(in: Self.self, with: .setup) - enum SetupError: Error { - /// fetched keys error - case emptyFetchedKeys - /// error while key parsing (associated error for verbose message) - case parseKey(Error) - /// no backups found while searching - case noBackups - } - - private let fetchedEncryptedKeys: [KeyDetails] - init( fetchedEncryptedKeys: [KeyDetails], router: GlobalRouterType = GlobalRouter(), @@ -42,9 +31,6 @@ final class SetupViewController: TableNodeViewController { keyMethods: KeyMethodsType = KeyMethods(), user: UserId ) { - if fetchedEncryptedKeys.isEmpty { - assertionFailure("Should be handled in SetupInitialViewController") - } self.fetchedEncryptedKeys = fetchedEncryptedKeys self.router = router self.storage = storage @@ -64,18 +50,18 @@ final class SetupViewController: TableNodeViewController { override func viewDidLoad() { super.viewDidLoad() setupUI() - processBackupsFetchResult() + handleBackups() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - navigationController?.setNavigationBarHidden(true, animated: animated) + navigationController?.setNavigationBarHidden(false, animated: animated) } } // MARK: - Setup -extension SetupViewController { +extension SetupBackupsViewController { private func setupUI() { node.delegate = self node.dataSource = self @@ -109,10 +95,14 @@ extension SetupViewController { } } -// MARK: - Key processing +// MARK: - Actions + +extension SetupBackupsViewController { + private func handleBackups() { + guard fetchedEncryptedKeys.isNotEmpty else { + return assertionFailure("Should be handled in SetupInitialViewController") + } -extension SetupViewController { - private func processBackupsFetchResult() { node.reloadData() node.visibleNodes @@ -120,90 +110,7 @@ extension SetupViewController { .first? .becomeFirstResponder() } -} - -// MARK: - Error Handling - -extension SetupViewController { - private func handleError(with error: SetupError) { - hideSpinner() - logger.logWarning("handling error during setup: \(error)") - - switch error { - case .emptyFetchedKeys: - let user = DataService.shared.email ?? "unknown_title".localized - let msg = "setup_no_backups".localized + user - showSearchBackupError(with: msg) - case .noBackups: - showSearchBackupError(with: "setup_no_backups".localized) - case let .parseKey(error): - showErrorAlert(with: "setup_action_failed".localized, error: error) - } - } - - private func errorAlert(with message: String) -> UIAlertController { - let alert = UIAlertController(title: "Notice", message: message, preferredStyle: .alert) - - let useOtherAccountAction = UIAlertAction( - title: "setup_use_otherAccount".localized, - style: .default - ) { [weak self] _ in - self?.handleOtherAccount() - } - - let retryAction = UIAlertAction( - title: "Retry", - style: .default - ) { [weak self] _ in - // TODO: - ANTON - rework retry logic - proceed to searching - } - - alert.addAction(useOtherAccountAction) - alert.addAction(retryAction) - - return alert - } - - private func showErrorAlert(with msg: String, error: Error? = nil) { - hideSpinner() - - let errStr: String = { - guard let err = error else { return "" } - return "\n\n\(err)" - }() - - let alert = errorAlert(with: msg + errStr) - - present(alert, animated: true, completion: nil) - } - - private func showSearchBackupError(with message: String) { - let alert = errorAlert(with: message) - - let importAction = UIAlertAction( - title: "setup_action_import".localized, - style: .default - ) { [weak self] _ in - self?.proceedToKeyImport() - } - - let createNewPrivateKeyAction = UIAlertAction( - title: "setup_action_create_new".localized, - style: .default - ) { [weak self] _ in - self?.proceedToCreatingNewKey() - } - - alert.addAction(importAction) - alert.addAction(createNewPrivateKeyAction) - - present(alert, animated: true, completion: nil) - } -} -// MARK: - Recover account - -extension SetupViewController { private func recoverAccount(with backups: [KeyDetails], and passPhrase: String) { let matchingKeyBackups = keyMethods.filterByPassPhraseMatch(keys: backups, passPhrase: passPhrase) @@ -217,28 +124,6 @@ extension SetupViewController { moveToMainFlow() } -} - -// MARK: - Navigation - -extension SetupViewController { - private func proceedToKeyImport() { - hideSpinner() - // TODO: - ANTON - check proceedToKeyImport - let viewController = UIViewController() - navigationController?.pushViewController(viewController, animated: true) - } - - private func proceedToCreatingNewKey() { - hideSpinner() - let viewController = SetupKeyViewController(user: user) - navigationController?.pushViewController(viewController, animated: true) - } -} - -// MARK: - Events - -extension SetupViewController { private func handleButtonPressed() { view.endEditing(true) @@ -270,7 +155,7 @@ extension SetupViewController { // MARK: - ASTableDelegate, ASTableDataSource -extension SetupViewController: ASTableDelegate, ASTableDataSource { +extension SetupBackupsViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { Parts.allCases.count } @@ -282,7 +167,7 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { case .title: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.setupTitle, + title: self.decorator.title(for: .setup), insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) @@ -322,11 +207,7 @@ extension SetupViewController: ASTableDelegate, ASTableDataSource { $0.button.accessibilityIdentifier = "load_account" } case .optionalAction: - return ButtonCellNode( - title: self.decorator.useAnotherAccountTitle, - insets: self.decorator.insets.optionalButtonInsets, - color: .white - ) { [weak self] in + return ButtonCellNode(input: .chooseAnotherAccount) { [weak self] in self?.handleOtherAccount() } case .divider: diff --git a/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift similarity index 89% rename from FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift rename to FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift index 2ece15f91..11561feb4 100644 --- a/FlowCrypt/Controllers/Key/Enter Pass Phrase/EnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift @@ -9,7 +9,8 @@ import AsyncDisplayKit import FlowCryptUI -final class EnterPassPhraseViewController: TableNodeViewController { +// TODO: - ANTON - add radio button +final class SetupEnterPassPhraseViewController: TableNodeViewController { private enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, enterPhrase, chooseAnother @@ -18,7 +19,7 @@ final class EnterPassPhraseViewController: TableNodeViewController { } } - private let decorator: EnterPassPhraseViewDecorator + private let decorator: SetupViewDecorator private let email: String private let fetchedKeys: [KeyDetails] private let keyMethods: KeyMethodsType @@ -29,7 +30,7 @@ final class EnterPassPhraseViewController: TableNodeViewController { private var passPhrase: String? init( - decorator: EnterPassPhraseViewDecorator = EnterPassPhraseViewDecorator(), + decorator: SetupViewDecorator = SetupViewDecorator(), keyMethods: KeyMethodsType = KeyMethods(), keysService: KeyDataServiceType = DataService.shared, router: GlobalRouterType = GlobalRouter(), @@ -67,7 +68,7 @@ final class EnterPassPhraseViewController: TableNodeViewController { private func setupUI() { node.delegate = self node.dataSource = self - title = decorator.sceneTitle + title = decorator.sceneTitle(for: .enterPassPhrase) node.view.contentInsetAdjustmentBehavior = .never } @@ -83,7 +84,7 @@ final class EnterPassPhraseViewController: TableNodeViewController { // MARK: - Keyboard -extension EnterPassPhraseViewController { +extension SetupEnterPassPhraseViewController { // swiftlint:disable discarded_notification_center_observer /// Observation should be removed in a place where subscription is private func observeKeyboardNotifications() { @@ -113,7 +114,7 @@ extension EnterPassPhraseViewController { // MARK: - ASTableDelegate, ASTableDataSource -extension EnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { +extension SetupEnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { Parts.allCases.count } @@ -125,8 +126,8 @@ extension EnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { case .title: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.passPhraseTitle, - insets: self.decorator.titleInsets, + title: self.decorator.title(for: .enterPassPhrase), + insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) ) @@ -134,7 +135,7 @@ extension EnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { return SetupTitleNode( SetupTitleNode.Input( title: self.decorator.subtitleStyle(self.email), - insets: self.decorator.subTitleInset, + insets: self.decorator.insets.subTitleInset, backgroundColor: .backgroundColor ) ) @@ -152,15 +153,15 @@ extension EnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { } case .enterPhrase: return ButtonCellNode( - title: self.decorator.passPhraseContine, - insets: self.decorator.passPhraseInsets + title: self.decorator.buttonTitle(for: .passPhraseContinue), + insets: self.decorator.insets.buttonInsets ) { [weak self] in self?.handleContinueAction() } case .chooseAnother: return ButtonCellNode( - title: self.decorator.passPhraseChooseAnother, - insets: self.decorator.buttonInsets, + title: self.decorator.buttonTitle(for: .passPhraseChooseAnother), + insets: self.decorator.insets.buttonInsets, color: .lightGray ) { [weak self] in self?.navigationController?.popViewController(animated: true) @@ -174,7 +175,7 @@ extension EnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { // MARK: - Actions -extension EnterPassPhraseViewController { +extension SetupEnterPassPhraseViewController { private func handleContinueAction() { view.endEditing(true) guard let passPhrase = passPhrase else { return } @@ -255,5 +256,3 @@ extension EnterPassPhraseViewController { router.proceed() } } - -// TODO: - ANTON - add radio button diff --git a/FlowCrypt/Controllers/Key/Import Key/ImportKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift similarity index 86% rename from FlowCrypt/Controllers/Key/Import Key/ImportKeyViewController.swift rename to FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift index 089493e9c..fa0ad3d8c 100644 --- a/FlowCrypt/Controllers/Key/Import Key/ImportKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift @@ -10,7 +10,7 @@ import AsyncDisplayKit import FlowCryptUI import MobileCoreServices -final class ImportKeyViewController: TableNodeViewController { +final class SetupImportKeyViewController: TableNodeViewController { private enum Parts: Int, CaseIterable { case title, description, fileImport, pasteBoardImport @@ -19,7 +19,7 @@ final class ImportKeyViewController: TableNodeViewController { } } - private let decorator: EnterPassPhraseViewDecorator + private let decorator: SetupViewDecorator private let pasteboard: UIPasteboard private let dataService: DataServiceType private let core: Core @@ -29,7 +29,7 @@ final class ImportKeyViewController: TableNodeViewController { } init( - decorator: EnterPassPhraseViewDecorator = EnterPassPhraseViewDecorator(), + decorator: SetupViewDecorator = SetupViewDecorator(), pasteboard: UIPasteboard = UIPasteboard.general, core: Core = Core.shared, dataService: DataServiceType = DataService.shared @@ -65,7 +65,7 @@ final class ImportKeyViewController: TableNodeViewController { private func setupUI() { node.delegate = self node.dataSource = self - title = decorator.sceneTitle + title = decorator.sceneTitle(for: .importKey) } private func updateSubtitle() { @@ -77,20 +77,20 @@ final class ImportKeyViewController: TableNodeViewController { // MARK: - ASTableDelegate, ASTableDataSource -extension ImportKeyViewController: ASTableDelegate, ASTableDataSource { +extension SetupImportKeyViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { Parts.allCases.count } func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { - { [weak self] in + return { [weak self] in guard let self = self, let part = Parts(rawValue: indexPath.row) else { return ASCellNode() } switch part { case .title: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.title, - insets: self.decorator.titleInsets, + title: self.decorator.title(for: .importKey), + insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) ) @@ -98,21 +98,21 @@ extension ImportKeyViewController: ASTableDelegate, ASTableDataSource { return SetupTitleNode( SetupTitleNode.Input( title: self.decorator.subtitleStyle(self.userInfoMessage), - insets: self.decorator.subTitleInset, + insets: self.decorator.insets.subTitleInset, backgroundColor: .backgroundColor ) ) case .fileImport: return ButtonCellNode( - title: self.decorator.fileImportTitle, - insets: self.decorator.buttonInsets + title: self.decorator.buttonTitle(for: .fileImport), + insets: self.decorator.insets.buttonInsets ) { [weak self] in self?.proceedToKeyImportFromFile() } case .pasteBoardImport: return ButtonCellNode( - title: self.decorator.pasteBoardTitle, - insets: self.decorator.buttonInsets + title: self.decorator.buttonTitle(for: .pasteBoard), + insets: self.decorator.insets.buttonInsets ) { [weak self] in self?.proceedToKeyImportFromPasteboard() } @@ -126,7 +126,7 @@ extension ImportKeyViewController: ASTableDelegate, ASTableDataSource { // MARK: - Actions -extension ImportKeyViewController { +extension SetupImportKeyViewController { private func proceedToKeyImportFromFile() { let acceptableDocumentTypes = [ String(kUTTypeText), @@ -171,7 +171,7 @@ extension ImportKeyViewController { } private func proceedToPassPhrase(with email: String, keys: [KeyDetails]) { - let viewController = EnterPassPhraseViewController( + let viewController = SetupEnterPassPhraseViewController( decorator: decorator, email: email, fetchedKeys: keys @@ -186,7 +186,7 @@ extension ImportKeyViewController { // MARK: - UIDocumentPickerDelegate -extension ImportKeyViewController: UIDocumentPickerDelegate { +extension SetupImportKeyViewController: UIDocumentPickerDelegate { func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let pickedURL = urls.first else { return } handlePicked(document: pickedURL) @@ -208,5 +208,3 @@ extension ImportKeyViewController: UIDocumentPickerDelegate { } } } - -// TODO: - ANTON - add radio button diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index d03f0518f..81c2544a0 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -9,9 +9,10 @@ import AsyncDisplayKit import FlowCryptUI +// TODO: - ANTON - add refresh controller final class SetupInitialViewController: TableNodeViewController { private enum Parts: Int, CaseIterable { - case title, createKey, importKey, anotherAccount + case title, description, createKey, importKey, anotherAccount } private enum State { @@ -37,6 +38,10 @@ final class SetupInitialViewController: TableNodeViewController { didSet { handleNewState() } } + override var preferredStatusBarStyle: UIStatusBarStyle { + .default + } + private let backupService: BackupServiceType private let user: UserId private let router: GlobalRouterType @@ -46,7 +51,7 @@ final class SetupInitialViewController: TableNodeViewController { init( user: UserId, - backupService: BackupServiceType = BackupServiceMock(), + backupService: BackupServiceType = BackupService(), router: GlobalRouterType = GlobalRouter(), decorator: SetupViewDecorator = SetupViewDecorator() ) { @@ -66,26 +71,22 @@ final class SetupInitialViewController: TableNodeViewController { override func viewDidLoad() { super.viewDidLoad() setupUI() + state = .searching } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - navigationController?.setNavigationBarHidden(true, animated: animated) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) + setNeedsStatusBarAppearanceUpdate() state = .searching } - - private func setupUI() { - node.delegate = self - node.dataSource = self - } } +// MARK: - Action Handling extension SetupInitialViewController { + private func handleNewState() { + logger.logInfo("Changed to new state \(state)") + switch state { case .searching: searchBackups() @@ -96,7 +97,7 @@ extension SetupInitialViewController { } private func searchBackups() { - logger.logInfo("[Setup] searching for backups in inbox") + logger.logInfo("Searching for backups in inbox") backupService.fetchBackups(for: user) .then(on: .main) { [weak self] keys in @@ -111,25 +112,13 @@ extension SetupInitialViewController { router.signOut() } - private func proceedToSetupWith(keys: [KeyDetails]) { - logger.logInfo("Finish searching for backups in inbox") - - if keys.isEmpty { - logger.logInfo("No key backups found in inbox") - state = .noKeyBackups - } else { - logger.logInfo("\(keys.count) key backups found in inbox") - let viewController = SetupViewController(fetchedEncryptedKeys: keys, user: user) - navigationController?.pushViewController(viewController, animated: true) - } - } - private func handle(error: Error) { handleCommon(error: error) state = .error(error) } } +// MARK: - ASTableDelegate, ASTableDataSource extension SetupInitialViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { state.numberOfRows @@ -153,13 +142,21 @@ extension SetupInitialViewController: ASTableDelegate, ASTableDataSource { } } +// MARK: - UI extension SetupInitialViewController { + private func setupUI() { + node.delegate = self + node.dataSource = self + + title = decorator.sceneTitle(for: .setup) + } + private func searchStateNode(for indexPath: IndexPath) -> ASCellNode { switch indexPath.row { case 0: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.setupTitle, + title: self.decorator.title(for: .setup), insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) @@ -176,29 +173,37 @@ extension SetupInitialViewController { case .title: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.setupTitle, + title: self.decorator.title(for: .setup), insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) ) + case .description: + return SetupTitleNode( + SetupTitleNode.Input( + title: self.decorator.subtitle(for: .noBackups), + insets: self.decorator.insets.subTitleInset, + backgroundColor: .backgroundColor + ) + ) case .createKey: - return ButtonCellNode( + let input = ButtonCellNode.Input( title: self.decorator.buttonTitle(for: .createKey), insets: self.decorator.insets.buttonInsets - ) { [weak self] in + ) + return ButtonCellNode(input: input) { [weak self] in + self?.proceedToCreatingNewKey() } case .importKey: - return ButtonCellNode( + let input = ButtonCellNode.Input( title: self.decorator.buttonTitle(for: .importKey), insets: self.decorator.insets.buttonInsets - ) { [weak self] in + ) + return ButtonCellNode(input: input) { [weak self] in + self?.proceedToKeyImport() } case .anotherAccount: - return ButtonCellNode( - title: self.decorator.useAnotherAccountTitle, - insets: self.decorator.insets.optionalButtonInsets, - color: .white - ) { [weak self] in + return ButtonCellNode(input: .chooseAnotherAccount) { [weak self] in self?.handleOtherAccount() } } @@ -209,7 +214,7 @@ extension SetupInitialViewController { case 0: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.setupTitle, + title: self.decorator.title(for: .setup), insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) @@ -232,3 +237,29 @@ extension SetupInitialViewController { } } } + +// MARK: - Navigation +extension SetupInitialViewController { + private func proceedToKeyImport() { + let viewController = SetupImportKeyViewController() + navigationController?.pushViewController(viewController, animated: true) + } + + private func proceedToCreatingNewKey() { + let viewController = SetupKeyViewController(user: user) + navigationController?.pushViewController(viewController, animated: true) + } + + private func proceedToSetupWith(keys: [KeyDetails]) { + logger.logInfo("Finish searching for backups in inbox") + + if keys.isEmpty { + logger.logInfo("No key backups found in inbox") + state = .noKeyBackups + } else { + logger.logInfo("\(keys.count) key backups found in inbox") + let viewController = SetupBackupsViewController(fetchedEncryptedKeys: keys, user: user) + navigationController?.pushViewController(viewController, animated: true) + } + } +} diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index 9ac775d77..af65d728a 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -25,7 +25,7 @@ enum CreateKeyError: Error { final class SetupKeyViewController: TableNodeViewController { enum Parts: Int, CaseIterable { - case title, description, passPhrase, divider, action, subtitle + case title, description, passPhrase, divider, saveLocally, saveInMemory, action, subtitle } private let parts = Parts.allCases @@ -37,6 +37,10 @@ final class SetupKeyViewController: TableNodeViewController { private let storage: DataServiceType & KeyDataServiceType private let attester: AttesterApiType + private var shouldSaveLocally = true { + didSet { handleSelectedOption() } + } + init( user: UserId, backupService: BackupServiceType = BackupService(), @@ -74,6 +78,8 @@ extension SetupKeyViewController { private func setupUI() { node.delegate = self node.dataSource = self + + title = decorator.sceneTitle(for: .createKey) observeKeyboardNotifications() } @@ -103,6 +109,11 @@ extension SetupKeyViewController { node.contentInset = insets node.scrollToRow(at: IndexPath(item: Parts.passPhrase.rawValue, section: 0), at: .middle, animated: true) } + + private func handleSelectedOption() { + let rows = [Parts.saveLocally, Parts.saveInMemory].map { IndexPath(row: $0.rawValue, section: 0) } + node.reloadRows(at: rows, with: .automatic) + } } // MARK: - Setup @@ -234,7 +245,7 @@ extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { case .title: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.setupTitle, + title: self.decorator.title(for: .setup), insets: self.decorator.insets.titleInset, backgroundColor: .backgroundColor ) @@ -275,12 +286,26 @@ extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { ) case .divider: return DividerCellNode(inset: self.decorator.insets.dividerInsets) + case .saveLocally: + return CheckBoxTextNode(input: .passPhraseLocally(isSelected: self.shouldSaveLocally)) + case .saveInMemory: + return CheckBoxTextNode(input: .passPhraseMemory(isSelected: !self.shouldSaveLocally)) } } } func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { - guard let part = Parts(rawValue: indexPath.row), case .description = part else { return } - showChoosingOptions() + guard let part = Parts(rawValue: indexPath.row) else { return } + + switch part { + case .description: + showChoosingOptions() + case .saveLocally: + shouldSaveLocally = true + case .saveInMemory: + shouldSaveLocally = false + default: + break + } } } diff --git a/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift b/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift index 9913f3e31..a68d224cd 100644 --- a/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift +++ b/FlowCrypt/Controllers/Setup/SetupViewDecorator.swift @@ -11,8 +11,8 @@ import FlowCryptUI import UIKit struct SetupViewInsets { - let titleInset = UIEdgeInsets(top: 92, left: 16, bottom: 100, right: 16) - let subTitleInset = UIEdgeInsets(top: 0, left: 16, bottom: 60, right: 16) + let titleInset = UIEdgeInsets(top: 64, left: 16, bottom: 64, right: 16) + let subTitleInset = UIEdgeInsets(top: 0, left: 16, bottom: 24, right: 16) let buttonInsets = UIEdgeInsets(top: 8, left: 24, bottom: 8, right: 24) let optionalButtonInsets = UIEdgeInsets(top: 0, left: 24, bottom: 8, right: 24) let dividerInsets = UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24) @@ -21,25 +21,6 @@ struct SetupViewInsets { struct SetupViewDecorator { let insets = SetupViewInsets() - let setupTitle = "setup_title" - .localized - .attributed( - .bold(35), - color: .mainTextColor, - alignment: .center - ) - - let useAnotherAccountTitle = "setup_use_another" - .localized - .attributed( - .regular(15), - color: UIColor.colorFor( - darkStyle: .black, - lightStyle: .blueColor - ), - alignment: .center - ) - let passPhraseLostDescription = "create_pass_phrase_lost" .localized .attributed( @@ -48,9 +29,44 @@ struct SetupViewDecorator { alignment: .center ) + // MARK: Title + enum TitleType { + case setup, enterPassPhrase, importKey, createKey + } + + func title(for titleType: TitleType) -> NSAttributedString { + let text: String + + switch titleType { + case .setup, .createKey: + text = "setup_title" + case .enterPassPhrase, .importKey: + text = "import_key_description" + } + + return text + .localized + .attributed( + .bold(35), + color: .mainTextColor, + alignment: .center + ) + } + + func sceneTitle(for titleType: TitleType) -> String { + switch titleType { + case .setup: + return "FlowCrypt" + case .enterPassPhrase, .importKey: + return "import_key_title".localized + case .createKey: + return "setup_create_key_title".localized + } + } + // MARK: Subtitle enum SubtitleType { - case common, fetchedKeys(Int), choosingPassPhrase + case common, fetchedKeys(Int), choosingPassPhrase, noBackups } func subtitle(for subtitleType: SubtitleType) -> NSAttributedString { @@ -63,14 +79,25 @@ struct SetupViewDecorator { subtitle = "setup_description".localized case .choosingPassPhrase: subtitle = "create_pass_phrase_description".localized + case .noBackups: + let user = DataService.shared.email ?? "unknown_title".localized + let msg = "setup_no_backups".localized + user + subtitle = msg } - return subtitle.attributed(.regular(17)) + return subtitle + .attributed( + .regular(17), + alignment: .center + ) + } + + var subtitleStyle: (String) -> NSAttributedString { { $0.attributed(.regular(17), alignment: .center) } } // MARK: Button enum ButtonAction { - case createKey, importKey, loadAccount, setPassPhrase + case createKey, importKey, loadAccount, setPassPhrase, pasteBoard, passPhraseContinue, passPhraseChooseAnother, fileImport } func buttonTitle(for action: ButtonAction) -> NSAttributedString { @@ -85,6 +112,14 @@ struct SetupViewDecorator { buttonTitle = "setup_load" case .setPassPhrase: buttonTitle = "create_pass_phrase_set_title" + case .pasteBoard: + buttonTitle = "import_key_paste" + case .passPhraseContinue: + buttonTitle = "import_key_continue" + case .passPhraseChooseAnother: + buttonTitle = "import_key_choose" + case .fileImport: + buttonTitle = "import_key_file" } return buttonTitle diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index 49856136b..f5cafd348 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -54,17 +54,22 @@ struct AppStartup { return } -// switch entryPoint { -// case .mainFlow: -// window.rootViewController = SideMenuNavigationController() -// case .signIn: -// window.rootViewController = MainNavigationController(rootViewController: SignInViewController()) -// case .setupFlow(let userId): - // TODO: - ANTON -// let setupViewController = SetupInitialViewController(user: userId) - let setupViewController = SetupInitialViewController(user: .init(email: "flow.test.anton@gmail.com", name: "Name")) - window.rootViewController = MainNavigationController(rootViewController: setupViewController) -// } + let viewController: UIViewController + + switch entryPoint { + case .mainFlow: + viewController = SideMenuNavigationController() + case .signIn: + viewController = MainNavigationController(rootViewController: SignInViewController()) + case .setupFlow(let userId): + let setupViewController = SetupInitialViewController(user: userId) + viewController = MainNavigationController(rootViewController: setupViewController) + } + + // TODO: - ANTON - warning + // SetupEnterPassPhraseViewController(email: "", fetchedKeys: []) + window.rootViewController = MainNavigationController(rootViewController: SetupKeyViewController(user: UserId.init(email: "a", name: "a"))) +// window.rootViewController = viewController } private func entryPointForUser(session: SessionType?) -> EntryPoint? { diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift index 0dab95762..ec176c0fb 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift @@ -9,7 +9,6 @@ import Foundation import Promises -// TODO: - ANTON - remove from FlowCrypt target protocol BackupServiceType { /// get all existed backups func fetchBackups(for userId: UserId) -> Promise<[KeyDetails]> diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index cff6189e0..f150ae340 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -88,6 +88,9 @@ "setup_create_key" = "Create"; "setup_initial_create_key" = "Create a new key"; "setup_initial_import_key" = "Import my key"; +"setup_create_key_title" = "Create key"; +"setup_save_pass_locally" = "Store pass phrase locally"; +"setup_save_pass_in_memory" = "Keep pass phrase in memory"; // Key Import "import_key_title" = "Import Key"; diff --git a/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift b/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift index fc87f1ba7..20c27fa39 100644 --- a/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift +++ b/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift @@ -8,18 +8,16 @@ import Foundation import Promises -//@testable import FlowCrypt -// TODO: - ANTON - remove from FlowCrypt target final class BackupServiceMock: BackupServiceType { - var fetchBackupsResult: Result<[KeyDetails], Error> = .failure(MockError.some) + var fetchBackupsResult: Result<[KeyDetails], Error> = .success([]) func fetchBackups(for userId: UserId) -> Promise<[KeyDetails]> { - .resolveAfter(with: fetchBackupsResult) + Promise<[KeyDetails]>.resolveAfter(with: fetchBackupsResult) } var backupToInboxResult: Result = .success(()) func backupToInbox(keys: [KeyDetails], for userId: UserId) -> Promise { - .resolveAfter(with: backupToInboxResult) + Promise.resolveAfter(with: backupToInboxResult) } var isBackupAsFile = false diff --git a/FlowCryptUI/Cell Nodes/ButtonCellNode.swift b/FlowCryptUI/Cell Nodes/ButtonCellNode.swift index 5456a444f..346bceba1 100644 --- a/FlowCryptUI/Cell Nodes/ButtonCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ButtonCellNode.swift @@ -17,7 +17,7 @@ public final class ButtonCellNode: CellNode { public init( title: NSAttributedString, insets: UIEdgeInsets, - color: UIColor? + color: UIColor? = nil ) { self.title = title self.insets = insets diff --git a/FlowCryptUI/Cell Nodes/CheckBoxTextNode.swift b/FlowCryptUI/Cell Nodes/CheckBoxTextNode.swift index d165155cd..34e0668a9 100644 --- a/FlowCryptUI/Cell Nodes/CheckBoxTextNode.swift +++ b/FlowCryptUI/Cell Nodes/CheckBoxTextNode.swift @@ -55,22 +55,11 @@ public final class CheckBoxTextNode: CellNode { checkBox.style.preferredSize = input.preferredSize if input.subtitle != nil { -// textNode.style.flexGrow = 1 -// textNode.style.flexShrink = 1 -// subtitleTextNode.style.flexGrow = 1 -// subtitleTextNode.style.flexShrink = 1 - let textStack = ASStackLayoutSpec() textStack.direction = .vertical textStack.style.flexGrow = 1 textStack.style.flexShrink = 1 textStack.children = [textNode, subtitleTextNode] -// direction: .vertical, -// spacing: 8, -// justifyContent: .start, -// alignItems: .baselineFirst, -// children: -// ) let stack = ASStackLayoutSpec( direction: .horizontal, From 99d69bf29835a871c5bed160768bf8778b9205c6 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Thu, 3 Jun 2021 14:48:48 +0300 Subject: [PATCH 08/26] Update ui for all pass phrase related controllers --- FlowCrypt.xcodeproj/project.pbxproj | 12 +++- .../Setup/PassPhraseStorageService.swift | 51 ++++++++++++++ .../Controllers/Setup/PassPraseSaveable.swift | 37 ++++++++++ .../Setup/SetupBackupsViewController.swift | 69 +++++++++---------- .../SetupEnterPassPhraseViewController.swift | 32 ++++++++- .../Setup/SetupInitialViewController.swift | 19 +++++ .../Setup/SetupKeyViewController.swift | 26 ++++--- .../DataManager/DataService.swift | 10 ++- .../Encrypted Storage/EncryptedStorage.swift | 15 ++-- .../Functionality/Services/AppStartup.swift | 9 ++- 10 files changed, 217 insertions(+), 63 deletions(-) create mode 100644 FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift create mode 100644 FlowCrypt/Controllers/Setup/PassPraseSaveable.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 5e07b1bf3..9c07d1d73 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -112,6 +112,8 @@ 9F716308234FC73E0031645E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9F71630A234FC73E0031645E /* Localizable.strings */; }; 9F7920E32666D28400DA3D80 /* BackupServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163EC266574CB00106194 /* BackupServiceMock.swift */; }; 9F7920EE2666D32500DA3D80 /* BackupServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F416427266575DC00106194 /* BackupServiceType.swift */; }; + 9F7920F52667CEF100DA3D80 /* PassPraseSaveable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */; }; + 9F7920FC2668080F00DA3D80 /* PassPhraseStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7920FB2668080F00DA3D80 /* PassPhraseStorageService.swift */; }; 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA0C3D34A69851A238E87 /* Core.swift */; }; 9F7DE8C6232029D000F10B3E /* CoreTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAC732B988D9704658812 /* CoreTypes.swift */; }; 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCACB22E895C2500A99350 /* CoreHost.swift */; }; @@ -488,6 +490,8 @@ 9F716309234FC73E0031645E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 9F71630B234FC7500031645E /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 9F72E866263ECE2A0039CF81 /* Trace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trace.swift; sourceTree = ""; }; + 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPraseSaveable.swift; sourceTree = ""; }; + 9F7920FB2668080F00DA3D80 /* PassPhraseStorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhraseStorageService.swift; sourceTree = ""; }; 9F8220D426336626004B2009 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 9F8277952373732000E19C07 /* UIImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; 9F82779723737E0900E19C07 /* MessageViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewDecorator.swift; sourceTree = ""; }; @@ -1423,11 +1427,13 @@ isa = PBXGroup; children = ( 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */, - 9F268890237DC55600428A94 /* SetupImportKeyViewController.swift */, + 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */, C192421E1EC48B6900C3D251 /* SetupBackupsViewController.swift */, + 9F268890237DC55600428A94 /* SetupImportKeyViewController.swift */, 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */, - 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */, 9F17976C2368EEBD002BF770 /* SetupViewDecorator.swift */, + 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */, + 9F7920FB2668080F00DA3D80 /* PassPhraseStorageService.swift */, ); path = Setup; sourceTree = ""; @@ -2396,6 +2402,7 @@ D20D3C672520AB1000D4AA9A /* BackupViewController.swift in Sources */, D2E26F6324F1698100612AF1 /* ContactsListViewController.swift in Sources */, 9F53CB7B2555E1E300C0157A /* GmailService+folders.swift in Sources */, + 9F7920FC2668080F00DA3D80 /* PassPhraseStorageService.swift in Sources */, D2E26F6624F169B400612AF1 /* ContactsListDecorator.swift in Sources */, D2FF6968243115F9007182F0 /* EmailProviderViewDecorator.swift in Sources */, 9F589F0D238C7A9B007FD759 /* LocalStorage.swift in Sources */, @@ -2405,6 +2412,7 @@ D212D35D24C1AACF00035991 /* PrvKeyInfo.swift in Sources */, 9F23EA50237217140017DFED /* ComposeViewDecorator.swift in Sources */, D21574B724376852006B094F /* ConnectionType.swift in Sources */, + 9F7920F52667CEF100DA3D80 /* PassPraseSaveable.swift in Sources */, 5ADEDCAF23A3EA9E00EC495E /* KeySettingsViewDecorator.swift in Sources */, 9F3EF33123B1785600FA0CEF /* MsgListViewConroller.swift in Sources */, 9F31ABA0232C071700CF87EA /* GlobalRouter.swift in Sources */, diff --git a/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift b/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift new file mode 100644 index 000000000..85707ac8f --- /dev/null +++ b/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift @@ -0,0 +1,51 @@ +// +// PassPhraseStorageService.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 02.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import UIKit + +final class PassPhraseStorageService { + struct Context { + let passPhrase: String + let keys: [KeyDetails] + let source: KeySource + let isLocally: Bool + } + + let storage: KeyStorageType + + init(storage: KeyStorageType = EncryptedStorage()) { + self.storage = storage + } + + func savePassPhrase(with context: Context) { + if context.isLocally { + storage.addKeys(keyDetails: context.keys, passPhrase: context.passPhrase, source: context.source) + } else { + // TODO: - ANTON + } + } +} + +import RealmSwift +class KeyStorageMock: KeyStorageType { + func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { + } + + func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { + } + + var publicKeyResult: () -> (String?) = { nil } + func publicKey() -> String? { + publicKeyResult() + } + + var keysResult: () -> ([KeyInfo]) = { [] } + func keys() -> [KeyInfo] { + keysResult() + } +} diff --git a/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift b/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift new file mode 100644 index 000000000..52f6d7fb0 --- /dev/null +++ b/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift @@ -0,0 +1,37 @@ +// +// PassPhraseSaveable.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 02.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import FlowCryptUI + +protocol PassPhraseSaveable { + var shouldSaveLocally: Bool { get set } + var passPhraseIndexes: [IndexPath] { get } + var saveLocallyNode: CellNode { get } + var saveInMemoryNode: CellNode { get } + + func handleSelectedPassPhraseOption() + func showPassPhraseErrorAlert() +} + +extension PassPhraseSaveable where Self: TableNodeViewController { + func handleSelectedPassPhraseOption() { + node.reloadRows(at: passPhraseIndexes, with: .automatic) + } + + var saveLocallyNode: CellNode { + CheckBoxTextNode(input: .passPhraseLocally(isSelected: self.shouldSaveLocally)) + } + + var saveInMemoryNode: CellNode { + CheckBoxTextNode(input: .passPhraseLocally(isSelected: !self.shouldSaveLocally)) + } + + func showPassPhraseErrorAlert() { + showAlert(message: "setup_enter_pass_phrase".localized) + } +} diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index 4d894b9cc..aea20b63f 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -6,9 +6,9 @@ import AsyncDisplayKit import FlowCryptUI import Promises -final class SetupBackupsViewController: TableNodeViewController { +final class SetupBackupsViewController: TableNodeViewController, PassPhraseSaveable { private enum Parts: Int, CaseIterable { - case title, description, passPhrase, divider, action, optionalAction + case title, description, passPhrase, divider, saveLocally, saveInMemory, action, optionalAction } private let router: GlobalRouterType @@ -22,6 +22,17 @@ final class SetupBackupsViewController: TableNodeViewController { private var passPhrase: String? private lazy var logger = Logger.nested(in: Self.self, with: .setup) + var shouldSaveLocally = true { + didSet { + handleSelectedPassPhraseOption() + } + } + + var passPhraseIndexes: [IndexPath] { + [Parts.saveLocally, Parts.saveInMemory] + .map { IndexPath(row: $0.rawValue, section: 0) } + } + init( fetchedEncryptedKeys: [KeyDetails], router: GlobalRouterType = GlobalRouter(), @@ -60,7 +71,6 @@ final class SetupBackupsViewController: TableNodeViewController { } // MARK: - Setup - extension SetupBackupsViewController { private func setupUI() { node.delegate = self @@ -96,7 +106,6 @@ extension SetupBackupsViewController { } // MARK: - Actions - extension SetupBackupsViewController { private func handleBackups() { guard fetchedEncryptedKeys.isNotEmpty else { @@ -130,7 +139,7 @@ extension SetupBackupsViewController { guard let passPhrase = passPhrase else { return } guard passPhrase.isNotEmpty else { - showAlert(message: "setup_enter_pass_phrase".localized) + showPassPhraseErrorAlert() return } @@ -154,7 +163,6 @@ extension SetupBackupsViewController { } // MARK: - ASTableDelegate, ASTableDataSource - extension SetupBackupsViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { Parts.allCases.count @@ -175,14 +183,12 @@ extension SetupBackupsViewController: ASTableDelegate, ASTableDataSource { case .description: return SetupTitleNode( SetupTitleNode.Input( - // TODO: - ANTON - check text - title: self.decorator.subtitle(for: .choosingPassPhrase), + title: self.decorator.subtitle(for: .common), insets: self.decorator.insets.subTitleInset, backgroundColor: .backgroundColor ) ) case .passPhrase: - // TODO: - ANTON - check text return TextFieldCellNode(input: .passPhraseTextFieldStyle) { [weak self] action in guard case let .didEndEditing(value) = action else { return } self?.passPhrase = value @@ -196,11 +202,11 @@ extension SetupBackupsViewController: ASTableDelegate, ASTableDataSource { return true } case .action: - return ButtonCellNode( - // TODO: - ANTON - check text + let input = ButtonCellNode.Input( title: self.decorator.buttonTitle(for: .loadAccount), insets: self.decorator.insets.buttonInsets - ) { [weak self] in + ) + return ButtonCellNode(input: input) { [weak self] in self?.handleButtonPressed() } .then { @@ -212,31 +218,24 @@ extension SetupBackupsViewController: ASTableDelegate, ASTableDataSource { } case .divider: return DividerCellNode(inset: self.decorator.insets.dividerInsets) + case .saveLocally: + return self.saveLocallyNode + case .saveInMemory: + return self.saveInMemoryNode } } } -} - -// TODO: - ANTON - -/* - During setup - new key - when importing key - when loading from backup - creating - entering pass phrase - -the user should see two radio buttons: - o store pass phrase locally - Default is to store. - o keep pass phrase in memory + func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { + guard let part = Parts(rawValue: indexPath.row) else { return } - If the user switches it, - then we do not store pass phrase with the key (or at all). - We only keep it in memory for up to 4 hours from the moment it was stored - then it needs to be forgotten. - During those 4 hours, the key will be used for actions (eg decrypt messages). - After those 4 hours, the user will be prompted for a pass phrase with a modal / alert to re-enter it, at which point it will be again remembered for 4 hours. - - If app gets killed, pass phrase gets forgotten. - */ + switch part { + case .saveLocally: + shouldSaveLocally = true + case .saveInMemory: + shouldSaveLocally = false + default: + break + } + } +} diff --git a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift index 11561feb4..3567f35f0 100644 --- a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift @@ -10,9 +10,9 @@ import AsyncDisplayKit import FlowCryptUI // TODO: - ANTON - add radio button -final class SetupEnterPassPhraseViewController: TableNodeViewController { +final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhraseSaveable { private enum Parts: Int, CaseIterable { - case title, description, passPhrase, divider, enterPhrase, chooseAnother + case title, description, passPhrase, divider, saveLocally, saveInMemory, enterPhrase, chooseAnother var indexPath: IndexPath { IndexPath(row: rawValue, section: 0) @@ -29,6 +29,17 @@ final class SetupEnterPassPhraseViewController: TableNodeViewController { private var passPhrase: String? + var shouldSaveLocally = true { + didSet { + handleSelectedPassPhraseOption() + } + } + + var passPhraseIndexes: [IndexPath] { + [Parts.saveLocally, Parts.saveInMemory] + .map { IndexPath(row: $0.rawValue, section: 0) } + } + init( decorator: SetupViewDecorator = SetupViewDecorator(), keyMethods: KeyMethodsType = KeyMethods(), @@ -168,9 +179,26 @@ extension SetupEnterPassPhraseViewController: ASTableDelegate, ASTableDataSource } case .divider: return DividerCellNode(inset: UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24)) + case .saveLocally: + return self.saveLocallyNode + case .saveInMemory: + return self.saveInMemoryNode } } } + + func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { + guard let part = Parts(rawValue: indexPath.row) else { return } + + switch part { + case .saveLocally: + shouldSaveLocally = true + case .saveInMemory: + shouldSaveLocally = false + default: + break + } + } } // MARK: - Actions diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 81c2544a0..fe0d83206 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -263,3 +263,22 @@ extension SetupInitialViewController { } } } + +// TODO: - ANTON + +/* + During setup + new key - SetupKeyViewController + when loading from backup - SetupBackupsViewController + when importing key + creating + entering pass phrase + + If the user switches it, + then we do not store pass phrase with the key (or at all). + We only keep it in memory for up to 4 hours from the moment it was stored - then it needs to be forgotten. + During those 4 hours, the key will be used for actions (eg decrypt messages). + After those 4 hours, the user will be prompted for a pass phrase with a modal / alert to re-enter it, at which point it will be again remembered for 4 hours. + + If app gets killed, pass phrase gets forgotten. + */ diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index af65d728a..9769f2847 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -23,7 +23,7 @@ enum CreateKeyError: Error { case conformingPassPhraseError } -final class SetupKeyViewController: TableNodeViewController { +final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable { enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, saveLocally, saveInMemory, action, subtitle } @@ -37,8 +37,15 @@ final class SetupKeyViewController: TableNodeViewController { private let storage: DataServiceType & KeyDataServiceType private let attester: AttesterApiType - private var shouldSaveLocally = true { - didSet { handleSelectedOption() } + var shouldSaveLocally = true { + didSet { + handleSelectedPassPhraseOption() + } + } + + var passPhraseIndexes: [IndexPath] { + [Parts.saveLocally, Parts.saveInMemory] + .map { IndexPath(row: $0.rawValue, section: 0) } } init( @@ -109,11 +116,6 @@ extension SetupKeyViewController { node.contentInset = insets node.scrollToRow(at: IndexPath(item: Parts.passPhrase.rawValue, section: 0), at: .middle, animated: true) } - - private func handleSelectedOption() { - let rows = [Parts.saveLocally, Parts.saveInMemory].map { IndexPath(row: $0.rawValue, section: 0) } - node.reloadRows(at: rows, with: .automatic) - } } // MARK: - Setup @@ -262,6 +264,9 @@ extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { return TextFieldCellNode(input: .passPhraseTextFieldStyle) { [weak self] action in guard case let .didEndEditing(value) = action else { return } } + + // TODO: - ANTON - passPhrase didEndEditing + .onShouldReturn { [weak self] _ in self?.view.endEditing(true) @@ -275,6 +280,7 @@ extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { title: self.decorator.buttonTitle(for: .setPassPhrase), insets: self.decorator.insets.buttonInsets ) { [weak self] in + // TODO: - ANTON - setPassPhrase } case .subtitle: return SetupTitleNode( @@ -287,9 +293,9 @@ extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { case .divider: return DividerCellNode(inset: self.decorator.insets.dividerInsets) case .saveLocally: - return CheckBoxTextNode(input: .passPhraseLocally(isSelected: self.shouldSaveLocally)) + return self.saveLocallyNode case .saveInMemory: - return CheckBoxTextNode(input: .passPhraseMemory(isSelected: !self.shouldSaveLocally)) + return self.saveInMemoryNode } } } diff --git a/FlowCrypt/Functionality/DataManager/DataService.swift b/FlowCrypt/Functionality/DataManager/DataService.swift index a6361d487..70d9cd75a 100644 --- a/FlowCrypt/Functionality/DataManager/DataService.swift +++ b/FlowCrypt/Functionality/DataManager/DataService.swift @@ -73,10 +73,8 @@ extension DataService: DataServiceType { guard let currentUser = currentUser else { return false } - guard let keys = encryptedStorage.keys() else { - return false - } - let isAnyKeysForCurrentUser = keys + + let isAnyKeysForCurrentUser = encryptedStorage.keys() .map(\.account) .map { $0.contains(currentUser.email) } .contains(true) @@ -123,8 +121,8 @@ extension DataService: DataServiceType { // MARK: - DataKeyServiceType extension DataService: KeyDataServiceType { var keys: [PrvKeyInfo]? { - guard let keys = encryptedStorage.keys() else { return nil } - return Array(keys).map(PrvKeyInfo.init) + encryptedStorage.keys() + .map(PrvKeyInfo.init) } var publicKey: String? { diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index 2941ef728..1d60d16e4 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -11,13 +11,15 @@ import Foundation import Promises import RealmSwift -protocol EncryptedStorageType { - var storage: Realm { get } - +protocol KeyStorageType { func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) func publicKey() -> String? - func keys() -> Results? + func keys() -> [KeyInfo] +} + +protocol EncryptedStorageType: KeyStorageType { + var storage: Realm { get } func getAllUsers() -> [UserObject] func saveActiveUser(with user: UserObject) @@ -180,8 +182,9 @@ extension EncryptedStorage { } } - func keys() -> Results? { - storage.objects(KeyInfo.self) + func keys() -> [KeyInfo] { + let result = storage.objects(KeyInfo.self) + return Array(result) } func publicKey() -> String? { diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index f5cafd348..28fa0e1cd 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -68,8 +68,13 @@ struct AppStartup { // TODO: - ANTON - warning // SetupEnterPassPhraseViewController(email: "", fetchedKeys: []) - window.rootViewController = MainNavigationController(rootViewController: SetupKeyViewController(user: UserId.init(email: "a", name: "a"))) -// window.rootViewController = viewController +// window.rootViewController = MainNavigationController( +// rootViewController: +//// SetupKeyViewController(user: UserId.init(email: "a", name: "a") +// SetupBackupsViewController(fetchedEncryptedKeys: [], user: UserId.init(email: "test@gmail.com", name: "a")) +//// SetupEnterPassPhraseViewController(email: "test@gmail.com", fetchedKeys: []) +// ) + window.rootViewController = viewController } private func entryPointForUser(session: SessionType?) -> EntryPoint? { From 3ae96154689703b3fb40b76356dc957a32e361c0 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Thu, 3 Jun 2021 23:16:31 +0300 Subject: [PATCH 09/26] Adopt current functionality to work with pass phrase in memory --- FlowCrypt.xcodeproj/project.pbxproj | 16 +++---- .../Compose/ComposeViewController.swift | 9 ++-- .../Inbox/InboxViewController.swift | 5 +- .../Msg/MessageViewController.swift | 10 ++-- .../Setup/PassPhraseStorageService.swift | 36 +++++++------- .../Setup/SetupBackupsViewController.swift | 4 +- .../SetupEnterPassPhraseViewController.swift | 4 +- .../Setup/SetupKeyViewController.swift | 9 ++-- FlowCrypt/Core/Models/PrvKeyInfo.swift | 15 +++--- .../DataManager/DataService.swift | 22 +-------- .../Encrypted Storage/EncryptedStorage.swift | 4 +- .../KeyServiceErrorHandler.swift | 11 +++-- .../Message Provider/MessageService.swift | 14 ++++-- .../Key Services/KeyDataStorage.swift | 47 +++++++++++++++++++ .../Services/Key Services/KeyService.swift | 39 +++++++++------ .../PassPhraseStorageService.swift | 43 +++++++++++++++++ FlowCrypt/Models/Realm Models/KeyInfo.swift | 8 ++-- 17 files changed, 195 insertions(+), 101 deletions(-) create mode 100644 FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift create mode 100644 FlowCrypt/Functionality/Services/Key Services/PassPhraseStorageService.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 9c07d1d73..c66c7a454 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -113,7 +113,8 @@ 9F7920E32666D28400DA3D80 /* BackupServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163EC266574CB00106194 /* BackupServiceMock.swift */; }; 9F7920EE2666D32500DA3D80 /* BackupServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F416427266575DC00106194 /* BackupServiceType.swift */; }; 9F7920F52667CEF100DA3D80 /* PassPraseSaveable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */; }; - 9F7920FC2668080F00DA3D80 /* PassPhraseStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7920FB2668080F00DA3D80 /* PassPhraseStorageService.swift */; }; + 9F79228826696B0200DA3D80 /* PassPhraseStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F79228726696B0200DA3D80 /* PassPhraseStorageService.swift */; }; + 9F79229426696B9300DA3D80 /* KeyDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */; }; 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA0C3D34A69851A238E87 /* Core.swift */; }; 9F7DE8C6232029D000F10B3E /* CoreTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAC732B988D9704658812 /* CoreTypes.swift */; }; 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCACB22E895C2500A99350 /* CoreHost.swift */; }; @@ -138,7 +139,6 @@ 9FB22CE425715D3E0026EE64 /* GmailServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CE325715D3E0026EE64 /* GmailServiceErrorHandler.swift */; }; 9FB22CF025715D960026EE64 /* BackupServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CEF25715D960026EE64 /* BackupServiceError.swift */; }; 9FB22CF725715DC50026EE64 /* KeyServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CF625715DC50026EE64 /* KeyServiceErrorHandler.swift */; }; - 9FB22D0425715DF00026EE64 /* KeyServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22D0325715DF00026EE64 /* KeyServiceError.swift */; }; 9FBEAE5525D41BFF009E98D4 /* UserMailSessionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBEAE5425D41BFF009E98D4 /* UserMailSessionProvider.swift */; }; 9FBEAF3125DFB8E1009E98D4 /* DBMigrationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBEAF3025DFB8E1009E98D4 /* DBMigrationService.swift */; }; 9FC411212595EA12001180A8 /* MessageSearchProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC411202595EA12001180A8 /* MessageSearchProvider.swift */; }; @@ -491,7 +491,8 @@ 9F71630B234FC7500031645E /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 9F72E866263ECE2A0039CF81 /* Trace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trace.swift; sourceTree = ""; }; 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPraseSaveable.swift; sourceTree = ""; }; - 9F7920FB2668080F00DA3D80 /* PassPhraseStorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhraseStorageService.swift; sourceTree = ""; }; + 9F79228726696B0200DA3D80 /* PassPhraseStorageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassPhraseStorageService.swift; sourceTree = ""; }; + 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyDataStorage.swift; sourceTree = ""; }; 9F8220D426336626004B2009 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 9F8277952373732000E19C07 /* UIImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; 9F82779723737E0900E19C07 /* MessageViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewDecorator.swift; sourceTree = ""; }; @@ -520,7 +521,6 @@ 9FB22CE325715D3E0026EE64 /* GmailServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceErrorHandler.swift; sourceTree = ""; }; 9FB22CEF25715D960026EE64 /* BackupServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceError.swift; sourceTree = ""; }; 9FB22CF625715DC50026EE64 /* KeyServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyServiceErrorHandler.swift; sourceTree = ""; }; - 9FB22D0325715DF00026EE64 /* KeyServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyServiceError.swift; sourceTree = ""; }; 9FBEAE5425D41BFF009E98D4 /* UserMailSessionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMailSessionProvider.swift; sourceTree = ""; }; 9FBEAF3025DFB8E1009E98D4 /* DBMigrationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBMigrationService.swift; sourceTree = ""; }; 9FC411202595EA12001180A8 /* MessageSearchProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSearchProvider.swift; sourceTree = ""; }; @@ -1159,8 +1159,9 @@ 9FB22CFD25715DDF0026EE64 /* Key Services */ = { isa = PBXGroup; children = ( + 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */, + 9F79228726696B0200DA3D80 /* PassPhraseStorageService.swift */, D2891AC124C59EFA008918E3 /* KeyService.swift */, - 9FB22D0325715DF00026EE64 /* KeyServiceError.swift */, ); path = "Key Services"; sourceTree = ""; @@ -1433,7 +1434,6 @@ 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */, 9F17976C2368EEBD002BF770 /* SetupViewDecorator.swift */, 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */, - 9F7920FB2668080F00DA3D80 /* PassPhraseStorageService.swift */, ); path = Setup; sourceTree = ""; @@ -2402,12 +2402,11 @@ D20D3C672520AB1000D4AA9A /* BackupViewController.swift in Sources */, D2E26F6324F1698100612AF1 /* ContactsListViewController.swift in Sources */, 9F53CB7B2555E1E300C0157A /* GmailService+folders.swift in Sources */, - 9F7920FC2668080F00DA3D80 /* PassPhraseStorageService.swift in Sources */, + 9F79228826696B0200DA3D80 /* PassPhraseStorageService.swift in Sources */, D2E26F6624F169B400612AF1 /* ContactsListDecorator.swift in Sources */, D2FF6968243115F9007182F0 /* EmailProviderViewDecorator.swift in Sources */, 9F589F0D238C7A9B007FD759 /* LocalStorage.swift in Sources */, D2FC1C0624D82C9F003B949D /* ContactsService.swift in Sources */, - 9FB22D0425715DF00026EE64 /* KeyServiceError.swift in Sources */, 9FD364862381EFCB00657302 /* SetupEnterPassPhraseViewController.swift in Sources */, D212D35D24C1AACF00035991 /* PrvKeyInfo.swift in Sources */, 9F23EA50237217140017DFED /* ComposeViewDecorator.swift in Sources */, @@ -2461,6 +2460,7 @@ 9F003D6125E1B4ED00EB38C0 /* TrashFolderProvider.swift in Sources */, 9FF0671025520D7100FCC9E6 /* MessageGateway.swift in Sources */, 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */, + 9F79229426696B9300DA3D80 /* KeyDataStorage.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewControllerContainer.swift in Sources */, D227C0E3250538100070F805 /* LocalFoldersProvider.swift in Sources */, 9FA405C7265AEBA50084D133 /* SetupKeyViewController.swift in Sources */, diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 300b06ad9..6669800f4 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -46,7 +46,7 @@ final class ComposeViewController: TableNodeViewController { private let messageSender: MessageGateway private let notificationCenter: NotificationCenter - private let dataService: DataServiceType & KeyDataServiceType + private let dataService: KeyDataStorageType private let decorator: ComposeViewDecoratorType private let core: Core private let contactsService: ContactsServiceType @@ -59,11 +59,13 @@ final class ComposeViewController: TableNodeViewController { private var contextToSend = Context() private var state: State = .main + private let email: String init( + email: String, messageSender: MessageGateway = MailProvider.shared.messageSender, notificationCenter: NotificationCenter = .default, - dataService: DataServiceType & KeyDataServiceType = DataService.shared, + dataService: KeyDataStorageType = KeyDataStorage(), decorator: ComposeViewDecoratorType = ComposeViewDecorator(), input: ComposeViewController.Input = .empty, core: Core = Core.shared, @@ -71,6 +73,7 @@ final class ComposeViewController: TableNodeViewController { userDefaults: UserDefaults = .standard, contactsService: ContactsServiceType = ContactsService() ) { + self.email = email self.messageSender = messageSender self.notificationCenter = notificationCenter self.dataService = dataService @@ -309,7 +312,7 @@ extension ComposeViewController { to: to, cc: cc, bcc: bcc, - from: dataService.email ?? "", + from: email, subject: subject, replyToMimeMsg: replyToMimeMsg, atts: atts diff --git a/FlowCrypt/Controllers/Inbox/InboxViewController.swift b/FlowCrypt/Controllers/Inbox/InboxViewController.swift index fdfd07784..bb099d3dc 100644 --- a/FlowCrypt/Controllers/Inbox/InboxViewController.swift +++ b/FlowCrypt/Controllers/Inbox/InboxViewController.swift @@ -261,8 +261,11 @@ extension InboxViewController { } private func btnComposeTap() { + guard let email = DataService.shared.email else { + return + } TapTicFeedback.generate(.light) - let composeVc = ComposeViewController() + let composeVc = ComposeViewController(email: email) navigationController?.pushViewController(composeVc, animated: true) } } diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index e23f09675..22de92a29 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -61,7 +61,6 @@ final class MessageViewController: TableNodeViewController { messageService: MessageService = MessageService(), messageOperationsProvider: MessageOperationsProvider = MailProvider.shared.messageOperationsProvider, decorator: MessageViewDecorator = MessageViewDecorator(dateFormatter: DateFormatter()), - storage: DataServiceType & KeyDataServiceType = DataService.shared, trashFolderProvider: TrashFolderProviderType = TrashFolderProvider(), input: MessageViewController.Input, completion: MsgViewControllerCompletion? @@ -285,7 +284,7 @@ extension MessageViewController { } private func handleReplyTap() { - guard let input = input else { return } + guard let input = input, let email = DataService.shared.email else { return } let replyInfo = ComposeViewController.Input.ReplyInfo( recipient: input.objMessage.sender, @@ -295,12 +294,9 @@ extension MessageViewController { message: fetchedMessage.text ) + let composeInput = ComposeViewController.Input(type: .reply(replyInfo)) navigationController?.pushViewController( - ComposeViewController( - input: ComposeViewController.Input( - type: .reply(replyInfo) - ) - ), + ComposeViewController(email: email, input: composeInput), animated: true ) } diff --git a/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift b/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift index 85707ac8f..5c66ade0d 100644 --- a/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift +++ b/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift @@ -31,21 +31,21 @@ final class PassPhraseStorageService { } } -import RealmSwift -class KeyStorageMock: KeyStorageType { - func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { - } - - func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { - } - - var publicKeyResult: () -> (String?) = { nil } - func publicKey() -> String? { - publicKeyResult() - } - - var keysResult: () -> ([KeyInfo]) = { [] } - func keys() -> [KeyInfo] { - keysResult() - } -} +//import RealmSwift +//class KeyStorageMock: KeyStorageType { +// func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { +// } +// +// func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { +// } +// +// var publicKeyResult: () -> (String?) = { nil } +// func publicKey() -> String? { +// publicKeyResult() +// } +// +// var keysResult: () -> ([KeyInfo]) = { [] } +// func keys() -> [KeyInfo] { +// keysResult() +// } +//} diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index aea20b63f..ead71fc7e 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -12,7 +12,7 @@ final class SetupBackupsViewController: TableNodeViewController, PassPhraseSavea } private let router: GlobalRouterType - private let storage: DataServiceType & KeyDataServiceType + private let storage: KeyDataStorageType private let decorator: SetupViewDecorator private let core: Core private let keyMethods: KeyMethodsType @@ -36,7 +36,7 @@ final class SetupBackupsViewController: TableNodeViewController, PassPhraseSavea init( fetchedEncryptedKeys: [KeyDetails], router: GlobalRouterType = GlobalRouter(), - storage: DataServiceType & KeyDataServiceType = DataService.shared, + storage: KeyDataStorageType = KeyDataStorage(), decorator: SetupViewDecorator = SetupViewDecorator(), core: Core = Core.shared, keyMethods: KeyMethodsType = KeyMethods(), diff --git a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift index 3567f35f0..57d71369e 100644 --- a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift @@ -23,7 +23,7 @@ final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhr private let email: String private let fetchedKeys: [KeyDetails] private let keyMethods: KeyMethodsType - private let keysDataService: KeyDataServiceType + private let keysDataService: KeyDataStorageType private let keyService: KeyServiceType private let router: GlobalRouterType @@ -43,7 +43,7 @@ final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhr init( decorator: SetupViewDecorator = SetupViewDecorator(), keyMethods: KeyMethodsType = KeyMethods(), - keysService: KeyDataServiceType = DataService.shared, + keysService: KeyDataStorageType = KeyDataStorage(), router: GlobalRouterType = GlobalRouter(), keyService: KeyServiceType = KeyService(), email: String, diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index 9769f2847..30b063cec 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -34,7 +34,8 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable private let router: GlobalRouterType private let user: UserId private let backupService: BackupServiceType - private let storage: DataServiceType & KeyDataServiceType + private let storage: DataServiceType + private let keyStorage: KeyDataStorageType private let attester: AttesterApiType var shouldSaveLocally = true { @@ -54,7 +55,8 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable core: Core = .shared, router: GlobalRouterType = GlobalRouter(), decorator: SetupViewDecorator = SetupViewDecorator(), - storage: DataServiceType & KeyDataServiceType = DataService.shared, + storage: DataServiceType = DataService.shared, + keyStorage: KeyDataStorageType = KeyDataStorage(), attester: AttesterApiType = AttesterApi() ) { self.user = user @@ -64,6 +66,7 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable self.backupService = backupService self.storage = storage self.attester = attester + self.keyStorage = keyStorage super.init(node: TableNode()) } @@ -133,7 +136,7 @@ extension SetupKeyViewController { try awaitPromise(self.backupService.backupToInbox(keys: [encryptedPrv.key], for: self.user)) - self.storage.addKeys(keyDetails: [encryptedPrv.key], passPhrase: passPhrase, source: .generated) + self.keyStorage.addKeys(keyDetails: [encryptedPrv.key], passPhrase: passPhrase, source: .generated) let updateKey = self.attester.updateKey( email: userId.email, diff --git a/FlowCrypt/Core/Models/PrvKeyInfo.swift b/FlowCrypt/Core/Models/PrvKeyInfo.swift index 3b9978564..40c7b492d 100644 --- a/FlowCrypt/Core/Models/PrvKeyInfo.swift +++ b/FlowCrypt/Core/Models/PrvKeyInfo.swift @@ -14,10 +14,11 @@ struct PrvKeyInfo: Encodable { let passphrase: String? } -extension PrvKeyInfo { - init(from keyInfo: KeyInfo) { - self.private = keyInfo.private - self.longid = keyInfo.longid - self.passphrase = keyInfo.passphrase - } -} +// TODO: - ANTON +//extension PrvKeyInfo { +// init(from keyInfo: KeyInfo) { +// self.private = keyInfo.private +// self.longid = keyInfo.longid +// self.passphrase = keyInfo.passphrase +// } +//} diff --git a/FlowCrypt/Functionality/DataManager/DataService.swift b/FlowCrypt/Functionality/DataManager/DataService.swift index 70d9cd75a..39ce49cd8 100644 --- a/FlowCrypt/Functionality/DataManager/DataService.swift +++ b/FlowCrypt/Functionality/DataManager/DataService.swift @@ -74,7 +74,7 @@ extension DataService: DataServiceType { return false } - let isAnyKeysForCurrentUser = encryptedStorage.keys() + let isAnyKeysForCurrentUser = encryptedStorage.keysInfo() .map(\.account) .map { $0.contains(currentUser.email) } .contains(true) @@ -118,26 +118,6 @@ extension DataService: DataServiceType { } } -// MARK: - DataKeyServiceType -extension DataService: KeyDataServiceType { - var keys: [PrvKeyInfo]? { - encryptedStorage.keys() - .map(PrvKeyInfo.init) - } - - var publicKey: String? { - encryptedStorage.publicKey() - } - - func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { - encryptedStorage.addKeys(keyDetails: keyDetails, passPhrase: passPhrase, source: source) - } - - func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { - encryptedStorage.updateKeys(keyDetails: keyDetails, passPhrase: passPhrase, source: source) - } -} - // MARK: - Migration extension DataService: DBMigration { /// Perform all kind of migrations diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index 1d60d16e4..bd980ec29 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -15,7 +15,7 @@ protocol KeyStorageType { func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) func publicKey() -> String? - func keys() -> [KeyInfo] + func keysInfo() -> [KeyInfo] } protocol EncryptedStorageType: KeyStorageType { @@ -182,7 +182,7 @@ extension EncryptedStorage { } } - func keys() -> [KeyInfo] { + func keysInfo() -> [KeyInfo] { let result = storage.objects(KeyInfo.self) return Array(result) } diff --git a/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift b/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift index 19b2970e2..ef0adf6f0 100644 --- a/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift +++ b/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift @@ -11,13 +11,16 @@ import UIKit struct KeyServiceErrorHandler: ErrorHandler { func handle(error: Error, for viewController: UIViewController) -> Bool { let errorMessage: String? - switch error { - case KeyServiceError.retrieve: + switch error as? KeyServiceError { + case .retrieve: errorMessage = "keyServiceError_retrieve_error" - case KeyServiceError.parse: + case .parsingError: errorMessage = "keyServiceError_retrieve_parse" - case KeyServiceError.unexpected: + case .unexpected: errorMessage = "keyServiceError_retrieve_unexpected" + case .emptyKeys: + // TODO: - ANTON + errorMessage = "" default: errorMessage = nil } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index 4c86e8983..4594fce76 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -40,16 +40,16 @@ extension FetchedMessage { // MARK: - MessageService final class MessageService { private let messageProvider: MessageProvider - private let dataService: DataServiceType & KeyDataServiceType + private let keyService: KeyServiceType private let core: Core init( messageProvider: MessageProvider = MailProvider.shared.messageProvider, - dataService: DataServiceType & KeyDataServiceType = DataService.shared, + keyService: KeyServiceType = KeyService(), core: Core = Core.shared ) { self.messageProvider = messageProvider - self.dataService = dataService + self.keyService = keyService self.core = core } @@ -61,11 +61,17 @@ final class MessageService { self.messageProvider.fetchMsg(message: input, folder: folder) ) - guard let keys = self.dataService.keys else { + guard let keys = try? self.keyService.getPrivateKeys().get() else { + // TODO: - ANTON + return + } + + guard keys.isNotEmpty else { reject(CoreError.notReady("Could not fetch keys")) return } + // TODO: - ANTON - match keys and pass phrase let decrypted = try self.core.parseDecryptMsg( encrypted: rawMimeData, keys: keys, diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift new file mode 100644 index 000000000..fb1d10194 --- /dev/null +++ b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift @@ -0,0 +1,47 @@ +// +// KeyDataStorageService.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 03.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +protocol KeyDataStorageType { + var keysInfo: [KeyInfo] { get } + var publicKey: String? { get } + func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) + func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) +} + +final class KeyDataStorage { + private let encryptedStorage: EncryptedStorageType + private let passPhraseStorage: PassPhraseStorageType + + init( + encryptedStorage: EncryptedStorageType = EncryptedStorage(), + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage() + ) { + self.encryptedStorage = encryptedStorage + self.passPhraseStorage = passPhraseStorage + } +} + +extension KeyDataStorage: KeyDataStorageType { + var keysInfo: [KeyInfo] { + encryptedStorage.keysInfo() + } + + var publicKey: String? { + encryptedStorage.publicKey() + } + + func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { + encryptedStorage.addKeys(keyDetails: keyDetails, passPhrase: passPhrase, source: source) + } + + func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { + encryptedStorage.updateKeys(keyDetails: keyDetails, passPhrase: passPhrase, source: source) + } +} diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift index 48145db15..784204d9a 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift @@ -8,28 +8,35 @@ import Foundation -// Data Service -protocol KeyDataServiceType { - var keys: [PrvKeyInfo]? { get } - var publicKey: String? { get } - func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) - func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) -} - protocol KeyServiceType { func retrieveKeyDetails() -> Result<[KeyDetails], KeyServiceError> + func getPrivateKeys() -> Result<[PrvKeyInfo], KeyServiceError> +} + +enum KeyServiceError: Error { + case emptyKeys, unexpected, parsingError, retrieve, test // TODO: - ANTON } struct KeyService: KeyServiceType { let coreService: Core = .shared - let dataService: KeyDataServiceType = DataService.shared + let dataService: KeyDataStorageType = KeyDataStorage() func retrieveKeyDetails() -> Result<[KeyDetails], KeyServiceError> { - guard let keys = dataService.keys else { - return .failure(.retrieve) + let keysInfo = dataService.keysInfo + + // TODO: - ANTON - Match all keysInfo + // TODO: - ANTON - get all available pass phrases + // TODO: - ANTON - match them by longId to create PrvKeyInfo + // TODO: - ANTON - Handle error by showing alert for user + + let privateKeys: [PrvKeyInfo] = [] + +// let keys = dataService.privateKeys + guard privateKeys.isNotEmpty else { + return .failure(.emptyKeys) } - let keyDetails = keys + let keyDetails = privateKeys .compactMap { try? coreService .parseKeys(armoredOrBinary: $0.private.data()) @@ -37,10 +44,14 @@ struct KeyService: KeyServiceType { } .flatMap { $0 } - guard keyDetails.count == keys.count else { - return .failure(.parse) + guard keyDetails.count == privateKeys.count else { + return .failure(.parsingError) } return .success(keyDetails) } + + func getPrivateKeys() -> Result<[PrvKeyInfo], KeyServiceError> { + .failure(.test) + } } diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorageService.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorageService.swift new file mode 100644 index 000000000..021fd585e --- /dev/null +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorageService.swift @@ -0,0 +1,43 @@ +// +// PassPhraseStorageService.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 02.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import UIKit + +protocol PassPhraseStorageType { + func getPassPhrases() -> [PassPhrase] +} + +final class PassPhraseStorage: PassPhraseStorageType { + let storage: EncryptedStorage + let localStorage: UserDefaults + + init( + storage: EncryptedStorage = EncryptedStorage(), + localStorage: UserDefaults = .standard + ) { + self.storage = storage + self.localStorage = localStorage + } + + func savePassPhrase(with passPhrase: PassPhrase) { +// if context.isLocally { +// storage.addKeys(keyDetails: context.keys, passPhrase: context.passPhrase, source: context.source) +// } else { +// // TODO: - ANTON +// } + } + + func getPassPhrases() -> [PassPhrase] { + [] + } +} + +struct PassPhrase { + let value: String + let longId: String +} diff --git a/FlowCrypt/Models/Realm Models/KeyInfo.swift b/FlowCrypt/Models/Realm Models/KeyInfo.swift index 77d7e721e..082e4e1b7 100644 --- a/FlowCrypt/Models/Realm Models/KeyInfo.swift +++ b/FlowCrypt/Models/Realm Models/KeyInfo.swift @@ -19,7 +19,6 @@ final class KeyInfo: Object { @objc dynamic var `private`: String = "" @objc dynamic var `public`: String = "" @objc dynamic var longid: String = "" - @objc dynamic var passphrase: String = "" @objc dynamic var source: String = "" @objc dynamic var account: String = "" @@ -33,10 +32,9 @@ final class KeyInfo: Object { assertionFailure("Will not store Private Key that is not fully encrypted") // crash tests throw KeyInfoError.notEncrypted("Will not store Private Key that is not fully encrypted") } - `private` = privateKey - `public` = keyDetails.public - longid = keyDetails.ids[0].longid - self.passphrase = passphrase + self.`private` = privateKey + self.`public` = keyDetails.public + self.longid = keyDetails.ids[0].longid self.source = source.rawValue self.account = keyDetails.users.first ?? "" } From e922beb8871ef80939afeb1a9b9bebe3b4799b09 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Mon, 7 Jun 2021 00:14:20 +0300 Subject: [PATCH 10/26] Add Pass Phrase saving logic --- FlowCrypt.xcodeproj/project.pbxproj | 12 +- .../Compose/ComposeViewController.swift | 40 +++-- .../Msg/MessageViewController.swift | 55 +++++- .../Controllers/Setup/PassPraseSaveable.swift | 4 +- .../Setup/SetupBackupsViewController.swift | 26 ++- .../SetupEnterPassPhraseViewController.swift | 49 +++--- .../Setup/SetupInitialViewController.swift | 2 - .../Setup/SetupKeyViewController.swift | 21 ++- FlowCrypt/Core/Models/KeyDetails.swift | 4 + FlowCrypt/Core/Models/PassPhrase.swift | 49 ++++++ FlowCrypt/Core/Models/PrvKeyInfo.swift | 11 +- .../Encrypted Storage/EncryptedStorage.swift | 65 ++++--- .../Message Provider/MessageService.swift | 10 +- .../Folders Services/FoldersService.swift | 4 +- .../Key Services/KeyDataStorage.swift | 23 +-- .../Services/Key Services/KeyService.swift | 70 ++++++-- .../Key Services/PassPhraseStorage.swift | 162 ++++++++++++++++++ .../PassPhraseStorageService.swift | 43 ----- FlowCrypt/Models/Realm Models/KeyInfo.swift | 12 +- FlowCryptTests/FlowCryptCoreTests.swift | 4 +- 20 files changed, 482 insertions(+), 184 deletions(-) create mode 100644 FlowCrypt/Core/Models/PassPhrase.swift create mode 100644 FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift delete mode 100644 FlowCrypt/Functionality/Services/Key Services/PassPhraseStorageService.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index c66c7a454..a0c98e5e0 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -113,7 +113,7 @@ 9F7920E32666D28400DA3D80 /* BackupServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4163EC266574CB00106194 /* BackupServiceMock.swift */; }; 9F7920EE2666D32500DA3D80 /* BackupServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F416427266575DC00106194 /* BackupServiceType.swift */; }; 9F7920F52667CEF100DA3D80 /* PassPraseSaveable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */; }; - 9F79228826696B0200DA3D80 /* PassPhraseStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F79228726696B0200DA3D80 /* PassPhraseStorageService.swift */; }; + 9F79228826696B0200DA3D80 /* PassPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F79228726696B0200DA3D80 /* PassPhraseStorage.swift */; }; 9F79229426696B9300DA3D80 /* KeyDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */; }; 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA0C3D34A69851A238E87 /* Core.swift */; }; 9F7DE8C6232029D000F10B3E /* CoreTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAC732B988D9704658812 /* CoreTypes.swift */; }; @@ -146,6 +146,7 @@ 9FC411352595EA94001180A8 /* Imap+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC411342595EA94001180A8 /* Imap+Search.swift */; }; 9FC4114C25961CEA001180A8 /* MailServiceProviderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC4114B25961CEA001180A8 /* MailServiceProviderType.swift */; }; 9FC411902596229D001180A8 /* AppErr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA4B11D4531B3B04D01D1 /* AppErr.swift */; }; + 9FC7EAB3266A404D00F3BF5D /* PassPhrase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EAB2266A404D00F3BF5D /* PassPhrase.swift */; }; 9FD364862381EFCB00657302 /* SetupEnterPassPhraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */; }; 9FDF364D235A1CCD00614596 /* SignInTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF364C235A1CCD00614596 /* SignInTest.swift */; }; 9FDF3650235A1D3F00614596 /* UITestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF364F235A1D3F00614596 /* UITestHelper.swift */; }; @@ -491,7 +492,7 @@ 9F71630B234FC7500031645E /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 9F72E866263ECE2A0039CF81 /* Trace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trace.swift; sourceTree = ""; }; 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPraseSaveable.swift; sourceTree = ""; }; - 9F79228726696B0200DA3D80 /* PassPhraseStorageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassPhraseStorageService.swift; sourceTree = ""; }; + 9F79228726696B0200DA3D80 /* PassPhraseStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassPhraseStorage.swift; sourceTree = ""; }; 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyDataStorage.swift; sourceTree = ""; }; 9F8220D426336626004B2009 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 9F8277952373732000E19C07 /* UIImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; @@ -527,6 +528,7 @@ 9FC4112D2595EA8B001180A8 /* Gmail+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Gmail+Search.swift"; sourceTree = ""; }; 9FC411342595EA94001180A8 /* Imap+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+Search.swift"; sourceTree = ""; }; 9FC4114B25961CEA001180A8 /* MailServiceProviderType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailServiceProviderType.swift; sourceTree = ""; }; + 9FC7EAB2266A404D00F3BF5D /* PassPhrase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhrase.swift; sourceTree = ""; }; 9FD22A19230FD781005067A6 /* NavigationBarItemsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarItemsView.swift; sourceTree = ""; }; 9FD22A1B230FE7D0005067A6 /* Then.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Then.swift; sourceTree = ""; }; 9FD22A1E230FEFC6005067A6 /* NavigationBarActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarActionButton.swift; sourceTree = ""; }; @@ -1160,7 +1162,7 @@ isa = PBXGroup; children = ( 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */, - 9F79228726696B0200DA3D80 /* PassPhraseStorageService.swift */, + 9F79228726696B0200DA3D80 /* PassPhraseStorage.swift */, D2891AC124C59EFA008918E3 /* KeyService.swift */, ); path = "Key Services"; @@ -1469,6 +1471,7 @@ D212D36324C1AC4800035991 /* KeyId.swift */, D212D35C24C1AACF00035991 /* PrvKeyInfo.swift */, D2E26F6B24F25B1F00612AF1 /* KeyAlgo.swift */, + 9FC7EAB2266A404D00F3BF5D /* PassPhrase.swift */, ); path = Models; sourceTree = ""; @@ -2402,7 +2405,7 @@ D20D3C672520AB1000D4AA9A /* BackupViewController.swift in Sources */, D2E26F6324F1698100612AF1 /* ContactsListViewController.swift in Sources */, 9F53CB7B2555E1E300C0157A /* GmailService+folders.swift in Sources */, - 9F79228826696B0200DA3D80 /* PassPhraseStorageService.swift in Sources */, + 9F79228826696B0200DA3D80 /* PassPhraseStorage.swift in Sources */, D2E26F6624F169B400612AF1 /* ContactsListDecorator.swift in Sources */, D2FF6968243115F9007182F0 /* EmailProviderViewDecorator.swift in Sources */, 9F589F0D238C7A9B007FD759 /* LocalStorage.swift in Sources */, @@ -2459,6 +2462,7 @@ D29AFFF92409767F00C1387D /* GoogleContactsResponse.swift in Sources */, 9F003D6125E1B4ED00EB38C0 /* TrashFolderProvider.swift in Sources */, 9FF0671025520D7100FCC9E6 /* MessageGateway.swift in Sources */, + 9FC7EAB3266A404D00F3BF5D /* PassPhrase.swift in Sources */, 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */, 9F79229426696B9300DA3D80 /* KeyDataStorage.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewControllerContainer.swift in Sources */, diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 6669800f4..78a70343a 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -46,7 +46,7 @@ final class ComposeViewController: TableNodeViewController { private let messageSender: MessageGateway private let notificationCenter: NotificationCenter - private let dataService: KeyDataStorageType + private let dataService: KeyStorageType private let decorator: ComposeViewDecoratorType private let core: Core private let contactsService: ContactsServiceType @@ -65,7 +65,7 @@ final class ComposeViewController: TableNodeViewController { email: String, messageSender: MessageGateway = MailProvider.shared.messageSender, notificationCenter: NotificationCenter = .default, - dataService: KeyDataStorageType = KeyDataStorage(), + dataService: KeyStorageType = KeyDataStorage(), decorator: ComposeViewDecoratorType = ComposeViewDecorator(), input: ComposeViewController.Input = .empty, core: Core = Core.shared, @@ -246,23 +246,25 @@ extension ComposeViewController { ?? self.contextToSend.subject ?? "(no subject)" - guard let myPubKey = self.dataService.publicKey else { - self.showAlert(message: "compose_no_pub_sender".localized) - return false - } - - guard let allRecipientPubs = self.getPubKeys(for: recipients) else { - return false - } - - let encrypted = self.encryptMsg( - pubkeys: allRecipientPubs + [myPubKey], - subject: subject, - message: text, - to: recipients.map(\.email) - ) - - try awaitPromise(self.messageSender.sendMail(mime: encrypted.mimeEncoded)) + // TODO: - ANTON - encryptAndSendMessage +// +// guard let myPubKey = self.dataService.publicKey else { +// self.showAlert(message: "compose_no_pub_sender".localized) +// return false +// } +// +// guard let allRecipientPubs = self.getPubKeys(for: recipients) else { +// return false +// } +// +// let encrypted = self.encryptMsg( +// pubkeys: allRecipientPubs + [myPubKey], +// subject: subject, +// message: text, +// to: recipients.map(\.email) +// ) + +// try awaitPromise(self.messageSender.sendMail(mime: encrypted.mimeEncoded)) return true } diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index 22de92a29..33379a57b 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -56,12 +56,14 @@ final class MessageViewController: TableNodeViewController { private let messageOperationsProvider: MessageOperationsProvider private let trashFolderProvider: TrashFolderProviderType private var fetchedMessage: FetchedMessage = .empty + private let passPhraseStorage: PassPhraseStorageType init( messageService: MessageService = MessageService(), messageOperationsProvider: MessageOperationsProvider = MailProvider.shared.messageOperationsProvider, decorator: MessageViewDecorator = MessageViewDecorator(dateFormatter: DateFormatter()), trashFolderProvider: TrashFolderProviderType = TrashFolderProvider(), + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage(), input: MessageViewController.Input, completion: MsgViewControllerCompletion? ) { @@ -71,6 +73,7 @@ final class MessageViewController: TableNodeViewController { self.decorator = decorator self.trashFolderProvider = trashFolderProvider self.onCompletion = completion + self.passPhraseStorage = passPhraseStorage super.init(node: TableNode()) } @@ -115,7 +118,7 @@ final class MessageViewController: TableNodeViewController { // we need to have only help and trash buttons items = [helpButton, trashButton] - // TODO: - ANTON - Check if this should be fixed + // TODO: - Ticket - Check if this should be fixed case "inbox": // for Gmail inbox we also need to have archive and unread buttons items = [helpButton, archiveButton, trashButton, unreadButton] @@ -153,15 +156,51 @@ extension MessageViewController { } private func handleError(_ error: Error, path: String) { - if let someError = error as NSError?, someError.code == Imap.Err.fetch.rawValue { - // todo - the missing msg should be removed from the list in inbox view - // reproduce: 1) load inbox 2) move msg to trash on another email client 3) open trashed message in inbox - showToast("Message not found in folder: \(path)") + // TODO: - Ticket - Improve error handling for MessageViewController + if error is MessageServiceError { + showPassPhraseAlert() } else { - // todo - this should be a retry / cancel alert - showAlert(error: error, message: "message_failed_open".localized + "\n\n\(error)") + if let someError = error as NSError?, someError.code == Imap.Err.fetch.rawValue { + // todo - the missing msg should be removed from the list in inbox view + // reproduce: 1) load inbox 2) move msg to trash on another email client 3) open trashed message in inbox + showToast("Message not found in folder: \(path)") + } else { + // todo - this should be a retry / cancel alert + showAlert(error: error, message: "message_failed_open".localized + "\n\n\(error)") + } + navigationController?.popViewController(animated: true) } - navigationController?.popViewController(animated: true) + } + + private func showPassPhraseAlert() { + hideSpinner() + let alert = UIAlertController(title: "Please enter pass phrase", message: nil, preferredStyle: .alert) + alert.addTextField { tf in + tf.isSecureTextEntry = true + } + + let saveAction = UIAlertAction(title: "Save", style: .default) { [weak self] _ in + guard let textField = alert.textFields?.first, + let passPhrase = textField.text, + passPhrase.isNotEmpty + else { + alert.dismiss(animated: true, completion: nil) + return + } + self?.passPhraseStorage.saveLocally(passPhrase: passPhrase) + alert.dismiss(animated: true) { + self?.fetchDecryptAndRenderMsg() + } + } + + let cancelAction = UIAlertAction(title: "Cancel", style: .destructive) { _ in + alert.dismiss(animated: true, completion: nil) + } + + alert.addAction(saveAction) + alert.addAction(cancelAction) + + present(alert, animated: true, completion: nil) } private func asyncMarkAsReadIfNotAlreadyMarked() { diff --git a/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift b/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift index 52f6d7fb0..0b8f4e18b 100644 --- a/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift +++ b/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift @@ -14,6 +14,8 @@ protocol PassPhraseSaveable { var saveLocallyNode: CellNode { get } var saveInMemoryNode: CellNode { get } + var passPhraseStorage: PassPhraseStorageType { get } + func handleSelectedPassPhraseOption() func showPassPhraseErrorAlert() } @@ -28,7 +30,7 @@ extension PassPhraseSaveable where Self: TableNodeViewController { } var saveInMemoryNode: CellNode { - CheckBoxTextNode(input: .passPhraseLocally(isSelected: !self.shouldSaveLocally)) + CheckBoxTextNode(input: .passPhraseMemory(isSelected: !self.shouldSaveLocally)) } func showPassPhraseErrorAlert() { diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index ead71fc7e..b3121d4a6 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -12,12 +12,13 @@ final class SetupBackupsViewController: TableNodeViewController, PassPhraseSavea } private let router: GlobalRouterType - private let storage: KeyDataStorageType private let decorator: SetupViewDecorator private let core: Core private let keyMethods: KeyMethodsType private let user: UserId private let fetchedEncryptedKeys: [KeyDetails] + private let keyStorage: KeyStorageType + let passPhraseStorage: PassPhraseStorageType private var passPhrase: String? private lazy var logger = Logger.nested(in: Self.self, with: .setup) @@ -36,19 +37,21 @@ final class SetupBackupsViewController: TableNodeViewController, PassPhraseSavea init( fetchedEncryptedKeys: [KeyDetails], router: GlobalRouterType = GlobalRouter(), - storage: KeyDataStorageType = KeyDataStorage(), + keyStorage: KeyStorageType = KeyDataStorage(), decorator: SetupViewDecorator = SetupViewDecorator(), core: Core = Core.shared, keyMethods: KeyMethodsType = KeyMethods(), - user: UserId + user: UserId, + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage() ) { self.fetchedEncryptedKeys = fetchedEncryptedKeys self.router = router - self.storage = storage + self.keyStorage = keyStorage self.decorator = decorator self.core = core self.keyMethods = keyMethods self.user = user + self.passPhraseStorage = passPhraseStorage super.init(node: TableNode()) } @@ -121,15 +124,24 @@ extension SetupBackupsViewController { } private func recoverAccount(with backups: [KeyDetails], and passPhrase: String) { - - let matchingKeyBackups = keyMethods.filterByPassPhraseMatch(keys: backups, passPhrase: passPhrase) + let matchingKeyBackups = Set(keyMethods.filterByPassPhraseMatch(keys: backups, passPhrase: passPhrase)) guard matchingKeyBackups.isNotEmpty else { showAlert(message: "setup_wrong_pass_phrase_retry".localized) return } - storage.addKeys(keyDetails: matchingKeyBackups, passPhrase: passPhrase, source: .backup) + // save pass phrase + matchingKeyBackups + .map { + PassPhrase(value: passPhrase, longid: $0.longid) + } + .forEach { + passPhraseStorage.savePassPhrase(with: $0, isLocally: shouldSaveLocally) + } + + // save keys + keyStorage.addKeys(keyDetails: Array(matchingKeyBackups), source: .backup) moveToMainFlow() } diff --git a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift index 57d71369e..d1b37a3f6 100644 --- a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift @@ -9,7 +9,6 @@ import AsyncDisplayKit import FlowCryptUI -// TODO: - ANTON - add radio button final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhraseSaveable { private enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, saveLocally, saveInMemory, enterPhrase, chooseAnother @@ -23,9 +22,10 @@ final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhr private let email: String private let fetchedKeys: [KeyDetails] private let keyMethods: KeyMethodsType - private let keysDataService: KeyDataStorageType + private let keysStorage: KeyStorageType private let keyService: KeyServiceType private let router: GlobalRouterType + let passPhraseStorage: PassPhraseStorageType private var passPhrase: String? @@ -43,9 +43,10 @@ final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhr init( decorator: SetupViewDecorator = SetupViewDecorator(), keyMethods: KeyMethodsType = KeyMethods(), - keysService: KeyDataStorageType = KeyDataStorage(), + keysService: KeyStorageType = KeyDataStorage(), router: GlobalRouterType = GlobalRouter(), keyService: KeyServiceType = KeyService(), + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage(), email: String, fetchedKeys: [KeyDetails] ) { @@ -53,9 +54,11 @@ final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhr self.email = email self.decorator = decorator self.keyMethods = keyMethods - self.keysDataService = keysService + self.keysStorage = keysService self.router = router self.keyService = keyService + self.passPhraseStorage = passPhraseStorage + super.init(node: TableNode()) } @@ -163,18 +166,15 @@ extension SetupEnterPassPhraseViewController: ASTableDelegate, ASTableDataSource return true } case .enterPhrase: - return ButtonCellNode( + let input = ButtonCellNode.Input( title: self.decorator.buttonTitle(for: .passPhraseContinue), insets: self.decorator.insets.buttonInsets - ) { [weak self] in + ) + return ButtonCellNode(input: input) { [weak self] in self?.handleContinueAction() } case .chooseAnother: - return ButtonCellNode( - title: self.decorator.buttonTitle(for: .passPhraseChooseAnother), - insets: self.decorator.insets.buttonInsets, - color: .lightGray - ) { [weak self] in + return ButtonCellNode(input: .chooseAnotherAccount) { [weak self] in self?.navigationController?.popViewController(animated: true) } case .divider: @@ -236,17 +236,24 @@ extension SetupEnterPassPhraseViewController { let keysToUpdate = Array(Set(existedKeys).intersection(fetchedKeys)) let newKeysToAdd = Array(Set(fetchedKeys).subtracting(existedKeys)) - keysDataService.addKeys( - keyDetails: newKeysToAdd, - passPhrase: passPhrase, - source: .imported - ) + keysStorage.addKeys(keyDetails: newKeysToAdd, source: .imported) + keysStorage.updateKeys(keyDetails: keysToUpdate, source: .imported) - keysDataService.updateKeys( - keyDetails: keysToUpdate, - passPhrase: passPhrase, - source: .imported - ) + keysToUpdate + .map { + PassPhrase(value: passPhrase, longid: $0.longid) + } + .forEach { + passPhraseStorage.updatePassPhrase(with: $0, isLocally: shouldSaveLocally) + } + + newKeysToAdd + .map { + PassPhrase(value: passPhrase, longid: $0.longid) + } + .forEach { + passPhraseStorage.savePassPhrase(with: $0, isLocally: shouldSaveLocally) + } hideSpinner() diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index fe0d83206..501b4e702 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -9,7 +9,6 @@ import AsyncDisplayKit import FlowCryptUI -// TODO: - ANTON - add refresh controller final class SetupInitialViewController: TableNodeViewController { private enum Parts: Int, CaseIterable { case title, description, createKey, importKey, anotherAccount @@ -71,7 +70,6 @@ final class SetupInitialViewController: TableNodeViewController { override func viewDidLoad() { super.viewDidLoad() setupUI() - state = .searching } override func viewWillAppear(_ animated: Bool) { diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index 30b063cec..c3c6abf7c 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -35,8 +35,9 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable private let user: UserId private let backupService: BackupServiceType private let storage: DataServiceType - private let keyStorage: KeyDataStorageType + private let keyStorage: KeyStorageType private let attester: AttesterApiType + let passPhraseStorage: PassPhraseStorageType var shouldSaveLocally = true { didSet { @@ -56,8 +57,9 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable router: GlobalRouterType = GlobalRouter(), decorator: SetupViewDecorator = SetupViewDecorator(), storage: DataServiceType = DataService.shared, - keyStorage: KeyDataStorageType = KeyDataStorage(), - attester: AttesterApiType = AttesterApi() + keyStorage: KeyStorageType = KeyDataStorage(), + attester: AttesterApiType = AttesterApi(), + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage() ) { self.user = user self.core = core @@ -67,6 +69,7 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable self.storage = storage self.attester = attester self.keyStorage = keyStorage + self.passPhraseStorage = passPhraseStorage super.init(node: TableNode()) } @@ -93,7 +96,7 @@ extension SetupKeyViewController { observeKeyboardNotifications() } - // TODO: - ANTON - Unify this logic for all controllers + // TODO: - Ticket? - Unify this logic for all controllers // swiftlint:disable discarded_notification_center_observer private func observeKeyboardNotifications() { NotificationCenter.default.addObserver( @@ -136,13 +139,17 @@ extension SetupKeyViewController { try awaitPromise(self.backupService.backupToInbox(keys: [encryptedPrv.key], for: self.user)) - self.keyStorage.addKeys(keyDetails: [encryptedPrv.key], passPhrase: passPhrase, source: .generated) + let passPhrase = PassPhrase(value: passPhrase, longid: encryptedPrv.key.longid) + + self.keyStorage.addKeys(keyDetails: [encryptedPrv.key], source: .generated) + self.passPhraseStorage.savePassPhrase(with: passPhrase, isLocally: self.shouldSaveLocally) let updateKey = self.attester.updateKey( email: userId.email, pubkey: encryptedPrv.key.public, token: self.storage.token ) + try awaitPromise(self.alertAndSkipOnRejection( updateKey, fail: "Failed to submit Public Key") @@ -268,7 +275,7 @@ extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { guard case let .didEndEditing(value) = action else { return } } - // TODO: - ANTON - passPhrase didEndEditing + // TODO: - ANTON - ui logic - passPhrase didEndEditing .onShouldReturn { [weak self] _ in self?.view.endEditing(true) @@ -283,7 +290,7 @@ extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { title: self.decorator.buttonTitle(for: .setPassPhrase), insets: self.decorator.insets.buttonInsets ) { [weak self] in - // TODO: - ANTON - setPassPhrase + // TODO: - ANTON - ui logic - passPhrase didEndEditing } case .subtitle: return SetupTitleNode( diff --git a/FlowCrypt/Core/Models/KeyDetails.swift b/FlowCrypt/Core/Models/KeyDetails.swift index 5fa653af7..aa9b8bd63 100644 --- a/FlowCrypt/Core/Models/KeyDetails.swift +++ b/FlowCrypt/Core/Models/KeyDetails.swift @@ -18,6 +18,10 @@ struct KeyDetails: Decodable { let created: Int let users: [String] let algo: KeyAlgo? + + var longid: String { + ids[0].longid + } } extension KeyDetails: Hashable { diff --git a/FlowCrypt/Core/Models/PassPhrase.swift b/FlowCrypt/Core/Models/PassPhrase.swift new file mode 100644 index 000000000..d485bebb3 --- /dev/null +++ b/FlowCrypt/Core/Models/PassPhrase.swift @@ -0,0 +1,49 @@ +// +// PassPhraseInfo.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 04.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation +import RealmSwift + +// Should be operated in app +struct PassPhrase: Codable { + let value: String + let longid: String + + init(value: String, longid: String) { + self.value = value + self.longid = longid + } +} + +extension PassPhrase { + init(object: PassPhraseObject) { + self.value = object.value + self.longid = object.longid + } +} + +/// PassPhrase object to store in Realm +final class PassPhraseObject: Object { + @objc dynamic var longid: String = "" + @objc dynamic var value: String = "" + + convenience init( + longid: String = "", + value: String = "" + ) { + self.init() + self.value = value + self.longid = longid + } +} + +extension PassPhraseObject { + convenience init(_ passPhrase: PassPhrase) { + self.init(longid: passPhrase.longid, value: passPhrase.value) + } +} diff --git a/FlowCrypt/Core/Models/PrvKeyInfo.swift b/FlowCrypt/Core/Models/PrvKeyInfo.swift index 40c7b492d..419aded42 100644 --- a/FlowCrypt/Core/Models/PrvKeyInfo.swift +++ b/FlowCrypt/Core/Models/PrvKeyInfo.swift @@ -11,14 +11,5 @@ import Foundation struct PrvKeyInfo: Encodable { let `private`: String let longid: String - let passphrase: String? + let passphrase: String } - -// TODO: - ANTON -//extension PrvKeyInfo { -// init(from keyInfo: KeyInfo) { -// self.private = keyInfo.private -// self.longid = keyInfo.longid -// self.passphrase = keyInfo.passphrase -// } -//} diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index bd980ec29..b37a1edd8 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -12,8 +12,8 @@ import Promises import RealmSwift protocol KeyStorageType { - func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) - func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) + func addKeys(keyDetails: [KeyDetails], source: KeySource) + func updateKeys(keyDetails: [KeyDetails], source: KeySource) func publicKey() -> String? func keysInfo() -> [KeyInfo] } @@ -25,6 +25,11 @@ protocol EncryptedStorageType: KeyStorageType { func saveActiveUser(with user: UserObject) var activeUser: UserObject? { get } + func addPassPhrase(object: PassPhraseObject) + func updatePassPhrase(object: PassPhraseObject) + func getPassPhrases() -> [PassPhraseObject] + func removePassPhrase(object: PassPhraseObject) + func cleanup() } @@ -111,14 +116,20 @@ extension EncryptedStorage: LogOutHandler { destroyEncryptedStorage() } else { // remove user and keys for this user - let userToDelete = users.filter { $0.email == email } - let keys = storage.objects(KeyInfo.self).filter { $0.account.contains(email) } - let sessions = storage.objects(SessionObject.self).filter { $0.email == email } + let userToDelete = users + .filter { $0.email == email } + let keys = storage.objects(KeyInfo.self) + .filter { $0.account.contains(email) } + let passPhrases = storage.objects(PassPhraseObject.self) + .filter { keys.map(\.longid).contains($0.longid) } + let sessions = storage.objects(SessionObject.self) + .filter { $0.email == email } try storage.write { storage.delete(userToDelete) storage.delete(keys) storage.delete(sessions) + storage.delete(passPhrases) } } } @@ -155,29 +166,18 @@ extension EncryptedStorage { // MARK: - Keys extension EncryptedStorage { - func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { + func addKeys(keyDetails: [KeyDetails], source: KeySource) { try! storage.write { for key in keyDetails { - storage.add(try! KeyInfo(key, passphrase: passPhrase, source: source)) + storage.add(try! KeyInfo(key, source: source)) } } } - func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { - // KeyInfo doesn't have primaty key, to avoid migration we need to delete keys and then save them - - // delete keys - keyDetails.forEach { keyDetail in - try? storage.write { - storage.delete(storage.objects(KeyInfo.self) - .filter("longid=%@", keyDetail.ids[0].longid)) - } - } - - // add new keys + func updateKeys(keyDetails: [KeyDetails], source: KeySource) { try! storage.write { for key in keyDetails { - storage.add(try! KeyInfo(key, passphrase: passPhrase, source: source)) + storage.add(try! KeyInfo(key, source: source), update: .all) } } } @@ -194,6 +194,31 @@ extension EncryptedStorage { } } +// MARK: - PassPhrase +extension EncryptedStorage { + func addPassPhrase(object: PassPhraseObject) { + try! storage.write { + storage.add(object) + } + } + + func updatePassPhrase(object: PassPhraseObject) { + try! storage.write { + storage.add(object, update: .all) + } + } + + func removePassPhrase(object: PassPhraseObject) { + try! storage.write { + storage.delete(object) + } + } + + func getPassPhrases() -> [PassPhraseObject] { + Array(storage.objects(PassPhraseObject.self)) + } +} + // MARK: - User extension EncryptedStorage { var activeUser: UserObject? { diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index 4594fce76..f28df4ed5 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -28,7 +28,7 @@ struct FetchedMessage { } extension FetchedMessage { - // TODO: - ANTON - fix with empty state for MessageViewController + // TODO: - Ticket - fix with empty state for MessageViewController static let empty = FetchedMessage( rawMimeData: Data(), text: "loading_title".localized + "...", @@ -38,6 +38,10 @@ extension FetchedMessage { } // MARK: - MessageService +enum MessageServiceError: Error { + case missedPassPhrase +} + final class MessageService { private let messageProvider: MessageProvider private let keyService: KeyServiceType @@ -62,8 +66,7 @@ final class MessageService { ) guard let keys = try? self.keyService.getPrivateKeys().get() else { - // TODO: - ANTON - return + return reject(MessageServiceError.missedPassPhrase) } guard keys.isNotEmpty else { @@ -71,7 +74,6 @@ final class MessageService { return } - // TODO: - ANTON - match keys and pass phrase let decrypted = try self.core.parseDecryptMsg( encrypted: rawMimeData, keys: keys, diff --git a/FlowCrypt/Functionality/Services/Folders Services/FoldersService.swift b/FlowCrypt/Functionality/Services/Folders Services/FoldersService.swift index d58c32b5a..e5f93adeb 100644 --- a/FlowCrypt/Functionality/Services/Folders Services/FoldersService.swift +++ b/FlowCrypt/Functionality/Services/Folders Services/FoldersService.swift @@ -18,7 +18,7 @@ protocol FoldersServiceType { } final class FoldersService: FoldersServiceType { - // TODO: - ANTON - consider rework with CacheService for trash path instead + // TODO: - Ticket? - consider rework with CacheService for trash path instead private let localStorage: LocalStorageType let localFoldersProvider: LocalFoldersProviderType @@ -53,7 +53,7 @@ final class FoldersService: FoldersServiceType { let remoteFolders = try awaitPromise(self.remoteFoldersProvider.fetchFolders()) DispatchQueue.main.async { - // TODO: - ANTON - instead of removing all folders remove only + // TODO: - Ticket? - instead of removing all folders remove only // those folders which are in DB and not in remoteFolders self.localFoldersProvider.removeFolders() diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift index fb1d10194..ccd49f966 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift @@ -8,13 +8,6 @@ import Foundation -protocol KeyDataStorageType { - var keysInfo: [KeyInfo] { get } - var publicKey: String? { get } - func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) - func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) -} - final class KeyDataStorage { private let encryptedStorage: EncryptedStorageType private let passPhraseStorage: PassPhraseStorageType @@ -28,20 +21,20 @@ final class KeyDataStorage { } } -extension KeyDataStorage: KeyDataStorageType { - var keysInfo: [KeyInfo] { - encryptedStorage.keysInfo() +extension KeyDataStorage: KeyStorageType { + func updateKeys(keyDetails: [KeyDetails], source: KeySource) { + encryptedStorage.updateKeys(keyDetails: keyDetails, source: source) } - var publicKey: String? { + func publicKey() -> String? { encryptedStorage.publicKey() } - func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { - encryptedStorage.addKeys(keyDetails: keyDetails, passPhrase: passPhrase, source: source) + func keysInfo() -> [KeyInfo] { + encryptedStorage.keysInfo() } - func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { - encryptedStorage.updateKeys(keyDetails: keyDetails, passPhrase: passPhrase, source: source) + func addKeys(keyDetails: [KeyDetails], source: KeySource) { + encryptedStorage.addKeys(keyDetails: keyDetails, source: source) } } diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift index 784204d9a..95b52dcda 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift @@ -14,32 +14,33 @@ protocol KeyServiceType { } enum KeyServiceError: Error { - case emptyKeys, unexpected, parsingError, retrieve, test // TODO: - ANTON + case emptyKeys, unexpected, parsingError, retrieve } -struct KeyService: KeyServiceType { +final class KeyService: KeyServiceType { let coreService: Core = .shared - let dataService: KeyDataStorageType = KeyDataStorage() + let storage: KeyStorageType + let passPhraseStorage: PassPhraseStorageType + let currentUserEmail: () -> (String?) - func retrieveKeyDetails() -> Result<[KeyDetails], KeyServiceError> { - let keysInfo = dataService.keysInfo - - // TODO: - ANTON - Match all keysInfo - // TODO: - ANTON - get all available pass phrases - // TODO: - ANTON - match them by longId to create PrvKeyInfo - // TODO: - ANTON - Handle error by showing alert for user - - let privateKeys: [PrvKeyInfo] = [] + init( + storage: KeyStorageType = KeyDataStorage(), + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage(), + currentUserEmail: @autoclosure @escaping () -> (String?) = DataService.shared.email + ) { + self.storage = storage + self.passPhraseStorage = passPhraseStorage + self.currentUserEmail = currentUserEmail + } -// let keys = dataService.privateKeys - guard privateKeys.isNotEmpty else { + func retrieveKeyDetails() -> Result<[KeyDetails], KeyServiceError> { + guard let privateKeys = try? getPrivateKeys().get(), privateKeys.isNotEmpty else { return .failure(.emptyKeys) } let keyDetails = privateKeys .compactMap { - try? coreService - .parseKeys(armoredOrBinary: $0.private.data()) + try? coreService.parseKeys(armoredOrBinary: $0.private.data()) .keyDetails } .flatMap { $0 } @@ -52,6 +53,41 @@ struct KeyService: KeyServiceType { } func getPrivateKeys() -> Result<[PrvKeyInfo], KeyServiceError> { - .failure(.test) + guard let email = currentUserEmail() else { + return .failure(.retrieve) + } + + let keysInfo = storage.keysInfo() + .filter { $0.account.contains(email) } + + let passPhrases = passPhraseStorage.getPassPhrases() + + guard keysInfo.isNotEmpty, passPhrases.isNotEmpty else { + return .failure(.emptyKeys) + } + + let privateKeys = keysInfo.compactMap { (keyInfo) -> PrvKeyInfo? in + guard let passPhrase = passPhrases.first(where: { $0.longid == keyInfo.longid }) else { + return nil + } + + let passPhraseValue = passPhrase.value + + guard passPhraseValue.isNotEmpty else { + return nil + } + + return PrvKeyInfo( + private: keyInfo.private, + longid: keyInfo.longid, + passphrase: passPhraseValue + ) + } + + guard privateKeys.isNotEmpty else { + return .failure(.emptyKeys) + } + + return .success(privateKeys) } } diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift new file mode 100644 index 000000000..fd6a85caa --- /dev/null +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift @@ -0,0 +1,162 @@ +// +// PassPhraseStorageService.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 02.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import UIKit + +protocol PassPhraseStorageType { + func getPassPhrases() -> [PassPhrase] + func savePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) + func updatePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) + + func saveLocally(passPhrase: String) +} + +final class PassPhraseStorage: PassPhraseStorageType { + private enum Constants { + static let passPhraseIndex = "passPhraseIndex" + } + private lazy var logger = Logger.nested(Self.self) + + let currentUserEmail: () -> (String?) + let storage: EncryptedStorage + let localStorage: UserDefaults + let timeout: Int + let encoder = JSONEncoder() + let decoder = JSONDecoder() + + private var subscription: NSObjectProtocol? + + init( + storage: EncryptedStorage = EncryptedStorage(), + localStorage: UserDefaults = .standard, + timeout: Int = 4, + currentUserEmail: @autoclosure @escaping () -> (String?) = DataService.shared.email + ) { + self.storage = storage + self.localStorage = localStorage + self.timeout = timeout + self.currentUserEmail = currentUserEmail + } + + deinit { + if let subscription = subscription { + NotificationCenter.default.removeObserver(subscription) + } + } + + private func subscribeToTerminateNotification() { + subscription = NotificationCenter.default.addObserver( + forName: UIApplication.willTerminateNotification, + object: nil, + queue: .main + ) { _ in + self.logger.logInfo("App is about to terminate") + self.localStorage.removeObject(forKey: Constants.passPhraseIndex) + } + } + + func savePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) { + if isLocally { + logger.logInfo("Save locally \(passPhrase.longid)") + + saveLocally(passPhrase: passPhrase) + + let alreadySaved = storage.getPassPhrases() + + if alreadySaved.contains(where: { $0.longid == passPhrase.longid }) { + storage.removePassPhrase(object: PassPhraseObject(passPhrase)) + } + } else { + logger.logInfo("Save to storage \(passPhrase.longid)") + + storage.addPassPhrase(object: PassPhraseObject(passPhrase)) + } + } + + func updatePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) { + storage.updatePassPhrase(object: PassPhraseObject(passPhrase)) + } + + func getPassPhrases() -> [PassPhrase] { + let dbPassPhrases = storage.getPassPhrases() + .map(PassPhrase.init) + + logger.logInfo("dbPassPhrases \(dbPassPhrases.count)") + + let calendar = Calendar.current + + var validPassPhrases: [PassPhrase] = [] + var invalidPassPhrases: [LocalPassPhrase] = [] + + getAllLocallySavedPassPhrases() + .forEach { localPassPhrases in + guard calendar.component(.hour, from: localPassPhrases.date) < timeout else { + self.logger.logInfo("pass phrase is invalid \(localPassPhrases.passPhrase.longid)") + invalidPassPhrases.append(localPassPhrases) + return + } + validPassPhrases.append(localPassPhrases.passPhrase) + } + removeInvalidPassPhrases(with: invalidPassPhrases) + + logger.logInfo("validPassPhrases \(validPassPhrases.count)") + return dbPassPhrases + validPassPhrases + } + + func saveLocally(passPhrase: String) { + guard let email = currentUserEmail() else { + return + } + + storage.keysInfo() + .filter { + $0.account.contains(email) + } + .forEach { + saveLocally(passPhrase: PassPhrase(value: passPhrase, longid: $0.longid)) + } + } + + private func saveLocally(passPhrase: PassPhrase) { + // get all saved + var temporaryPassPhrases = getAllLocallySavedPassPhrases() + // update with new pass + temporaryPassPhrases.append(LocalPassPhrase(passPhrase: passPhrase, date: Date())) + // save to storage + encodeAndSave(passPhrases: temporaryPassPhrases) + } + + private func removeInvalidPassPhrases(with objects: [LocalPassPhrase]) { + var temporaryPassPhrases = getAllLocallySavedPassPhrases() + + objects.forEach { localPassPhrases in + temporaryPassPhrases.removeAll(where: { $0.date == localPassPhrases.date }) + } + + encodeAndSave(passPhrases: temporaryPassPhrases) + } + + private func getAllLocallySavedPassPhrases() -> [LocalPassPhrase] { + guard let data = localStorage.data(forKey: Constants.passPhraseIndex), + let result = try? decoder.decode([LocalPassPhrase].self, from: data) else { + return [] + } + + return result + } + + private func encodeAndSave(passPhrases: [LocalPassPhrase]) { + let objectsToSave = try? encoder.encode(passPhrases) + localStorage.set(objectsToSave, forKey: Constants.passPhraseIndex) + } +} + +private struct LocalPassPhrase: Codable { + let passPhrase: PassPhrase + let date: Date +} diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorageService.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorageService.swift deleted file mode 100644 index 021fd585e..000000000 --- a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorageService.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// PassPhraseStorageService.swift -// FlowCrypt -// -// Created by Anton Kharchevskyi on 02.06.2021. -// Copyright © 2021 FlowCrypt Limited. All rights reserved. -// - -import UIKit - -protocol PassPhraseStorageType { - func getPassPhrases() -> [PassPhrase] -} - -final class PassPhraseStorage: PassPhraseStorageType { - let storage: EncryptedStorage - let localStorage: UserDefaults - - init( - storage: EncryptedStorage = EncryptedStorage(), - localStorage: UserDefaults = .standard - ) { - self.storage = storage - self.localStorage = localStorage - } - - func savePassPhrase(with passPhrase: PassPhrase) { -// if context.isLocally { -// storage.addKeys(keyDetails: context.keys, passPhrase: context.passPhrase, source: context.source) -// } else { -// // TODO: - ANTON -// } - } - - func getPassPhrases() -> [PassPhrase] { - [] - } -} - -struct PassPhrase { - let value: String - let longId: String -} diff --git a/FlowCrypt/Models/Realm Models/KeyInfo.swift b/FlowCrypt/Models/Realm Models/KeyInfo.swift index 082e4e1b7..08beb9449 100644 --- a/FlowCrypt/Models/Realm Models/KeyInfo.swift +++ b/FlowCrypt/Models/Realm Models/KeyInfo.swift @@ -22,7 +22,7 @@ final class KeyInfo: Object { @objc dynamic var source: String = "" @objc dynamic var account: String = "" - convenience init(_ keyDetails: KeyDetails, passphrase: String, source: KeySource) throws { + convenience init(_ keyDetails: KeyDetails, source: KeySource) throws { self.init() guard let privateKey = keyDetails.private else { assertionFailure("storing pubkey as private") // crash tests @@ -34,8 +34,16 @@ final class KeyInfo: Object { } self.`private` = privateKey self.`public` = keyDetails.public - self.longid = keyDetails.ids[0].longid + self.longid = keyDetails.longid self.source = source.rawValue self.account = keyDetails.users.first ?? "" } + + override class func primaryKey() -> String? { + "private" + } + + override var description: String { + "account = \(account) ####### longid = \(longid)" + } } diff --git a/FlowCryptTests/FlowCryptCoreTests.swift b/FlowCryptTests/FlowCryptCoreTests.swift index 7f23e42fd..1eaba1116 100644 --- a/FlowCryptTests/FlowCryptCoreTests.swift +++ b/FlowCryptTests/FlowCryptCoreTests.swift @@ -68,13 +68,13 @@ class FlowCryptCoreTests: XCTestCase { XCTAssertNil(k0.private) XCTAssertNil(k0.isFullyDecrypted) XCTAssertNil(k0.isFullyEncrypted) - XCTAssertEqual(k0.ids[0].longid, TestData.k0.longid) + XCTAssertEqual(k0.longid, TestData.k0.longid) // k1 is private let k1 = r.keyDetails[1] XCTAssertNotNil(k1.private) XCTAssertEqual(k1.isFullyDecrypted, false) XCTAssertEqual(k1.isFullyEncrypted, true) - XCTAssertEqual(k1.ids[0].longid, TestData.k1.longid) + XCTAssertEqual(k1.longid, TestData.k1.longid) // todo - could test user ids } From 4056936b2062946dc4dbba32e53edfb80f85a596 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Mon, 7 Jun 2021 16:19:33 +0300 Subject: [PATCH 11/26] Polish PassPhrase logic --- .../SegmentedViewController.swift | 2 +- .../Compose/ComposeViewController.swift | 36 +++++++++---------- .../Msg/MessageViewController.swift | 3 +- .../Search/SearchViewController.swift | 2 +- .../Setup/SetupInitialViewController.swift | 19 ---------- .../Setup/SetupKeyViewController.swift | 32 +++++++++++------ .../Functionality/Services/AppStartup.swift | 10 +----- .../Key Services/PassPhraseStorage.swift | 24 +++++++++---- .../Resources/en.lproj/Localizable.strings | 3 ++ 9 files changed, 64 insertions(+), 67 deletions(-) diff --git a/FlowCrypt/Common UI/View Controllers/SegmentedViewController.swift b/FlowCrypt/Common UI/View Controllers/SegmentedViewController.swift index 2f6378cbc..60e962ba3 100644 --- a/FlowCrypt/Common UI/View Controllers/SegmentedViewController.swift +++ b/FlowCrypt/Common UI/View Controllers/SegmentedViewController.swift @@ -9,7 +9,7 @@ import AsyncDisplayKit import FlowCryptUI -// TODO: - ANTON - Move to FlowCryptUI +// TODO: - Ticket - Move to FlowCryptUI struct Segment { let viewController: UIViewController let title: NSAttributedString diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 78a70343a..7685dd38e 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -246,25 +246,23 @@ extension ComposeViewController { ?? self.contextToSend.subject ?? "(no subject)" - // TODO: - ANTON - encryptAndSendMessage -// -// guard let myPubKey = self.dataService.publicKey else { -// self.showAlert(message: "compose_no_pub_sender".localized) -// return false -// } -// -// guard let allRecipientPubs = self.getPubKeys(for: recipients) else { -// return false -// } -// -// let encrypted = self.encryptMsg( -// pubkeys: allRecipientPubs + [myPubKey], -// subject: subject, -// message: text, -// to: recipients.map(\.email) -// ) - -// try awaitPromise(self.messageSender.sendMail(mime: encrypted.mimeEncoded)) + guard let myPubKey = self.dataService.publicKey() else { + self.showAlert(message: "compose_no_pub_sender".localized) + return false + } + + guard let allRecipientPubs = self.getPubKeys(for: recipients) else { + return false + } + + let encrypted = self.encryptMsg( + pubkeys: allRecipientPubs + [myPubKey], + subject: subject, + message: text, + to: recipients.map(\.email) + ) + + try awaitPromise(self.messageSender.sendMail(mime: encrypted.mimeEncoded)) return true } diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index 33379a57b..cc29ca25d 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -174,7 +174,7 @@ extension MessageViewController { private func showPassPhraseAlert() { hideSpinner() - let alert = UIAlertController(title: "Please enter pass phrase", message: nil, preferredStyle: .alert) + let alert = UIAlertController(title: "setup_enter_pass_phrase".localized, message: nil, preferredStyle: .alert) alert.addTextField { tf in tf.isSecureTextEntry = true } @@ -417,3 +417,4 @@ extension MessageViewController: ASTableDelegate, ASTableDataSource { ) } } + diff --git a/FlowCrypt/Controllers/Search/SearchViewController.swift b/FlowCrypt/Controllers/Search/SearchViewController.swift index ed9b745ec..798481e47 100644 --- a/FlowCrypt/Controllers/Search/SearchViewController.swift +++ b/FlowCrypt/Controllers/Search/SearchViewController.swift @@ -12,7 +12,7 @@ import FlowCryptUI final class SearchViewController: TableNodeViewController { private enum Constants { - // TODO: - Add pagination for SearchViewController + // TODO: - Ticket - Add pagination for SearchViewController static let messageCount = 100 } enum State { diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 501b4e702..7eb5e3012 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -261,22 +261,3 @@ extension SetupInitialViewController { } } } - -// TODO: - ANTON - -/* - During setup - new key - SetupKeyViewController - when loading from backup - SetupBackupsViewController - when importing key - creating - entering pass phrase - - If the user switches it, - then we do not store pass phrase with the key (or at all). - We only keep it in memory for up to 4 hours from the moment it was stored - then it needs to be forgotten. - During those 4 hours, the key will be used for actions (eg decrypt messages). - After those 4 hours, the user will be prompted for a pass phrase with a modal / alert to re-enter it, at which point it will be again remembered for 4 hours. - - If app gets killed, pass phrase gets forgotten. - */ diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index c3c6abf7c..da58d8f32 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -50,6 +50,8 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable .map { IndexPath(row: $0.rawValue, section: 0) } } + private var passPhrase: String? + init( user: UserId, backupService: BackupServiceType = BackupService(), @@ -130,6 +132,7 @@ extension SetupKeyViewController { private func setupAccountWithGeneratedKey(with passPhrase: String) { Promise { [weak self] in guard let self = self else { return } + self.showSpinner() let userId = try self.getUserId() @@ -161,10 +164,13 @@ extension SetupKeyViewController { ) } .then(on: .main) { [weak self] in + self?.hideSpinner() self?.moveToMainFlow() } .catch(on: .main) { [weak self] error in guard let self = self else { return } + self.hideSpinner() + let isErrorHandled = self.handleCommon(error: error) if !isErrorHandled { @@ -219,11 +225,11 @@ extension SetupKeyViewController { textField.accessibilityLabel = "textField" } - alert.addAction(UIAlertAction(title: "Cancel", style: .default) { _ in + alert.addAction(UIAlertAction(title: "cancel".localized, style: .default) { _ in resolve(nil) }) - alert.addAction(UIAlertAction(title: "OK", style: .default) { [weak alert] _ in + alert.addAction(UIAlertAction(title: "ok".localized, style: .default) { [weak alert] _ in resolve(alert?.textFields?[0].text) }) @@ -238,9 +244,16 @@ extension SetupKeyViewController { } private func showChoosingOptions() { - // showToast("Not implemented yet") } + + private func handleButtonAction() { + guard let passPhrase = passPhrase, passPhrase.isNotEmpty else { + showAlert(message: "setup_wrong_pass_phrase_retry".localized) + return + } + setupAccountWithGeneratedKey(with: passPhrase) + } } // MARK: - ASTableDelegate, ASTableDataSource @@ -273,24 +286,23 @@ extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { case .passPhrase: return TextFieldCellNode(input: .passPhraseTextFieldStyle) { [weak self] action in guard case let .didEndEditing(value) = action else { return } + self?.passPhrase = value } - - // TODO: - ANTON - ui logic - passPhrase didEndEditing - .onShouldReturn { [weak self] _ in self?.view.endEditing(true) - + self?.handleButtonAction() return true } .then { $0.becomeFirstResponder() } case .action: - return ButtonCellNode( + let input = ButtonCellNode.Input( title: self.decorator.buttonTitle(for: .setPassPhrase), insets: self.decorator.insets.buttonInsets - ) { [weak self] in - // TODO: - ANTON - ui logic - passPhrase didEndEditing + ) + return ButtonCellNode(input: input) { [weak self] in + self?.handleButtonAction() } case .subtitle: return SetupTitleNode( diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index 28fa0e1cd..b962b4c79 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -65,15 +65,7 @@ struct AppStartup { let setupViewController = SetupInitialViewController(user: userId) viewController = MainNavigationController(rootViewController: setupViewController) } - - // TODO: - ANTON - warning - // SetupEnterPassPhraseViewController(email: "", fetchedKeys: []) -// window.rootViewController = MainNavigationController( -// rootViewController: -//// SetupKeyViewController(user: UserId.init(email: "a", name: "a") -// SetupBackupsViewController(fetchedEncryptedKeys: [], user: UserId.init(email: "test@gmail.com", name: "a")) -//// SetupEnterPassPhraseViewController(email: "test@gmail.com", fetchedKeys: []) -// ) + window.rootViewController = viewController } diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift index fd6a85caa..672f7b701 100644 --- a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift @@ -25,21 +25,21 @@ final class PassPhraseStorage: PassPhraseStorageType { let currentUserEmail: () -> (String?) let storage: EncryptedStorage let localStorage: UserDefaults - let timeout: Int let encoder = JSONEncoder() let decoder = JSONDecoder() + let timeoutContext: (component: Calendar.Component, timeout: Int) private var subscription: NSObjectProtocol? init( storage: EncryptedStorage = EncryptedStorage(), localStorage: UserDefaults = .standard, - timeout: Int = 4, + timeoutContext: (Calendar.Component, Int) = (.hour, 4), currentUserEmail: @autoclosure @escaping () -> (String?) = DataService.shared.email ) { self.storage = storage self.localStorage = localStorage - self.timeout = timeout + self.timeoutContext = timeoutContext self.currentUserEmail = currentUserEmail } @@ -95,12 +95,22 @@ final class PassPhraseStorage: PassPhraseStorageType { getAllLocallySavedPassPhrases() .forEach { localPassPhrases in - guard calendar.component(.hour, from: localPassPhrases.date) < timeout else { - self.logger.logInfo("pass phrase is invalid \(localPassPhrases.passPhrase.longid)") + let calculatedTime = calendar.dateComponents( + [timeoutContext.component], + from: localPassPhrases.date, + to: Date() + ).hour ?? 0 + + let isPassPhraseValid = calculatedTime < timeoutContext.timeout + + if isPassPhraseValid { + validPassPhrases.append(localPassPhrases.passPhrase) + } else { invalidPassPhrases.append(localPassPhrases) - return } - validPassPhrases.append(localPassPhrases.passPhrase) + + let message = "pass phrase is \(isPassPhraseValid ? "valid" : "invalid") \(localPassPhrases.passPhrase.longid)" + self.logger.logInfo(message) } removeInvalidPassPhrases(with: invalidPassPhrases) diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index f150ae340..ecb064a4a 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -3,6 +3,8 @@ "sending_title" = "Sending"; "unknown_title" = "(unknown)"; "retry_title" = "retry"; +"ok" = "Ok"; +"cancel" = "Cancel"; // EMAIL "email_removed" = "Email moved to Trash"; @@ -91,6 +93,7 @@ "setup_create_key_title" = "Create key"; "setup_save_pass_locally" = "Store pass phrase locally"; "setup_save_pass_in_memory" = "Keep pass phrase in memory"; +"setup_enter_pass_phrase" = "Please enter pass phrase"; // Key Import "import_key_title" = "Import Key"; From 599bd41f8de534464864e9d1bb6d413b01824548 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Mon, 7 Jun 2021 22:46:05 +0300 Subject: [PATCH 12/26] Addd error handling --- .../Msg/MessageViewController.swift | 5 +-- .../Setup/SetupKeyViewController.swift | 3 +- .../Error Handling/ErrorHandler.swift | 3 +- .../KeyServiceErrorHandler.swift | 32 +++++++++++++++++-- .../Functionality/Services/AppStartup.swift | 2 +- .../Resources/en.lproj/Localizable.strings | 2 ++ 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index cc29ca25d..ddfa8e44b 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -194,7 +194,9 @@ extension MessageViewController { } let cancelAction = UIAlertAction(title: "Cancel", style: .destructive) { _ in - alert.dismiss(animated: true, completion: nil) + alert.dismiss(animated: true) { [weak self] in + self?.navigationController?.popViewController(animated: true) + } } alert.addAction(saveAction) @@ -417,4 +419,3 @@ extension MessageViewController: ASTableDelegate, ASTableDataSource { ) } } - diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index da58d8f32..d3c802b72 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -11,7 +11,6 @@ import FlowCryptUI import Promises enum CreateKeyError: Error { - // "Pass phrase strength: \(strength.word.word)\ncrack time: \(strength.time)\n\nWe recommend to use 5-6 unrelated words as your Pass Phrase.") case weakPassPhrase(_ strength: CoreRes.ZxcvbnStrengthBar) // Missing user email case missedUserEmail @@ -52,6 +51,8 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable private var passPhrase: String? + private lazy var logger = Logger.nested(in: Self.self, with: .setup) + init( user: UserId, backupService: BackupServiceType = BackupService(), diff --git a/FlowCrypt/Functionality/Error Handling/ErrorHandler.swift b/FlowCrypt/Functionality/Error Handling/ErrorHandler.swift index 49d7ffab6..7f8693011 100644 --- a/FlowCrypt/Functionality/Error Handling/ErrorHandler.swift +++ b/FlowCrypt/Functionality/Error Handling/ErrorHandler.swift @@ -32,7 +32,8 @@ private struct ComposedErrorHandler: ErrorHandler { static let shared: ComposedErrorHandler = ComposedErrorHandler( handlers: [ KeyServiceErrorHandler(), - BackupServiceErrorHandler() + BackupServiceErrorHandler(), + CreateKeyErrorHandler() ] ) diff --git a/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift b/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift index ef0adf6f0..9bab6e8d0 100644 --- a/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift +++ b/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift @@ -8,6 +8,7 @@ import UIKit +// KeyServiceError struct KeyServiceErrorHandler: ErrorHandler { func handle(error: Error, for viewController: UIViewController) -> Bool { let errorMessage: String? @@ -19,8 +20,7 @@ struct KeyServiceErrorHandler: ErrorHandler { case .unexpected: errorMessage = "keyServiceError_retrieve_unexpected" case .emptyKeys: - // TODO: - ANTON - errorMessage = "" + errorMessage = nil default: errorMessage = nil } @@ -32,3 +32,31 @@ struct KeyServiceErrorHandler: ErrorHandler { return true } } + +// CreateKeyError +struct CreateKeyErrorHandler: ErrorHandler { + func handle(error: Error, for viewController: UIViewController) -> Bool { + let errorMessage: String? + + switch error as? CreateKeyError { + case .weakPassPhrase(let strength): + errorMessage = "Pass phrase strength: \(strength.word.word)\ncrack time: \(strength.time)\n\nWe recommend to use 5-6 unrelated words as your Pass Phrase." + case .missedUserEmail: + errorMessage = "backupServiceError_email".localized + case .missedUserName: + errorMessage = "backupServiceError_name".localized + case .doesntMatch: + errorMessage = "pass_phrase_match_error".localized + case .conformingPassPhraseError: + errorMessage = nil + case .none: + errorMessage = nil + } + + guard let message = errorMessage else { return false } + + viewController.showAlert(message: message) + + return true + } +} diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index b962b4c79..8e318ac65 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -65,7 +65,7 @@ struct AppStartup { let setupViewController = SetupInitialViewController(user: userId) viewController = MainNavigationController(rootViewController: setupViewController) } - + window.rootViewController = viewController } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index ecb064a4a..230db00fe 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -41,7 +41,9 @@ "backupServiceError_parse" = "Key can't be parsed"; "backupServiceError_email" = "Missing user email"; +"backupServiceError_name" = "Missing user name"; "backupServiceError_notEncrypted" = "Private Key must be fully encrypted before backing up"; +"pass_phrase_match_error" = "Pass phrases don't match"; // Compose "compose_enter_recipient" = "Enter recipient"; From 0e41fdb1075c47dbdab130356953e00dfcd51b60 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Tue, 8 Jun 2021 00:00:04 +0300 Subject: [PATCH 13/26] Refactor PassPhraseStorage to make it testable. Start working on tests --- FlowCrypt.xcodeproj/project.pbxproj | 40 +++++++++ .../Msg/MessageViewController.swift | 5 +- .../Setup/SetupBackupsViewController.swift | 5 +- .../SetupEnterPassPhraseViewController.swift | 5 +- .../Setup/SetupImportKeyViewController.swift | 10 ++- .../Setup/SetupKeyViewController.swift | 6 +- .../DataManager/DataService.swift | 2 +- .../Encrypted Storage/EncryptedStorage.swift | 14 +-- .../EncryptedStorageProtocols.swift | 24 +++++ .../KeyServiceErrorHandler.swift | 8 +- .../Key Services/KeyDataStorage.swift | 5 +- .../Services/Key Services/KeyService.swift | 5 +- .../Key Services/LocalPassPhraseStorage.swift | 68 ++++++++++++++ .../Key Services/PassPhraseStorage.swift | 89 +++++++------------ .../EmailProviderMock.swift | 15 ++++ .../EncryptedPassPhraseStorageMock.swift | 35 ++++++++ .../LocalPassPhraseStorageMock.swift | 23 +++++ .../PassPhraseStorageTests.swift | 59 ++++++++++++ 18 files changed, 332 insertions(+), 86 deletions(-) create mode 100644 FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorageProtocols.swift create mode 100644 FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift create mode 100644 FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EmailProviderMock.swift create mode 100644 FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EncryptedPassPhraseStorageMock.swift create mode 100644 FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift create mode 100644 FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index c7a5e269a..b26ea7f8c 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -153,6 +153,16 @@ 9FC4114C25961CEA001180A8 /* MailServiceProviderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC4114B25961CEA001180A8 /* MailServiceProviderType.swift */; }; 9FC411902596229D001180A8 /* AppErr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA4B11D4531B3B04D01D1 /* AppErr.swift */; }; 9FC7EAB3266A404D00F3BF5D /* PassPhrase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EAB2266A404D00F3BF5D /* PassPhrase.swift */; }; + 9FC7EB69266EB64F00F3BF5D /* PassPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F79228726696B0200DA3D80 /* PassPhraseStorage.swift */; }; + 9FC7EB6F266EB66200F3BF5D /* PassPhrase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EAB2266A404D00F3BF5D /* PassPhrase.swift */; }; + 9FC7EB76266EB67B00F3BF5D /* EncryptedStorageProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EB75266EB67B00F3BF5D /* EncryptedStorageProtocols.swift */; }; + 9FC7EB7C266EB67D00F3BF5D /* EncryptedStorageProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EB75266EB67B00F3BF5D /* EncryptedStorageProtocols.swift */; }; + 9FC7EBA3266EB95300F3BF5D /* PassPhraseStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBA2266EB95300F3BF5D /* PassPhraseStorageTests.swift */; }; + 9FC7EBAA266EBD3700F3BF5D /* LocalPassPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBA9266EBD3700F3BF5D /* LocalPassPhraseStorage.swift */; }; + 9FC7EBB0266EBD4600F3BF5D /* LocalPassPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBA9266EBD3700F3BF5D /* LocalPassPhraseStorage.swift */; }; + 9FC7EBC2266EBE0100F3BF5D /* EmailProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBC1266EBE0100F3BF5D /* EmailProviderMock.swift */; }; + 9FC7EBC9266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBC8266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift */; }; + 9FC7EBD0266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBCF266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift */; }; 9FD364862381EFCB00657302 /* SetupEnterPassPhraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */; }; 9FDF364D235A1CCD00614596 /* SignInTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF364C235A1CCD00614596 /* SignInTest.swift */; }; 9FDF3650235A1D3F00614596 /* UITestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF364F235A1D3F00614596 /* UITestHelper.swift */; }; @@ -540,6 +550,12 @@ 9FC411342595EA94001180A8 /* Imap+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+Search.swift"; sourceTree = ""; }; 9FC4114B25961CEA001180A8 /* MailServiceProviderType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailServiceProviderType.swift; sourceTree = ""; }; 9FC7EAB2266A404D00F3BF5D /* PassPhrase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhrase.swift; sourceTree = ""; }; + 9FC7EB75266EB67B00F3BF5D /* EncryptedStorageProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedStorageProtocols.swift; sourceTree = ""; }; + 9FC7EBA2266EB95300F3BF5D /* PassPhraseStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhraseStorageTests.swift; sourceTree = ""; }; + 9FC7EBA9266EBD3700F3BF5D /* LocalPassPhraseStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPassPhraseStorage.swift; sourceTree = ""; }; + 9FC7EBC1266EBE0100F3BF5D /* EmailProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailProviderMock.swift; sourceTree = ""; }; + 9FC7EBC8266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPassPhraseStorageMock.swift; sourceTree = ""; }; + 9FC7EBCF266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedPassPhraseStorageMock.swift; sourceTree = ""; }; 9FD22A19230FD781005067A6 /* NavigationBarItemsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarItemsView.swift; sourceTree = ""; }; 9FD22A1B230FE7D0005067A6 /* Then.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Then.swift; sourceTree = ""; }; 9FD22A1E230FEFC6005067A6 /* NavigationBarActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarActionButton.swift; sourceTree = ""; }; @@ -1026,6 +1042,7 @@ isa = PBXGroup; children = ( 9F4163FE2665750500106194 /* Backup Services */, + 9FC7EBB6266EBDF000F3BF5D /* PassPhraseStorageTests */, ); path = Services; sourceTree = ""; @@ -1115,6 +1132,7 @@ children = ( 9F92EE71236F165E009BE0D7 /* EncryptedStorage.swift */, 9F589F14238C8249007FD759 /* KeyChainService.swift */, + 9FC7EB75266EB67B00F3BF5D /* EncryptedStorageProtocols.swift */, ); path = "Encrypted Storage"; sourceTree = ""; @@ -1178,6 +1196,7 @@ children = ( 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */, 9F79228726696B0200DA3D80 /* PassPhraseStorage.swift */, + 9FC7EBA9266EBD3700F3BF5D /* LocalPassPhraseStorage.swift */, D2891AC124C59EFA008918E3 /* KeyService.swift */, ); path = "Key Services"; @@ -1229,6 +1248,17 @@ path = "Mail Provider"; sourceTree = ""; }; + 9FC7EBB6266EBDF000F3BF5D /* PassPhraseStorageTests */ = { + isa = PBXGroup; + children = ( + 9FC7EBA2266EB95300F3BF5D /* PassPhraseStorageTests.swift */, + 9FC7EBC8266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift */, + 9FC7EBC1266EBE0100F3BF5D /* EmailProviderMock.swift */, + 9FC7EBCF266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift */, + ); + path = PassPhraseStorageTests; + sourceTree = ""; + }; 9FD22A1D230FEEC8005067A6 /* Common UI */ = { isa = PBXGroup; children = ( @@ -2314,17 +2344,25 @@ buildActionMask = 2147483647; files = ( D2D27B7A248A874C007346FA /* BigIntExtension.swift in Sources */, + 9FC7EB7C266EB67D00F3BF5D /* EncryptedStorageProtocols.swift in Sources */, 9F3EF32B23B16ADE00FA0CEF /* CommonExtensions.swift in Sources */, + 9FC7EBC2266EBE0100F3BF5D /* EmailProviderMock.swift in Sources */, + 9FC7EBD0266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift in Sources */, 9FA0157A26565B7800CBBA05 /* KeyMethodsTest.swift in Sources */, A357699622EA2BC8009242C4 /* KeyInfo.swift in Sources */, 9FC411902596229D001180A8 /* AppErr.swift in Sources */, + 9FC7EB69266EB64F00F3BF5D /* PassPhraseStorage.swift in Sources */, D212D35E24C1AACF00035991 /* PrvKeyInfo.swift in Sources */, 9F1D5769263B540100477938 /* Logger.swift in Sources */, + 9FC7EBC9266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift in Sources */, A3DAD5FE22E4574B00F2C4CD /* FlowCryptCoreTests.swift in Sources */, + 9FC7EBB0266EBD4600F3BF5D /* LocalPassPhraseStorage.swift in Sources */, 21EA3B2326565B5D00691848 /* DomainRulesTests.swift in Sources */, 9F4164102665754A00106194 /* PromiseExtensions.swift in Sources */, 21F836CC2652A38700B2448C /* ZBase32EncodingTests.swift in Sources */, + 9FC7EBA3266EB95300F3BF5D /* PassPhraseStorageTests.swift in Sources */, 9F7920EE2666D32500DA3D80 /* BackupServiceType.swift in Sources */, + 9FC7EB6F266EB66200F3BF5D /* PassPhrase.swift in Sources */, D2A9CA45242622F800E1D898 /* GeneralConstantsTest.swift in Sources */, A3DAD60B22E458C300F2C4CD /* DataExtensions.swift in Sources */, 21EA3B532656611C00691848 /* OrganisationalRule.swift in Sources */, @@ -2470,6 +2508,7 @@ 9F0C3C102316DD5B00299985 /* GoogleUserService.swift in Sources */, D24F4C2223E2359B00C5EEE4 /* BootstrapViewController.swift in Sources */, D211CE7623FC36BC00D1CE38 /* UIColorExtension.swift in Sources */, + 9FC7EBAA266EBD3700F3BF5D /* LocalPassPhraseStorage.swift in Sources */, 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */, D2FF6966243115EC007182F0 /* EmailProviderViewController.swift in Sources */, D2CDC3D22402D4DA002B045F /* UIViewControllerExtensions.swift in Sources */, @@ -2506,6 +2545,7 @@ 5A39F43F239EE7D2001F4607 /* SegmentedViewController.swift in Sources */, 5A5C234B23A042520015E705 /* WebViewController.swift in Sources */, 9F5C2A7E257E64D500DE9B4B /* MessageOperationsProvider.swift in Sources */, + 9FC7EB76266EB67B00F3BF5D /* EncryptedStorageProtocols.swift in Sources */, 32DCACF9C6FC4B9330C9B362 /* Imap+send.swift in Sources */, 32DCAF95A6A329C3136B1C8E /* Imap+msg.swift in Sources */, 21EA3B592656611D00691848 /* OrganisationalRule.swift in Sources */, diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index ddfa8e44b..c32dd3de9 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -63,7 +63,10 @@ final class MessageViewController: TableNodeViewController { messageOperationsProvider: MessageOperationsProvider = MailProvider.shared.messageOperationsProvider, decorator: MessageViewDecorator = MessageViewDecorator(dateFormatter: DateFormatter()), trashFolderProvider: TrashFolderProviderType = TrashFolderProvider(), - passPhraseStorage: PassPhraseStorageType = PassPhraseStorage(), + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage( + storage: EncryptedStorage(), + emailProvider: DataService.shared + ), input: MessageViewController.Input, completion: MsgViewControllerCompletion? ) { diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index b3121d4a6..3b4db870a 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -42,7 +42,10 @@ final class SetupBackupsViewController: TableNodeViewController, PassPhraseSavea core: Core = Core.shared, keyMethods: KeyMethodsType = KeyMethods(), user: UserId, - passPhraseStorage: PassPhraseStorageType = PassPhraseStorage() + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage( + storage: EncryptedStorage(), + emailProvider: DataService.shared + ) ) { self.fetchedEncryptedKeys = fetchedEncryptedKeys self.router = router diff --git a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift index d1b37a3f6..294d57bdb 100644 --- a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift @@ -46,7 +46,10 @@ final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhr keysService: KeyStorageType = KeyDataStorage(), router: GlobalRouterType = GlobalRouter(), keyService: KeyServiceType = KeyService(), - passPhraseStorage: PassPhraseStorageType = PassPhraseStorage(), + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage( + storage: EncryptedStorage(), + emailProvider: DataService.shared + ), email: String, fetchedKeys: [KeyDetails] ) { diff --git a/FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift index fa0ad3d8c..7e5c829cf 100644 --- a/FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift @@ -103,17 +103,19 @@ extension SetupImportKeyViewController: ASTableDelegate, ASTableDataSource { ) ) case .fileImport: - return ButtonCellNode( + let input = ButtonCellNode.Input( title: self.decorator.buttonTitle(for: .fileImport), insets: self.decorator.insets.buttonInsets - ) { [weak self] in + ) + return ButtonCellNode(input: input) { [weak self] in self?.proceedToKeyImportFromFile() } case .pasteBoardImport: - return ButtonCellNode( + let input = ButtonCellNode.Input( title: self.decorator.buttonTitle(for: .pasteBoard), insets: self.decorator.insets.buttonInsets - ) { [weak self] in + ) + return ButtonCellNode(input: input) { [weak self] in self?.proceedToKeyImportFromPasteboard() } .then { diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index d3c802b72..e5ce44fbe 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -62,7 +62,10 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable storage: DataServiceType = DataService.shared, keyStorage: KeyStorageType = KeyDataStorage(), attester: AttesterApiType = AttesterApi(), - passPhraseStorage: PassPhraseStorageType = PassPhraseStorage() + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage( + storage: EncryptedStorage(), + emailProvider: DataService.shared + ) ) { self.user = user self.core = core @@ -253,6 +256,7 @@ extension SetupKeyViewController { showAlert(message: "setup_wrong_pass_phrase_retry".localized) return } + logger.logInfo("Setup account with \(passPhrase)") setupAccountWithGeneratedKey(with: passPhrase) } } diff --git a/FlowCrypt/Functionality/DataManager/DataService.swift b/FlowCrypt/Functionality/DataManager/DataService.swift index 39ce49cd8..40da8f7f5 100644 --- a/FlowCrypt/Functionality/DataManager/DataService.swift +++ b/FlowCrypt/Functionality/DataManager/DataService.swift @@ -10,7 +10,7 @@ import Foundation import Promises import RealmSwift -protocol DataServiceType { +protocol DataServiceType: EmailProviderType { // data var email: String? { get } var currentUser: User? { get } diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index b37a1edd8..1079ced62 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -11,13 +11,6 @@ import Foundation import Promises import RealmSwift -protocol KeyStorageType { - func addKeys(keyDetails: [KeyDetails], source: KeySource) - func updateKeys(keyDetails: [KeyDetails], source: KeySource) - func publicKey() -> String? - func keysInfo() -> [KeyInfo] -} - protocol EncryptedStorageType: KeyStorageType { var storage: Realm { get } @@ -25,11 +18,6 @@ protocol EncryptedStorageType: KeyStorageType { func saveActiveUser(with user: UserObject) var activeUser: UserObject? { get } - func addPassPhrase(object: PassPhraseObject) - func updatePassPhrase(object: PassPhraseObject) - func getPassPhrases() -> [PassPhraseObject] - func removePassPhrase(object: PassPhraseObject) - func cleanup() } @@ -195,7 +183,7 @@ extension EncryptedStorage { } // MARK: - PassPhrase -extension EncryptedStorage { +extension EncryptedStorage: EncryptedPassPhraseStorage { func addPassPhrase(object: PassPhraseObject) { try! storage.write { storage.add(object) diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorageProtocols.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorageProtocols.swift new file mode 100644 index 000000000..44ec94c44 --- /dev/null +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorageProtocols.swift @@ -0,0 +1,24 @@ +// +// EncryptedStorageProtocols.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 07.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +protocol KeyStorageType { + func addKeys(keyDetails: [KeyDetails], source: KeySource) + func updateKeys(keyDetails: [KeyDetails], source: KeySource) + func publicKey() -> String? + func keysInfo() -> [KeyInfo] +} + +protocol EncryptedPassPhraseStorage { + func addPassPhrase(object: PassPhraseObject) + func updatePassPhrase(object: PassPhraseObject) + func getPassPhrases() -> [PassPhraseObject] + func removePassPhrase(object: PassPhraseObject) + func keysInfo() -> [KeyInfo] +} diff --git a/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift b/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift index 9bab6e8d0..d9081cb7f 100644 --- a/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift +++ b/FlowCrypt/Functionality/Error Handling/KeyServiceErrorHandler.swift @@ -37,7 +37,7 @@ struct KeyServiceErrorHandler: ErrorHandler { struct CreateKeyErrorHandler: ErrorHandler { func handle(error: Error, for viewController: UIViewController) -> Bool { let errorMessage: String? - + switch error as? CreateKeyError { case .weakPassPhrase(let strength): errorMessage = "Pass phrase strength: \(strength.word.word)\ncrack time: \(strength.time)\n\nWe recommend to use 5-6 unrelated words as your Pass Phrase." @@ -52,11 +52,11 @@ struct CreateKeyErrorHandler: ErrorHandler { case .none: errorMessage = nil } - + guard let message = errorMessage else { return false } - + viewController.showAlert(message: message) - + return true } } diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift index ccd49f966..39d0b7c6a 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift @@ -14,7 +14,10 @@ final class KeyDataStorage { init( encryptedStorage: EncryptedStorageType = EncryptedStorage(), - passPhraseStorage: PassPhraseStorageType = PassPhraseStorage() + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage( + storage: EncryptedStorage(), + emailProvider: DataService.shared + ) ) { self.encryptedStorage = encryptedStorage self.passPhraseStorage = passPhraseStorage diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift index 95b52dcda..ab02348c3 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift @@ -25,7 +25,10 @@ final class KeyService: KeyServiceType { init( storage: KeyStorageType = KeyDataStorage(), - passPhraseStorage: PassPhraseStorageType = PassPhraseStorage(), + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage( + storage: EncryptedStorage(), + emailProvider: DataService.shared + ), currentUserEmail: @autoclosure @escaping () -> (String?) = DataService.shared.email ) { self.storage = storage diff --git a/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift new file mode 100644 index 000000000..ee0637081 --- /dev/null +++ b/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift @@ -0,0 +1,68 @@ +// +// LocalPassPhraseStorage.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 07.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import UIKit + +protocol LocalPassPhraseStorageType { + func getAllLocallySavedPassPhrases() -> [LocalPassPhrase] + func encodeAndSave(passPhrases: [LocalPassPhrase]) +} + +struct LocalPassPhrase: Codable { + let passPhrase: PassPhrase + let date: Date +} + +final class LocalPassPhraseStorage: LocalPassPhraseStorageType { + private enum Constants { + static let passPhraseIndex = "passPhraseIndex" + } + private lazy var logger = Logger.nested(Self.self) + + let localStorage: UserDefaults + let encoder = JSONEncoder() + let decoder = JSONDecoder() + + private var subscription: NSObjectProtocol? + + init(localStorage: UserDefaults = .standard) { + self.localStorage = localStorage + subscribeToTerminateNotification() + } + + deinit { + if let subscription = subscription { + NotificationCenter.default.removeObserver(subscription) + } + } + + private func subscribeToTerminateNotification() { + subscription = NotificationCenter.default.addObserver( + forName: UIApplication.willTerminateNotification, + object: nil, + queue: .main + ) { _ in + self.logger.logInfo("App is about to terminate") + self.localStorage.removeObject(forKey: Constants.passPhraseIndex) + } + } + + func getAllLocallySavedPassPhrases() -> [LocalPassPhrase] { + guard let data = localStorage.data(forKey: Constants.passPhraseIndex), + let result = try? decoder.decode([LocalPassPhrase].self, from: data) else { + return [] + } + + return result + } + + func encodeAndSave(passPhrases: [LocalPassPhrase]) { + let objectsToSave = try? encoder.encode(passPhrases) + localStorage.set(objectsToSave, forKey: Constants.passPhraseIndex) + } +} diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift index 672f7b701..498814b4a 100644 --- a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift @@ -16,48 +16,33 @@ protocol PassPhraseStorageType { func saveLocally(passPhrase: String) } +protocol EmailProviderType { + var email: String? { get } +} + final class PassPhraseStorage: PassPhraseStorageType { - private enum Constants { - static let passPhraseIndex = "passPhraseIndex" - } private lazy var logger = Logger.nested(Self.self) - let currentUserEmail: () -> (String?) - let storage: EncryptedStorage - let localStorage: UserDefaults - let encoder = JSONEncoder() - let decoder = JSONDecoder() + let currentUserEmail: String? + let storage: EncryptedPassPhraseStorage + let localStorage: LocalPassPhraseStorageType let timeoutContext: (component: Calendar.Component, timeout: Int) - private var subscription: NSObjectProtocol? + /// used for tests only, otherwise seconds will be used + let isHours: Bool init( - storage: EncryptedStorage = EncryptedStorage(), - localStorage: UserDefaults = .standard, + storage: EncryptedPassPhraseStorage, + localStorage: LocalPassPhraseStorageType = LocalPassPhraseStorage(), timeoutContext: (Calendar.Component, Int) = (.hour, 4), - currentUserEmail: @autoclosure @escaping () -> (String?) = DataService.shared.email + emailProvider: EmailProviderType, + isHours: Bool = true ) { self.storage = storage self.localStorage = localStorage self.timeoutContext = timeoutContext - self.currentUserEmail = currentUserEmail - } - - deinit { - if let subscription = subscription { - NotificationCenter.default.removeObserver(subscription) - } - } - - private func subscribeToTerminateNotification() { - subscription = NotificationCenter.default.addObserver( - forName: UIApplication.willTerminateNotification, - object: nil, - queue: .main - ) { _ in - self.logger.logInfo("App is about to terminate") - self.localStorage.removeObject(forKey: Constants.passPhraseIndex) - } + self.currentUserEmail = emailProvider.email + self.isHours = isHours } func savePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) { @@ -93,15 +78,22 @@ final class PassPhraseStorage: PassPhraseStorageType { var validPassPhrases: [PassPhrase] = [] var invalidPassPhrases: [LocalPassPhrase] = [] - getAllLocallySavedPassPhrases() + localStorage.getAllLocallySavedPassPhrases() .forEach { localPassPhrases in - let calculatedTime = calendar.dateComponents( + let components = calendar.dateComponents( [timeoutContext.component], from: localPassPhrases.date, to: Date() - ).hour ?? 0 + ) + + let timePassed: Int + if self.isHours { + timePassed = components.hour ?? 0 + } else { + timePassed = components.second ?? 0 + } - let isPassPhraseValid = calculatedTime < timeoutContext.timeout + let isPassPhraseValid = timePassed < timeoutContext.timeout if isPassPhraseValid { validPassPhrases.append(localPassPhrases.passPhrase) @@ -119,7 +111,7 @@ final class PassPhraseStorage: PassPhraseStorageType { } func saveLocally(passPhrase: String) { - guard let email = currentUserEmail() else { + guard let email = currentUserEmail else { return } @@ -134,39 +126,20 @@ final class PassPhraseStorage: PassPhraseStorageType { private func saveLocally(passPhrase: PassPhrase) { // get all saved - var temporaryPassPhrases = getAllLocallySavedPassPhrases() + var temporaryPassPhrases = localStorage.getAllLocallySavedPassPhrases() // update with new pass temporaryPassPhrases.append(LocalPassPhrase(passPhrase: passPhrase, date: Date())) // save to storage - encodeAndSave(passPhrases: temporaryPassPhrases) + localStorage.encodeAndSave(passPhrases: temporaryPassPhrases) } private func removeInvalidPassPhrases(with objects: [LocalPassPhrase]) { - var temporaryPassPhrases = getAllLocallySavedPassPhrases() + var temporaryPassPhrases = localStorage.getAllLocallySavedPassPhrases() objects.forEach { localPassPhrases in temporaryPassPhrases.removeAll(where: { $0.date == localPassPhrases.date }) } - encodeAndSave(passPhrases: temporaryPassPhrases) - } - - private func getAllLocallySavedPassPhrases() -> [LocalPassPhrase] { - guard let data = localStorage.data(forKey: Constants.passPhraseIndex), - let result = try? decoder.decode([LocalPassPhrase].self, from: data) else { - return [] - } - - return result - } - - private func encodeAndSave(passPhrases: [LocalPassPhrase]) { - let objectsToSave = try? encoder.encode(passPhrases) - localStorage.set(objectsToSave, forKey: Constants.passPhraseIndex) + localStorage.encodeAndSave(passPhrases: temporaryPassPhrases) } } - -private struct LocalPassPhrase: Codable { - let passPhrase: PassPhrase - let date: Date -} diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EmailProviderMock.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EmailProviderMock.swift new file mode 100644 index 000000000..5fb90bad6 --- /dev/null +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EmailProviderMock.swift @@ -0,0 +1,15 @@ +// +// EmailProviderMock.swift +// FlowCryptTests +// +// Created by Anton Kharchevskyi on 07.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +class EmailProviderMock: EmailProviderType { + var email: String? { + "test@gmail.com" + } +} diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EncryptedPassPhraseStorageMock.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EncryptedPassPhraseStorageMock.swift new file mode 100644 index 000000000..e40d6e05e --- /dev/null +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EncryptedPassPhraseStorageMock.swift @@ -0,0 +1,35 @@ +// +// EncryptedPassPhraseStorageMock.swift +// FlowCryptTests +// +// Created by Anton Kharchevskyi on 07.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +class EncryptedPassPhraseStorageMock: EncryptedPassPhraseStorage { + func addPassPhrase(object: PassPhraseObject) { + + } + + func updatePassPhrase(object: PassPhraseObject) { + + } + + var getPassPhrasesResult: () -> ([PassPhraseObject]) = { + [PassPhraseObject(longid: "longid", value: "value")] + } + func getPassPhrases() -> [PassPhraseObject] { + getPassPhrasesResult() + } + + func removePassPhrase(object: PassPhraseObject) { + + } + + var keysInfoResult: () -> ([KeyInfo]) = { [] } + func keysInfo() -> [KeyInfo] { + keysInfoResult() + } +} diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift new file mode 100644 index 000000000..db132ea32 --- /dev/null +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift @@ -0,0 +1,23 @@ +// +// LocalPassPhraseStorageMock.swift +// FlowCryptTests +// +// Created by Anton Kharchevskyi on 07.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +class LocalPassPhraseStorageMock: LocalPassPhraseStorageType { + var getAllLocallySavedPassPhrasesResult: () -> ([LocalPassPhrase]) = { [] } + func getAllLocallySavedPassPhrases() -> [LocalPassPhrase] { + getAllLocallySavedPassPhrasesResult() + } + + var encodeAndSaveResult: ([LocalPassPhrase]) -> () = { _ in + + } + func encodeAndSave(passPhrases: [LocalPassPhrase]) { + encodeAndSaveResult(passPhrases) + } +} diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift new file mode 100644 index 000000000..34edda8e8 --- /dev/null +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift @@ -0,0 +1,59 @@ +// +// PassPhraseStorageTests.swift +// FlowCryptTests +// +// Created by Anton Kharchevskyi on 07.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import XCTest + +class PassPhraseStorageTests: XCTestCase { + + var sut: PassPhraseStorage! + var storage: EncryptedPassPhraseStorageMock! + var emailProvider: EmailProviderMock! + var localStorage: LocalPassPhraseStorageMock! + + override func setUp() { + storage = EncryptedPassPhraseStorageMock() + emailProvider = EmailProviderMock() + localStorage = LocalPassPhraseStorageMock() + + sut = PassPhraseStorage( + storage: storage, + localStorage: localStorage, + timeoutContext: (Calendar.Component.second, 4), + emailProvider: emailProvider, + isHours: false + ) + } + + func testGetPassPhrasesWhenEmpty() { + // no pass phrases in storage + storage.getPassPhrasesResult = { [] } + // no pass phrases in localStorage + localStorage.getAllLocallySavedPassPhrasesResult = { [] } + + let result = sut.getPassPhrases() + + XCTAssertTrue(result.isEmpty) + } + + func testValidPassPhraseInStorage() { + let passPhrase = PassPhraseObject( + longid: "A123", + value: "some" + ) + + // no pass phrases in storage + storage.getPassPhrasesResult = { [passPhrase] } + // no pass phrases in localStorage + localStorage.getAllLocallySavedPassPhrasesResult = { [] } + + let result = sut.getPassPhrases() + + XCTAssertTrue(result.count == 1) + } + +} From d900ac875677c9940e019eb061d28c4ba0c2d452 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Tue, 8 Jun 2021 13:43:33 +0300 Subject: [PATCH 14/26] Rework LocalPassPhraseStorage to keep pass phrases only in memory --- FlowCrypt/Core/Models/PassPhrase.swift | 2 +- .../Key Services/LocalPassPhraseStorage.swift | 55 +++++-------------- .../Key Services/PassPhraseStorage.swift | 32 +++-------- .../LocalPassPhraseStorageMock.swift | 16 +++--- .../PassPhraseStorageTests.swift | 4 +- 5 files changed, 33 insertions(+), 76 deletions(-) diff --git a/FlowCrypt/Core/Models/PassPhrase.swift b/FlowCrypt/Core/Models/PassPhrase.swift index d485bebb3..963929e9d 100644 --- a/FlowCrypt/Core/Models/PassPhrase.swift +++ b/FlowCrypt/Core/Models/PassPhrase.swift @@ -10,7 +10,7 @@ import Foundation import RealmSwift // Should be operated in app -struct PassPhrase: Codable { +struct PassPhrase: Codable, Hashable, Equatable { let value: String let longid: String diff --git a/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift index ee0637081..659a38bdd 100644 --- a/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift @@ -9,60 +9,33 @@ import UIKit protocol LocalPassPhraseStorageType { - func getAllLocallySavedPassPhrases() -> [LocalPassPhrase] - func encodeAndSave(passPhrases: [LocalPassPhrase]) + var passPhrases: Set { get } + func save(passPhrase: LocalPassPhrase) + func removePassPhrases(with objects: [LocalPassPhrase]) } -struct LocalPassPhrase: Codable { +struct LocalPassPhrase: Codable, Hashable, Equatable { let passPhrase: PassPhrase let date: Date } final class LocalPassPhraseStorage: LocalPassPhraseStorageType { - private enum Constants { - static let passPhraseIndex = "passPhraseIndex" - } - private lazy var logger = Logger.nested(Self.self) - - let localStorage: UserDefaults - let encoder = JSONEncoder() - let decoder = JSONDecoder() + static let shared: LocalPassPhraseStorage = LocalPassPhraseStorage() - private var subscription: NSObjectProtocol? + private(set) var passPhrases: Set = [] - init(localStorage: UserDefaults = .standard) { - self.localStorage = localStorage - subscribeToTerminateNotification() + private init() { } - deinit { - if let subscription = subscription { - NotificationCenter.default.removeObserver(subscription) - } + func save(passPhrase: LocalPassPhrase) { + passPhrases.insert(passPhrase) } - private func subscribeToTerminateNotification() { - subscription = NotificationCenter.default.addObserver( - forName: UIApplication.willTerminateNotification, - object: nil, - queue: .main - ) { _ in - self.logger.logInfo("App is about to terminate") - self.localStorage.removeObject(forKey: Constants.passPhraseIndex) + func removePassPhrases(with objects: [LocalPassPhrase]) { + objects.forEach { + if passPhrases.contains($0) { + passPhrases.remove($0) + } } } - - func getAllLocallySavedPassPhrases() -> [LocalPassPhrase] { - guard let data = localStorage.data(forKey: Constants.passPhraseIndex), - let result = try? decoder.decode([LocalPassPhrase].self, from: data) else { - return [] - } - - return result - } - - func encodeAndSave(passPhrases: [LocalPassPhrase]) { - let objectsToSave = try? encoder.encode(passPhrases) - localStorage.set(objectsToSave, forKey: Constants.passPhraseIndex) - } } diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift index 498814b4a..cfe4d6e47 100644 --- a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift @@ -33,7 +33,7 @@ final class PassPhraseStorage: PassPhraseStorageType { init( storage: EncryptedPassPhraseStorage, - localStorage: LocalPassPhraseStorageType = LocalPassPhraseStorage(), + localStorage: LocalPassPhraseStorageType = LocalPassPhraseStorage.shared, timeoutContext: (Calendar.Component, Int) = (.hour, 4), emailProvider: EmailProviderType, isHours: Bool = true @@ -49,7 +49,8 @@ final class PassPhraseStorage: PassPhraseStorageType { if isLocally { logger.logInfo("Save locally \(passPhrase.longid)") - saveLocally(passPhrase: passPhrase) + let locallPassPhrase = LocalPassPhrase(passPhrase: passPhrase, date: Date()) + localStorage.save(passPhrase: locallPassPhrase) let alreadySaved = storage.getPassPhrases() @@ -78,7 +79,7 @@ final class PassPhraseStorage: PassPhraseStorageType { var validPassPhrases: [PassPhrase] = [] var invalidPassPhrases: [LocalPassPhrase] = [] - localStorage.getAllLocallySavedPassPhrases() + localStorage.passPhrases .forEach { localPassPhrases in let components = calendar.dateComponents( [timeoutContext.component], @@ -104,7 +105,8 @@ final class PassPhraseStorage: PassPhraseStorageType { let message = "pass phrase is \(isPassPhraseValid ? "valid" : "invalid") \(localPassPhrases.passPhrase.longid)" self.logger.logInfo(message) } - removeInvalidPassPhrases(with: invalidPassPhrases) + + localStorage.removePassPhrases(with: invalidPassPhrases) logger.logInfo("validPassPhrases \(validPassPhrases.count)") return dbPassPhrases + validPassPhrases @@ -120,26 +122,8 @@ final class PassPhraseStorage: PassPhraseStorageType { $0.account.contains(email) } .forEach { - saveLocally(passPhrase: PassPhrase(value: passPhrase, longid: $0.longid)) + let passPhrase = PassPhrase(value: passPhrase, longid: $0.longid) + localStorage.save(passPhrase: LocalPassPhrase(passPhrase: passPhrase, date: Date())) } } - - private func saveLocally(passPhrase: PassPhrase) { - // get all saved - var temporaryPassPhrases = localStorage.getAllLocallySavedPassPhrases() - // update with new pass - temporaryPassPhrases.append(LocalPassPhrase(passPhrase: passPhrase, date: Date())) - // save to storage - localStorage.encodeAndSave(passPhrases: temporaryPassPhrases) - } - - private func removeInvalidPassPhrases(with objects: [LocalPassPhrase]) { - var temporaryPassPhrases = localStorage.getAllLocallySavedPassPhrases() - - objects.forEach { localPassPhrases in - temporaryPassPhrases.removeAll(where: { $0.date == localPassPhrases.date }) - } - - localStorage.encodeAndSave(passPhrases: temporaryPassPhrases) - } } diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift index db132ea32..68b51577d 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift @@ -9,15 +9,15 @@ import Foundation class LocalPassPhraseStorageMock: LocalPassPhraseStorageType { - var getAllLocallySavedPassPhrasesResult: () -> ([LocalPassPhrase]) = { [] } - func getAllLocallySavedPassPhrases() -> [LocalPassPhrase] { - getAllLocallySavedPassPhrasesResult() - } + var passPhrases: Set = [] - var encodeAndSaveResult: ([LocalPassPhrase]) -> () = { _ in - + func save(passPhrase: LocalPassPhrase) { + passPhrases.insert(passPhrase) } - func encodeAndSave(passPhrases: [LocalPassPhrase]) { - encodeAndSaveResult(passPhrases) + + func removePassPhrases(with objects: [LocalPassPhrase]) { + objects.forEach { + passPhrases.remove($0) + } } } diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift index 34edda8e8..008be05e0 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift @@ -33,7 +33,7 @@ class PassPhraseStorageTests: XCTestCase { // no pass phrases in storage storage.getPassPhrasesResult = { [] } // no pass phrases in localStorage - localStorage.getAllLocallySavedPassPhrasesResult = { [] } + localStorage.passPhrases = [] let result = sut.getPassPhrases() @@ -49,7 +49,7 @@ class PassPhraseStorageTests: XCTestCase { // no pass phrases in storage storage.getPassPhrasesResult = { [passPhrase] } // no pass phrases in localStorage - localStorage.getAllLocallySavedPassPhrasesResult = { [] } + localStorage.passPhrases = [] let result = sut.getPassPhrases() From cf4df4fb079ceb65a57a1cb82814b7dffc8f203c Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Tue, 8 Jun 2021 13:48:42 +0300 Subject: [PATCH 15/26] Add tests --- .../PassPhraseStorageTests.swift | 88 +++++++++++++++++-- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift index 008be05e0..ed0b28076 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift @@ -23,7 +23,7 @@ class PassPhraseStorageTests: XCTestCase { sut = PassPhraseStorage( storage: storage, localStorage: localStorage, - timeoutContext: (Calendar.Component.second, 4), + timeoutContext: (Calendar.Component.second, 2), emailProvider: emailProvider, isHours: false ) @@ -40,20 +40,96 @@ class PassPhraseStorageTests: XCTestCase { XCTAssertTrue(result.isEmpty) } - func testValidPassPhraseInStorage() { - let passPhrase = PassPhraseObject( + func testGetValidPassPhraseFromStorage() { + let passPhrase1 = PassPhraseObject( + longid: "A123", + value: "some" + ) + let passPhrase2 = PassPhraseObject( longid: "A123", value: "some" ) - // no pass phrases in storage - storage.getPassPhrasesResult = { [passPhrase] } + storage.getPassPhrasesResult = { [passPhrase1] } // no pass phrases in localStorage localStorage.passPhrases = [] - let result = sut.getPassPhrases() + var result = sut.getPassPhrases() XCTAssertTrue(result.count == 1) + + storage.getPassPhrasesResult = { + [passPhrase1, passPhrase2] + } + + result = sut.getPassPhrases() + + XCTAssertTrue(result.count == 2) } + func testGetValidPassPhraseInLocalStorage() { + storage.getPassPhrasesResult = { [] } + + let savedDate = Date() + let localPassPhrase = LocalPassPhrase( + passPhrase: PassPhrase( + value: "value", + longid: "longid"), + date: savedDate + ) + localStorage.passPhrases = [localPassPhrase] + + // current timeout = 2 + sleep(1) + + let result = sut.getPassPhrases() + XCTAssertTrue(result.isNotEmpty) + } + + func testGetExpiredPassPhraseInLocalStorage() { + storage.getPassPhrasesResult = { [] } + + let savedDate = Date() + let localPassPhrase = LocalPassPhrase( + passPhrase: PassPhrase( + value: "value", + longid: "longid"), + date: savedDate + ) + localStorage.passPhrases = [localPassPhrase] + + // current timeout = 2 + sleep(3) + + let result = sut.getPassPhrases() + XCTAssertTrue(result.isEmpty) + } + + func testBothStorageContainsValidPassPhrase() { + let passPhrase1 = PassPhraseObject( + longid: "A123", + value: "some" + ) + let passPhrase2 = PassPhraseObject( + longid: "A123", + value: "some" + ) + + storage.getPassPhrasesResult = { + [passPhrase1, passPhrase2] + } + + let savedDate = Date() + let localPassPhrase = LocalPassPhrase( + passPhrase: PassPhrase( + value: "value", + longid: "longid"), + date: savedDate + ) + + localStorage.passPhrases = [localPassPhrase] + + let result = sut.getPassPhrases() + XCTAssertTrue(result.count == 3) + } } From 4749413932c3763bc48bfd580a13d33e207e21db Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Tue, 8 Jun 2021 14:27:00 +0300 Subject: [PATCH 16/26] UI fixes for text color --- FlowCrypt/Common UI/CommonNodesInputs.swift | 2 +- FlowCrypt/Controllers/Msg/MessageViewController.swift | 2 +- FlowCrypt/Controllers/Msg/MessageViewDecorator.swift | 11 ++++++++++- .../SideMenu/Menu/MyMenuViewController.swift | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/FlowCrypt/Common UI/CommonNodesInputs.swift b/FlowCrypt/Common UI/CommonNodesInputs.swift index 67569f88b..3a0360162 100644 --- a/FlowCrypt/Common UI/CommonNodesInputs.swift +++ b/FlowCrypt/Common UI/CommonNodesInputs.swift @@ -48,7 +48,7 @@ extension ButtonCellNode.Input { extension CheckBoxTextNode.Input { static func common(with text: String, isSelected: Bool) -> CheckBoxTextNode.Input { let attributedTitle = text - .attributed(.bold(14), color: .textColor, alignment: .center) + .attributed(.bold(14), color: .mainTextColor, alignment: .center) let checkboxColor: UIColor = isSelected ? .main diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index c32dd3de9..17d4f1360 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -376,7 +376,7 @@ extension MessageViewController: ASTableDelegate, ASTableDataSource { } func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { - { [weak self] in + return { [weak self] in guard let self = self, let section = Sections(rawValue: indexPath.section) else { return ASCellNode() } switch section { diff --git a/FlowCrypt/Controllers/Msg/MessageViewDecorator.swift b/FlowCrypt/Controllers/Msg/MessageViewDecorator.swift index e30b49aef..b03d5f6ce 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewDecorator.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewDecorator.swift @@ -32,7 +32,16 @@ struct MessageViewDecorator { } func attributedMessage(from fetchedMessage: FetchedMessage) -> NSAttributedString { - fetchedMessage.text.attributed() + let textColor: UIColor + switch fetchedMessage.messageType { + case .encrypted: + textColor = .main + case .error: + textColor = .red + case .plain: + textColor = .mainTextColor + } + return fetchedMessage.text.attributed(color: textColor) } } diff --git a/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift b/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift index ed6cfe440..f5c2391e5 100644 --- a/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift +++ b/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift @@ -308,6 +308,7 @@ extension MyMenuViewController { // MARK: - SideMenuViewController extension MyMenuViewController: SideMenuViewController { func didOpen() { + tableNode.reloadData() fetchFolders() } } From c6f17e9d5714dfaf8cf38bf367e2ab6f26c0fe62 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Tue, 8 Jun 2021 15:47:30 +0300 Subject: [PATCH 17/26] Add PassPhraseStorage tests --- .../Key Services/LocalPassPhraseStorage.swift | 4 + .../Key Services/PassPhraseStorage.swift | 7 +- .../EmailProviderMock.swift | 4 +- .../EncryptedPassPhraseStorageMock.swift | 3 +- .../LocalPassPhraseStorageMock.swift | 4 + .../PassPhraseStorageTests.swift | 117 +++++++++++++++++- 6 files changed, 132 insertions(+), 7 deletions(-) diff --git a/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift index 659a38bdd..e1f97a77d 100644 --- a/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift @@ -17,6 +17,10 @@ protocol LocalPassPhraseStorageType { struct LocalPassPhrase: Codable, Hashable, Equatable { let passPhrase: PassPhrase let date: Date + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.passPhrase.longid == rhs.passPhrase.longid + } } final class LocalPassPhraseStorage: LocalPassPhraseStorageType { diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift index cfe4d6e47..ecdaeafe4 100644 --- a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift @@ -65,7 +65,12 @@ final class PassPhraseStorage: PassPhraseStorageType { } func updatePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) { - storage.updatePassPhrase(object: PassPhraseObject(passPhrase)) + if isLocally { + let updated = LocalPassPhrase(passPhrase: passPhrase, date: Date()) + localStorage.save(passPhrase: updated) + } else { + storage.updatePassPhrase(object: PassPhraseObject(passPhrase)) + } } func getPassPhrases() -> [PassPhrase] { diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EmailProviderMock.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EmailProviderMock.swift index 5fb90bad6..28f7a1849 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EmailProviderMock.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EmailProviderMock.swift @@ -9,7 +9,5 @@ import Foundation class EmailProviderMock: EmailProviderType { - var email: String? { - "test@gmail.com" - } + var email: String? = "test@gmail.com" } diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EncryptedPassPhraseStorageMock.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EncryptedPassPhraseStorageMock.swift index e40d6e05e..ea0404af0 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EncryptedPassPhraseStorageMock.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/EncryptedPassPhraseStorageMock.swift @@ -24,8 +24,9 @@ class EncryptedPassPhraseStorageMock: EncryptedPassPhraseStorage { getPassPhrasesResult() } + var isRemovePassPhraseResult: ((PassPhraseObject) -> ())? func removePassPhrase(object: PassPhraseObject) { - + isRemovePassPhraseResult?(object) } var keysInfoResult: () -> ([KeyInfo]) = { [] } diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift index 68b51577d..1bfd9b3d2 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift @@ -11,8 +11,12 @@ import Foundation class LocalPassPhraseStorageMock: LocalPassPhraseStorageType { var passPhrases: Set = [] + var isSaveCalled = false func save(passPhrase: LocalPassPhrase) { + isSaveCalled = true passPhrases.insert(passPhrase) + + print("^^ \(passPhrases)") } func removePassPhrases(with objects: [LocalPassPhrase]) { diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift index ed0b28076..1c9e76969 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift @@ -9,7 +9,7 @@ import XCTest class PassPhraseStorageTests: XCTestCase { - + var sut: PassPhraseStorage! var storage: EncryptedPassPhraseStorageMock! var emailProvider: EmailProviderMock! @@ -66,7 +66,7 @@ class PassPhraseStorageTests: XCTestCase { XCTAssertTrue(result.count == 2) } - + func testGetValidPassPhraseInLocalStorage() { storage.getPassPhrasesResult = { [] } @@ -132,4 +132,117 @@ class PassPhraseStorageTests: XCTestCase { let result = sut.getPassPhrases() XCTAssertTrue(result.count == 3) } + + func testSavePassPhraseWithEmptEmail() { + emailProvider.email = nil + sut.saveLocally(passPhrase: "Pass phrase") + XCTAssertFalse(localStorage.isSaveCalled) + } + + func testSavePassPhraseString() { + let account = "test@gmail.com" + let passPhrase = "Pass Phrase" + + // + emailProvider.email = account + + // encrypted storage contains key for account + storage.keysInfoResult = { + [ + KeyInfo.mock(with: "public 1", longid: "longid1"), + KeyInfo.mock(with: "public 2", account: account, longid: "longid2"), + KeyInfo.mock(with: "public 2", account: account, longid: "longid3") + ] + } + + sut.saveLocally(passPhrase: passPhrase) + + let ids = Set(sut.localStorage.passPhrases.map(\.passPhrase.longid)) + let expected = Set(["longid2", "longid3"]) + XCTAssert(ids == expected) + XCTAssertTrue(localStorage.isSaveCalled) + } + + func testSavePassPhraseLocally() { + let passPhraseToSave = PassPhrase(value: "pass", longid: "12345") + + let expectation = XCTestExpectation() + expectation.expectedFulfillmentCount = 1 + + // encrypted storage contains pass phrase which should be saved locally + storage.getPassPhrasesResult = { + [ + PassPhraseObject(longid: "12345", value: "pass") + ] + } + + + // encrypted storage should not contains pass phrase which user decide to save locally + storage.isRemovePassPhraseResult = { passPhraseToRemove in + if passPhraseToRemove.longid == "12345" { + expectation.fulfill() + } + } + + sut.savePassPhrase(with: passPhraseToSave, isLocally: true) + + XCTAssertTrue(localStorage.isSaveCalled) + + wait(for: [expectation], timeout: 0.1, enforceOrder: false) + } + + func testSavePassPhraseLocallyWithoutAnyPassPhrases() { + let passPhraseToSave = PassPhrase(value: "pass", longid: "12345") + + let expectation = XCTestExpectation() + expectation.isInverted = true + + // encrypted storage is empty + storage.getPassPhrasesResult = { [ ] } + + storage.isRemovePassPhraseResult = { _ in + expectation.fulfill() + } + + sut.savePassPhrase(with: passPhraseToSave, isLocally: true) + + XCTAssertTrue(localStorage.isSaveCalled) + + wait(for: [expectation], timeout: 0.1, enforceOrder: false) + } + + func testSavePassPhraseInStorage() { + let passPhraseToSave = PassPhrase(value: "pass", longid: "12345") + sut.savePassPhrase(with: passPhraseToSave, isLocally: false) + + XCTAssertFalse(localStorage.isSaveCalled) + } +} + +extension KeyInfo { + // extend with more parameters if needed + static func mock( + with publicValue: String, + account: String = "", + longid: String + ) -> KeyInfo { + let key = try! KeyInfo( + KeyDetails( + public: publicValue, + private: "private", + isFullyDecrypted: true, + isFullyEncrypted: true, + ids: [ + KeyId(shortid: "shortId", longid: "longid", fingerprint: "fingerprint", keywords: "keywords") + ], + created: 1234, + users: [], + algo: nil + ), + source: .backup + ) + key.account = account + key.longid = longid + return key + } } From 52e14d610a4f666769b1bcb69a7be3c215bbecac Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 13 Jun 2021 00:08:04 +0800 Subject: [PATCH 18/26] fixed swiftlint --- Scripts/format.sh | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/Scripts/format.sh b/Scripts/format.sh index 6262b8eee..3d5e0ab52 100755 --- a/Scripts/format.sh +++ b/Scripts/format.sh @@ -1,7 +1,10 @@ #!/bin/bash -# Do not run format on CI -if "$CI"; then +## Do not run format on CI +if [ -z "$CI" ]; then + echo "Not on CI - running" +else + echo "On CI - skipping" exit 0 fi @@ -10,12 +13,38 @@ if which swiftformat >/dev/null; then echo "Start formatting" # swiftlint autocorrect --path . swiftformat "FlowCrypt" \ - --rules trailingSpace, blankLinesAtEndOfScope, consecutiveBlankLines, consecutiveSpaces, \ - duplicateImports, initCoderUnavailable, isEmpty, leadingDelimiters, preferKeyPath, redundantBreak, \ - redundantExtensionACL, redundantFileprivate, redundantGet, redundantLet, redundantLetError, \ - redundantNilInit, redundantParens, redundantPattern, redundantVoidReturnType, semicolons, \ - sortedImports, spaceAroundBraces, spaceAroundBrackets, spaceAroundGenerics, spaceInsideBraces, spaceInsideGenerics, \ - strongifiedSelf, trailingClosures, void + --rules trailingSpace \ + --rules blankLinesAtEndOfScope \ + --rules consecutiveBlankLines \ + --rules consecutiveSpaces \ + --rules duplicateImports \ + --rules isEmpty \ + --rules leadingDelimiters \ + --rules redundantBreak \ + --rules redundantExtensionACL \ + --rules redundantFileprivate \ + --rules redundantGet \ + --rules redundantLet \ + --rules redundantLetError \ + --rules redundantNilInit\ + --rules redundantParens \ + --rules redundantPattern \ + --rules redundantVoidReturnType \ + --rules semicolons \ + --rules sortedImports \ + --rules spaceAroundBraces \ + --rules spaceAroundBrackets \ + --rules spaceAroundGenerics \ + --rules spaceInsideBraces \ + --rules spaceInsideGenerics \ + --rules strongifiedSelf \ + --rules trailingClosures \ + --rules void + +# following rules were not available on swiftformat version 0.40.12 +# --rules preferKeyPath \ +# --rules initCoderUnavailable \ + else echo "warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat" brew install swiftformat From 0325ff0f610ae9d654a365a2bf34b7f5a2ffbe62 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 13 Jun 2021 00:20:38 +0800 Subject: [PATCH 19/26] fixed swiftlint --- Scripts/format.sh | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Scripts/format.sh b/Scripts/format.sh index 3d5e0ab52..6c64824c0 100755 --- a/Scripts/format.sh +++ b/Scripts/format.sh @@ -1,10 +1,11 @@ #!/bin/bash -## Do not run format on CI -if [ -z "$CI" ]; then - echo "Not on CI - running" +set -euxo pipefail # debug + fail when any command fails + +if [ -z "${CI:-}" ]; then + echo "Not on CI - running SwiftFormat" else - echo "On CI - skipping" + echo "On CI - skipping SwiftFormat" exit 0 fi @@ -41,10 +42,6 @@ if which swiftformat >/dev/null; then --rules trailingClosures \ --rules void -# following rules were not available on swiftformat version 0.40.12 -# --rules preferKeyPath \ -# --rules initCoderUnavailable \ - else echo "warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat" brew install swiftformat @@ -53,3 +50,7 @@ fi ################### RULES https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md # ASCellNodeBlock - removed due to Opening Brace Spacing Violation when dealing with ASCellNodeBlock + +# following rules were not available on swiftformat version 0.40.12 +# --rules preferKeyPath \ +# --rules initCoderUnavailable \ From 74afdcb13ebea803b1f5a17d87d459b80c86af32 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Sun, 13 Jun 2021 18:43:18 +0300 Subject: [PATCH 20/26] Fix for saving pass phrase --- .../Services/Key Services/PassPhraseStorage.swift | 11 +++++------ .../PassPhraseStorageTests.swift | 15 ++++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift index ecdaeafe4..f2e4b3654 100644 --- a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift @@ -47,6 +47,9 @@ final class PassPhraseStorage: PassPhraseStorageType { func savePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) { if isLocally { + logger.logInfo("Save to storage \(passPhrase.longid)") + storage.addPassPhrase(object: PassPhraseObject(passPhrase)) + } else { logger.logInfo("Save locally \(passPhrase.longid)") let locallPassPhrase = LocalPassPhrase(passPhrase: passPhrase, date: Date()) @@ -57,19 +60,15 @@ final class PassPhraseStorage: PassPhraseStorageType { if alreadySaved.contains(where: { $0.longid == passPhrase.longid }) { storage.removePassPhrase(object: PassPhraseObject(passPhrase)) } - } else { - logger.logInfo("Save to storage \(passPhrase.longid)") - - storage.addPassPhrase(object: PassPhraseObject(passPhrase)) } } func updatePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) { if isLocally { + storage.updatePassPhrase(object: PassPhraseObject(passPhrase)) + } else { let updated = LocalPassPhrase(passPhrase: passPhrase, date: Date()) localStorage.save(passPhrase: updated) - } else { - storage.updatePassPhrase(object: PassPhraseObject(passPhrase)) } } diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift index 1c9e76969..2ef8e6bc5 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift @@ -163,11 +163,12 @@ class PassPhraseStorageTests: XCTestCase { XCTAssertTrue(localStorage.isSaveCalled) } - func testSavePassPhraseLocally() { + func testSavePassPhraseInStorage() { let passPhraseToSave = PassPhrase(value: "pass", longid: "12345") let expectation = XCTestExpectation() expectation.expectedFulfillmentCount = 1 + expectation.isInverted = true // encrypted storage contains pass phrase which should be saved locally storage.getPassPhrasesResult = { @@ -186,12 +187,12 @@ class PassPhraseStorageTests: XCTestCase { sut.savePassPhrase(with: passPhraseToSave, isLocally: true) - XCTAssertTrue(localStorage.isSaveCalled) + XCTAssertFalse(localStorage.isSaveCalled) wait(for: [expectation], timeout: 0.1, enforceOrder: false) } - func testSavePassPhraseLocallyWithoutAnyPassPhrases() { + func testSavePassPhraseInStorageWithoutAnyPassPhrases() { let passPhraseToSave = PassPhrase(value: "pass", longid: "12345") let expectation = XCTestExpectation() @@ -206,16 +207,16 @@ class PassPhraseStorageTests: XCTestCase { sut.savePassPhrase(with: passPhraseToSave, isLocally: true) - XCTAssertTrue(localStorage.isSaveCalled) + XCTAssertFalse(localStorage.isSaveCalled) wait(for: [expectation], timeout: 0.1, enforceOrder: false) } - func testSavePassPhraseInStorage() { + func testSavePassPhraseInMemory() { let passPhraseToSave = PassPhrase(value: "pass", longid: "12345") sut.savePassPhrase(with: passPhraseToSave, isLocally: false) - - XCTAssertFalse(localStorage.isSaveCalled) + + XCTAssertTrue(localStorage.isSaveCalled) } } From beac9aae46c9c104128fe7370115e9cbce401bd7 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Wed, 16 Jun 2021 16:06:37 +0300 Subject: [PATCH 21/26] Save only valid pass phrase --- FlowCrypt.xcodeproj/project.pbxproj | 4 + .../Msg/MessageViewController.swift | 113 ++++++++++-------- .../Setup/SetupBackupsViewController.swift | 2 +- .../SetupEnterPassPhraseViewController.swift | 4 +- .../Setup/SetupKeyViewController.swift | 2 +- FlowCrypt/Core/CoreTypes.swift | 13 ++ .../Message Provider/MessageService.swift | 64 ++++++++-- .../Services/Key Services/KeyService.swift | 47 +++++--- .../Key Services/PassPhraseStorage.swift | 29 +---- .../PassPhraseStorageTests.swift | 36 +----- 10 files changed, 179 insertions(+), 135 deletions(-) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index b26ea7f8c..50b71b624 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -124,6 +124,7 @@ 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA0C3D34A69851A238E87 /* Core.swift */; }; 9F7DE8C6232029D000F10B3E /* CoreTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAC732B988D9704658812 /* CoreTypes.swift */; }; 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCACB22E895C2500A99350 /* CoreHost.swift */; }; + 9F7E5101267A0AAC00CE37C3 /* AppAlertFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7E5100267A0AAC00CE37C3 /* AppAlertFactory.swift */; }; 9F8220D526336626004B2009 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8220D426336626004B2009 /* Logger.swift */; }; 9F82779823737E0900E19C07 /* MessageViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F82779723737E0900E19C07 /* MessageViewDecorator.swift */; }; 9F82D352256D74FA0069A702 /* InboxViewControllerContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F82D351256D74FA0069A702 /* InboxViewControllerContainer.swift */; }; @@ -515,6 +516,7 @@ 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPraseSaveable.swift; sourceTree = ""; }; 9F79228726696B0200DA3D80 /* PassPhraseStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassPhraseStorage.swift; sourceTree = ""; }; 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyDataStorage.swift; sourceTree = ""; }; + 9F7E5100267A0AAC00CE37C3 /* AppAlertFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppAlertFactory.swift; path = ../../../../AppAlertFactory.swift; sourceTree = ""; }; 9F8220D426336626004B2009 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 9F8277952373732000E19C07 /* UIImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; 9F82779723737E0900E19C07 /* MessageViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewDecorator.swift; sourceTree = ""; }; @@ -1786,6 +1788,7 @@ children = ( D952B71C1ED0CB2500E5C02B /* MessageViewController.swift */, 9F82779723737E0900E19C07 /* MessageViewDecorator.swift */, + 9F7E5100267A0AAC00CE37C3 /* AppAlertFactory.swift */, ); path = Msg; sourceTree = ""; @@ -2509,6 +2512,7 @@ D24F4C2223E2359B00C5EEE4 /* BootstrapViewController.swift in Sources */, D211CE7623FC36BC00D1CE38 /* UIColorExtension.swift in Sources */, 9FC7EBAA266EBD3700F3BF5D /* LocalPassPhraseStorage.swift in Sources */, + 9F7E5101267A0AAC00CE37C3 /* AppAlertFactory.swift in Sources */, 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */, D2FF6966243115EC007182F0 /* EmailProviderViewController.swift in Sources */, D2CDC3D22402D4DA002B045F /* UIViewControllerExtensions.swift in Sources */, diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index 17d4f1360..0b82326a7 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -148,64 +148,30 @@ extension MessageViewController { self.fetchedMessage = message } .then(on: .main) { [weak self] in - self?.hideSpinner() - self?.node.reloadData() - self?.asyncMarkAsReadIfNotAlreadyMarked() + self?.handleReceivedMessage() } .catch(on: .main) { [weak self] error in - self?.hideSpinner() - self?.handleError(error, path: input.path) + self?.handleError(error) } } - private func handleError(_ error: Error, path: String) { - // TODO: - Ticket - Improve error handling for MessageViewController - if error is MessageServiceError { - showPassPhraseAlert() - } else { - if let someError = error as NSError?, someError.code == Imap.Err.fetch.rawValue { - // todo - the missing msg should be removed from the list in inbox view - // reproduce: 1) load inbox 2) move msg to trash on another email client 3) open trashed message in inbox - showToast("Message not found in folder: \(path)") - } else { - // todo - this should be a retry / cancel alert - showAlert(error: error, message: "message_failed_open".localized + "\n\n\(error)") - } - navigationController?.popViewController(animated: true) - } - } - - private func showPassPhraseAlert() { - hideSpinner() - let alert = UIAlertController(title: "setup_enter_pass_phrase".localized, message: nil, preferredStyle: .alert) - alert.addTextField { tf in - tf.isSecureTextEntry = true - } + private func validateMessage(rawMimeData: Data, with passPhrase: String) { + showSpinner("loading_title".localized, isUserInteractionEnabled: true) - let saveAction = UIAlertAction(title: "Save", style: .default) { [weak self] _ in - guard let textField = alert.textFields?.first, - let passPhrase = textField.text, - passPhrase.isNotEmpty - else { - alert.dismiss(animated: true, completion: nil) - return - } - self?.passPhraseStorage.saveLocally(passPhrase: passPhrase) - alert.dismiss(animated: true) { - self?.fetchDecryptAndRenderMsg() + messageService.validateMessage(rawMimeData: rawMimeData, with: passPhrase) + .then(on: .main) { [weak self] message in + self?.fetchedMessage = message + self?.handleReceivedMessage() } - } - - let cancelAction = UIAlertAction(title: "Cancel", style: .destructive) { _ in - alert.dismiss(animated: true) { [weak self] in - self?.navigationController?.popViewController(animated: true) + .catch(on: .main) { [weak self] error in + self?.handleError(error) } - } - - alert.addAction(saveAction) - alert.addAction(cancelAction) + } - present(alert, animated: true, completion: nil) + private func handleReceivedMessage() { + hideSpinner() + node.reloadData() + asyncMarkAsReadIfNotAlreadyMarked() } private func asyncMarkAsReadIfNotAlreadyMarked() { @@ -231,6 +197,55 @@ extension MessageViewController { self?.onCompletion?(operation, input.objMessage) } } +} + +// MARK: - Error Handling + +extension MessageViewController { + private func handleError(_ error: Error) { + hideSpinner() + + switch error as? MessageServiceError { + case let .missedPassPhrase(rawMimeData): + handleMissedPassPhrase(for: rawMimeData) + case let .wrongPassPhrase(rawMimeData, passPhrase): + handleWrongPathPhrase(for: rawMimeData, with: passPhrase) + default: + // TODO: - Ticket - Improve error handling for MessageViewController + if let someError = error as NSError?, someError.code == Imap.Err.fetch.rawValue { + // todo - the missing msg should be removed from the list in inbox view + // reproduce: 1) load inbox 2) move msg to trash on another email client 3) open trashed message in inbox + showToast("Message not found in folder: \(input?.path ?? "N/A")") + } else { + // todo - this should be a retry / cancel alert + showAlert(error: error, message: "message_failed_open".localized + "\n\n\(error)") + } + navigationController?.popViewController(animated: true) + } + } + + private func handleMissedPassPhrase(for rawMimeData: Data) { + let alert = AppAlertFactory.makePassPhraseAlert( + onCancel: { [weak self] in + self?.navigationController?.popViewController(animated: true) + }, + onCompletion: { [weak self] passPhrase in + self?.validateMessage(rawMimeData: rawMimeData, with: passPhrase) + }) + + present(alert, animated: true, completion: nil) + } + + private func handleWrongPathPhrase(for rawMimeData: Data, with phrase: String) { + let alert = AppAlertFactory.makeWrongPassPhraseAlert( + onCancel: { [weak self] in + self?.navigationController?.popViewController(animated: true) + }, + onCompletion: { [weak self] passPhrase in + self?.validateMessage(rawMimeData: rawMimeData, with: passPhrase) + }) + present(alert, animated: true, completion: nil) + } private func handleOpErr(operation: MessageAction) { hideSpinner() diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index 3b4db870a..9c6772f94 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -140,7 +140,7 @@ extension SetupBackupsViewController { PassPhrase(value: passPhrase, longid: $0.longid) } .forEach { - passPhraseStorage.savePassPhrase(with: $0, isLocally: shouldSaveLocally) + passPhraseStorage.savePassPhrase(with: $0, inStorage: shouldSaveLocally) } // save keys diff --git a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift index 294d57bdb..7b2ce69ae 100644 --- a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift @@ -247,7 +247,7 @@ extension SetupEnterPassPhraseViewController { PassPhrase(value: passPhrase, longid: $0.longid) } .forEach { - passPhraseStorage.updatePassPhrase(with: $0, isLocally: shouldSaveLocally) + passPhraseStorage.updatePassPhrase(with: $0, inStorage: shouldSaveLocally) } newKeysToAdd @@ -255,7 +255,7 @@ extension SetupEnterPassPhraseViewController { PassPhrase(value: passPhrase, longid: $0.longid) } .forEach { - passPhraseStorage.savePassPhrase(with: $0, isLocally: shouldSaveLocally) + passPhraseStorage.savePassPhrase(with: $0, inStorage: shouldSaveLocally) } hideSpinner() diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift index e5ce44fbe..93abfa6bb 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift @@ -149,7 +149,7 @@ extension SetupKeyViewController { let passPhrase = PassPhrase(value: passPhrase, longid: encryptedPrv.key.longid) self.keyStorage.addKeys(keyDetails: [encryptedPrv.key], source: .generated) - self.passPhraseStorage.savePassPhrase(with: passPhrase, isLocally: self.shouldSaveLocally) + self.passPhraseStorage.savePassPhrase(with: passPhrase, inStorage: self.shouldSaveLocally) let updateKey = self.attester.updateKey( email: userId.email, diff --git a/FlowCrypt/Core/CoreTypes.swift b/FlowCrypt/Core/CoreTypes.swift index 49a0ccd22..26c0fa0e2 100644 --- a/FlowCrypt/Core/CoreTypes.swift +++ b/FlowCrypt/Core/CoreTypes.swift @@ -186,3 +186,16 @@ struct MsgBlock: Decodable { // case cryptupVerification; // not sure if Swift code will ever encounter this } } + +// TODO: - ANTON - tests +extension MsgBlock { + var isAttachmentBlock: Bool { + type == .plainAtt || type == .encryptedAtt || type == .decryptedAtt + } +} + +extension Array where Element == MsgBlock { + var isAnyError: Bool { + self.first(where: { $0.decryptErr != nil }) != nil + } +} diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index f28df4ed5..084db0edf 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -39,22 +39,72 @@ extension FetchedMessage { // MARK: - MessageService enum MessageServiceError: Error { - case missedPassPhrase + case missedPassPhrase(_ rawMimeData: Data) + case wrongPassPhrase(_ rawMimeData: Data, _ passPhrase: String) + // Could not fetch keys + case emptyKeys } final class MessageService { private let messageProvider: MessageProvider private let keyService: KeyServiceType + private let passPhraseStorage: PassPhraseStorageType private let core: Core init( messageProvider: MessageProvider = MailProvider.shared.messageProvider, keyService: KeyServiceType = KeyService(), - core: Core = Core.shared + core: Core = Core.shared, + passPhraseStorage: PassPhraseStorageType = PassPhraseStorage( + storage: EncryptedStorage(), + emailProvider: DataService.shared + ) ) { self.messageProvider = messageProvider self.keyService = keyService self.core = core + self.passPhraseStorage = passPhraseStorage + } + + func validateMessage(rawMimeData: Data, with passPhrase: String) -> Promise { + Promise { [weak self] resolve, reject in + guard let self = self else { return } + + guard let keys = try? self.keyService.getPrivateKeys(with: passPhrase).get(), keys.isNotEmpty else { + return reject(MessageServiceError.emptyKeys) + } + + // TODO: - Tom - is it possible to get longid of the key which was used for decryption? + let decrypted = try self.core.parseDecryptMsg( + encrypted: rawMimeData, + keys: keys, + msgPwd: nil, + isEmail: true + ) + + let isDecryptError = decrypted.blocks.isAnyError + + if isDecryptError { + reject(MessageServiceError.wrongPassPhrase(rawMimeData, passPhrase)) + } else { + keys + .map { PassPhrase(value: passPhrase, longid: $0.longid) } + .forEach { self.passPhraseStorage.savePassPhrase(with: $0, inStorage: false) } + + let attachments = decrypted.blocks + .filter(\.isAttachmentBlock) + .map(MessageAttachment.init) + + let fetchedMessage = FetchedMessage( + rawMimeData: rawMimeData, + text: decrypted.text, + attachments: attachments, + messageType: decrypted.replyType == CoreRes.ReplyType.encrypted ? .encrypted : .plain + ) + + resolve(fetchedMessage) + } + } } func getMessage(with input: Message, folder: String) -> Promise { @@ -65,8 +115,8 @@ final class MessageService { self.messageProvider.fetchMsg(message: input, folder: folder) ) - guard let keys = try? self.keyService.getPrivateKeys().get() else { - return reject(MessageServiceError.missedPassPhrase) + guard let keys = try? self.keyService.getPrivateKeys(with: nil).get() else { + return reject(MessageServiceError.missedPassPhrase(rawMimeData)) } guard keys.isNotEmpty else { @@ -119,9 +169,3 @@ private extension MessageAttachment { self.size = block.attMeta?.length ?? 0 } } - -private extension MsgBlock { - var isAttachmentBlock: Bool { - type == .plainAtt || type == .encryptedAtt || type == .decryptedAtt - } -} diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift index ab02348c3..55cacba32 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift @@ -10,7 +10,7 @@ import Foundation protocol KeyServiceType { func retrieveKeyDetails() -> Result<[KeyDetails], KeyServiceError> - func getPrivateKeys() -> Result<[PrvKeyInfo], KeyServiceError> + func getPrivateKeys(with passPhrase: String?) -> Result<[PrvKeyInfo], KeyServiceError> } enum KeyServiceError: Error { @@ -55,7 +55,7 @@ final class KeyService: KeyServiceType { return .success(keyDetails) } - func getPrivateKeys() -> Result<[PrvKeyInfo], KeyServiceError> { + func getPrivateKeys(with passPhrase: String? = nil) -> Result<[PrvKeyInfo], KeyServiceError> { guard let email = currentUserEmail() else { return .failure(.retrieve) } @@ -63,28 +63,43 @@ final class KeyService: KeyServiceType { let keysInfo = storage.keysInfo() .filter { $0.account.contains(email) } - let passPhrases = passPhraseStorage.getPassPhrases() + let storedPassPhrases = passPhraseStorage.getPassPhrases() - guard keysInfo.isNotEmpty, passPhrases.isNotEmpty else { + guard keysInfo.isNotEmpty else { return .failure(.emptyKeys) } - let privateKeys = keysInfo.compactMap { (keyInfo) -> PrvKeyInfo? in - guard let passPhrase = passPhrases.first(where: { $0.longid == keyInfo.longid }) else { - return nil - } + // get all private keys with already saved pass phrases + var privateKeys = keysInfo + .compactMap { (keyInfo) -> PrvKeyInfo? in + guard let passPhrase = storedPassPhrases.first(where: { $0.longid == keyInfo.longid }) else { + return nil + } + + let passPhraseValue = passPhrase.value - let passPhraseValue = passPhrase.value + guard passPhraseValue.isNotEmpty else { + return nil + } + + return PrvKeyInfo( + private: keyInfo.private, + longid: keyInfo.longid, + passphrase: passPhraseValue + ) + } - guard passPhraseValue.isNotEmpty else { - return nil + // append keys to ensure with a pass phrase + if let passPhrase = passPhrase { + let keysToEnsure = keysInfo.map { + PrvKeyInfo( + private: $0.private, + longid: $0.longid, + passphrase: passPhrase + ) } - return PrvKeyInfo( - private: keyInfo.private, - longid: keyInfo.longid, - passphrase: passPhraseValue - ) + privateKeys.append(contentsOf: keysToEnsure) } guard privateKeys.isNotEmpty else { diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift index f2e4b3654..cbb0e24bc 100644 --- a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift @@ -10,10 +10,8 @@ import UIKit protocol PassPhraseStorageType { func getPassPhrases() -> [PassPhrase] - func savePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) - func updatePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) - - func saveLocally(passPhrase: String) + func savePassPhrase(with passPhrase: PassPhrase, inStorage: Bool) + func updatePassPhrase(with passPhrase: PassPhrase, inStorage: Bool) } protocol EmailProviderType { @@ -45,8 +43,8 @@ final class PassPhraseStorage: PassPhraseStorageType { self.isHours = isHours } - func savePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) { - if isLocally { + func savePassPhrase(with passPhrase: PassPhrase, inStorage: Bool) { + if inStorage { logger.logInfo("Save to storage \(passPhrase.longid)") storage.addPassPhrase(object: PassPhraseObject(passPhrase)) } else { @@ -63,8 +61,8 @@ final class PassPhraseStorage: PassPhraseStorageType { } } - func updatePassPhrase(with passPhrase: PassPhrase, isLocally: Bool) { - if isLocally { + func updatePassPhrase(with passPhrase: PassPhrase, inStorage: Bool) { + if inStorage { storage.updatePassPhrase(object: PassPhraseObject(passPhrase)) } else { let updated = LocalPassPhrase(passPhrase: passPhrase, date: Date()) @@ -115,19 +113,4 @@ final class PassPhraseStorage: PassPhraseStorageType { logger.logInfo("validPassPhrases \(validPassPhrases.count)") return dbPassPhrases + validPassPhrases } - - func saveLocally(passPhrase: String) { - guard let email = currentUserEmail else { - return - } - - storage.keysInfo() - .filter { - $0.account.contains(email) - } - .forEach { - let passPhrase = PassPhrase(value: passPhrase, longid: $0.longid) - localStorage.save(passPhrase: LocalPassPhrase(passPhrase: passPhrase, date: Date())) - } - } } diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift index 2ef8e6bc5..7d0ef385d 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift @@ -133,36 +133,6 @@ class PassPhraseStorageTests: XCTestCase { XCTAssertTrue(result.count == 3) } - func testSavePassPhraseWithEmptEmail() { - emailProvider.email = nil - sut.saveLocally(passPhrase: "Pass phrase") - XCTAssertFalse(localStorage.isSaveCalled) - } - - func testSavePassPhraseString() { - let account = "test@gmail.com" - let passPhrase = "Pass Phrase" - - // - emailProvider.email = account - - // encrypted storage contains key for account - storage.keysInfoResult = { - [ - KeyInfo.mock(with: "public 1", longid: "longid1"), - KeyInfo.mock(with: "public 2", account: account, longid: "longid2"), - KeyInfo.mock(with: "public 2", account: account, longid: "longid3") - ] - } - - sut.saveLocally(passPhrase: passPhrase) - - let ids = Set(sut.localStorage.passPhrases.map(\.passPhrase.longid)) - let expected = Set(["longid2", "longid3"]) - XCTAssert(ids == expected) - XCTAssertTrue(localStorage.isSaveCalled) - } - func testSavePassPhraseInStorage() { let passPhraseToSave = PassPhrase(value: "pass", longid: "12345") @@ -185,7 +155,7 @@ class PassPhraseStorageTests: XCTestCase { } } - sut.savePassPhrase(with: passPhraseToSave, isLocally: true) + sut.savePassPhrase(with: passPhraseToSave, inStorage: true) XCTAssertFalse(localStorage.isSaveCalled) @@ -205,7 +175,7 @@ class PassPhraseStorageTests: XCTestCase { expectation.fulfill() } - sut.savePassPhrase(with: passPhraseToSave, isLocally: true) + sut.savePassPhrase(with: passPhraseToSave, inStorage: true) XCTAssertFalse(localStorage.isSaveCalled) @@ -214,7 +184,7 @@ class PassPhraseStorageTests: XCTestCase { func testSavePassPhraseInMemory() { let passPhraseToSave = PassPhrase(value: "pass", longid: "12345") - sut.savePassPhrase(with: passPhraseToSave, isLocally: false) + sut.savePassPhrase(with: passPhraseToSave, inStorage: false) XCTAssertTrue(localStorage.isSaveCalled) } From 512b8aa55d6c14504a12dcbba657a0e7e5aa8efd Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Thu, 17 Jun 2021 00:31:57 +0300 Subject: [PATCH 22/26] Minor fix --- FlowCrypt.xcodeproj/project.pbxproj | 8 +- FlowCrypt/Controllers/Msg/AlertsFactory.swift | 78 +++++++++++++++++++ .../Msg/MessageViewController.swift | 4 +- 3 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 FlowCrypt/Controllers/Msg/AlertsFactory.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 50b71b624..045ac4580 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -124,7 +124,7 @@ 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA0C3D34A69851A238E87 /* Core.swift */; }; 9F7DE8C6232029D000F10B3E /* CoreTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAC732B988D9704658812 /* CoreTypes.swift */; }; 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCACB22E895C2500A99350 /* CoreHost.swift */; }; - 9F7E5101267A0AAC00CE37C3 /* AppAlertFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7E5100267A0AAC00CE37C3 /* AppAlertFactory.swift */; }; + 9F7E5137267AA51B00CE37C3 /* AlertsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7E5136267AA51B00CE37C3 /* AlertsFactory.swift */; }; 9F8220D526336626004B2009 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8220D426336626004B2009 /* Logger.swift */; }; 9F82779823737E0900E19C07 /* MessageViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F82779723737E0900E19C07 /* MessageViewDecorator.swift */; }; 9F82D352256D74FA0069A702 /* InboxViewControllerContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F82D351256D74FA0069A702 /* InboxViewControllerContainer.swift */; }; @@ -516,7 +516,7 @@ 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPraseSaveable.swift; sourceTree = ""; }; 9F79228726696B0200DA3D80 /* PassPhraseStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassPhraseStorage.swift; sourceTree = ""; }; 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyDataStorage.swift; sourceTree = ""; }; - 9F7E5100267A0AAC00CE37C3 /* AppAlertFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppAlertFactory.swift; path = ../../../../AppAlertFactory.swift; sourceTree = ""; }; + 9F7E5136267AA51B00CE37C3 /* AlertsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertsFactory.swift; sourceTree = ""; }; 9F8220D426336626004B2009 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 9F8277952373732000E19C07 /* UIImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; 9F82779723737E0900E19C07 /* MessageViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewDecorator.swift; sourceTree = ""; }; @@ -1788,7 +1788,7 @@ children = ( D952B71C1ED0CB2500E5C02B /* MessageViewController.swift */, 9F82779723737E0900E19C07 /* MessageViewDecorator.swift */, - 9F7E5100267A0AAC00CE37C3 /* AppAlertFactory.swift */, + 9F7E5136267AA51B00CE37C3 /* AlertsFactory.swift */, ); path = Msg; sourceTree = ""; @@ -2457,6 +2457,7 @@ 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */, 5ADEDCB923A42B9400EC495E /* KeyDetailViewDecorator.swift in Sources */, 9F416428266575DC00106194 /* BackupServiceType.swift in Sources */, + 9F7E5137267AA51B00CE37C3 /* AlertsFactory.swift in Sources */, 5A39F437239ECC23001F4607 /* KeySettingsViewController.swift in Sources */, 9FF0673325520DE400FCC9E6 /* GmailService+send.swift in Sources */, D20D3C892520B67C00D4AA9A /* BackupOptionsViewController.swift in Sources */, @@ -2512,7 +2513,6 @@ D24F4C2223E2359B00C5EEE4 /* BootstrapViewController.swift in Sources */, D211CE7623FC36BC00D1CE38 /* UIColorExtension.swift in Sources */, 9FC7EBAA266EBD3700F3BF5D /* LocalPassPhraseStorage.swift in Sources */, - 9F7E5101267A0AAC00CE37C3 /* AppAlertFactory.swift in Sources */, 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */, D2FF6966243115EC007182F0 /* EmailProviderViewController.swift in Sources */, D2CDC3D22402D4DA002B045F /* UIViewControllerExtensions.swift in Sources */, diff --git a/FlowCrypt/Controllers/Msg/AlertsFactory.swift b/FlowCrypt/Controllers/Msg/AlertsFactory.swift new file mode 100644 index 000000000..99ebd904e --- /dev/null +++ b/FlowCrypt/Controllers/Msg/AlertsFactory.swift @@ -0,0 +1,78 @@ +// +// AlertsFactory.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 17.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +enum AlertsFactory { + typealias PassPhraseCompletion = ((String) -> Void) + typealias CancelCompletion = (() -> Void) + + static func makePassPhraseAlert(onCancel: @escaping CancelCompletion, onCompletion: @escaping PassPhraseCompletion) -> UIAlertController { + let alert = UIAlertController(title: "setup_enter_pass_phrase".localized, message: nil, preferredStyle: .alert) + alert.addTextField { tf in + tf.isSecureTextEntry = true + } + + let saveAction = UIAlertAction(title: "Save", style: .default) { _ in + guard let textField = alert.textFields?.first, + let passPhrase = textField.text, + passPhrase.isNotEmpty + else { + alert.dismiss(animated: true, completion: nil) + return + } + + alert.dismiss(animated: true) { + onCompletion(passPhrase) + } + } + + let cancelAction = UIAlertAction(title: "Cancel", style: .destructive) { _ in + alert.dismiss(animated: true) { + onCancel() + } + } + + alert.addAction(cancelAction) + alert.addAction(saveAction) + + return alert + } + + static func makeWrongPassPhraseAlert(onCancel: @escaping CancelCompletion, onCompletion: @escaping PassPhraseCompletion) -> UIAlertController { + let alert = UIAlertController(title: "setup_wrong_pass_phrase_retry".localized, message: nil, preferredStyle: .alert) + alert.addTextField { tf in + tf.isSecureTextEntry = true + } + + let saveAction = UIAlertAction(title: "Save", style: .default) { _ in + guard let textField = alert.textFields?.first, + let passPhrase = textField.text, + passPhrase.isNotEmpty + else { + alert.dismiss(animated: true, completion: nil) + return + } + + alert.dismiss(animated: true) { + onCompletion(passPhrase) + } + } + + let cancelAction = UIAlertAction(title: "Cancel", style: .destructive) { _ in + alert.dismiss(animated: true) { + onCancel() + } + } + + alert.addAction(cancelAction) + alert.addAction(saveAction) + + return alert + } +} diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index 0b82326a7..daa1cb2dc 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -225,7 +225,7 @@ extension MessageViewController { } private func handleMissedPassPhrase(for rawMimeData: Data) { - let alert = AppAlertFactory.makePassPhraseAlert( + let alert = AlertsFactory.makePassPhraseAlert( onCancel: { [weak self] in self?.navigationController?.popViewController(animated: true) }, @@ -237,7 +237,7 @@ extension MessageViewController { } private func handleWrongPathPhrase(for rawMimeData: Data, with phrase: String) { - let alert = AppAlertFactory.makeWrongPassPhraseAlert( + let alert = AlertsFactory.makeWrongPassPhraseAlert( onCancel: { [weak self] in self?.navigationController?.popViewController(animated: true) }, From 529d7455719a1bf86c7f82b7f837f3d7f26268c4 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Thu, 17 Jun 2021 09:46:45 +0300 Subject: [PATCH 23/26] Fix tests --- FlowCrypt/Controllers/Msg/AlertsFactory.swift | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/FlowCrypt/Controllers/Msg/AlertsFactory.swift b/FlowCrypt/Controllers/Msg/AlertsFactory.swift index 99ebd904e..bf79666e1 100644 --- a/FlowCrypt/Controllers/Msg/AlertsFactory.swift +++ b/FlowCrypt/Controllers/Msg/AlertsFactory.swift @@ -6,14 +6,21 @@ // Copyright © 2021 FlowCrypt Limited. All rights reserved. // -import Foundation +import UIKit enum AlertsFactory { typealias PassPhraseCompletion = ((String) -> Void) typealias CancelCompletion = (() -> Void) - - static func makePassPhraseAlert(onCancel: @escaping CancelCompletion, onCompletion: @escaping PassPhraseCompletion) -> UIAlertController { - let alert = UIAlertController(title: "setup_enter_pass_phrase".localized, message: nil, preferredStyle: .alert) + + static func makePassPhraseAlert( + onCancel: @escaping CancelCompletion, + onCompletion: @escaping PassPhraseCompletion + ) -> UIAlertController { + let alert = UIAlertController( + title: "setup_enter_pass_phrase".localized, + message: nil, + preferredStyle: .alert + ) alert.addTextField { tf in tf.isSecureTextEntry = true } @@ -40,12 +47,20 @@ enum AlertsFactory { alert.addAction(cancelAction) alert.addAction(saveAction) - + return alert } - - static func makeWrongPassPhraseAlert(onCancel: @escaping CancelCompletion, onCompletion: @escaping PassPhraseCompletion) -> UIAlertController { - let alert = UIAlertController(title: "setup_wrong_pass_phrase_retry".localized, message: nil, preferredStyle: .alert) + + static func makeWrongPassPhraseAlert( + onCancel: @escaping CancelCompletion, + onCompletion: @escaping PassPhraseCompletion + ) -> UIAlertController { + let alert = UIAlertController( + title: "setup_wrong_pass_phrase_retry".localized, + message: nil, + preferredStyle: .alert + ) + alert.addTextField { tf in tf.isSecureTextEntry = true } @@ -69,10 +84,10 @@ enum AlertsFactory { onCancel() } } - + alert.addAction(cancelAction) alert.addAction(saveAction) - + return alert } } From 0290da44689fc8c14f3b64e97ad2c881efc8ff3b Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Sat, 19 Jun 2021 14:21:26 +0300 Subject: [PATCH 24/26] PR fixes --- FlowCrypt.xcodeproj/project.pbxproj | 24 ++++----- .../Msg/MessageViewController.swift | 16 +++--- .../Msg/MessageViewDecorator.swift | 6 +-- .../Search/SearchViewController.swift | 4 +- .../Backups Scene/BackupViewController.swift | 2 +- .../Key List/KeySettingsViewController.swift | 2 +- .../Setup/PassPhraseStorageService.swift | 51 ------------------- ...t => SetupGenerateKeyViewController.swift} | 10 ++-- .../Setup/SetupInitialViewController.swift | 6 +-- ...nuallyEnterPassPhraseViewController.swift} | 8 +-- ...etupManuallyImportKeyViewController.swift} | 16 +++--- .../Message Provider/MessageService.swift | 24 ++++----- .../Backup Services/BackupService.swift | 6 +-- .../Backup Services/BackupServiceType.swift | 2 +- .../Key Services/PassPhraseStorage.swift | 21 +++----- .../Backup Services/BackupServiceMock.swift | 2 +- .../PassPhraseStorageTests.swift | 2 +- 17 files changed, 71 insertions(+), 131 deletions(-) delete mode 100644 FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift rename FlowCrypt/Controllers/Setup/{SetupKeyViewController.swift => SetupGenerateKeyViewController.swift} (97%) rename FlowCrypt/Controllers/Setup/{SetupEnterPassPhraseViewController.swift => SetupManuallyEnterPassPhraseViewController.swift} (96%) rename FlowCrypt/Controllers/Setup/{SetupImportKeyViewController.swift => SetupManuallyImportKeyViewController.swift} (92%) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 045ac4580..d76765431 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -80,7 +80,7 @@ 9F228BA923C67587005D2CB6 /* UserCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F228BA823C67587005D2CB6 /* UserCredentials.swift */; }; 9F228BAA23C67729005D2CB6 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAEFF16F5D91A35791730 /* DataExtensions.swift */; }; 9F23EA50237217140017DFED /* ComposeViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23EA4F237217140017DFED /* ComposeViewDecorator.swift */; }; - 9F268891237DC55600428A94 /* SetupImportKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268890237DC55600428A94 /* SetupImportKeyViewController.swift */; }; + 9F268891237DC55600428A94 /* SetupManuallyImportKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268890237DC55600428A94 /* SetupManuallyImportKeyViewController.swift */; }; 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB8B23298B3F00CF87EA /* Imap+retry.swift */; }; 9F31AB8E23298BCF00CF87EA /* Imap+folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB8D23298BCF00CF87EA /* Imap+folders.swift */; }; 9F31AB91232993F500CF87EA /* Imap+session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB90232993F500CF87EA /* Imap+session.swift */; }; @@ -139,7 +139,7 @@ 9FA0157A26565B7800CBBA05 /* KeyMethodsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */; }; 9FA0158026565B9D00CBBA05 /* KeyMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F953E08238310D500AEB98B /* KeyMethods.swift */; }; 9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA1988F253C841F008C9CF2 /* TableViewController.swift */; }; - 9FA405C7265AEBA50084D133 /* SetupKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */; }; + 9FA405C7265AEBA50084D133 /* SetupGenerateKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* SetupGenerateKeyViewController.swift */; }; 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA9C83B264C2D75005A9670 /* MessageService.swift */; }; 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */; }; 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */; }; @@ -164,7 +164,7 @@ 9FC7EBC2266EBE0100F3BF5D /* EmailProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBC1266EBE0100F3BF5D /* EmailProviderMock.swift */; }; 9FC7EBC9266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBC8266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift */; }; 9FC7EBD0266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBCF266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift */; }; - 9FD364862381EFCB00657302 /* SetupEnterPassPhraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */; }; + 9FD364862381EFCB00657302 /* SetupManuallyEnterPassPhraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD364852381EFCB00657302 /* SetupManuallyEnterPassPhraseViewController.swift */; }; 9FDF364D235A1CCD00614596 /* SignInTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF364C235A1CCD00614596 /* SignInTest.swift */; }; 9FDF3650235A1D3F00614596 /* UITestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF364F235A1D3F00614596 /* UITestHelper.swift */; }; 9FDF3652235A1EDE00614596 /* XCUIApplicationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDF3651235A1EDE00614596 /* XCUIApplicationBuilder.swift */; }; @@ -466,7 +466,7 @@ 9F228BA823C67587005D2CB6 /* UserCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCredentials.swift; sourceTree = ""; }; 9F23EA4D237216FA0017DFED /* TextViewCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewCellNode.swift; sourceTree = ""; }; 9F23EA4F237217140017DFED /* ComposeViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewDecorator.swift; sourceTree = ""; }; - 9F268890237DC55600428A94 /* SetupImportKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupImportKeyViewController.swift; sourceTree = ""; }; + 9F268890237DC55600428A94 /* SetupManuallyImportKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupManuallyImportKeyViewController.swift; sourceTree = ""; }; 9F31AB8B23298B3F00CF87EA /* Imap+retry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+retry.swift"; sourceTree = ""; }; 9F31AB8D23298BCF00CF87EA /* Imap+folders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+folders.swift"; sourceTree = ""; }; 9F31AB90232993F500CF87EA /* Imap+session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+session.swift"; sourceTree = ""; }; @@ -538,7 +538,7 @@ 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContext.swift; sourceTree = ""; }; 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyMethodsTest.swift; sourceTree = ""; }; 9FA1988F253C841F008C9CF2 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; - 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupKeyViewController.swift; sourceTree = ""; }; + 9FA405C6265AEBA40084D133 /* SetupGenerateKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupGenerateKeyViewController.swift; sourceTree = ""; }; 9FA9C83B264C2D75005A9670 /* MessageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageService.swift; sourceTree = ""; }; 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceErrorHandler.swift; sourceTree = ""; }; 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceError.swift; sourceTree = ""; }; @@ -561,7 +561,7 @@ 9FD22A19230FD781005067A6 /* NavigationBarItemsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarItemsView.swift; sourceTree = ""; }; 9FD22A1B230FE7D0005067A6 /* Then.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Then.swift; sourceTree = ""; }; 9FD22A1E230FEFC6005067A6 /* NavigationBarActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarActionButton.swift; sourceTree = ""; }; - 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupEnterPassPhraseViewController.swift; sourceTree = ""; }; + 9FD364852381EFCB00657302 /* SetupManuallyEnterPassPhraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupManuallyEnterPassPhraseViewController.swift; sourceTree = ""; }; 9FDF3637235A0B3100614596 /* InfoCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoCellNode.swift; sourceTree = ""; }; 9FDF3639235A0B3B00614596 /* HeaderNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderNode.swift; sourceTree = ""; }; 9FDF3641235A1B0100614596 /* FlowCryptUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowCryptUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1477,10 +1477,10 @@ isa = PBXGroup; children = ( 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */, - 9FA405C6265AEBA40084D133 /* SetupKeyViewController.swift */, + 9FA405C6265AEBA40084D133 /* SetupGenerateKeyViewController.swift */, C192421E1EC48B6900C3D251 /* SetupBackupsViewController.swift */, - 9F268890237DC55600428A94 /* SetupImportKeyViewController.swift */, - 9FD364852381EFCB00657302 /* SetupEnterPassPhraseViewController.swift */, + 9F268890237DC55600428A94 /* SetupManuallyImportKeyViewController.swift */, + 9FD364852381EFCB00657302 /* SetupManuallyEnterPassPhraseViewController.swift */, 9F17976C2368EEBD002BF770 /* SetupViewDecorator.swift */, 9F7920F42667CEF100DA3D80 /* PassPraseSaveable.swift */, ); @@ -2470,7 +2470,7 @@ D2FF6968243115F9007182F0 /* EmailProviderViewDecorator.swift in Sources */, 9F589F0D238C7A9B007FD759 /* LocalStorage.swift in Sources */, D2FC1C0624D82C9F003B949D /* ContactsService.swift in Sources */, - 9FD364862381EFCB00657302 /* SetupEnterPassPhraseViewController.swift in Sources */, + 9FD364862381EFCB00657302 /* SetupManuallyEnterPassPhraseViewController.swift in Sources */, D212D35D24C1AACF00035991 /* PrvKeyInfo.swift in Sources */, 9F23EA50237217140017DFED /* ComposeViewDecorator.swift in Sources */, D21574B724376852006B094F /* ConnectionType.swift in Sources */, @@ -2528,7 +2528,7 @@ 9F79229426696B9300DA3D80 /* KeyDataStorage.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewControllerContainer.swift in Sources */, D227C0E3250538100070F805 /* LocalFoldersProvider.swift in Sources */, - 9FA405C7265AEBA50084D133 /* SetupKeyViewController.swift in Sources */, + 9FA405C7265AEBA50084D133 /* SetupGenerateKeyViewController.swift in Sources */, 9F6EE1552597399D0059BA51 /* BackupProvider.swift in Sources */, 9FEED1D2230DAD1E00700F8E /* InboxViewModel.swift in Sources */, 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */, @@ -2543,7 +2543,7 @@ 9F003DB625EA92BC00EB38C0 /* LogOutHandler.swift in Sources */, 9F0C3C122316DDA500299985 /* DataService.swift in Sources */, D20D3C6E2520AB3900D4AA9A /* BackupViewDecorator.swift in Sources */, - 9F268891237DC55600428A94 /* SetupImportKeyViewController.swift in Sources */, + 9F268891237DC55600428A94 /* SetupManuallyImportKeyViewController.swift in Sources */, D2E26F7024F266F300612AF1 /* ContactDetailViewController.swift in Sources */, 9FDF3656235A22DA00614596 /* AppReset.swift in Sources */, 5A39F43F239EE7D2001F4607 /* SegmentedViewController.swift in Sources */, diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index daa1cb2dc..f1a30549c 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -55,7 +55,7 @@ final class MessageViewController: TableNodeViewController { private let messageService: MessageService private let messageOperationsProvider: MessageOperationsProvider private let trashFolderProvider: TrashFolderProviderType - private var fetchedMessage: FetchedMessage = .empty + private var processedMessage: ProcessedMessage = .empty private let passPhraseStorage: PassPhraseStorageType init( @@ -145,7 +145,7 @@ extension MessageViewController { guard let self = self else { return } let promise = self.messageService.getMessage(with: input.objMessage, folder: input.path) let message = try awaitPromise(promise) - self.fetchedMessage = message + self.processedMessage = message } .then(on: .main) { [weak self] in self?.handleReceivedMessage() @@ -160,7 +160,7 @@ extension MessageViewController { messageService.validateMessage(rawMimeData: rawMimeData, with: passPhrase) .then(on: .main) { [weak self] message in - self?.fetchedMessage = message + self?.processedMessage = message self?.handleReceivedMessage() } .catch(on: .main) { [weak self] error in @@ -348,9 +348,9 @@ extension MessageViewController { let replyInfo = ComposeViewController.Input.ReplyInfo( recipient: input.objMessage.sender, subject: input.objMessage.subject, - mime: fetchedMessage.rawMimeData, + mime: processedMessage.rawMimeData, sentDate: input.objMessage.date, - message: fetchedMessage.text + message: processedMessage.text ) let composeInput = ComposeViewController.Input(type: .reply(replyInfo)) @@ -386,7 +386,7 @@ extension MessageViewController: ASTableDelegate, ASTableDataSource { case .main: return Parts.allCases.count case .attributes: - return fetchedMessage.attachments.count + return processedMessage.attachments.count } } @@ -424,7 +424,7 @@ extension MessageViewController: ASTableDelegate, ASTableDataSource { case .subject: return MessageSubjectNode(subject, time: time) case .text: - let messageInput = self.decorator.attributedMessage(from: self.fetchedMessage) + let messageInput = self.decorator.attributedMessage(from: self.processedMessage) return MessageTextSubjectNode(messageInput) } } @@ -432,7 +432,7 @@ extension MessageViewController: ASTableDelegate, ASTableDataSource { private func attachmentNode(for index: Int) -> ASCellNode { AttachmentNode( input: .init( - msgAttachment: fetchedMessage.attachments[index] + msgAttachment: processedMessage.attachments[index] ) ) } diff --git a/FlowCrypt/Controllers/Msg/MessageViewDecorator.swift b/FlowCrypt/Controllers/Msg/MessageViewDecorator.swift index b03d5f6ce..82357ab9a 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewDecorator.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewDecorator.swift @@ -31,9 +31,9 @@ struct MessageViewDecorator { (text ?? "").attributed(.regular(17), color: color) } - func attributedMessage(from fetchedMessage: FetchedMessage) -> NSAttributedString { + func attributedMessage(from processedMessage: ProcessedMessage) -> NSAttributedString { let textColor: UIColor - switch fetchedMessage.messageType { + switch processedMessage.messageType { case .encrypted: textColor = .main case .error: @@ -41,7 +41,7 @@ struct MessageViewDecorator { case .plain: textColor = .mainTextColor } - return fetchedMessage.text.attributed(color: textColor) + return processedMessage.text.attributed(color: textColor) } } diff --git a/FlowCrypt/Controllers/Search/SearchViewController.swift b/FlowCrypt/Controllers/Search/SearchViewController.swift index 798481e47..c1d2d0dad 100644 --- a/FlowCrypt/Controllers/Search/SearchViewController.swift +++ b/FlowCrypt/Controllers/Search/SearchViewController.swift @@ -279,11 +279,11 @@ extension SearchViewController: UISearchResultsUpdating { self?.handleError(with: error) } .then(on: .main) { [weak self] messages in - self?.handleFetchedMessages(with: messages) + self?.handleProcessedMessage(with: messages) } } - private func handleFetchedMessages(with messages: [Message]) { + private func handleProcessedMessage(with messages: [Message]) { if messages.isEmpty { state = .empty } else { diff --git a/FlowCrypt/Controllers/Settings/Backup/Backups Scene/BackupViewController.swift b/FlowCrypt/Controllers/Settings/Backup/Backups Scene/BackupViewController.swift index 5c3278ea3..11d7ae80b 100644 --- a/FlowCrypt/Controllers/Settings/Backup/Backups Scene/BackupViewController.swift +++ b/FlowCrypt/Controllers/Settings/Backup/Backups Scene/BackupViewController.swift @@ -75,7 +75,7 @@ extension BackupViewController { } private func fetchBackups() { - backupProvider.fetchBackups(for: userId) + backupProvider.fetchBackupsFromInbox(for: userId) .then { [weak self] keys in self?.state = keys.isEmpty ? .noBackups diff --git a/FlowCrypt/Controllers/Settings/KeySettings/Key List/KeySettingsViewController.swift b/FlowCrypt/Controllers/Settings/KeySettings/Key List/KeySettingsViewController.swift index aba6ee78f..f8277d2b9 100644 --- a/FlowCrypt/Controllers/Settings/KeySettings/Key List/KeySettingsViewController.swift +++ b/FlowCrypt/Controllers/Settings/KeySettings/Key List/KeySettingsViewController.swift @@ -63,7 +63,7 @@ extension KeySettingsViewController { extension KeySettingsViewController { @objc private func handleAddButtonTap() { - navigationController?.pushViewController(SetupImportKeyViewController(), animated: true) + navigationController?.pushViewController(SetupManuallyImportKeyViewController(), animated: true) } } diff --git a/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift b/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift deleted file mode 100644 index 5c66ade0d..000000000 --- a/FlowCrypt/Controllers/Setup/PassPhraseStorageService.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// PassPhraseStorageService.swift -// FlowCrypt -// -// Created by Anton Kharchevskyi on 02.06.2021. -// Copyright © 2021 FlowCrypt Limited. All rights reserved. -// - -import UIKit - -final class PassPhraseStorageService { - struct Context { - let passPhrase: String - let keys: [KeyDetails] - let source: KeySource - let isLocally: Bool - } - - let storage: KeyStorageType - - init(storage: KeyStorageType = EncryptedStorage()) { - self.storage = storage - } - - func savePassPhrase(with context: Context) { - if context.isLocally { - storage.addKeys(keyDetails: context.keys, passPhrase: context.passPhrase, source: context.source) - } else { - // TODO: - ANTON - } - } -} - -//import RealmSwift -//class KeyStorageMock: KeyStorageType { -// func addKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { -// } -// -// func updateKeys(keyDetails: [KeyDetails], passPhrase: String, source: KeySource) { -// } -// -// var publicKeyResult: () -> (String?) = { nil } -// func publicKey() -> String? { -// publicKeyResult() -// } -// -// var keysResult: () -> ([KeyInfo]) = { [] } -// func keys() -> [KeyInfo] { -// keysResult() -// } -//} diff --git a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift similarity index 97% rename from FlowCrypt/Controllers/Setup/SetupKeyViewController.swift rename to FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift index 93abfa6bb..ce6fc10ec 100644 --- a/FlowCrypt/Controllers/Setup/SetupKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift @@ -22,7 +22,7 @@ enum CreateKeyError: Error { case conformingPassPhraseError } -final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable { +final class SetupGenerateKeyViewController: TableNodeViewController, PassPhraseSaveable { enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, saveLocally, saveInMemory, action, subtitle } @@ -93,7 +93,7 @@ final class SetupKeyViewController: TableNodeViewController, PassPhraseSaveable // MARK: - UI -extension SetupKeyViewController { +extension SetupGenerateKeyViewController { private func setupUI() { node.delegate = self node.dataSource = self @@ -132,7 +132,7 @@ extension SetupKeyViewController { // MARK: - Setup -extension SetupKeyViewController { +extension SetupGenerateKeyViewController { private func setupAccountWithGeneratedKey(with passPhrase: String) { Promise { [weak self] in guard let self = self else { return } @@ -242,7 +242,7 @@ extension SetupKeyViewController { } } -extension SetupKeyViewController { +extension SetupGenerateKeyViewController { private func moveToMainFlow() { router.proceed() } @@ -263,7 +263,7 @@ extension SetupKeyViewController { // MARK: - ASTableDelegate, ASTableDataSource -extension SetupKeyViewController: ASTableDelegate, ASTableDataSource { +extension SetupGenerateKeyViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { parts.count } diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 7eb5e3012..ceea78488 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -97,7 +97,7 @@ extension SetupInitialViewController { private func searchBackups() { logger.logInfo("Searching for backups in inbox") - backupService.fetchBackups(for: user) + backupService.fetchBackupsFromInbox(for: user) .then(on: .main) { [weak self] keys in self?.proceedToSetupWith(keys: keys) } @@ -239,12 +239,12 @@ extension SetupInitialViewController { // MARK: - Navigation extension SetupInitialViewController { private func proceedToKeyImport() { - let viewController = SetupImportKeyViewController() + let viewController = SetupManuallyImportKeyViewController() navigationController?.pushViewController(viewController, animated: true) } private func proceedToCreatingNewKey() { - let viewController = SetupKeyViewController(user: user) + let viewController = SetupGenerateKeyViewController(user: user) navigationController?.pushViewController(viewController, animated: true) } diff --git a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift similarity index 96% rename from FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift rename to FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift index 7b2ce69ae..9f77d5553 100644 --- a/FlowCrypt/Controllers/Setup/SetupEnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift @@ -9,7 +9,7 @@ import AsyncDisplayKit import FlowCryptUI -final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhraseSaveable { +final class SetupManuallyEnterPassPhraseViewController: TableNodeViewController, PassPhraseSaveable { private enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, saveLocally, saveInMemory, enterPhrase, chooseAnother @@ -101,7 +101,7 @@ final class SetupEnterPassPhraseViewController: TableNodeViewController, PassPhr // MARK: - Keyboard -extension SetupEnterPassPhraseViewController { +extension SetupManuallyEnterPassPhraseViewController { // swiftlint:disable discarded_notification_center_observer /// Observation should be removed in a place where subscription is private func observeKeyboardNotifications() { @@ -131,7 +131,7 @@ extension SetupEnterPassPhraseViewController { // MARK: - ASTableDelegate, ASTableDataSource -extension SetupEnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { +extension SetupManuallyEnterPassPhraseViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { Parts.allCases.count } @@ -206,7 +206,7 @@ extension SetupEnterPassPhraseViewController: ASTableDelegate, ASTableDataSource // MARK: - Actions -extension SetupEnterPassPhraseViewController { +extension SetupManuallyEnterPassPhraseViewController { private func handleContinueAction() { view.endEditing(true) guard let passPhrase = passPhrase else { return } diff --git a/FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupManuallyImportKeyViewController.swift similarity index 92% rename from FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift rename to FlowCrypt/Controllers/Setup/SetupManuallyImportKeyViewController.swift index 7e5c829cf..7765d1100 100644 --- a/FlowCrypt/Controllers/Setup/SetupImportKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupManuallyImportKeyViewController.swift @@ -10,7 +10,7 @@ import AsyncDisplayKit import FlowCryptUI import MobileCoreServices -final class SetupImportKeyViewController: TableNodeViewController { +final class SetupManuallyImportKeyViewController: TableNodeViewController { private enum Parts: Int, CaseIterable { case title, description, fileImport, pasteBoardImport @@ -77,7 +77,7 @@ final class SetupImportKeyViewController: TableNodeViewController { // MARK: - ASTableDelegate, ASTableDataSource -extension SetupImportKeyViewController: ASTableDelegate, ASTableDataSource { +extension SetupManuallyImportKeyViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { Parts.allCases.count } @@ -128,7 +128,7 @@ extension SetupImportKeyViewController: ASTableDelegate, ASTableDataSource { // MARK: - Actions -extension SetupImportKeyViewController { +extension SetupManuallyImportKeyViewController { private func proceedToKeyImportFromFile() { let acceptableDocumentTypes = [ String(kUTTypeText), @@ -152,10 +152,10 @@ extension SetupImportKeyViewController { private func proceedToKeyImportFromPasteboard() { guard let armoredKey = pasteboard.string else { return } - parseFetched(data: Data(armoredKey.utf8)) + parseUserProvided(data: Data(armoredKey.utf8)) } - private func parseFetched(data keyData: Data) { + private func parseUserProvided(data keyData: Data) { do { let keys = try core.parseKeys(armoredOrBinary: keyData) let privateKey = keys.keyDetails.filter { $0.private != nil } @@ -173,7 +173,7 @@ extension SetupImportKeyViewController { } private func proceedToPassPhrase(with email: String, keys: [KeyDetails]) { - let viewController = SetupEnterPassPhraseViewController( + let viewController = SetupManuallyEnterPassPhraseViewController( decorator: decorator, email: email, fetchedKeys: keys @@ -188,7 +188,7 @@ extension SetupImportKeyViewController { // MARK: - UIDocumentPickerDelegate -extension SetupImportKeyViewController: UIDocumentPickerDelegate { +extension SetupManuallyImportKeyViewController: UIDocumentPickerDelegate { func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let pickedURL = urls.first else { return } handlePicked(document: pickedURL) @@ -206,7 +206,7 @@ extension SetupImportKeyViewController: UIDocumentPickerDelegate { document.open { [weak self] success in guard success else { assertionFailure("Failed to open doc"); return } guard let metadata = document.data else { assertionFailure("Failed to fetch data"); return } - self?.parseFetched(data: metadata) + self?.parseUserProvided(data: metadata) } } } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index 084db0edf..8f2b43937 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -15,8 +15,8 @@ struct MessageAttachment { let size: Int } -// MARK: - FetchedMessage -struct FetchedMessage { +// MARK: - ProcessedMessage +struct ProcessedMessage { enum MessageType { case error, encrypted, plain } @@ -27,9 +27,9 @@ struct FetchedMessage { let messageType: MessageType } -extension FetchedMessage { +extension ProcessedMessage { // TODO: - Ticket - fix with empty state for MessageViewController - static let empty = FetchedMessage( + static let empty = ProcessedMessage( rawMimeData: Data(), text: "loading_title".localized + "...", attachments: [], @@ -66,8 +66,8 @@ final class MessageService { self.passPhraseStorage = passPhraseStorage } - func validateMessage(rawMimeData: Data, with passPhrase: String) -> Promise { - Promise { [weak self] resolve, reject in + func validateMessage(rawMimeData: Data, with passPhrase: String) -> Promise { + Promise { [weak self] resolve, reject in guard let self = self else { return } guard let keys = try? self.keyService.getPrivateKeys(with: passPhrase).get(), keys.isNotEmpty else { @@ -95,19 +95,19 @@ final class MessageService { .filter(\.isAttachmentBlock) .map(MessageAttachment.init) - let fetchedMessage = FetchedMessage( + let processedMessage = ProcessedMessage( rawMimeData: rawMimeData, text: decrypted.text, attachments: attachments, messageType: decrypted.replyType == CoreRes.ReplyType.encrypted ? .encrypted : .plain ) - resolve(fetchedMessage) + resolve(processedMessage) } } } - func getMessage(with input: Message, folder: String) -> Promise { + func getMessage(with input: Message, folder: String) -> Promise { Promise { [weak self] resolve, reject in guard let self = self else { return } @@ -138,7 +138,7 @@ final class MessageService { .filter(\.isAttachmentBlock) .map(MessageAttachment.init) - let messageType: FetchedMessage.MessageType + let messageType: ProcessedMessage.MessageType let text: String if let decryptErrBlock = decryptErrBlocks.first { @@ -151,14 +151,14 @@ final class MessageService { messageType = decrypted.replyType == CoreRes.ReplyType.encrypted ? .encrypted : .plain } - let fetchedMessage = FetchedMessage( + let processedMessage = ProcessedMessage( rawMimeData: rawMimeData, text: text, attachments: attachments, messageType: messageType ) - resolve(fetchedMessage) + resolve(processedMessage) } } } diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift index 89457ce4d..55d78a1f5 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift @@ -27,7 +27,7 @@ final class BackupService { // MARK: - BackupServiceType extension BackupService: BackupServiceType { - func fetchBackups(for userId: UserId) -> Promise<[KeyDetails]> { + func fetchBackupsFromInbox(for userId: UserId) -> Promise<[KeyDetails]> { Promise<[KeyDetails]> { [weak self] resolve, reject in guard let self = self else { throw AppErr.nilSelf } @@ -59,7 +59,7 @@ extension BackupService: BackupServiceType { let privateKeyData = privateKeyContext.data().base64EncodedString() - let filename = "flowcrypt-backup-\(userId.email.userReadableEmail).key" + let filename = "flowcrypt-backup-\(userId.email.withoutSpecialCharacters).key" let attachments = [SendableMsg.Attachment(name: filename, type: "text/plain", base64: privateKeyData)] let message = SendableMsg( text: "setup_backup_email".localized, @@ -88,7 +88,7 @@ extension BackupService: BackupServiceType { // MARK: - Helpers private extension String { - var userReadableEmail: String { + var withoutSpecialCharacters: String { self.replacingOccurrences( of: "[^a-z0-9]", with: "", diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift index ec176c0fb..d46a7519f 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift @@ -11,7 +11,7 @@ import Promises protocol BackupServiceType { /// get all existed backups - func fetchBackups(for userId: UserId) -> Promise<[KeyDetails]> + func fetchBackupsFromInbox(for userId: UserId) -> Promise<[KeyDetails]> /// backup keys to user inbox func backupToInbox(keys: [KeyDetails], for userId: UserId) -> Promise /// show activity sheet to save keys as file diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift index cbb0e24bc..60b024311 100644 --- a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift @@ -24,23 +24,19 @@ final class PassPhraseStorage: PassPhraseStorageType { let currentUserEmail: String? let storage: EncryptedPassPhraseStorage let localStorage: LocalPassPhraseStorageType - let timeoutContext: (component: Calendar.Component, timeout: Int) - - /// used for tests only, otherwise seconds will be used - let isHours: Bool + let timeoutInSeconds: Int init( storage: EncryptedPassPhraseStorage, localStorage: LocalPassPhraseStorageType = LocalPassPhraseStorage.shared, - timeoutContext: (Calendar.Component, Int) = (.hour, 4), + timeoutInSeconds: Int = 4*60*60, // 4 hours emailProvider: EmailProviderType, isHours: Bool = true ) { self.storage = storage self.localStorage = localStorage - self.timeoutContext = timeoutContext + self.timeoutInSeconds = timeoutInSeconds self.currentUserEmail = emailProvider.email - self.isHours = isHours } func savePassPhrase(with passPhrase: PassPhrase, inStorage: Bool) { @@ -84,19 +80,14 @@ final class PassPhraseStorage: PassPhraseStorageType { localStorage.passPhrases .forEach { localPassPhrases in let components = calendar.dateComponents( - [timeoutContext.component], + [.second], from: localPassPhrases.date, to: Date() ) - let timePassed: Int - if self.isHours { - timePassed = components.hour ?? 0 - } else { - timePassed = components.second ?? 0 - } + let timePassed = components.second ?? 0 - let isPassPhraseValid = timePassed < timeoutContext.timeout + let isPassPhraseValid = timePassed < timeoutInSeconds if isPassPhraseValid { validPassPhrases.append(localPassPhrases.passPhrase) diff --git a/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift b/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift index 20c27fa39..b681506ec 100644 --- a/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift +++ b/FlowCryptTests/Functionallity/Services/Backup Services/BackupServiceMock.swift @@ -11,7 +11,7 @@ import Promises final class BackupServiceMock: BackupServiceType { var fetchBackupsResult: Result<[KeyDetails], Error> = .success([]) - func fetchBackups(for userId: UserId) -> Promise<[KeyDetails]> { + func fetchBackupsFromInbox(for userId: UserId) -> Promise<[KeyDetails]> { Promise<[KeyDetails]>.resolveAfter(with: fetchBackupsResult) } diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift index 7d0ef385d..f0ec8ff9f 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift @@ -23,7 +23,7 @@ class PassPhraseStorageTests: XCTestCase { sut = PassPhraseStorage( storage: storage, localStorage: localStorage, - timeoutContext: (Calendar.Component.second, 2), + timeoutInSeconds: 2, emailProvider: emailProvider, isHours: false ) From 2fdda2f0064105dd107ce0ab6f1739b9f198a71f Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Sat, 19 Jun 2021 14:41:45 +0300 Subject: [PATCH 25/26] Change wrong pass phrase error handling --- FlowCrypt/Common UI/CommonNodesInputs.swift | 2 +- FlowCrypt/Core/CoreTypes.swift | 6 -- .../Message Provider/MessageService.swift | 80 +++++++++---------- 3 files changed, 40 insertions(+), 48 deletions(-) diff --git a/FlowCrypt/Common UI/CommonNodesInputs.swift b/FlowCrypt/Common UI/CommonNodesInputs.swift index 3a0360162..10f06baca 100644 --- a/FlowCrypt/Common UI/CommonNodesInputs.swift +++ b/FlowCrypt/Common UI/CommonNodesInputs.swift @@ -35,7 +35,7 @@ extension ButtonCellNode.Input { .attributed( .regular(15), color: UIColor.colorFor( - darkStyle: .black, + darkStyle: .white, lightStyle: .blueColor ), alignment: .center diff --git a/FlowCrypt/Core/CoreTypes.swift b/FlowCrypt/Core/CoreTypes.swift index 26c0fa0e2..20d12e14e 100644 --- a/FlowCrypt/Core/CoreTypes.swift +++ b/FlowCrypt/Core/CoreTypes.swift @@ -193,9 +193,3 @@ extension MsgBlock { type == .plainAtt || type == .encryptedAtt || type == .decryptedAtt } } - -extension Array where Element == MsgBlock { - var isAnyError: Bool { - self.first(where: { $0.decryptErr != nil }) != nil - } -} diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index 8f2b43937..9e97fd622 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -82,25 +82,21 @@ final class MessageService { isEmail: true ) - let isDecryptError = decrypted.blocks.isAnyError - - if isDecryptError { + let isWrongPassPhraseError = decrypted.blocks.first(where: { (block) -> Bool in + guard let errorBlock = block.decryptErr, case .needPassphrase = errorBlock.error.type else { + return false + } + return true + }) + + if isWrongPassPhraseError != nil { reject(MessageServiceError.wrongPassPhrase(rawMimeData, passPhrase)) } else { keys .map { PassPhrase(value: passPhrase, longid: $0.longid) } .forEach { self.passPhraseStorage.savePassPhrase(with: $0, inStorage: false) } - let attachments = decrypted.blocks - .filter(\.isAttachmentBlock) - .map(MessageAttachment.init) - - let processedMessage = ProcessedMessage( - rawMimeData: rawMimeData, - text: decrypted.text, - attachments: attachments, - messageType: decrypted.replyType == CoreRes.ReplyType.encrypted ? .encrypted : .plain - ) + let processedMessage = self.processMessage(rawMimeData: rawMimeData, with: decrypted) resolve(processedMessage) } @@ -123,7 +119,6 @@ final class MessageService { reject(CoreError.notReady("Could not fetch keys")) return } - let decrypted = try self.core.parseDecryptMsg( encrypted: rawMimeData, keys: keys, @@ -131,36 +126,39 @@ final class MessageService { isEmail: true ) - let decryptErrBlocks = decrypted.blocks - .filter { $0.decryptErr != nil } - - let attachments = decrypted.blocks - .filter(\.isAttachmentBlock) - .map(MessageAttachment.init) - - let messageType: ProcessedMessage.MessageType - let text: String - - if let decryptErrBlock = decryptErrBlocks.first { - let rawMsg = decryptErrBlock.content - let err = decryptErrBlock.decryptErr?.error - text = "Could not decrypt:\n\(err?.type.rawValue ?? "UNKNOWN"): \(err?.message ?? "??")\n\n\n\(rawMsg)" - messageType = .error - } else { - text = decrypted.text - messageType = decrypted.replyType == CoreRes.ReplyType.encrypted ? .encrypted : .plain - } - - let processedMessage = ProcessedMessage( - rawMimeData: rawMimeData, - text: text, - attachments: attachments, - messageType: messageType - ) - + let processedMessage = self.processMessage(rawMimeData: rawMimeData, with: decrypted) resolve(processedMessage) } } + + private func processMessage(rawMimeData: Data, with decrypted: CoreRes.ParseDecryptMsg) -> ProcessedMessage { + let decryptErrBlocks = decrypted.blocks + .filter { $0.decryptErr != nil } + + let attachments = decrypted.blocks + .filter(\.isAttachmentBlock) + .map(MessageAttachment.init) + + let messageType: ProcessedMessage.MessageType + let text: String + + if let decryptErrBlock = decryptErrBlocks.first { + let rawMsg = decryptErrBlock.content + let err = decryptErrBlock.decryptErr?.error + text = "Could not decrypt:\n\(err?.type.rawValue ?? "UNKNOWN"): \(err?.message ?? "??")\n\n\n\(rawMsg)" + messageType = .error + } else { + text = decrypted.text + messageType = decrypted.replyType == CoreRes.ReplyType.encrypted ? .encrypted : .plain + } + + return ProcessedMessage( + rawMimeData: rawMimeData, + text: text, + attachments: attachments, + messageType: messageType + ) + } } private extension MessageAttachment { From a45339f89afc965a30f00a7a560a0d1a3d1d0902 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Sat, 19 Jun 2021 16:21:09 +0300 Subject: [PATCH 26/26] Rename according to PR comments --- FlowCrypt.xcodeproj/project.pbxproj | 12 ++--- .../InMemoryPassPhraseStorage.swift | 45 +++++++++++++++++++ .../Key Services/LocalPassPhraseStorage.swift | 45 ------------------- .../Key Services/PassPhraseStorage.swift | 34 +++++++------- .../LocalPassPhraseStorageMock.swift | 8 ++-- .../PassPhraseStorageTests.swift | 6 +-- 6 files changed, 75 insertions(+), 75 deletions(-) create mode 100644 FlowCrypt/Functionality/Services/Key Services/InMemoryPassPhraseStorage.swift delete mode 100644 FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index d76765431..26b3e8600 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -159,8 +159,8 @@ 9FC7EB76266EB67B00F3BF5D /* EncryptedStorageProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EB75266EB67B00F3BF5D /* EncryptedStorageProtocols.swift */; }; 9FC7EB7C266EB67D00F3BF5D /* EncryptedStorageProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EB75266EB67B00F3BF5D /* EncryptedStorageProtocols.swift */; }; 9FC7EBA3266EB95300F3BF5D /* PassPhraseStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBA2266EB95300F3BF5D /* PassPhraseStorageTests.swift */; }; - 9FC7EBAA266EBD3700F3BF5D /* LocalPassPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBA9266EBD3700F3BF5D /* LocalPassPhraseStorage.swift */; }; - 9FC7EBB0266EBD4600F3BF5D /* LocalPassPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBA9266EBD3700F3BF5D /* LocalPassPhraseStorage.swift */; }; + 9FC7EBAA266EBD3700F3BF5D /* InMemoryPassPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBA9266EBD3700F3BF5D /* InMemoryPassPhraseStorage.swift */; }; + 9FC7EBB0266EBD4600F3BF5D /* InMemoryPassPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBA9266EBD3700F3BF5D /* InMemoryPassPhraseStorage.swift */; }; 9FC7EBC2266EBE0100F3BF5D /* EmailProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBC1266EBE0100F3BF5D /* EmailProviderMock.swift */; }; 9FC7EBC9266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBC8266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift */; }; 9FC7EBD0266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBCF266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift */; }; @@ -554,7 +554,7 @@ 9FC7EAB2266A404D00F3BF5D /* PassPhrase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhrase.swift; sourceTree = ""; }; 9FC7EB75266EB67B00F3BF5D /* EncryptedStorageProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedStorageProtocols.swift; sourceTree = ""; }; 9FC7EBA2266EB95300F3BF5D /* PassPhraseStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhraseStorageTests.swift; sourceTree = ""; }; - 9FC7EBA9266EBD3700F3BF5D /* LocalPassPhraseStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPassPhraseStorage.swift; sourceTree = ""; }; + 9FC7EBA9266EBD3700F3BF5D /* InMemoryPassPhraseStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemoryPassPhraseStorage.swift; sourceTree = ""; }; 9FC7EBC1266EBE0100F3BF5D /* EmailProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailProviderMock.swift; sourceTree = ""; }; 9FC7EBC8266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPassPhraseStorageMock.swift; sourceTree = ""; }; 9FC7EBCF266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedPassPhraseStorageMock.swift; sourceTree = ""; }; @@ -1198,7 +1198,7 @@ children = ( 9F79229326696B9300DA3D80 /* KeyDataStorage.swift */, 9F79228726696B0200DA3D80 /* PassPhraseStorage.swift */, - 9FC7EBA9266EBD3700F3BF5D /* LocalPassPhraseStorage.swift */, + 9FC7EBA9266EBD3700F3BF5D /* InMemoryPassPhraseStorage.swift */, D2891AC124C59EFA008918E3 /* KeyService.swift */, ); path = "Key Services"; @@ -2359,7 +2359,7 @@ 9F1D5769263B540100477938 /* Logger.swift in Sources */, 9FC7EBC9266EBE0F00F3BF5D /* LocalPassPhraseStorageMock.swift in Sources */, A3DAD5FE22E4574B00F2C4CD /* FlowCryptCoreTests.swift in Sources */, - 9FC7EBB0266EBD4600F3BF5D /* LocalPassPhraseStorage.swift in Sources */, + 9FC7EBB0266EBD4600F3BF5D /* InMemoryPassPhraseStorage.swift in Sources */, 21EA3B2326565B5D00691848 /* DomainRulesTests.swift in Sources */, 9F4164102665754A00106194 /* PromiseExtensions.swift in Sources */, 21F836CC2652A38700B2448C /* ZBase32EncodingTests.swift in Sources */, @@ -2512,7 +2512,7 @@ 9F0C3C102316DD5B00299985 /* GoogleUserService.swift in Sources */, D24F4C2223E2359B00C5EEE4 /* BootstrapViewController.swift in Sources */, D211CE7623FC36BC00D1CE38 /* UIColorExtension.swift in Sources */, - 9FC7EBAA266EBD3700F3BF5D /* LocalPassPhraseStorage.swift in Sources */, + 9FC7EBAA266EBD3700F3BF5D /* InMemoryPassPhraseStorage.swift in Sources */, 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */, D2FF6966243115EC007182F0 /* EmailProviderViewController.swift in Sources */, D2CDC3D22402D4DA002B045F /* UIViewControllerExtensions.swift in Sources */, diff --git a/FlowCrypt/Functionality/Services/Key Services/InMemoryPassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/InMemoryPassPhraseStorage.swift new file mode 100644 index 000000000..a18ef8291 --- /dev/null +++ b/FlowCrypt/Functionality/Services/Key Services/InMemoryPassPhraseStorage.swift @@ -0,0 +1,45 @@ +// +// LocalPassPhraseStorage.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 07.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import UIKit + +protocol InMemoryPassPhraseStorageType { + var passPhrases: Set { get } + func save(passPhrase: InMemoryPassPhrase) + func removePassPhrases(with objects: [InMemoryPassPhrase]) +} + +struct InMemoryPassPhrase: Codable, Hashable, Equatable { + let passPhrase: PassPhrase + let date: Date + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.passPhrase.longid == rhs.passPhrase.longid + } +} + +final class InMemoryPassPhraseStorage: InMemoryPassPhraseStorageType { + static let shared: InMemoryPassPhraseStorage = InMemoryPassPhraseStorage() + + private(set) var passPhrases: Set = [] + + private init() { + } + + func save(passPhrase: InMemoryPassPhrase) { + passPhrases.insert(passPhrase) + } + + func removePassPhrases(with objects: [InMemoryPassPhrase]) { + objects.forEach { + if passPhrases.contains($0) { + passPhrases.remove($0) + } + } + } +} diff --git a/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift deleted file mode 100644 index e1f97a77d..000000000 --- a/FlowCrypt/Functionality/Services/Key Services/LocalPassPhraseStorage.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// LocalPassPhraseStorage.swift -// FlowCrypt -// -// Created by Anton Kharchevskyi on 07.06.2021. -// Copyright © 2021 FlowCrypt Limited. All rights reserved. -// - -import UIKit - -protocol LocalPassPhraseStorageType { - var passPhrases: Set { get } - func save(passPhrase: LocalPassPhrase) - func removePassPhrases(with objects: [LocalPassPhrase]) -} - -struct LocalPassPhrase: Codable, Hashable, Equatable { - let passPhrase: PassPhrase - let date: Date - - static func == (lhs: Self, rhs: Self) -> Bool { - lhs.passPhrase.longid == rhs.passPhrase.longid - } -} - -final class LocalPassPhraseStorage: LocalPassPhraseStorageType { - static let shared: LocalPassPhraseStorage = LocalPassPhraseStorage() - - private(set) var passPhrases: Set = [] - - private init() { - } - - func save(passPhrase: LocalPassPhrase) { - passPhrases.insert(passPhrase) - } - - func removePassPhrases(with objects: [LocalPassPhrase]) { - objects.forEach { - if passPhrases.contains($0) { - passPhrases.remove($0) - } - } - } -} diff --git a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift index 60b024311..498e75a6a 100644 --- a/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/PassPhraseStorage.swift @@ -22,19 +22,19 @@ final class PassPhraseStorage: PassPhraseStorageType { private lazy var logger = Logger.nested(Self.self) let currentUserEmail: String? - let storage: EncryptedPassPhraseStorage - let localStorage: LocalPassPhraseStorageType + let encryptedStorage: EncryptedPassPhraseStorage + let inMemoryStorage: InMemoryPassPhraseStorageType let timeoutInSeconds: Int init( storage: EncryptedPassPhraseStorage, - localStorage: LocalPassPhraseStorageType = LocalPassPhraseStorage.shared, + localStorage: InMemoryPassPhraseStorageType = InMemoryPassPhraseStorage.shared, timeoutInSeconds: Int = 4*60*60, // 4 hours emailProvider: EmailProviderType, isHours: Bool = true ) { - self.storage = storage - self.localStorage = localStorage + self.encryptedStorage = storage + self.inMemoryStorage = localStorage self.timeoutInSeconds = timeoutInSeconds self.currentUserEmail = emailProvider.email } @@ -42,32 +42,32 @@ final class PassPhraseStorage: PassPhraseStorageType { func savePassPhrase(with passPhrase: PassPhrase, inStorage: Bool) { if inStorage { logger.logInfo("Save to storage \(passPhrase.longid)") - storage.addPassPhrase(object: PassPhraseObject(passPhrase)) + encryptedStorage.addPassPhrase(object: PassPhraseObject(passPhrase)) } else { logger.logInfo("Save locally \(passPhrase.longid)") - let locallPassPhrase = LocalPassPhrase(passPhrase: passPhrase, date: Date()) - localStorage.save(passPhrase: locallPassPhrase) + let inMemoryPassPhrase = InMemoryPassPhrase(passPhrase: passPhrase, date: Date()) + inMemoryStorage.save(passPhrase: inMemoryPassPhrase) - let alreadySaved = storage.getPassPhrases() + let alreadySaved = encryptedStorage.getPassPhrases() if alreadySaved.contains(where: { $0.longid == passPhrase.longid }) { - storage.removePassPhrase(object: PassPhraseObject(passPhrase)) + encryptedStorage.removePassPhrase(object: PassPhraseObject(passPhrase)) } } } func updatePassPhrase(with passPhrase: PassPhrase, inStorage: Bool) { if inStorage { - storage.updatePassPhrase(object: PassPhraseObject(passPhrase)) + encryptedStorage.updatePassPhrase(object: PassPhraseObject(passPhrase)) } else { - let updated = LocalPassPhrase(passPhrase: passPhrase, date: Date()) - localStorage.save(passPhrase: updated) + let updated = InMemoryPassPhrase(passPhrase: passPhrase, date: Date()) + inMemoryStorage.save(passPhrase: updated) } } func getPassPhrases() -> [PassPhrase] { - let dbPassPhrases = storage.getPassPhrases() + let dbPassPhrases = encryptedStorage.getPassPhrases() .map(PassPhrase.init) logger.logInfo("dbPassPhrases \(dbPassPhrases.count)") @@ -75,9 +75,9 @@ final class PassPhraseStorage: PassPhraseStorageType { let calendar = Calendar.current var validPassPhrases: [PassPhrase] = [] - var invalidPassPhrases: [LocalPassPhrase] = [] + var invalidPassPhrases: [InMemoryPassPhrase] = [] - localStorage.passPhrases + inMemoryStorage.passPhrases .forEach { localPassPhrases in let components = calendar.dateComponents( [.second], @@ -99,7 +99,7 @@ final class PassPhraseStorage: PassPhraseStorageType { self.logger.logInfo(message) } - localStorage.removePassPhrases(with: invalidPassPhrases) + inMemoryStorage.removePassPhrases(with: invalidPassPhrases) logger.logInfo("validPassPhrases \(validPassPhrases.count)") return dbPassPhrases + validPassPhrases diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift index 1bfd9b3d2..9910c6417 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/LocalPassPhraseStorageMock.swift @@ -8,18 +8,18 @@ import Foundation -class LocalPassPhraseStorageMock: LocalPassPhraseStorageType { - var passPhrases: Set = [] +class LocalPassPhraseStorageMock: InMemoryPassPhraseStorageType { + var passPhrases: Set = [] var isSaveCalled = false - func save(passPhrase: LocalPassPhrase) { + func save(passPhrase: InMemoryPassPhrase) { isSaveCalled = true passPhrases.insert(passPhrase) print("^^ \(passPhrases)") } - func removePassPhrases(with objects: [LocalPassPhrase]) { + func removePassPhrases(with objects: [InMemoryPassPhrase]) { objects.forEach { passPhrases.remove($0) } diff --git a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift index f0ec8ff9f..f707bbd8d 100644 --- a/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift +++ b/FlowCryptTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift @@ -71,7 +71,7 @@ class PassPhraseStorageTests: XCTestCase { storage.getPassPhrasesResult = { [] } let savedDate = Date() - let localPassPhrase = LocalPassPhrase( + let localPassPhrase = InMemoryPassPhrase( passPhrase: PassPhrase( value: "value", longid: "longid"), @@ -90,7 +90,7 @@ class PassPhraseStorageTests: XCTestCase { storage.getPassPhrasesResult = { [] } let savedDate = Date() - let localPassPhrase = LocalPassPhrase( + let localPassPhrase = InMemoryPassPhrase( passPhrase: PassPhrase( value: "value", longid: "longid"), @@ -120,7 +120,7 @@ class PassPhraseStorageTests: XCTestCase { } let savedDate = Date() - let localPassPhrase = LocalPassPhrase( + let localPassPhrase = InMemoryPassPhrase( passPhrase: PassPhrase( value: "value", longid: "longid"),