Add support for Account Provisioning links. (#4108)

* Add support for account provisioning links and route them to the authentication flow.

* Use the provisioning parameters to configure the authentication flow.

* Add UI tests for the provisioned authentication flow.

* Record new preview snapshots.

* Add unit tests.

* Make the domain configurable in the app settings.

* Use the loginHint in the login screen too.
This commit is contained in:
Doug
2025-05-12 13:28:34 +01:00
committed by GitHub
parent e795d07477
commit 876618d557
40 changed files with 698 additions and 159 deletions
@@ -11,6 +11,8 @@ import SwiftUI
struct LoginScreenCoordinatorParameters {
/// The service used to authenticate the user.
let authenticationService: AuthenticationServiceProtocol
/// An optional hint that can be used to pre-fill the form.
let loginHint: String?
let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
}
@@ -42,6 +44,7 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
self.parameters = parameters
viewModel = LoginScreenViewModel(authenticationService: parameters.authenticationService,
loginHint: parameters.loginHint,
userIndicatorController: parameters.userIndicatorController,
analytics: parameters.analytics)
}
@@ -21,13 +21,21 @@ class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtoc
}
init(authenticationService: AuthenticationServiceProtocol,
loginHint: String?,
userIndicatorController: UserIndicatorControllerProtocol,
analytics: AnalyticsService) {
self.authenticationService = authenticationService
self.userIndicatorController = userIndicatorController
self.analytics = analytics
let viewState = LoginScreenViewState(homeserver: authenticationService.homeserver.value)
let username = switch loginHint {
case .some(let hint) where hint.hasPrefix("mxid:"): String(hint.dropFirst(5)) // MSC4198
case .some(let hint): hint
case .none: ""
}
let viewState = LoginScreenViewState(homeserver: authenticationService.homeserver.value,
bindings: LoginScreenBindings(username: username))
super.init(initialViewState: viewState)
@@ -161,6 +161,7 @@ struct LoginScreen_Previews: PreviewProvider, TestablePreview {
Task { await authenticationService.configure(for: homeserverAddress, flow: .login) }
let viewModel = LoginScreenViewModel(authenticationService: authenticationService,
loginHint: nil,
userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics)
@@ -15,6 +15,8 @@ class OIDCAuthenticationPresenter: NSObject {
private let presentationAnchor: UIWindow
private let userIndicatorController: UserIndicatorControllerProtocol
private var activeSession: ASWebAuthenticationSession?
init(authenticationService: AuthenticationServiceProtocol,
oidcRedirectURL: URL,
presentationAnchor: UIWindow,
@@ -36,9 +38,12 @@ class OIDCAuthenticationPresenter: NSObject {
session.prefersEphemeralWebBrowserSession = false
session.presentationContextProvider = self
activeSession = session
session.start()
}
activeSession = nil
guard let url else {
// Check for user cancellation to avoid showing an alert in that instance.
if let nsError = error as? NSError,
@@ -76,6 +81,10 @@ class OIDCAuthenticationPresenter: NSObject {
}
}
func cancel() {
activeSession?.cancel()
}
private static let loadingIndicatorID = "\(OIDCAuthenticationPresenter.self)-Loading"
private func startLoading(delay: Duration? = nil) {
@@ -101,7 +101,7 @@ class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType,
startLoading() // Uses the same ID, so no need to worry if the indicator already exists
defer { stopLoading() }
switch await authenticationService.urlForOIDCLogin() {
switch await authenticationService.urlForOIDCLogin(loginHint: nil) {
case .success(let oidcData):
actionsSubject.send(.continueWithOIDC(data: oidcData, window: window))
case .failure:
@@ -134,7 +134,7 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
startLoading()
Task {
switch await authenticationService.urlForOIDCLogin() {
switch await authenticationService.urlForOIDCLogin(loginHint: nil) {
case .failure(let error):
stopLoading()
handleError(error)