mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Merge the AuthenticationService with the QRCodeLoginService. (#4323)
* Merge the AuthenticationServer with the QRCodeLoginService. * Merge AuthenticationClientBuilderFactory and AuthenticationClientBuilder into AuthenticationClientFactory The separation is no longer needed now that password/OIDC login and QR code login have a similar API shape.
This commit is contained in:
@@ -546,13 +546,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
encryptionKeyProvider: encryptionKeyProvider,
|
||||
appSettings: appSettings,
|
||||
appHooks: appHooks)
|
||||
let qrCodeLoginService = QRCodeLoginService(encryptionKeyProvider: encryptionKeyProvider,
|
||||
userSessionStore: userSessionStore,
|
||||
appSettings: appSettings,
|
||||
appHooks: appHooks)
|
||||
|
||||
let coordinator = AuthenticationFlowCoordinator(authenticationService: authenticationService,
|
||||
qrCodeLoginService: qrCodeLoginService,
|
||||
bugReportService: ServiceLocator.shared.bugReportService,
|
||||
navigationRootCoordinator: navigationRootCoordinator,
|
||||
appMediator: appMediator,
|
||||
|
||||
@@ -23,7 +23,6 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let appSettings: AppSettings
|
||||
private let analytics: AnalyticsService
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let qrCodeLoginService: QRCodeLoginServiceProtocol
|
||||
|
||||
enum State: StateType {
|
||||
/// The state machine hasn't started.
|
||||
@@ -102,7 +101,6 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
weak var delegate: AuthenticationFlowCoordinatorDelegate?
|
||||
|
||||
init(authenticationService: AuthenticationServiceProtocol,
|
||||
qrCodeLoginService: QRCodeLoginServiceProtocol,
|
||||
bugReportService: BugReportServiceProtocol,
|
||||
navigationRootCoordinator: NavigationRootCoordinator,
|
||||
appMediator: AppMediatorProtocol,
|
||||
@@ -116,7 +114,6 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self.appSettings = appSettings
|
||||
self.analytics = analytics
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.qrCodeLoginService = qrCodeLoginService
|
||||
|
||||
navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
@@ -300,7 +297,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
// MARK: - QR Code
|
||||
|
||||
private func showQRCodeLoginScreen() {
|
||||
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(qrCodeLoginService: qrCodeLoginService,
|
||||
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(qrCodeLoginService: authenticationService,
|
||||
canSignInManually: appSettings.allowOtherAccountProviders, // No need to worry about provisioning links as we hide QR login.
|
||||
orientationManager: appMediator.windowManager,
|
||||
appMediator: appMediator))
|
||||
|
||||
@@ -8,15 +8,39 @@
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
extension AuthenticationClientBuilderFactoryMock {
|
||||
extension AuthenticationClientFactoryMock {
|
||||
struct Configuration {
|
||||
var builderConfiguration: AuthenticationClientBuilderMock.Configuration = .init()
|
||||
var homeserverClients = [
|
||||
"matrix.org": ClientSDKMock(configuration: .init()),
|
||||
"example.com": ClientSDKMock(configuration: .init(serverAddress: "example.com",
|
||||
homeserverURL: "https://matrix.example.com",
|
||||
slidingSyncVersion: .native,
|
||||
oidcLoginURL: nil,
|
||||
supportsOIDCCreatePrompt: false,
|
||||
supportsPasswordLogin: true)),
|
||||
"company.com": ClientSDKMock(configuration: .init(serverAddress: "company.com",
|
||||
homeserverURL: "https://matrix.company.com",
|
||||
slidingSyncVersion: .native,
|
||||
oidcLoginURL: "https://auth.company.com/oidc",
|
||||
supportsOIDCCreatePrompt: false,
|
||||
supportsPasswordLogin: false)),
|
||||
"server.net": ClientSDKMock(configuration: .init(serverAddress: "server.net",
|
||||
homeserverURL: "https://matrix.example.com",
|
||||
slidingSyncVersion: .native,
|
||||
oidcLoginURL: nil,
|
||||
supportsOIDCCreatePrompt: false,
|
||||
supportsPasswordLogin: false))
|
||||
]
|
||||
}
|
||||
|
||||
convenience init(configuration: Configuration) {
|
||||
self.init()
|
||||
|
||||
let clientBuilder = AuthenticationClientBuilderMock(configuration: configuration.builderConfiguration)
|
||||
makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValue = clientBuilder
|
||||
makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure = { address, _, _, _, _, _ in
|
||||
guard let client = configuration.homeserverClients[address] else {
|
||||
throw ClientBuildError.ServerUnreachable(message: "Not a known homeserver.")
|
||||
}
|
||||
return client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
extension AuthenticationClientBuilderMock {
|
||||
struct Configuration {
|
||||
var homeserverClients = [
|
||||
"matrix.org": ClientSDKMock(configuration: .init()),
|
||||
"example.com": ClientSDKMock(configuration: .init(serverAddress: "example.com",
|
||||
homeserverURL: "https://matrix.example.com",
|
||||
slidingSyncVersion: .native,
|
||||
oidcLoginURL: nil,
|
||||
supportsOIDCCreatePrompt: false,
|
||||
supportsPasswordLogin: true)),
|
||||
"company.com": ClientSDKMock(configuration: .init(serverAddress: "company.com",
|
||||
homeserverURL: "https://matrix.company.com",
|
||||
slidingSyncVersion: .native,
|
||||
oidcLoginURL: "https://auth.company.com/oidc",
|
||||
supportsOIDCCreatePrompt: false,
|
||||
supportsPasswordLogin: false)),
|
||||
"server.net": ClientSDKMock(configuration: .init(serverAddress: "server.net",
|
||||
homeserverURL: "https://matrix.example.com",
|
||||
slidingSyncVersion: .native,
|
||||
oidcLoginURL: nil,
|
||||
supportsOIDCCreatePrompt: false,
|
||||
supportsPasswordLogin: false))
|
||||
]
|
||||
}
|
||||
|
||||
convenience init(configuration: Configuration) {
|
||||
self.init()
|
||||
|
||||
buildHomeserverAddressClosure = { address in
|
||||
guard let client = configuration.homeserverClients[address] else {
|
||||
throw ClientBuildError.ServerUnreachable(message: "Not a known homeserver.")
|
||||
}
|
||||
return client
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1823,19 +1823,20 @@ class AudioSessionMock: AudioSessionProtocol, @unchecked Sendable {
|
||||
try setActiveOptionsClosure?(active, options)
|
||||
}
|
||||
}
|
||||
class AuthenticationClientBuilderFactoryMock: AuthenticationClientBuilderFactoryProtocol, @unchecked Sendable {
|
||||
class AuthenticationClientFactoryMock: AuthenticationClientFactoryProtocol, @unchecked Sendable {
|
||||
|
||||
//MARK: - makeBuilder
|
||||
//MARK: - makeClient
|
||||
|
||||
var makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = 0
|
||||
var makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount: Int {
|
||||
var makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksThrowableError: Error?
|
||||
var makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = 0
|
||||
var makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount
|
||||
return makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount
|
||||
returnValue = makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -1843,103 +1844,29 @@ class AuthenticationClientBuilderFactoryMock: AuthenticationClientBuilderFactory
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = newValue
|
||||
makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = newValue
|
||||
makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCalled: Bool {
|
||||
return makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount > 0
|
||||
var makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCalled: Bool {
|
||||
return makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount > 0
|
||||
}
|
||||
var makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments: (sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)?
|
||||
var makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocations: [(sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] = []
|
||||
var makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments: (homeserverAddress: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)?
|
||||
var makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocations: [(homeserverAddress: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] = []
|
||||
|
||||
var makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue: AuthenticationClientBuilderProtocol!
|
||||
var makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValue: AuthenticationClientBuilderProtocol! {
|
||||
var makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue: ClientProtocol!
|
||||
var makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValue: ClientProtocol! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: AuthenticationClientBuilderProtocol? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure: ((SessionDirectories, String, ClientSessionDelegate, AppSettings, AppHooks) -> AuthenticationClientBuilderProtocol)?
|
||||
|
||||
func makeBuilder(sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks) -> AuthenticationClientBuilderProtocol {
|
||||
makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount += 1
|
||||
makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments = (sessionDirectories: sessionDirectories, passphrase: passphrase, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)
|
||||
DispatchQueue.main.async {
|
||||
self.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocations.append((sessionDirectories: sessionDirectories, passphrase: passphrase, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks))
|
||||
}
|
||||
if let makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure = makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure {
|
||||
return makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure(sessionDirectories, passphrase, clientSessionDelegate, appSettings, appHooks)
|
||||
} else {
|
||||
return makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
class AuthenticationClientBuilderMock: AuthenticationClientBuilderProtocol, @unchecked Sendable {
|
||||
|
||||
//MARK: - build
|
||||
|
||||
var buildHomeserverAddressThrowableError: Error?
|
||||
var buildHomeserverAddressUnderlyingCallsCount = 0
|
||||
var buildHomeserverAddressCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return buildHomeserverAddressUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = buildHomeserverAddressUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
buildHomeserverAddressUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
buildHomeserverAddressUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var buildHomeserverAddressCalled: Bool {
|
||||
return buildHomeserverAddressCallsCount > 0
|
||||
}
|
||||
var buildHomeserverAddressReceivedHomeserverAddress: String?
|
||||
var buildHomeserverAddressReceivedInvocations: [String] = []
|
||||
|
||||
var buildHomeserverAddressUnderlyingReturnValue: ClientProtocol!
|
||||
var buildHomeserverAddressReturnValue: ClientProtocol! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return buildHomeserverAddressUnderlyingReturnValue
|
||||
return makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: ClientProtocol? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = buildHomeserverAddressUnderlyingReturnValue
|
||||
returnValue = makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -1947,29 +1874,29 @@ class AuthenticationClientBuilderMock: AuthenticationClientBuilderProtocol, @unc
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
buildHomeserverAddressUnderlyingReturnValue = newValue
|
||||
makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
buildHomeserverAddressUnderlyingReturnValue = newValue
|
||||
makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var buildHomeserverAddressClosure: ((String) async throws -> ClientProtocol)?
|
||||
var makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure: ((String, SessionDirectories, String, ClientSessionDelegate, AppSettings, AppHooks) async throws -> ClientProtocol)?
|
||||
|
||||
func build(homeserverAddress: String) async throws -> ClientProtocol {
|
||||
if let error = buildHomeserverAddressThrowableError {
|
||||
func makeClient(homeserverAddress: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks) async throws -> ClientProtocol {
|
||||
if let error = makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksThrowableError {
|
||||
throw error
|
||||
}
|
||||
buildHomeserverAddressCallsCount += 1
|
||||
buildHomeserverAddressReceivedHomeserverAddress = homeserverAddress
|
||||
makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount += 1
|
||||
makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments = (homeserverAddress: homeserverAddress, sessionDirectories: sessionDirectories, passphrase: passphrase, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)
|
||||
DispatchQueue.main.async {
|
||||
self.buildHomeserverAddressReceivedInvocations.append(homeserverAddress)
|
||||
self.makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocations.append((homeserverAddress: homeserverAddress, sessionDirectories: sessionDirectories, passphrase: passphrase, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks))
|
||||
}
|
||||
if let buildHomeserverAddressClosure = buildHomeserverAddressClosure {
|
||||
return try await buildHomeserverAddressClosure(homeserverAddress)
|
||||
if let makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure = makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure {
|
||||
return try await makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure(homeserverAddress, sessionDirectories, passphrase, clientSessionDelegate, appSettings, appHooks)
|
||||
} else {
|
||||
return buildHomeserverAddressReturnValue
|
||||
return makeClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13192,13 +13119,13 @@ class QRCodeLoginServiceMock: QRCodeLoginServiceProtocol, @unchecked Sendable {
|
||||
var loginWithQRCodeDataReceivedData: Data?
|
||||
var loginWithQRCodeDataReceivedInvocations: [Data] = []
|
||||
|
||||
var loginWithQRCodeDataUnderlyingReturnValue: Result<UserSessionProtocol, QRCodeLoginServiceError>!
|
||||
var loginWithQRCodeDataReturnValue: Result<UserSessionProtocol, QRCodeLoginServiceError>! {
|
||||
var loginWithQRCodeDataUnderlyingReturnValue: Result<UserSessionProtocol, AuthenticationServiceError>!
|
||||
var loginWithQRCodeDataReturnValue: Result<UserSessionProtocol, AuthenticationServiceError>! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return loginWithQRCodeDataUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: Result<UserSessionProtocol, QRCodeLoginServiceError>? = nil
|
||||
var returnValue: Result<UserSessionProtocol, AuthenticationServiceError>? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = loginWithQRCodeDataUnderlyingReturnValue
|
||||
}
|
||||
@@ -13216,9 +13143,9 @@ class QRCodeLoginServiceMock: QRCodeLoginServiceProtocol, @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
}
|
||||
var loginWithQRCodeDataClosure: ((Data) async -> Result<UserSessionProtocol, QRCodeLoginServiceError>)?
|
||||
var loginWithQRCodeDataClosure: ((Data) async -> Result<UserSessionProtocol, AuthenticationServiceError>)?
|
||||
|
||||
func loginWithQRCode(data: Data) async -> Result<UserSessionProtocol, QRCodeLoginServiceError> {
|
||||
func loginWithQRCode(data: Data) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||
loginWithQRCodeDataCallsCount += 1
|
||||
loginWithQRCodeDataReceivedData = data
|
||||
DispatchQueue.main.async {
|
||||
|
||||
@@ -110,13 +110,15 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
case let .success(session):
|
||||
MXLog.info("QR Login completed")
|
||||
actionsSubject.send(.done(userSession: session))
|
||||
case .failure(let error):
|
||||
handleError(error: error)
|
||||
case .failure(.qrCodeError(let error)):
|
||||
handleError(error)
|
||||
case .failure:
|
||||
handleError(.unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleError(error: QRCodeLoginServiceError) {
|
||||
private func handleError(_ error: QRCodeLoginError) {
|
||||
MXLog.error("Failed to scan the QR code: \(error)")
|
||||
switch error {
|
||||
case .invalidQRCode:
|
||||
@@ -137,7 +139,7 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
state.state = .error(.expired)
|
||||
case .deviceNotSupported:
|
||||
state.state = .error(.deviceNotSupported)
|
||||
case .failedLoggingIn, .unknown:
|
||||
case .unknown:
|
||||
state.state = .error(.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol AuthenticationClientBuilderProtocol {
|
||||
func build(homeserverAddress: String) async throws -> ClientProtocol
|
||||
}
|
||||
|
||||
/// A wrapper around `ClientBuilder` to share reusable code between Normal and QR logins.
|
||||
struct AuthenticationClientBuilder: AuthenticationClientBuilderProtocol {
|
||||
let sessionDirectories: SessionDirectories
|
||||
let passphrase: String
|
||||
let clientSessionDelegate: ClientSessionDelegate
|
||||
|
||||
let appSettings: AppSettings
|
||||
let appHooks: AppHooks
|
||||
|
||||
/// Builds a Client for login using OIDC or password authentication.
|
||||
func build(homeserverAddress: String) async throws -> ClientProtocol {
|
||||
try await makeClientBuilder().serverNameOrHomeserverUrl(serverNameOrUrl: homeserverAddress).build()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// The base builder configuration used for authentication within the app.
|
||||
private func makeClientBuilder() -> ClientBuilder {
|
||||
ClientBuilder
|
||||
.baseBuilder(httpProxy: appSettings.websiteURL.globalProxy,
|
||||
slidingSync: .discover,
|
||||
sessionDelegate: clientSessionDelegate,
|
||||
appHooks: appHooks,
|
||||
enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode,
|
||||
enableKeyShareOnInvite: appSettings.enableKeyShareOnInvite,
|
||||
threadsEnabled: appSettings.threadsEnabled)
|
||||
.sessionPaths(dataPath: sessionDirectories.dataPath,
|
||||
cachePath: sessionDirectories.cachePath)
|
||||
.sessionPassphrase(passphrase: passphrase)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol AuthenticationClientBuilderFactoryProtocol {
|
||||
func makeBuilder(sessionDirectories: SessionDirectories,
|
||||
passphrase: String,
|
||||
clientSessionDelegate: ClientSessionDelegate,
|
||||
appSettings: AppSettings,
|
||||
appHooks: AppHooks) -> AuthenticationClientBuilderProtocol
|
||||
}
|
||||
|
||||
/// A wrapper around `ClientBuilder` to share reusable code between Normal and QR logins.
|
||||
struct AuthenticationClientBuilderFactory: AuthenticationClientBuilderFactoryProtocol {
|
||||
func makeBuilder(sessionDirectories: SessionDirectories,
|
||||
passphrase: String,
|
||||
clientSessionDelegate: ClientSessionDelegate,
|
||||
appSettings: AppSettings,
|
||||
appHooks: AppHooks) -> AuthenticationClientBuilderProtocol {
|
||||
AuthenticationClientBuilder(sessionDirectories: sessionDirectories,
|
||||
passphrase: passphrase,
|
||||
clientSessionDelegate: clientSessionDelegate,
|
||||
appSettings: appSettings,
|
||||
appHooks: appHooks)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol AuthenticationClientFactoryProtocol {
|
||||
func makeClient(homeserverAddress: String,
|
||||
sessionDirectories: SessionDirectories,
|
||||
passphrase: String,
|
||||
clientSessionDelegate: ClientSessionDelegate,
|
||||
appSettings: AppSettings,
|
||||
appHooks: AppHooks) async throws -> ClientProtocol
|
||||
}
|
||||
|
||||
/// A wrapper around `ClientBuilder` to allow for mocked clients to be injected into authentication tests.
|
||||
struct AuthenticationClientFactory: AuthenticationClientFactoryProtocol {
|
||||
func makeClient(homeserverAddress: String,
|
||||
sessionDirectories: SessionDirectories,
|
||||
passphrase: String,
|
||||
clientSessionDelegate: ClientSessionDelegate,
|
||||
appSettings: AppSettings,
|
||||
appHooks: AppHooks) async throws -> ClientProtocol {
|
||||
try await ClientBuilder
|
||||
.baseBuilder(httpProxy: appSettings.websiteURL.globalProxy,
|
||||
slidingSync: .discover,
|
||||
sessionDelegate: clientSessionDelegate,
|
||||
appHooks: appHooks,
|
||||
enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode,
|
||||
enableKeyShareOnInvite: appSettings.enableKeyShareOnInvite,
|
||||
threadsEnabled: appSettings.threadsEnabled)
|
||||
.sessionPaths(dataPath: sessionDirectories.dataPath,
|
||||
cachePath: sessionDirectories.cachePath)
|
||||
.sessionPassphrase(passphrase: passphrase)
|
||||
.serverNameOrHomeserverUrl(serverNameOrUrl: homeserverAddress)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
private var sessionDirectories: SessionDirectories
|
||||
private let passphrase: String
|
||||
|
||||
private let clientBuilderFactory: AuthenticationClientBuilderFactoryProtocol
|
||||
private let clientFactory: AuthenticationClientFactoryProtocol
|
||||
private let userSessionStore: UserSessionStoreProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let appHooks: AppHooks
|
||||
@@ -23,14 +23,19 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { homeserverSubject.asCurrentValuePublisher() }
|
||||
private(set) var flow: AuthenticationFlow
|
||||
|
||||
private let qrLoginProgressSubject = PassthroughSubject<QrLoginProgress, Never>()
|
||||
var qrLoginProgressPublisher: AnyPublisher<QrLoginProgress, Never> {
|
||||
qrLoginProgressSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(userSessionStore: UserSessionStoreProtocol,
|
||||
encryptionKeyProvider: EncryptionKeyProviderProtocol,
|
||||
clientBuilderFactory: AuthenticationClientBuilderFactoryProtocol = AuthenticationClientBuilderFactory(),
|
||||
clientFactory: AuthenticationClientFactoryProtocol = AuthenticationClientFactory(),
|
||||
appSettings: AppSettings,
|
||||
appHooks: AppHooks) {
|
||||
sessionDirectories = .init()
|
||||
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
|
||||
self.clientBuilderFactory = clientBuilderFactory
|
||||
self.clientFactory = clientFactory
|
||||
self.userSessionStore = userSessionStore
|
||||
self.appSettings = appSettings
|
||||
self.appHooks = appHooks
|
||||
@@ -46,7 +51,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
do {
|
||||
var homeserver = LoginHomeserver(address: homeserverAddress, loginMode: .unknown)
|
||||
|
||||
let client = try await makeClientBuilder().build(homeserverAddress: homeserverAddress)
|
||||
let client = try await makeClient(homeserverAddress: homeserverAddress)
|
||||
let loginDetails = await client.homeserverLoginDetails()
|
||||
|
||||
MXLog.info("Sliding sync: \(client.slidingSyncVersion())")
|
||||
@@ -146,6 +151,45 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func loginWithQRCode(data: Data) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||
let qrData: QrCodeData
|
||||
do {
|
||||
qrData = try QrCodeData.fromBytes(bytes: data)
|
||||
} catch {
|
||||
MXLog.error("QRCode decode error: \(error)")
|
||||
return .failure(.qrCodeError(.invalidQRCode))
|
||||
}
|
||||
|
||||
guard let scannedServerName = qrData.serverName() else {
|
||||
MXLog.error("The QR code is from a device that is not yet signed in.")
|
||||
return .failure(.qrCodeError(.deviceNotSignedIn))
|
||||
}
|
||||
|
||||
if !appSettings.allowOtherAccountProviders, !appSettings.accountProviders.contains(scannedServerName) {
|
||||
MXLog.error("The scanned device's server is not allowed: \(scannedServerName)")
|
||||
return .failure(.qrCodeError(.providerNotAllowed(scannedProvider: scannedServerName, allowedProviders: appSettings.accountProviders)))
|
||||
}
|
||||
|
||||
let listener = SDKListener { [weak self] progress in
|
||||
self?.qrLoginProgressSubject.send(progress)
|
||||
}
|
||||
|
||||
do {
|
||||
let client = try await makeClient(homeserverAddress: scannedServerName)
|
||||
try await client.loginWithQrCode(qrCodeData: qrData,
|
||||
oidcConfiguration: appSettings.oidcConfiguration.rustValue,
|
||||
progressListener: listener)
|
||||
MXLog.info("Sliding sync: \(client.slidingSyncVersion())")
|
||||
return await userSession(for: client)
|
||||
} catch let error as HumanQrLoginError {
|
||||
MXLog.error("QRCode login error: \(error)")
|
||||
return .failure(error.serviceError)
|
||||
} catch {
|
||||
MXLog.error("QRCode login unknown error: \(error)")
|
||||
return .failure(.qrCodeError(.unknown))
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
homeserverSubject.send(LoginHomeserver(address: appSettings.accountProviders[0], loginMode: .unknown))
|
||||
flow = .login
|
||||
@@ -154,16 +198,17 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func makeClientBuilder() -> AuthenticationClientBuilderProtocol {
|
||||
private func makeClient(homeserverAddress: String) async throws -> ClientProtocol {
|
||||
// Use a fresh session directory each time the user enters a different server
|
||||
// so that caches (e.g. server versions) are always fresh for the new server.
|
||||
rotateSessionDirectory()
|
||||
|
||||
return clientBuilderFactory.makeBuilder(sessionDirectories: sessionDirectories,
|
||||
passphrase: passphrase,
|
||||
clientSessionDelegate: userSessionStore.clientSessionDelegate,
|
||||
appSettings: appSettings,
|
||||
appHooks: appHooks)
|
||||
return try await clientFactory.makeClient(homeserverAddress: homeserverAddress,
|
||||
sessionDirectories: sessionDirectories,
|
||||
passphrase: passphrase,
|
||||
clientSessionDelegate: userSessionStore.clientSessionDelegate,
|
||||
appSettings: appSettings,
|
||||
appHooks: appHooks)
|
||||
}
|
||||
|
||||
private func rotateSessionDirectory() {
|
||||
@@ -181,13 +226,36 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private extension HumanQrLoginError {
|
||||
var serviceError: AuthenticationServiceError {
|
||||
switch self {
|
||||
case .Cancelled:
|
||||
.qrCodeError(.cancelled)
|
||||
case .ConnectionInsecure:
|
||||
.qrCodeError(.connectionInsecure)
|
||||
case .Declined:
|
||||
.qrCodeError(.declined)
|
||||
case .LinkingNotSupported:
|
||||
.qrCodeError(.linkingNotSupported)
|
||||
case .Expired:
|
||||
.qrCodeError(.expired)
|
||||
case .SlidingSyncNotAvailable:
|
||||
.qrCodeError(.deviceNotSupported)
|
||||
case .OtherDeviceNotSignedIn:
|
||||
.qrCodeError(.deviceNotSignedIn)
|
||||
case .Unknown, .OidcMetadataInvalid:
|
||||
.qrCodeError(.unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mocks
|
||||
|
||||
extension AuthenticationService {
|
||||
static var mock: AuthenticationService {
|
||||
AuthenticationService(userSessionStore: UserSessionStoreMock(configuration: .init()),
|
||||
encryptionKeyProvider: EncryptionKeyProvider(),
|
||||
clientBuilderFactory: AuthenticationClientBuilderFactoryMock(configuration: .init()),
|
||||
clientFactory: AuthenticationClientFactoryMock(configuration: .init()),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks())
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
@@ -19,6 +20,9 @@ enum AuthenticationFlow {
|
||||
enum AuthenticationServiceError: Error, Equatable {
|
||||
/// An error occurred during OIDC authentication.
|
||||
case oidcError(OIDCError)
|
||||
/// An error occurred during login with QR Code.
|
||||
case qrCodeError(QRCodeLoginError)
|
||||
|
||||
case invalidServer
|
||||
case invalidCredentials
|
||||
case invalidHomeserverAddress
|
||||
@@ -32,7 +36,7 @@ enum AuthenticationServiceError: Error, Equatable {
|
||||
case failedUsingWebCredentials
|
||||
}
|
||||
|
||||
protocol AuthenticationServiceProtocol {
|
||||
protocol AuthenticationServiceProtocol: QRCodeLoginServiceProtocol {
|
||||
/// The currently configured homeserver.
|
||||
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { get }
|
||||
/// The type of flow the service is currently configured with.
|
||||
@@ -86,3 +90,25 @@ extension OAuthAuthorizationData: @retroactive Hashable {
|
||||
hasher.combine(loginUrl())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Login with QR code
|
||||
|
||||
enum QRCodeLoginError: Error, Equatable {
|
||||
case invalidQRCode
|
||||
case providerNotAllowed(scannedProvider: String, allowedProviders: [String])
|
||||
case cancelled
|
||||
case connectionInsecure
|
||||
case declined
|
||||
case linkingNotSupported
|
||||
case expired
|
||||
case deviceNotSupported
|
||||
case deviceNotSignedIn
|
||||
case unknown
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol QRCodeLoginServiceProtocol {
|
||||
var qrLoginProgressPublisher: AnyPublisher<QrLoginProgress, Never> { get }
|
||||
|
||||
func loginWithQRCode(data: Data) async -> Result<UserSessionProtocol, AuthenticationServiceError>
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
import MatrixRustSDK
|
||||
|
||||
final class QRCodeLoginService: QRCodeLoginServiceProtocol {
|
||||
private var sessionDirectories: SessionDirectories
|
||||
private let passphrase: String
|
||||
|
||||
private let userSessionStore: UserSessionStoreProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let appHooks: AppHooks
|
||||
|
||||
private let qrLoginProgressSubject = PassthroughSubject<QrLoginProgress, Never>()
|
||||
var qrLoginProgressPublisher: AnyPublisher<QrLoginProgress, Never> {
|
||||
qrLoginProgressSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(encryptionKeyProvider: EncryptionKeyProviderProtocol,
|
||||
userSessionStore: UserSessionStoreProtocol,
|
||||
appSettings: AppSettings,
|
||||
appHooks: AppHooks) {
|
||||
sessionDirectories = .init()
|
||||
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
|
||||
self.userSessionStore = userSessionStore
|
||||
self.appSettings = appSettings
|
||||
self.appHooks = appHooks
|
||||
}
|
||||
|
||||
func loginWithQRCode(data: Data) async -> Result<UserSessionProtocol, QRCodeLoginServiceError> {
|
||||
let qrData: QrCodeData
|
||||
do {
|
||||
qrData = try QrCodeData.fromBytes(bytes: data)
|
||||
} catch {
|
||||
MXLog.error("QRCode decode error: \(error)")
|
||||
return .failure(.invalidQRCode)
|
||||
}
|
||||
|
||||
guard let scannedServerName = qrData.serverName() else {
|
||||
MXLog.error("The QR code is from a device that is not yet signed in.")
|
||||
return .failure(.deviceNotSignedIn)
|
||||
}
|
||||
|
||||
if !appSettings.allowOtherAccountProviders, !appSettings.accountProviders.contains(scannedServerName) {
|
||||
MXLog.error("The scanned device's server is not allowed: \(scannedServerName)")
|
||||
return .failure(.providerNotAllowed(scannedProvider: scannedServerName, allowedProviders: appSettings.accountProviders))
|
||||
}
|
||||
|
||||
let listener = SDKListener { [weak self] progress in
|
||||
self?.qrLoginProgressSubject.send(progress)
|
||||
}
|
||||
|
||||
do {
|
||||
let client = try await makeClientBuilder().build(homeserverAddress: scannedServerName)
|
||||
try await client.loginWithQrCode(qrCodeData: qrData,
|
||||
oidcConfiguration: appSettings.oidcConfiguration.rustValue,
|
||||
progressListener: listener)
|
||||
MXLog.info("Sliding sync: \(client.slidingSyncVersion())")
|
||||
return await userSession(for: client)
|
||||
} catch let error as HumanQrLoginError {
|
||||
MXLog.error("QRCode login error: \(error)")
|
||||
return .failure(error.serviceError)
|
||||
} catch {
|
||||
MXLog.error("QRCode login unknown error: \(error)")
|
||||
return .failure(.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func makeClientBuilder() -> AuthenticationClientBuilder {
|
||||
// Use a fresh session directory each time the user scans a QR code to ensure caches
|
||||
// (e.g. server versions) are always fresh in case a different server is used.
|
||||
rotateSessionDirectory()
|
||||
|
||||
return AuthenticationClientBuilder(sessionDirectories: sessionDirectories,
|
||||
passphrase: passphrase,
|
||||
clientSessionDelegate: userSessionStore.clientSessionDelegate,
|
||||
appSettings: appSettings,
|
||||
appHooks: appHooks)
|
||||
}
|
||||
|
||||
private func rotateSessionDirectory() {
|
||||
sessionDirectories.delete()
|
||||
sessionDirectories = .init()
|
||||
}
|
||||
|
||||
private func userSession(for client: ClientProtocol) async -> Result<UserSessionProtocol, QRCodeLoginServiceError> {
|
||||
switch await userSessionStore.userSession(for: client, sessionDirectories: sessionDirectories, passphrase: passphrase) {
|
||||
case .success(let session):
|
||||
return .success(session)
|
||||
case .failure(let error):
|
||||
MXLog.error("QRCode login failed error: \(error)")
|
||||
return .failure(.failedLoggingIn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension HumanQrLoginError {
|
||||
var serviceError: QRCodeLoginServiceError {
|
||||
switch self {
|
||||
case .Cancelled:
|
||||
return .cancelled
|
||||
case .ConnectionInsecure:
|
||||
return .connectionInsecure
|
||||
case .Declined:
|
||||
return .declined
|
||||
case .LinkingNotSupported:
|
||||
return .linkingNotSupported
|
||||
case .Expired:
|
||||
return .expired
|
||||
case .SlidingSyncNotAvailable:
|
||||
return .deviceNotSupported
|
||||
case .OtherDeviceNotSignedIn:
|
||||
return .deviceNotSignedIn
|
||||
case .Unknown, .OidcMetadataInvalid:
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
import MatrixRustSDK
|
||||
|
||||
enum QRCodeLoginServiceError: Error {
|
||||
case failedLoggingIn
|
||||
case invalidQRCode
|
||||
case providerNotAllowed(scannedProvider: String, allowedProviders: [String])
|
||||
case cancelled
|
||||
case connectionInsecure
|
||||
case declined
|
||||
case linkingNotSupported
|
||||
case expired
|
||||
case deviceNotSupported
|
||||
case deviceNotSignedIn
|
||||
case unknown
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol QRCodeLoginServiceProtocol {
|
||||
var qrLoginProgressPublisher: AnyPublisher<QrLoginProgress, Never> { get }
|
||||
|
||||
func loginWithQRCode(data: Data) async -> Result<UserSessionProtocol, QRCodeLoginServiceError>
|
||||
}
|
||||
@@ -146,7 +146,6 @@ class MockScreen: Identifiable {
|
||||
}
|
||||
|
||||
let flowCoordinator = AuthenticationFlowCoordinator(authenticationService: AuthenticationService.mock,
|
||||
qrCodeLoginService: QRCodeLoginServiceMock(),
|
||||
bugReportService: BugReportServiceMock(.init()),
|
||||
navigationRootCoordinator: navigationRootCoordinator,
|
||||
appMediator: AppMediatorMock.default,
|
||||
|
||||
Reference in New Issue
Block a user