Files
letro-ios/ElementX/Sources/Modules/Authentication/AuthenticationCoordinator.swift
2022-02-22 13:53:34 +02:00

187 lines
6.9 KiB
Swift

//
// AuthenticationCoordinator.swift
// ElementX
//
// Created by Stefan Ceriu on 11.02.2022.
//
import Foundation
import MatrixRustSDK
enum AuthenticationCoordinatorError: Error {
case failedLoggingIn
case failedRestoringLogin
case failedSettingUpSession
}
protocol AuthenticationCoordinatorDelegate: AnyObject {
func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator)
func authenticationCoordinatorDidSetupUserSession(_ authenticationCoordinator: AuthenticationCoordinator)
func authenticationCoordinatorDidTearDownUserSession(_ authenticationCoordinator: AuthenticationCoordinator)
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator,
didFailWithError error: AuthenticationCoordinatorError)
}
class AuthenticationCoordinator: Coordinator {
private let keychainController: KeychainControllerProtocol
private let navigationRouter: NavigationRouter
private(set) var userSession: UserSession?
var childCoordinators: [Coordinator] = []
weak var delegate: AuthenticationCoordinatorDelegate?
init(keychainController: KeychainControllerProtocol,
navigationRouter: NavigationRouter) {
self.keychainController = keychainController
self.navigationRouter = navigationRouter
}
func start() {
let availableRestoreTokens = keychainController.restoreTokens()
guard let usernameTokenTuple = availableRestoreTokens.first else {
startNewLoginFlow { result in
switch result {
case .success:
self.delegate?.authenticationCoordinatorDidSetupUserSession(self)
case .failure(let error):
self.delegate?.authenticationCoordinator(self, didFailWithError: error)
MXLog.error("Failed logging in user with error: \(error)")
}
}
return
}
restorePreviousLogin(usernameTokenTuple) { [weak self] result in
guard let self = self else { return }
switch result {
case .success:
self.delegate?.authenticationCoordinatorDidSetupUserSession(self)
case .failure(let error):
self.delegate?.authenticationCoordinator(self, didFailWithError: error)
MXLog.error("Failed restoring login with error: \(error)")
}
}
}
func logout() {
keychainController.removeAllTokens()
userSession = nil
delegate?.authenticationCoordinatorDidTearDownUserSession(self)
}
// MARK: - Private
private func startNewLoginFlow(_ completion: @escaping (Result<(), AuthenticationCoordinatorError>) -> Void) {
let parameters = LoginScreenCoordinatorParameters()
let coordinator = LoginScreenCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] result in
guard let self = self, let coordinator = coordinator else {
return
}
switch result {
case .login(let result):
self.login(username: result.username, password: result.password) { [weak self] result in
guard let self = self else { return }
switch result {
case .success:
completion(.success(()))
self.remove(childCoordinator: coordinator)
self.navigationRouter.dismissModule()
case .failure(let error):
completion(.failure(error))
}
}
}
}
add(childCoordinator: coordinator)
navigationRouter.present(coordinator)
coordinator.start()
}
private func login(username: String, password: String, completion: @escaping (Result<Void, AuthenticationCoordinatorError>) -> Void) {
delegate?.authenticationCoordinatorDidStartLoading(self)
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else { return }
do {
self.setupUserSessionForClient(try loginNewClient(basePath: self.baseDirectoryPathForUsername(username),
username: username,
password: password))
DispatchQueue.main.async {
completion(.success(()))
}
} catch {
DispatchQueue.main.async {
completion(.failure(.failedLoggingIn))
}
}
}
}
private func restorePreviousLogin(_ usernameTokenTuple: (username: String, token: String), completion: @escaping (Result<Void, AuthenticationCoordinatorError>) -> Void) {
delegate?.authenticationCoordinatorDidStartLoading(self)
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else { return }
do {
self.setupUserSessionForClient(try loginWithToken(basePath: self.baseDirectoryPathForUsername(usernameTokenTuple.username),
restoreToken: usernameTokenTuple.token))
DispatchQueue.main.async {
completion(.success(()))
}
} catch {
DispatchQueue.main.async {
completion(.failure(.failedRestoringLogin))
}
}
}
}
private func setupUserSessionForClient(_ client: Client) {
do {
let restoreToken = try client.restoreToken()
let userId = try client.userId()
keychainController.setRestoreToken(restoreToken, forUsername: userId)
} catch {
delegate?.authenticationCoordinator(self, didFailWithError: .failedSettingUpSession)
MXLog.error("Failed setting up user session with error: \(error)")
}
userSession = UserSession(client: client)
}
private func baseDirectoryPathForUsername(_ username: String) -> String {
guard var url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
fatalError("Should always be able to retrieve the caches directory")
}
url = url.appendingPathComponent(username)
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
return url.path
}
}