From 8fa61fa0aa171071b8acd886f7ce53ae78ca2640 Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Wed, 13 Jul 2022 03:57:47 -0300
Subject: [PATCH] Finishing backup sftp

---
 Package.swift                                 |  1 +
 Sources/App/AppDelegate.swift                 |  2 +-
 Sources/App/DependencyRegistrator.swift       |  2 -
 .../Controllers/BackupConfigController.swift  |  2 +-
 .../Controllers/BackupSFTPController.swift    | 80 ---------------
 .../Controllers/BackupSetupController.swift   |  5 +
 .../Coordinator/BackupCoordinator.swift       | 11 ---
 .../BackupFeature/Service/BackupService.swift | 40 ++++----
 .../BackupFeature/Views/BackupSetupView.swift | 32 +++---
 .../Controllers/RestoreListController.swift   |  8 +-
 .../Coordinator/RestoreCoordinator.swift      | 20 ----
 .../ViewModels/RestoreListViewModel.swift     | 34 ++++++-
 .../ViewModels/RestoreSFTPViewModel.swift     | 81 ---------------
 .../ViewModels/RestoreViewModel.swift         |  2 +-
 .../Views/RestoreSFTPView.swift               | 76 --------------
 .../SFTPController.swift}                     | 28 +++---
 Sources/SFTPFeature/SFTPService.swift         | 99 ++++++++++---------
 .../SFTPView.swift}                           |  2 +-
 .../SFTPViewModel.swift}                      | 35 ++++---
 .../Resources/en.lproj/Localizable.strings    |  2 +-
 20 files changed, 170 insertions(+), 392 deletions(-)
 delete mode 100644 Sources/BackupFeature/Controllers/BackupSFTPController.swift
 delete mode 100644 Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
 delete mode 100644 Sources/RestoreFeature/Views/RestoreSFTPView.swift
 rename Sources/{RestoreFeature/Controllers/RestoreSFTPController.swift => SFTPFeature/SFTPController.swift} (81%)
 rename Sources/{BackupFeature/Views/BackupSFTPView.swift => SFTPFeature/SFTPView.swift} (98%)
 rename Sources/{BackupFeature/ViewModels/BackupSFTPViewModel.swift => SFTPFeature/SFTPViewModel.swift} (63%)

diff --git a/Package.swift b/Package.swift
index 69613b7a..0e7bb614 100644
--- a/Package.swift
+++ b/Package.swift
@@ -216,6 +216,7 @@ let package = Package(
             .target(
                 name: "SFTPFeature",
                 dependencies: [
+                    "HUD",
                     "Shared",
                     "Keychain",
                     "InputField",
diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift
index 3675b450..72a7f682 100644
--- a/Sources/App/AppDelegate.swift
+++ b/Sources/App/AppDelegate.swift
@@ -1,8 +1,8 @@
 import UIKit
 import BackgroundTasks
 
-import XXModels
 import Theme
+import XXModels
 import XXLogger
 import Defaults
 import Integration
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index 471e1008..8caed374 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -122,7 +122,6 @@ struct DependencyRegistrator {
 
         container.register(
             BackupCoordinator(
-                sftpFactory: BackupSFTPController.init,
                 passphraseFactory: BackupPassphraseController.init(_:_:)
             ) as BackupCoordinating)
 
@@ -165,7 +164,6 @@ struct DependencyRegistrator {
             RestoreCoordinator(
                 successFactory: RestoreSuccessController.init,
                 chatListFactory: ChatListController.init,
-                sftpFactory: RestoreSFTPController.init(_:),
                 restoreFactory: RestoreController.init(_:_:),
                 passphraseFactory: RestorePassphraseController.init(_:)
             ) as RestoreCoordinating)
diff --git a/Sources/BackupFeature/Controllers/BackupConfigController.swift b/Sources/BackupFeature/Controllers/BackupConfigController.swift
index 4a2b5974..a901e6b9 100644
--- a/Sources/BackupFeature/Controllers/BackupConfigController.swift
+++ b/Sources/BackupFeature/Controllers/BackupConfigController.swift
@@ -122,7 +122,7 @@ final class BackupConfigController: UIViewController {
 
         screenView.sftpButton
             .publisher(for: .touchUpInside)
-            .sink { [unowned self] in coordinator.toSFTP(from: self) }
+            .sink { [unowned self] in viewModel.didTapService(.sftp, self) }
             .store(in: &cancellables)
 
         screenView.iCloudButton
diff --git a/Sources/BackupFeature/Controllers/BackupSFTPController.swift b/Sources/BackupFeature/Controllers/BackupSFTPController.swift
deleted file mode 100644
index f0f85186..00000000
--- a/Sources/BackupFeature/Controllers/BackupSFTPController.swift
+++ /dev/null
@@ -1,80 +0,0 @@
-import HUD
-import UIKit
-import Combine
-import DependencyInjection
-
-public final class BackupSFTPController: UIViewController {
-    @Dependency private var hud: HUDType
-
-    lazy private var screenView = BackupSFTPView()
-
-    private let viewModel = BackupSFTPViewModel()
-    private var cancellables = Set<AnyCancellable>()
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupNavigationBar()
-        setupBindings()
-    }
-
-    private func setupNavigationBar() {
-        navigationItem.backButtonTitle = ""
-
-        let back = UIButton.back()
-        back.addTarget(self, action: #selector(didTapBack), for: .touchUpInside)
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(
-            customView: UIStackView(arrangedSubviews: [back])
-        )
-    }
-
-    private func setupBindings() {
-        viewModel.hudPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
-
-        viewModel.popPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                navigationController?.popViewController(animated: true)
-            }.store(in: &cancellables)
-
-        screenView.hostField
-            .textPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didEnterHost($0) }
-            .store(in: &cancellables)
-
-        screenView.usernameField
-            .textPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didEnterUsername($0) }
-            .store(in: &cancellables)
-
-        screenView.passwordField
-            .textPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didEnterPassword($0) }
-            .store(in: &cancellables)
-
-        viewModel.statePublisher
-            .receive(on: DispatchQueue.main)
-            .map(\.isButtonEnabled)
-            .sink { [unowned self] in screenView.loginButton.isEnabled = $0 }
-            .store(in: &cancellables)
-
-        screenView.loginButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapLogin() }
-            .store(in: &cancellables)
-    }
-
-    @objc private func didTapBack() {
-        navigationController?.popViewController(animated: true)
-    }
-}
diff --git a/Sources/BackupFeature/Controllers/BackupSetupController.swift b/Sources/BackupFeature/Controllers/BackupSetupController.swift
index 49e05a2b..e22de517 100644
--- a/Sources/BackupFeature/Controllers/BackupSetupController.swift
+++ b/Sources/BackupFeature/Controllers/BackupSetupController.swift
@@ -37,5 +37,10 @@ final class BackupSetupController: UIViewController {
             .publisher(for: .touchUpInside)
             .sink { [unowned self] in viewModel.didTapService(.icloud, self) }
             .store(in: &cancellables)
+
+        screenView.sftpButton
+            .publisher(for: .touchUpInside)
+            .sink { [unowned self] in viewModel.didTapService(.sftp, self) }
+            .store(in: &cancellables)
     }
 }
diff --git a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
index c79cdcdb..a49b51bd 100644
--- a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
+++ b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
@@ -8,8 +8,6 @@ public protocol BackupCoordinating {
         from: UIViewController
     )
 
-    func toSFTP(from: UIViewController)
-
     func toPassphrase(
         from: UIViewController,
         cancelClosure: @escaping EmptyClosure,
@@ -18,27 +16,18 @@ public protocol BackupCoordinating {
 }
 
 public struct BackupCoordinator: BackupCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
     var bottomPresenter: Presenting = BottomPresenter()
 
-    var sftpFactory: () -> UIViewController
     var passphraseFactory: (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController
 
     public init(
-        sftpFactory: @escaping () -> UIViewController,
         passphraseFactory: @escaping (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController
     ) {
-        self.sftpFactory = sftpFactory
         self.passphraseFactory = passphraseFactory
     }
 }
 
 public extension BackupCoordinator {
-    func toSFTP(from parent: UIViewController) {
-        let screen = sftpFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
     func toDrawer(
         _ screen: UIViewController,
         from parent: UIViewController
diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift
index f1fbd846..54a3a5f6 100644
--- a/Sources/BackupFeature/Service/BackupService.swift
+++ b/Sources/BackupFeature/Service/BackupService.swift
@@ -154,7 +154,14 @@ extension BackupService {
                     }.store(in: &cancellables)
             }
         case .sftp:
-            break
+            if !sftpService.isAuthorized() {
+                sftpService.authorizeFlow((screen, { [weak self] in
+                    guard let self = self else { return }
+                    screen.navigationController?.popViewController(animated: true)
+                    self.refreshConnections()
+                    self.refreshBackups()
+                }))
+            }
         }
     }
 }
@@ -209,25 +216,20 @@ extension BackupService {
         }
 
         if sftpService.isAuthorized() {
-            if let keychain = try? DependencyInjection.Container.shared.resolve() as KeychainHandling,
-               let pwd = try? keychain.get(key: .pwd),
-               let host = try? keychain.get(key: .host),
-               let username = try? keychain.get(key: .username) {
-
-                let completion: SFTPFetchResult = { [weak settings] result in
-                    guard let settings = settings else { return }
-
-                    switch result {
-                    case .success(let backupSettings):
-                        settings.value.backups[.sftp] = backupSettings?.backup
-                    case .failure(let error):
-                        print(error.localizedDescription)
-                    }
+            sftpService.fetchMetadata({ [weak settings] in
+                guard let settings = settings else { return }
+
+                guard let metadata = try? $0.get()?.backup else {
+                    settings.value.backups[.sftp] = nil
+                    return
                 }
 
-                let authParams = SFTPAuthParams(host, username, pwd)
-                sftpService.fetch((authParams, completion))
-            }
+                settings.value.backups[.sftp] = Backup(
+                    id: metadata.id,
+                    date: metadata.date,
+                    size: metadata.size
+                )
+            })
         }
 
         if dropboxService.isAuthorized() {
@@ -329,7 +331,7 @@ extension BackupService {
             }
         case .sftp:
             do {
-                try sftpService.upload(url)
+                try sftpService.uploadBackup(url)
             } catch {
                 print(error.localizedDescription)
             }
diff --git a/Sources/BackupFeature/Views/BackupSetupView.swift b/Sources/BackupFeature/Views/BackupSetupView.swift
index eeaad481..3e19d500 100644
--- a/Sources/BackupFeature/Views/BackupSetupView.swift
+++ b/Sources/BackupFeature/Views/BackupSetupView.swift
@@ -6,6 +6,7 @@ final class BackupSetupView: UIView {
     let subtitleLabel = UILabel()
 
     let stackView = UIStackView()
+    let sftpButton = BackupSwitcherButton()
     let iCloudButton = BackupSwitcherButton()
     let dropboxButton = BackupSwitcherButton()
     let googleDriveButton = BackupSwitcherButton()
@@ -60,32 +61,37 @@ final class BackupSetupView: UIView {
         googleDriveButton.logoImageView.image = Asset.restoreDrive.image
         googleDriveButton.showChevron()
 
+        sftpButton.titleLabel.text = Localized.Backup.sftp
+        sftpButton.logoImageView.image = Asset.restoreSFTP.image
+        sftpButton.showChevron()
+
         stackView.axis = .vertical
         stackView.addArrangedSubview(googleDriveButton)
         stackView.addArrangedSubview(iCloudButton)
         stackView.addArrangedSubview(dropboxButton)
+        stackView.addArrangedSubview(sftpButton)
 
         addSubview(titleLabel)
         addSubview(subtitleLabel)
         addSubview(stackView)
 
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(15)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
+        titleLabel.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(15)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
         }
 
-        subtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
+        subtitleLabel.snp.makeConstraints {
+            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+            $0.left.equalToSuperview().offset(38)
+            $0.right.equalToSuperview().offset(-41)
         }
 
-        stackView.snp.makeConstraints { make in
-            make.top.equalTo(subtitleLabel.snp.bottom).offset(28)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.lessThanOrEqualToSuperview()
+        stackView.snp.makeConstraints {
+            $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.bottom.lessThanOrEqualToSuperview()
         }
     }
 
diff --git a/Sources/RestoreFeature/Controllers/RestoreListController.swift b/Sources/RestoreFeature/Controllers/RestoreListController.swift
index 803e46e7..c73e94ed 100644
--- a/Sources/RestoreFeature/Controllers/RestoreListController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreListController.swift
@@ -1,6 +1,6 @@
 import HUD
-import Shared
 import UIKit
+import Shared
 import Combine
 import DrawerFeature
 import DependencyInjection
@@ -51,12 +51,12 @@ public final class RestoreListController: UIViewController {
     }
 
     private func setupBindings() {
-        viewModel.hud
+        viewModel.hudPublisher
             .receive(on: DispatchQueue.main)
             .sink { [hud] in hud.update(with: $0) }
             .store(in: &cancellables)
 
-        viewModel.didFetchBackup
+        viewModel.backupPublisher
             .receive(on: DispatchQueue.main)
             .sink { [unowned self] in
                 coordinator.toRestore(using: ndf, with: $0, from: self)
@@ -88,7 +88,7 @@ public final class RestoreListController: UIViewController {
         screenView.sftpButton
             .publisher(for: .touchUpInside)
             .sink { [unowned self] in
-                coordinator.toSFTP(using: ndf, from: self)
+                viewModel.didTapCloud(.sftp, from: self)
             }.store(in: &cancellables)
     }
 
diff --git a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift b/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift
index a76b5c74..2183035b 100644
--- a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift
+++ b/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift
@@ -6,33 +6,27 @@ import Presentation
 public protocol RestoreCoordinating {
     func toChats(from: UIViewController)
     func toSuccess(from: UIViewController)
-    func toSFTP(using: String, from: UIViewController)
     func toDrawer(_: UIViewController, from: UIViewController)
     func toPassphrase(from: UIViewController, _: @escaping StringClosure)
     func toRestore(using: String, with: RestoreSettings, from: UIViewController)
-    func toRestoreReplacing(using: String, with: RestoreSettings, from: UIViewController)
 }
 
 public struct RestoreCoordinator: RestoreCoordinating {
     var pushPresenter: Presenting = PushPresenter()
     var bottomPresenter: Presenting = BottomPresenter()
     var replacePresenter: Presenting = ReplacePresenter()
-    var replaceLastPresenter: Presenting = ReplacePresenter(mode: .replaceLast)
 
     var successFactory: () -> UIViewController
     var chatListFactory: () -> UIViewController
-    var sftpFactory: (String) -> UIViewController
     var restoreFactory: (String, RestoreSettings) -> UIViewController
     var passphraseFactory: (@escaping StringClosure) -> UIViewController
 
     public init(
         successFactory: @escaping () -> UIViewController,
         chatListFactory: @escaping () -> UIViewController,
-        sftpFactory: @escaping (String) -> UIViewController,
         restoreFactory: @escaping (String, RestoreSettings) -> UIViewController,
         passphraseFactory: @escaping (@escaping StringClosure) -> UIViewController
     ) {
-        self.sftpFactory = sftpFactory
         self.successFactory = successFactory
         self.restoreFactory = restoreFactory
         self.chatListFactory = chatListFactory
@@ -50,15 +44,6 @@ public extension RestoreCoordinator {
         pushPresenter.present(screen, from: parent)
     }
 
-    func toRestoreReplacing(
-        using ndf: String,
-        with settings: RestoreSettings,
-        from parent: UIViewController
-    ) {
-        let screen = restoreFactory(ndf, settings)
-        replaceLastPresenter.present(screen, from: parent)
-    }
-
     func toChats(from parent: UIViewController) {
         let screen = chatListFactory()
         replacePresenter.present(screen, from: parent)
@@ -80,9 +65,4 @@ public extension RestoreCoordinator {
         let screen = passphraseFactory(completion)
         bottomPresenter.present(screen, from: parent)
     }
-
-    func toSFTP(using ndf: String, from parent: UIViewController) {
-        let screen = sftpFactory(ndf)
-        pushPresenter.present(screen, from: parent)
-    }
 }
diff --git a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
index d0119b84..a4c2402e 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
@@ -6,20 +6,22 @@ import Combine
 import BackupFeature
 import DependencyInjection
 
+import SFTPFeature
 import iCloudFeature
 import DropboxFeature
 import GoogleDriveFeature
 
 final class RestoreListViewModel {
+    @Dependency private var sftpService: SFTPService
     @Dependency private var icloudService: iCloudInterface
     @Dependency private var dropboxService: DropboxInterface
     @Dependency private var googleDriveService: GoogleDriveInterface
 
-    var hud: AnyPublisher<HUDStatus, Never> {
+    var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
     }
 
-    var didFetchBackup: AnyPublisher<RestoreSettings, Never> {
+    var backupPublisher: AnyPublisher<RestoreSettings, Never> {
         backupSubject.eraseToAnyPublisher()
     }
 
@@ -36,10 +38,36 @@ final class RestoreListViewModel {
         case .dropbox:
             didRequestDropboxAuthorization(from: parent)
         case .sftp:
-            break
+            didRequestSFTPAuthorization(from: parent)
         }
     }
 
+    private func didRequestSFTPAuthorization(from controller: UIViewController) {
+        let params = SFTPAuthorizationParams(controller, { [weak self] in
+            guard let self = self else { return }
+            controller.navigationController?.popViewController(animated: true)
+
+            self.hudSubject.send(.on(nil))
+
+            self.sftpService.fetchMetadata{ result in
+                switch result {
+                case .success(let settings):
+                    self.hudSubject.send(.none)
+
+                    if let settings = settings {
+                        self.backupSubject.send(settings)
+                    } else {
+                        self.backupSubject.send(.init(cloudService: .sftp))
+                    }
+                case .failure(let error):
+                    self.hudSubject.send(.error(.init(with: error)))
+                }
+            }
+        })
+
+        sftpService.authorizeFlow(params)
+    }
+
     private func didRequestDriveAuthorization(from controller: UIViewController) {
         googleDriveService.authorize(presenting: controller) { authResult in
             switch authResult {
diff --git a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
deleted file mode 100644
index 7bf34b4c..00000000
--- a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
+++ /dev/null
@@ -1,81 +0,0 @@
-import HUD
-import Models
-import Combine
-import Foundation
-import SFTPFeature
-import DependencyInjection
-
-struct RestoreSFTPViewState {
-    var host: String = ""
-    var username: String = ""
-    var password: String = ""
-    var isButtonEnabled: Bool = false
-}
-
-final class RestoreSFTPViewModel {
-    @Dependency private var service: SFTPService
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var backupPublisher: AnyPublisher<RestoreSettings, Never> {
-        backupSubject.eraseToAnyPublisher()
-    }
-
-    var statePublisher: AnyPublisher<RestoreSFTPViewState, Never> {
-        stateSubject.eraseToAnyPublisher()
-    }
-
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let backupSubject = PassthroughSubject<RestoreSettings, Never>()
-    private let stateSubject = CurrentValueSubject<RestoreSFTPViewState, Never>(.init())
-
-    func didEnterHost(_ string: String) {
-        stateSubject.value.host = string
-        validate()
-    }
-
-    func didEnterUsername(_ string: String) {
-        stateSubject.value.username = string
-        validate()
-    }
-
-    func didEnterPassword(_ string: String) {
-        stateSubject.value.password = string
-        validate()
-    }
-
-    func didTapLogin() {
-        hudSubject.send(.on(nil))
-
-        let host = stateSubject.value.host
-        let username = stateSubject.value.username
-        let password = stateSubject.value.password
-
-        let completion: SFTPFetchResult = { result in
-            switch result {
-            case .success(let backup):
-                self.hudSubject.send(.none)
-
-                if let backup = backup {
-                    self.backupSubject.send(backup)
-                } else {
-                    self.backupSubject.send(.init(cloudService: .sftp))
-                }
-            case .failure(let error):
-                self.hudSubject.send(.error(.init(with: error)))
-            }
-        }
-
-        let authParams = SFTPAuthParams(host, username, password)
-        service.fetch((authParams, completion))
-    }
-
-    private func validate() {
-        stateSubject.value.isButtonEnabled =
-        !stateSubject.value.host.isEmpty &&
-        !stateSubject.value.username.isEmpty &&
-        !stateSubject.value.password.isEmpty
-    }
-}
diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
index ccb417df..5f1d4414 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
@@ -91,7 +91,7 @@ final class RestoreViewModel {
 
     private func downloadBackupForSFTP(_ backup: Backup) {
         do {
-            try sftpService.download(backup.id)
+            try sftpService.downloadBackup(backup.id)
         } catch {
             print(error.localizedDescription)
         }
diff --git a/Sources/RestoreFeature/Views/RestoreSFTPView.swift b/Sources/RestoreFeature/Views/RestoreSFTPView.swift
deleted file mode 100644
index de41e371..00000000
--- a/Sources/RestoreFeature/Views/RestoreSFTPView.swift
+++ /dev/null
@@ -1,76 +0,0 @@
-import UIKit
-import Shared
-import InputField
-
-final class RestoreSFTPView: UIView {
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let hostField = OutlinedInputField()
-    let usernameField = OutlinedInputField()
-    let passwordField = OutlinedInputField()
-    let loginButton = CapsuleButton()
-    let stackView = UIStackView()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        titleLabel.textColor = Asset.neutralDark.color
-        titleLabel.text = Localized.AccountRestore.Sftp.title
-        titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        let attString = NSAttributedString(
-            string: Localized.AccountRestore.Sftp.subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ])
-
-        subtitleLabel.numberOfLines = 0
-        subtitleLabel.attributedText = attString
-
-        hostField.setup(title: Localized.AccountRestore.Sftp.host)
-        usernameField.setup(title: Localized.AccountRestore.Sftp.username)
-        passwordField.setup(title: Localized.AccountRestore.Sftp.password, sensitive: true)
-
-        loginButton.set(style: .brandColored, title: Localized.AccountRestore.Sftp.login)
-
-        stackView.spacing = 30
-        stackView.axis = .vertical
-        stackView.distribution = .fillEqually
-        stackView.addArrangedSubview(hostField)
-        stackView.addArrangedSubview(usernameField)
-        stackView.addArrangedSubview(passwordField)
-        stackView.addArrangedSubview(loginButton)
-
-        addSubview(titleLabel)
-        addSubview(subtitleLabel)
-        addSubview(stackView)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(15)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        subtitleLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        stackView.snp.makeConstraints {
-            $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-38)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift b/Sources/SFTPFeature/SFTPController.swift
similarity index 81%
rename from Sources/RestoreFeature/Controllers/RestoreSFTPController.swift
rename to Sources/SFTPFeature/SFTPController.swift
index 024b2df4..1fe7f4c4 100644
--- a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift
+++ b/Sources/SFTPFeature/SFTPController.swift
@@ -3,27 +3,26 @@ import UIKit
 import Combine
 import DependencyInjection
 
-public final class RestoreSFTPController: UIViewController {
+public final class SFTPController: UIViewController {
     @Dependency private var hud: HUDType
-    @Dependency private var coordinator: RestoreCoordinating
 
-    lazy private var screenView = RestoreSFTPView()
+    lazy private var screenView = SFTPView()
 
-    private let ndf: String
-    private let viewModel = RestoreSFTPViewModel()
+    private let completion: () -> Void
+    private let viewModel = SFTPViewModel()
     private var cancellables = Set<AnyCancellable>()
 
-    public override func loadView() {
-        view = screenView
-    }
-
-    public init(_ ndf: String) {
-        self.ndf = ndf
+    public init(_ completion: @escaping () -> Void) {
+        self.completion = completion
         super.init(nibName: nil, bundle: nil)
     }
 
     required init?(coder: NSCoder) { nil }
 
+    public override func loadView() {
+        view = screenView
+    }
+
     public override func viewDidLoad() {
         super.viewDidLoad()
         setupNavigationBar()
@@ -47,11 +46,10 @@ public final class RestoreSFTPController: UIViewController {
             .sink { [hud] in hud.update(with: $0) }
             .store(in: &cancellables)
 
-        viewModel.backupPublisher
+        viewModel.authPublisher
             .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                coordinator.toRestoreReplacing(using: ndf, with: $0, from: self)
-            }.store(in: &cancellables)
+            .sink { [unowned self] in completion() }
+            .store(in: &cancellables)
 
         screenView.hostField
             .textPublisher
diff --git a/Sources/SFTPFeature/SFTPService.swift b/Sources/SFTPFeature/SFTPService.swift
index b581362e..aca3e200 100644
--- a/Sources/SFTPFeature/SFTPService.swift
+++ b/Sources/SFTPFeature/SFTPService.swift
@@ -1,19 +1,22 @@
+import UIKit
 import Shout
 import Models
+import Combine
 import Keychain
 import Foundation
+import Presentation
 import DependencyInjection
 
-public typealias SFTPAuthParams = (String, String, String)
 public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void
-public typealias SFTPFetchParams = (SFTPAuthParams, SFTPFetchResult)
+public typealias SFTPAuthorizationParams = (UIViewController, () -> Void)
 
 public struct SFTPService {
     public var isAuthorized: () -> Bool
-    public var upload: (URL) throws -> Void
-    public var fetch: (SFTPFetchParams) -> Void
-    public var download: (String) throws -> Void
-    public var justAuthenticate: (SFTPAuthParams) throws -> Void
+    public var uploadBackup: (URL) throws -> Void
+    public var downloadBackup: (String) throws -> Void
+    public var fetchMetadata: (SFTPFetchResult) -> Void
+    public var authenticate: (String, String, String) throws -> Void
+    public var authorizeFlow: (SFTPAuthorizationParams) -> Void
 }
 
 public extension SFTPService {
@@ -22,27 +25,29 @@ public extension SFTPService {
             print("^^^ Requested auth status on sftp service")
             return true
         },
-        upload: { url in
+        uploadBackup: { url in
             print("^^^ Requested upload on sftp service")
             print("^^^ URL path: \(url.path)")
         },
-        fetch: { (authParams, completion) in
-            print("^^^ Requested backup metadata on sftp service.")
-            print("^^^ Host: \(authParams.0)")
-            print("^^^ Username: \(authParams.1)")
-            print("^^^ Password: \(authParams.2)")
-            completion(.success(nil))
-        },
-        download: { path in
+        downloadBackup: { path in
             print("^^^ Requested backup download on sftp service.")
             print("^^^ Path: \(path)")
         },
-        justAuthenticate: { host, username, password in
-            print("^^^ Requested to authenticate on sftp service")
+        fetchMetadata: { completion in
+            print("^^^ Requested backup metadata on sftp service.")
+            completion(.success(nil))
+        },
+        authenticate: { host, username, password in
+            print("^^^ Requested authentication on sftp service.")
             print("^^^ Host: \(host)")
             print("^^^ Username: \(username)")
             print("^^^ Password: \(password)")
-        })
+        },
+        authorizeFlow: { (_, completion) in
+            print("^^^ Requested authorizing flow on sftp service.")
+            completion()
+        }
+    )
 
     static var live = SFTPService(
         isAuthorized: {
@@ -51,11 +56,11 @@ public extension SFTPService {
                let host = try? keychain.get(key: .host),
                let username = try? keychain.get(key: .username) {
                 return true
-            } else {
-                return false
             }
+
+            return false
         },
-        upload: { url in
+        uploadBackup: { url in
             let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
             let host = try keychain.get(key: .host)
             let password = try keychain.get(key: .pwd)
@@ -67,20 +72,30 @@ public extension SFTPService {
 
             try sftp.upload(localURL: url, remotePath: "backup/backup.xxm")
         },
-        fetch: { (authParams, completion) in
-            let host = authParams.0
-            let username = authParams.1
-            let password = authParams.2
+        downloadBackup: { path in
+            let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
+            let host = try keychain.get(key: .host)
+            let password = try keychain.get(key: .pwd)
+            let username = try keychain.get(key: .username)
 
-            do {
-                let ssh = try SSH(host: host, port: 22)
-                try ssh.authenticate(username: username, password: password)
-                let sftp = try ssh.openSftp()
+            let ssh = try SSH(host: host!, port: 22)
+            try ssh.authenticate(username: username!, password: password!)
+            let sftp = try ssh.openSftp()
 
+            let temp = NSTemporaryDirectory()
+            try sftp.download(remotePath: path, localURL: URL(string: temp)!)
+            print(FileManager.default.fileExists(atPath: temp))
+        },
+        fetchMetadata: { completion in
+            do {
                 let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
-                try keychain.store(key: .host, value: host)
-                try keychain.store(key: .pwd, value: password)
-                try keychain.store(key: .username, value: username)
+                let host = try keychain.get(key: .host)
+                let password = try keychain.get(key: .pwd)
+                let username = try keychain.get(key: .username)
+
+                let ssh = try SSH(host: host!, port: 22)
+                try ssh.authenticate(username: username!, password: password!)
+                let sftp = try ssh.openSftp()
 
                 if let files = try? sftp.listFiles(in: "backup"),
                    let backup = files.filter({ file in file.0 == "backup.xxm" }).first {
@@ -101,25 +116,19 @@ public extension SFTPService {
                 completion(.failure(error))
             }
         },
-        download: { path in
-            let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
-            let host = try keychain.get(key: .host)
-            let password = try keychain.get(key: .pwd)
-            let username = try keychain.get(key: .username)
-
-            let ssh = try SSH(host: host!, port: 22)
-            try ssh.authenticate(username: username!, password: password!)
+        authenticate: { host, username, password in
+            let ssh = try SSH(host: host, port: 22)
+            try ssh.authenticate(username: username, password: password)
             let sftp = try ssh.openSftp()
 
-            let temp = NSTemporaryDirectory()
-            try sftp.download(remotePath: path, localURL: URL(string: temp)!)
-            print(FileManager.default.fileExists(atPath: temp))
-        },
-        justAuthenticate: { host, username, password in
             let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
             try keychain.store(key: .host, value: host)
             try keychain.store(key: .pwd, value: password)
             try keychain.store(key: .username, value: username)
+        },
+        authorizeFlow: { controller, completion in
+            var pushPresenter: Presenting = PushPresenter()
+            pushPresenter.present(SFTPController(completion), from: controller)
         }
     )
 }
diff --git a/Sources/BackupFeature/Views/BackupSFTPView.swift b/Sources/SFTPFeature/SFTPView.swift
similarity index 98%
rename from Sources/BackupFeature/Views/BackupSFTPView.swift
rename to Sources/SFTPFeature/SFTPView.swift
index 3a88917a..3e62e4ca 100644
--- a/Sources/BackupFeature/Views/BackupSFTPView.swift
+++ b/Sources/SFTPFeature/SFTPView.swift
@@ -2,7 +2,7 @@ import UIKit
 import Shared
 import InputField
 
-final class BackupSFTPView: UIView {
+final class SFTPView: UIView {
     let titleLabel = UILabel()
     let subtitleLabel = UILabel()
     let hostField = OutlinedInputField()
diff --git a/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift b/Sources/SFTPFeature/SFTPViewModel.swift
similarity index 63%
rename from Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
rename to Sources/SFTPFeature/SFTPViewModel.swift
index f9518a57..ea309cb6 100644
--- a/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
+++ b/Sources/SFTPFeature/SFTPViewModel.swift
@@ -1,35 +1,33 @@
 import HUD
-import Models
 import Combine
 import Foundation
-import SFTPFeature
 import DependencyInjection
 
-struct BackupSFTPViewState {
+struct SFTPViewState {
     var host: String = ""
     var username: String = ""
     var password: String = ""
     var isButtonEnabled: Bool = false
 }
 
-final class BackupSFTPViewModel {
+final class SFTPViewModel {
     @Dependency private var service: SFTPService
 
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
     }
 
-    var popPublisher: AnyPublisher<Void, Never> {
-        popSubject.eraseToAnyPublisher()
+    var statePublisher: AnyPublisher<SFTPViewState, Never> {
+        stateSubject.eraseToAnyPublisher()
     }
 
-    var statePublisher: AnyPublisher<BackupSFTPViewState, Never> {
-        stateSubject.eraseToAnyPublisher()
+    var authPublisher: AnyPublisher<Void, Never> {
+        authSubject.eraseToAnyPublisher()
     }
 
-    private let popSubject = PassthroughSubject<Void, Never>()
+    private let authSubject = PassthroughSubject<Void, Never>()
     private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let stateSubject = CurrentValueSubject<BackupSFTPViewState, Never>(.init())
+    private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init())
 
     func didEnterHost(_ string: String) {
         stateSubject.value.host = string
@@ -53,14 +51,15 @@ final class BackupSFTPViewModel {
         let username = stateSubject.value.username
         let password = stateSubject.value.password
 
-        let authParams = SFTPAuthParams(host, username, password)
-
-        do {
-            try service.justAuthenticate(authParams)
-            hudSubject.send(.none)
-            popSubject.send(())
-        } catch {
-            hudSubject.send(.error(.init(with: error)))
+        DispatchQueue.global().async { [weak self] in
+            guard let self = self else { return }
+            do {
+                try self.service.authenticate(host, username, password)
+                self.hudSubject.send(.none)
+                self.authSubject.send(())
+            } catch {
+                self.hudSubject.send(.error(.init(with: error)))
+            }
         }
     }
 
diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings
index 45568316..684962fc 100644
--- a/Sources/Shared/Resources/en.lproj/Localizable.strings
+++ b/Sources/Shared/Resources/en.lproj/Localizable.strings
@@ -834,7 +834,7 @@
 "accountRestore.sftp.title"
 = "Login to your SFTP";
 "accountRestore.sftp.subtitle"
-= "Login to your server. Your credentials will be automatically and securley saved locally on your device.";
+= "Login to your server. Your credentials will be automatically and securely saved locally on your device.";
 "accountRestore.sftp.host"
 = "Host";
 "accountRestore.sftp.username"
-- 
GitLab