Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 40 additions & 46 deletions Riot/Modules/Authentication/AuthenticationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc

let flow: AuthenticationFlow = initialScreen == .login ? .login : .register
do {
let homeserverAddress = authenticationService.state.homeserver.addressFromUser ?? authenticationService.state.homeserver.address
try await authenticationService.startFlow(flow, for: homeserverAddress)
// Start the flow using the default server (or a provisioning link if set).
try await authenticationService.startFlow(flow)
} catch {
MXLog.error("[AuthenticationCoordinator] start: Failed to start")
displayError(message: error.localizedDescription)
MXLog.error("[AuthenticationCoordinator] start: Failed to start, showing server selection.")
showServerSelectionScreen(for: flow)
return
}

Expand All @@ -155,6 +155,42 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
}
}

/// Pushes the server selection screen into the flow (other screens may also present it modally later).
@MainActor private func showServerSelectionScreen(for flow: AuthenticationFlow) {
MXLog.debug("[AuthenticationCoordinator] showServerSelectionScreen")
let parameters = AuthenticationServerSelectionCoordinatorParameters(authenticationService: authenticationService,
flow: flow,
hasModalPresentation: false)
let coordinator = AuthenticationServerSelectionCoordinator(parameters: parameters)
coordinator.callback = { [weak self, weak coordinator] result in
guard let self = self, let coordinator = coordinator else { return }
self.serverSelectionCoordinator(coordinator, didCompleteWith: result, for: flow)
}

coordinator.start()
add(childCoordinator: coordinator)

navigationRouter.push(coordinator, animated: true) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}

/// Shows the next screen in the flow after the server selection screen.
@MainActor private func serverSelectionCoordinator(_ coordinator: AuthenticationServerSelectionCoordinator,
didCompleteWith result: AuthenticationServerSelectionCoordinatorResult,
for flow: AuthenticationFlow) {
switch result {
case .updated:
if flow == .register {
showRegistrationScreen()
} else {
showLoginScreen()
}
case .dismiss:
MXLog.failure("[AuthenticationCoordinator] AuthenticationServerSelectionScreen is requesting dismiss when part of a stack.")
}
}

/// Presents an alert on top of the navigation router with the supplied error message.
@MainActor private func displayError(message: String) {
let alert = UIAlertController(title: VectorL10n.error, message: message, preferredStyle: .alert)
Expand Down Expand Up @@ -307,48 +343,6 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc

// MARK: - Registration

#warning("Unused.")
/// Pushes the server selection screen into the flow (other screens may also present it modally later).
@MainActor private func showServerSelectionScreen() {
MXLog.debug("[AuthenticationCoordinator] showServerSelectionScreen")
let parameters = AuthenticationServerSelectionCoordinatorParameters(authenticationService: authenticationService,
flow: .register,
hasModalPresentation: false)
let coordinator = AuthenticationServerSelectionCoordinator(parameters: parameters)
coordinator.callback = { [weak self, weak coordinator] result in
guard let self = self, let coordinator = coordinator else { return }
self.serverSelectionCoordinator(coordinator, didCompleteWith: result)
}

coordinator.start()
add(childCoordinator: coordinator)

if navigationRouter.modules.isEmpty {
navigationRouter.setRootModule(coordinator) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
} else {
navigationRouter.push(coordinator, animated: true) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
}

/// Shows the next screen in the flow after the server selection screen.
@MainActor private func serverSelectionCoordinator(_ coordinator: AuthenticationServerSelectionCoordinator,
didCompleteWith result: AuthenticationServerSelectionCoordinatorResult) {
switch result {
case .updated:
if authenticationService.state.homeserver.needsRegistrationFallback {
showFallback(for: .register)
} else {
showRegistrationScreen()
}
case .dismiss:
MXLog.failure("[AuthenticationCoordinator] AuthenticationServerSelectionScreen is requesting dismiss when part of a stack.")
}
}

/// Shows the registration screen.
@MainActor private func showRegistrationScreen() {
MXLog.debug("[AuthenticationCoordinator] showRegistrationScreen")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class AuthenticationService: NSObject {
private(set) var loginWizard: LoginWizard?
/// The current registration wizard or `nil` if `startFlow` hasn't been called for `.registration`.
private(set) var registrationWizard: RegistrationWizard?
/// The provisioning link the service is currently configured with.
private(set) var provisioningLink: UniversalLink?

/// The authentication service's delegate.
weak var delegate: AuthenticationServiceDelegate?
Expand Down Expand Up @@ -110,6 +112,9 @@ class AuthenticationService: NSObject {
state = AuthenticationState(flow: flow,
homeserverAddress: hsUrl ?? BuildSettings.serverConfigDefaultHomeserverUrlString,
identityServer: isUrl ?? BuildSettings.serverConfigDefaultIdentityServerUrlString)

// store the link to override the default homeserver address.
provisioningLink = universalLink
delegate?.authenticationService(self, didUpdateStateWithLink: universalLink)
} else {
// logged in
Expand All @@ -133,8 +138,15 @@ class AuthenticationService: NSObject {
MXKAccountManager.shared().activeAccounts?.first?.mxSession
}

func startFlow(_ flow: AuthenticationFlow, for homeserverAddress: String) async throws {
var (client, homeserver) = try await loginFlow(for: homeserverAddress)
/// Set up the service to start a new authentication flow.
/// - Parameters:
/// - flow: The flow to be started (login or register).
/// - homeserverAddress: The homeserver to start the flow for, or `nil` to use the default.
/// If a provisioning link has been set, it will override the default homeserver when passing `nil`.
func startFlow(_ flow: AuthenticationFlow, for homeserverAddress: String? = nil) async throws {
let address = homeserverAddress ?? provisioningLink?.homeserverUrl ?? BuildSettings.serverConfigDefaultHomeserverUrlString

var (client, homeserver) = try await loginFlow(for: address)

let loginWizard = LoginWizard(client: client, sessionCreator: sessionCreator)
self.loginWizard = loginWizard
Expand Down Expand Up @@ -179,8 +191,13 @@ class AuthenticationService: NSObject {
loginWizard = nil
registrationWizard = nil
softLogoutCredentials = nil

if useDefaultServer {
provisioningLink = nil
}

// The previously used homeserver is re-used as `startFlow` will be called again a replace it anyway.
// This address will be replaced when `startFlow` is called, but for
// completeness revert to the default homeserver if requested anyway.
let address = useDefaultServer ? BuildSettings.serverConfigDefaultHomeserverUrlString : state.homeserver.addressFromUser ?? state.homeserver.address
let identityServer = state.identityServer
self.state = AuthenticationState(flow: .login,
Expand Down
33 changes: 33 additions & 0 deletions RiotTests/Modules/Authentication/AuthenticationServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,39 @@ import XCTest
"The address should reset to the value configured in the build settings.")
}

func testProvisioningLink() async throws {
// Given a service that has begun login using a provisioning link.
let homeserverURL = "https://example.com"
let provisioningLink = URL(string: "app.element.io/register/?hs_url=\(homeserverURL)")!
let universalLink = UniversalLink(url: provisioningLink)
service.handleServerProvisioningLink(universalLink)

try await service.startFlow(.login)
XCTAssertEqual(universalLink.homeserverUrl, homeserverURL)
XCTAssertNotNil(service.provisioningLink, "The provisioning link should be stored in the service.")
XCTAssertEqual(service.provisioningLink?.homeserverUrl, homeserverURL, "The provisioning link's homeserver should not change.")
XCTAssertEqual(service.state.homeserver.address, "https://matrix.example.com", "The actual homeserver address should be discovered.")
XCTAssertEqual(service.state.homeserver.addressFromUser, homeserverURL, "The address from the provisioning link should be stored.")

// When resetting the service.
service.reset()

// Then the link should be remembered.
XCTAssertNotNil(service.provisioningLink, "The provisioning link should not be cleared.")
XCTAssertEqual(service.provisioningLink?.homeserverUrl, homeserverURL, "The provisioning link's homeserver should not change.")
XCTAssertEqual(service.state.homeserver.address, homeserverURL, "The address from the provisioning link should be stored.")
XCTAssertNil(service.state.homeserver.addressFromUser, "There shouldn't be an address from the user after resetting the service.")

// When resetting the service back to the default server.
service.reset(useDefaultServer: true)

// Then the link should be forgotten.
XCTAssertNil(service.provisioningLink, "The provisioning link should be forgotten after resetting back to the default server.")
XCTAssertNil(service.state.homeserver.addressFromUser, "There shouldn't be an address from the user after resetting the service.")
XCTAssertEqual(service.state.homeserver.address, BuildSettings.serverConfigDefaultHomeserverUrlString,
"The address should reset to the value configured in the build settings.")
}

func testHomeserverState() async throws {
// Given a service that has begun login for one homeserver.
try await service.startFlow(.login, for: "https://example.com")
Expand Down
1 change: 1 addition & 0 deletions changelog.d/6489.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Authentication: Always start a new authentication flow with the default homeserver (or the provisioning link if set).