#40: Use the Rust AuthenticationService.

* Update SDK package to 1.0.12-alpha.
* Use an app group for storage and stop stripping the http from the homeserver when configuring the service.
* Rename access token to restore token.
* Remove matrix.org server description inline with latest FTUE changes.
This commit is contained in:
Doug
2022-07-27 10:57:16 +01:00
committed by GitHub
parent 03d6097c72
commit 1878a16496
29 changed files with 284 additions and 204 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
}
private func startAuthentication() {
let authenticationService = AuthenticationService(userSessionStore: userSessionStore)
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore)
let coordinator = AuthenticationCoordinator(authenticationService: authenticationService,
navigationRouter: navigationRouter)
coordinator.delegate = self
+1 -1
View File
@@ -11,7 +11,7 @@ import Foundation
final class BuildSettings {
// MARK: - Servers
static let defaultHomeserverURLString = "https://matrix.org"
static let defaultHomeserverAddress = "matrix.org"
// MARK: - Bug report
@@ -22,6 +22,7 @@ internal enum ElementInfoPlist {
internal static let cfBundleVersion: String = _document["CFBundleVersion"]
internal static let uiLaunchStoryboardName: String = _document["UILaunchStoryboardName"]
internal static let uiSupportedInterfaceOrientations: [String] = _document["UISupportedInterfaceOrientations"]
internal static let appGroupIdentifier: String = _document["appGroupIdentifier"]
}
// swiftlint:enable identifier_name line_length type_body_length
@@ -16,18 +16,21 @@ protocol AuthenticationCoordinatorDelegate: AnyObject {
}
class AuthenticationCoordinator: Coordinator, Presentable {
private let authenticationService: AuthenticationServiceProtocol
private let authenticationService: AuthenticationServiceProxyProtocol
private let navigationRouter: NavigationRouter
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
private var activityIndicator: UserIndicator?
private(set) var clientProxy: ClientProxyProtocol?
var childCoordinators: [Coordinator] = []
weak var delegate: AuthenticationCoordinatorDelegate?
init(authenticationService: AuthenticationServiceProtocol,
init(authenticationService: AuthenticationServiceProxyProtocol,
navigationRouter: NavigationRouter) {
self.authenticationService = authenticationService
self.navigationRouter = navigationRouter
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: navigationRouter.toPresentable())
}
func start() {
@@ -47,7 +50,7 @@ class AuthenticationCoordinator: Coordinator, Presentable {
guard let self = self else { return }
switch action {
case .login:
self.showLoginScreen()
Task { await self.startAuthentication() }
}
}
@@ -59,19 +62,54 @@ class AuthenticationCoordinator: Coordinator, Presentable {
}
}
private func startAuthentication() async {
startLoading()
switch await authenticationService.configure(for: BuildSettings.defaultHomeserverAddress) {
case .success:
stopLoading()
showLoginScreen()
case .failure:
stopLoading()
showServerSelectionScreen()
}
}
private func showServerSelectionScreen() {
let parameters = ServerSelectionCoordinatorParameters(authenticationService: authenticationService,
hasModalPresentation: false)
let coordinator = ServerSelectionCoordinator(parameters: parameters)
coordinator.callback = { [weak self] action in
guard let self = self else { return }
switch action {
case .updated:
self.showLoginScreen()
case .dismiss:
MXLog.failure("[AuthenticationCoordinator] ServerSelectionScreen is requesting dismiss when part of a stack.")
}
}
coordinator.start()
add(childCoordinator: coordinator)
navigationRouter.push(coordinator) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
private func showLoginScreen() {
let parameters = LoginCoordinatorParameters(authenticationService: authenticationService,
navigationRouter: navigationRouter)
let coordinator = LoginCoordinator(parameters: parameters)
coordinator.callback = { [weak self, weak coordinator] action in
guard let self = self, let coordinator = coordinator else { return }
coordinator.callback = { [weak self] action in
guard let self = self else { return }
switch action {
case .signedIn(let userSession):
self.delegate?.authenticationCoordinator(self, didLoginWithSession: userSession)
self.remove(childCoordinator: coordinator)
self.navigationRouter.dismissModule()
case .continueWithOIDC:
break
}
@@ -84,4 +122,14 @@ class AuthenticationCoordinator: Coordinator, Presentable {
self?.remove(childCoordinator: coordinator)
}
}
/// Show a blocking activity indicator.
private func startLoading() {
activityIndicator = indicatorPresenter.present(.loading(label: ElementL10n.loading, isInteractionBlocking: true))
}
/// Hide the currently displayed activity indicator.
private func stopLoading() {
activityIndicator = nil
}
}
@@ -19,7 +19,7 @@ import SwiftUI
struct LoginCoordinatorParameters {
/// The service used to authenticate the user.
let authenticationService: AuthenticationServiceProtocol
let authenticationService: AuthenticationServiceProxyProtocol
/// The navigation router used to present the server selection screen.
let navigationRouter: NavigationRouterType
}
@@ -46,7 +46,7 @@ final class LoginCoordinator: Coordinator, Presentable {
}
}
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService }
private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService }
private var navigationRouter: NavigationRouterType { parameters.navigationRouter }
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
private var activityIndicator: UserIndicator?
@@ -163,7 +163,7 @@ final class LoginCoordinator: Coordinator, Presentable {
startLoading(isInteractionBlocking: false)
Task {
switch await authenticationService.startLogin(for: homeserverDomain) {
switch await authenticationService.configure(for: homeserverDomain) {
case .success:
updateViewModel()
stopLoading()
@@ -20,20 +20,15 @@ import Foundation
struct LoginHomeserver: Equatable {
/// The homeserver string to be shown to the user.
let address: String
/// Whether or not the homeserver is matrix.org.
let isMatrixDotOrg: Bool
/// The types login supported by the homeserver.
let loginMode: LoginMode
}
extension LoginHomeserver {
/// Temporary initialiser for use until the FFI has homeserver discovery etc.
init(address: String) {
var loginMode: LoginMode
/// Creates a new homeserver value.
init(address: String, loginMode: LoginMode) {
let address = Self.sanitized(address).components(separatedBy: "://").last ?? address
self.address = address
isMatrixDotOrg = address == "matrix.org"
loginMode = .password
self.loginMode = loginMode
}
/// Sanitizes a user entered homeserver address with the following rules
@@ -59,30 +54,23 @@ extension LoginHomeserver {
extension LoginHomeserver {
/// A mock homeserver that is configured just like matrix.org.
static var mockMatrixDotOrg: LoginHomeserver {
LoginHomeserver(address: "matrix.org",
isMatrixDotOrg: true,
loginMode: .password)
LoginHomeserver(address: "matrix.org", loginMode: .password)
}
/// A mock homeserver that supports login and registration via a password but has no SSO providers.
static var mockBasicServer: LoginHomeserver {
LoginHomeserver(address: "example.com",
isMatrixDotOrg: false,
loginMode: .password)
LoginHomeserver(address: "example.com", loginMode: .password)
}
/// A mock homeserver that supports only supports authentication via a single SSO provider.
static var mockOIDC: LoginHomeserver {
LoginHomeserver(address: "company.com",
isMatrixDotOrg: false,
// swiftlint:disable:next force_unwrapping
loginMode: .oidc(URL(string: "https://auth.company.com")!))
// swiftlint:disable:next force_unwrapping
let issuerURL = URL(string: "https://auth.company.com")!
return LoginHomeserver(address: "company.com", loginMode: .oidc(issuerURL))
}
/// A mock homeserver that only with no supported login flows.
static var mockUnsupported: LoginHomeserver {
LoginHomeserver(address: "server.net",
isMatrixDotOrg: false,
loginMode: .unsupported)
LoginHomeserver(address: "server.net", loginMode: .unsupported)
}
}
@@ -72,8 +72,7 @@ struct LoginScreen: View {
/// The sever information section that includes a button to select a different server.
var serverInfo: some View {
LoginServerInfoSection(address: context.viewState.homeserver.address,
showMatrixDotOrgInfo: context.viewState.homeserver.isMatrixDotOrg) {
LoginServerInfoSection(address: context.viewState.homeserver.address) {
context.send(viewAction: .selectServer)
}
}
@@ -23,8 +23,6 @@ struct LoginServerInfoSection: View {
/// The address shown for the server.
let address: String
/// Whether or not to show the matrix.org description.
let showMatrixDotOrgInfo: Bool
/// The action performed when tapping the edit button.
let editAction: () -> Void
@@ -37,18 +35,9 @@ struct LoginServerInfoSection: View {
.foregroundColor(.element.secondaryContent)
HStack {
VStack(alignment: .leading, spacing: 2) {
Text(address)
.font(.element.body)
.foregroundColor(.element.primaryContent)
if showMatrixDotOrgInfo {
Text(ElementL10n.authenticationServerInfoMatrixDescription)
.font(.element.caption1)
.foregroundColor(.element.tertiaryContent)
.accessibilityIdentifier("serverDescriptionText")
}
}
Text(address)
.font(.element.body)
.foregroundColor(.element.primaryContent)
Spacer()
@@ -18,7 +18,7 @@ import SwiftUI
struct ServerSelectionCoordinatorParameters {
/// The service used to authenticate the user.
let authenticationService: AuthenticationServiceProtocol
let authenticationService: AuthenticationServiceProxyProtocol
/// Whether the screen is presented modally or within a navigation stack.
let hasModalPresentation: Bool
}
@@ -37,7 +37,7 @@ final class ServerSelectionCoordinator: Coordinator, Presentable {
private let serverSelectionHostingController: UIViewController
private var serverSelectionViewModel: ServerSelectionViewModelProtocol
private var authenticationService: AuthenticationServiceProtocol { parameters.authenticationService }
private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService }
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
private var loadingIndicator: UserIndicator?
@@ -103,7 +103,7 @@ final class ServerSelectionCoordinator: Coordinator, Presentable {
startLoading()
Task {
switch await authenticationService.startLogin(for: homeserverAddress) {
switch await authenticationService.configure(for: homeserverAddress) {
case .success:
callback?(.updated)
stopLoading()
@@ -117,7 +117,7 @@ final class ServerSelectionCoordinator: Coordinator, Presentable {
/// Processes an error to either update the flow or display it to the user.
private func handleError(_ error: AuthenticationServiceError) {
switch error {
case .invalidServer:
case .invalidServer, .invalidHomeserverAddress:
serverSelectionViewModel.displayError(.footerMessage(ElementL10n.loginErrorHomeserverNotFound))
default:
serverSelectionViewModel.displayError(.footerMessage(ElementL10n.unknownError))
@@ -26,7 +26,7 @@ struct PlaceholderAvatarImage: View {
.padding(4)
.foregroundColor(.white)
// Make the text resizable (i.e. Make it large and then allow it to scale down)
.font(.system(size: 200))
.font(.system(size: 200).weight(.semibold))
.minimumScaleFactor(0.001)
}
.aspectRatio(1, contentMode: .fill)
@@ -9,42 +9,61 @@
import Foundation
import MatrixRustSDK
class AuthenticationService: AuthenticationServiceProtocol {
class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
// MARK: - Properties
// MARK: Private
private(set) var homeserver = LoginHomeserver(address: BuildSettings.defaultHomeserverURLString)
private let authenticationService: AuthenticationService
private let userSessionStore: UserSessionStoreProtocol
// MARK: Public
private(set) var homeserver = LoginHomeserver(address: BuildSettings.defaultHomeserverAddress, loginMode: .unknown)
// MARK: - Setup
init(userSessionStore: UserSessionStoreProtocol) {
self.userSessionStore = userSessionStore
authenticationService = AuthenticationService(basePath: userSessionStore.baseDirectoryPath)
}
// MARK: - Public
func startLogin(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError> {
homeserver = LoginHomeserver(address: homeserverAddress)
return .success(())
func configure(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError> {
let task = Task.detached { () -> LoginHomeserver in
var homeserver = LoginHomeserver(address: homeserverAddress, loginMode: .unknown)
try self.authenticationService.configureHomeserver(serverName: homeserverAddress)
guard let details = self.authenticationService.homeserverDetails() else { return homeserver }
if let issuer = details.authenticationIssuer(), let issuerURL = URL(string: issuer) {
homeserver.loginMode = .oidc(issuerURL)
} else if details.supportsPasswordLogin() {
homeserver.loginMode = .password
} else {
homeserver.loginMode = .unsupported
}
return homeserver
}
switch await task.result {
case .success(let homeserver):
self.homeserver = homeserver
return .success(())
case .failure(let error):
MXLog.error("Failed configuring a server: \(error)")
return .failure(.invalidHomeserverAddress)
}
}
func login(username: String, password: String) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
Benchmark.startTrackingForIdentifier("Login", message: "Started new login")
// Workaround whilst the SDK requires a full MXID.
let username = username.isMatrixUserID ? username : "@\(username):\(homeserver.address)"
let basePath = userSessionStore.baseDirectoryPath(for: username)
let builder = ClientBuilder()
.basePath(path: basePath)
.username(username: username)
let loginTask: Task<Client, Error> = Task.detached {
let client = try builder.build()
try client.login(username: username, password: password)
return client
try self.authenticationService.login(username: username, password: password)
}
switch await loginTask.result {
@@ -55,7 +74,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
Benchmark.endTrackingForIdentifier("Login", message: "Login failed")
MXLog.error("Failed logging in with error: \(error)")
guard let error = error as? ClientError else { return .failure(.failedLoggingIn) }
guard let error = error as? AuthenticationError else { return .failure(.failedLoggingIn) }
switch error.code {
case .forbidden:
@@ -1,5 +1,5 @@
//
// AuthenticationServiceProtocol.swift
// AuthenticationServiceProxyProtocol.swift
// ElementX
//
// Created by Doug on 29/06/2022.
@@ -11,16 +11,17 @@ import Foundation
enum AuthenticationServiceError: Error {
case invalidServer
case invalidCredentials
case invalidHomeserverAddress
case accountDeactivated
case failedLoggingIn
}
@MainActor
protocol AuthenticationServiceProtocol {
protocol AuthenticationServiceProxyProtocol {
var homeserver: LoginHomeserver { get }
/// Sets up the service for login on the specified homeserver address.
func startLogin(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError>
func configure(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError>
/// Performs a password login using the current homeserver.
func login(username: String, password: String) async -> Result<UserSessionProtocol, AuthenticationServiceError>
}
@@ -8,11 +8,11 @@
import Foundation
class MockAuthenticationService: AuthenticationServiceProtocol {
class MockAuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
let validCredentials = (username: "alice", password: "12345678")
private(set) var homeserver: LoginHomeserver = .mockMatrixDotOrg
func startLogin(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError> {
func configure(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError> {
// Map the address to the mock homeservers
if LoginHomeserver.mockMatrixDotOrg.address.contains(homeserverAddress) {
homeserver = .mockMatrixDotOrg
@@ -15,7 +15,7 @@ enum MatrixErrorCode: String, CaseIterable {
case forbidden = "M_FORBIDDEN"
}
extension ClientError {
extension AuthenticationError {
var code: MatrixErrorCode {
guard case let .Generic(message) = self else { return .unknown }
@@ -0,0 +1,16 @@
//
// FileManager.swift
// ElementX
//
// Created by Doug on 19/07/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
extension FileManager {
/// The URL of the primary app group container.
var appGroupContainerURL: URL? {
containerURL(forSecurityApplicationGroupIdentifier: ElementInfoPlist.appGroupIdentifier)
}
}
@@ -16,42 +16,42 @@ class KeychainController: KeychainControllerProtocol {
keychain = Keychain(service: identifier)
}
func setAccessToken(_ accessToken: String, forUsername username: String) {
func setRestoreToken(_ restoreToken: String, forUsername username: String) {
do {
try keychain.set(accessToken, key: username)
try keychain.set(restoreToken, key: username)
} catch {
MXLog.error("Failed storing user access token with error: \(error)")
MXLog.error("Failed storing user restore token with error: \(error)")
}
}
func accessTokenForUsername(_ username: String) -> String? {
func restoreTokenForUsername(_ username: String) -> String? {
do {
return try keychain.get(username)
} catch {
MXLog.error("Failed retrieving user access token")
MXLog.error("Failed retrieving user restore token")
return nil
}
}
func accessTokens() -> [(username: String, accessToken: String)] {
func restoreTokens() -> [KeychainCredentials] {
keychain.allKeys().compactMap { username in
guard let accessToken = accessTokenForUsername(username) else {
guard let restoreToken = restoreTokenForUsername(username) else {
return nil
}
return (username, accessToken)
return KeychainCredentials(userID: username, restoreToken: restoreToken)
}
}
func removeAccessTokenForUsername(_ username: String) {
func removeRestoreTokenForUsername(_ username: String) {
do {
try keychain.remove(username)
} catch {
MXLog.error("Failed removing access token with error: \(error)")
MXLog.error("Failed removing restore token with error: \(error)")
}
}
func removeAllAccessTokens() {
func removeAllRestoreTokens() {
do {
try keychain.removeAll()
} catch {
@@ -8,10 +8,15 @@
import Foundation
protocol KeychainControllerProtocol {
func setAccessToken(_ accessToken: String, forUsername username: String)
func accessTokenForUsername(_ username: String) -> String?
func accessTokens() -> [(username: String, accessToken: String)]
func removeAccessTokenForUsername(_ username: String)
func removeAllAccessTokens()
struct KeychainCredentials {
let userID: String
let restoreToken: String
}
protocol KeychainControllerProtocol {
func setRestoreToken(_ accessToken: String, forUsername username: String)
func restoreTokenForUsername(_ username: String) -> String?
func restoreTokens() -> [KeychainCredentials]
func removeRestoreTokenForUsername(_ username: String)
func removeAllRestoreTokens()
}
@@ -23,7 +23,10 @@ class UserSessionStore: UserSessionStoreProtocol {
private let backgroundTaskService: BackgroundTaskServiceProtocol
/// Whether or not there are sessions in the store.
var hasSessions: Bool { !keychainController.accessTokens().isEmpty }
var hasSessions: Bool { !keychainController.restoreTokens().isEmpty }
/// The base directory where all session data is stored.
var baseDirectoryPath: String { baseDirectory().path }
init(bundleIdentifier: String, backgroundTaskService: BackgroundTaskServiceProtocol) {
keychainController = KeychainController(identifier: bundleIdentifier)
@@ -31,13 +34,13 @@ class UserSessionStore: UserSessionStoreProtocol {
}
func restoreUserSession() async -> Result<UserSession, UserSessionStoreError> {
let availableAccessTokens = keychainController.accessTokens()
let availableCredentials = keychainController.restoreTokens()
guard let usernameTokenTuple = availableAccessTokens.first else {
guard let credentials = availableCredentials.first else {
return .failure(.missingCredentials)
}
switch await restorePreviousLogin(usernameTokenTuple) {
switch await restorePreviousLogin(credentials) {
case .success(let clientProxy):
return .success(UserSession(clientProxy: clientProxy,
mediaProvider: MediaProvider(clientProxy: clientProxy,
@@ -47,8 +50,8 @@ class UserSessionStore: UserSessionStoreProtocol {
MXLog.error("Failed restoring login with error: \(error)")
// On any restoration failure reset the token and restart
keychainController.removeAllAccessTokens()
deleteBaseDirectory(for: usernameTokenTuple.username)
keychainController.removeAllRestoreTokens()
deleteSessionDirectory(for: credentials.userID)
return .failure(error)
}
@@ -68,22 +71,21 @@ class UserSessionStore: UserSessionStoreProtocol {
}
func logout(userSession: UserSessionProtocol) {
let username = userSession.clientProxy.userIdentifier
keychainController.removeAccessTokenForUsername(username)
deleteBaseDirectory(for: username)
let userID = userSession.clientProxy.userIdentifier
keychainController.removeRestoreTokenForUsername(userID)
deleteSessionDirectory(for: userID)
}
private func restorePreviousLogin(_ usernameTokenTuple: (username: String, accessToken: String)) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
private func restorePreviousLogin(_ credentials: KeychainCredentials) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
Benchmark.startTrackingForIdentifier("Login", message: "Started restoring previous login")
let basePath = baseDirectoryPath(for: usernameTokenTuple.username)
let builder = ClientBuilder()
.basePath(path: basePath)
.username(username: usernameTokenTuple.username)
.basePath(path: baseDirectoryPath)
.username(username: credentials.userID)
let loginTask: Task<Client, Error> = Task.detached {
let client = try builder.build()
try client.restoreLogin(restoreToken: usernameTokenTuple.accessToken)
try client.restoreLogin(restoreToken: credentials.restoreToken)
return client
}
@@ -101,7 +103,7 @@ class UserSessionStore: UserSessionStoreProtocol {
let accessToken = try client.restoreToken()
let userId = try client.userId()
keychainController.setAccessToken(accessToken, forUsername: userId)
keychainController.setRestoreToken(accessToken, forUsername: userId)
} catch {
MXLog.error("Failed setting up user session with error: \(error)")
return .failure(.failedSettingUpSession)
@@ -112,25 +114,30 @@ class UserSessionStore: UserSessionStoreProtocol {
return .success(clientProxy)
}
func baseDirectoryPath(for 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")
private func deleteSessionDirectory(for userID: String) {
// Rust sanitises the user ID replacing invalid characters with an _
let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_")
let url = baseDirectory().appendingPathComponent(sanitisedUserID)
do {
try FileManager.default.removeItem(at: url)
} catch {
MXLog.failure("Failed deleting the session data: \(error)")
}
}
func baseDirectory() -> URL {
guard let appGroupContainerURL = FileManager.default.appGroupContainerURL else {
fatalError("Should always be able to retrieve the container directory")
}
url = url.appendingPathComponent(username)
let url = appGroupContainerURL
.appendingPathComponent("Library", isDirectory: true)
.appendingPathComponent("Caches", isDirectory: true)
.appendingPathComponent("Sessions", isDirectory: true)
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
return url.path
}
private func deleteBaseDirectory(for username: 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.removeItem(at: url)
return url
}
}
@@ -28,6 +28,9 @@ protocol UserSessionStoreProtocol {
/// Whether or not there are sessions in the store.
var hasSessions: Bool { get }
/// Returns the location to store user data for a particular username.
var baseDirectoryPath: String { get }
/// Restores an existing user session.
func restoreUserSession() async -> Result<UserSession, UserSessionStoreError>
@@ -36,7 +39,4 @@ protocol UserSessionStoreProtocol {
/// Logs out of the specified session.
func logout(userSession: UserSessionProtocol)
/// Returns the location to store user data for a particular username.
func baseDirectoryPath(for username: String) -> String
}
+4 -4
View File
@@ -60,16 +60,16 @@ class MockScreen: Identifiable {
lazy var coordinator: Coordinator & Presentable = {
switch id {
case .login:
return LoginCoordinator(parameters: .init(authenticationService: MockAuthenticationService(),
return LoginCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
navigationRouter: navigationRouter))
case .serverSelection:
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationService(),
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
hasModalPresentation: true))
case .serverSelectionNonModal:
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationService(),
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
hasModalPresentation: false))
case .authenticationFlow:
return AuthenticationCoordinator(authenticationService: MockAuthenticationService(),
return AuthenticationCoordinator(authenticationService: MockAuthenticationServiceProxy(),
navigationRouter: navigationRouter)
case .simpleRegular:
return TemplateCoordinator(parameters: .init(promptType: .regular))