Simplify authentication logic

matrix.org now supports MAS so we don't need the web registration helper anymore.
This commit is contained in:
Doug
2025-04-10 15:09:40 +01:00
committed by Doug
parent b2c647b1af
commit 1557054753
32 changed files with 86 additions and 709 deletions
@@ -16,10 +16,10 @@ class AuthenticationServiceTests: XCTestCase {
var service: AuthenticationService!
func testLogin() async {
setupMocks()
func testPasswordLogin() async {
setupMocks(serverAddress: "example.com")
switch await service.configure(for: "matrix.org", flow: .login) {
switch await service.configure(for: "example.com", flow: .login) {
case .success:
break
case .failure(let error):
@@ -27,7 +27,7 @@ class AuthenticationServiceTests: XCTestCase {
}
XCTAssertEqual(service.flow, .login)
XCTAssertEqual(service.homeserver.value, .mockMatrixDotOrg)
XCTAssertEqual(service.homeserver.value, .mockBasicServer)
switch await service.login(username: "alice", password: "12345678", initialDeviceName: nil, deviceID: nil) {
case .success:
@@ -40,7 +40,21 @@ class AuthenticationServiceTests: XCTestCase {
}
}
func testConfigureRegister() async {
func testConfigureLoginWithOIDC() async {
setupMocks()
switch await service.configure(for: "matrix.org", flow: .login) {
case .success:
break
case .failure(let error):
XCTFail("Unexpected failure: \(error)")
}
XCTAssertEqual(service.flow, .login)
XCTAssertEqual(service.homeserver.value, .mockMatrixDotOrg)
}
func testConfigureRegisterWithOIDC() async {
setupMocks()
switch await service.configure(for: "matrix.org", flow: .register) {
@@ -1,70 +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 XCTest
@testable import ElementX
@testable import MatrixRustSDK
class ClientProtocolTests: XCTestCase {
let server = "https://matrix.org"
let userIDServerName = "matrix.org"
let wellKnownURL = "https://matrix.org/.well-known/element/element.json"
var client: ClientProtocol!
func testWellKnownLoggedOut() async {
// Given a client that is logged out but has discovered a server.
let client = ClientSDKMock()
client.userIdServerNameThrowableError = MockError.notAvailable
client.serverReturnValue = server
// When discovering a server that contains the registration helper URL.
client.getUrlUrlClosure = { [wellKnownURL] url in
guard url == wellKnownURL else {
XCTFail("An unexpected URL was used.")
throw MockError.notAvailable
}
return "{\"registration_helper_url\":\"https://develop.element.io/#/mobile_register\"}"
}
guard case let .success(wellKnown) = await client.getElementWellKnown() else {
XCTFail("The request should succeed.")
return
}
// Then the well-known should include that URL.
XCTAssertEqual(wellKnown, .init(call: nil, registrationHelperUrl: "https://develop.element.io/#/mobile_register"))
}
func testWellKnownLoggedIn() async {
// Given a client that is logged in.
let client = ClientSDKMock()
client.userIdServerNameReturnValue = userIDServerName
// When discovering a server that contains a custom call widget URL.
client.getUrlUrlClosure = { [wellKnownURL] url in
guard url == wellKnownURL else {
XCTFail("An unexpected URL was used.")
throw MockError.notAvailable
}
return "{\"call\":{\"widget_url\":\"https://call.element.dev\"}}"
}
guard case let .success(wellKnown) = await client.getElementWellKnown() else {
XCTFail("The request should succeed.")
return
}
// Then the well-known should include that URL.
XCTAssertEqual(wellKnown, .init(call: .init(widgetUrl: "https://call.element.dev"), registrationHelperUrl: nil))
}
enum MockError: Error {
case notAvailable
}
}
@@ -17,7 +17,7 @@ class LoginScreenViewModelTests: XCTestCase {
var clientBuilderFactory: AuthenticationClientBuilderFactoryMock!
var service: AuthenticationServiceProtocol!
private func setupViewModel(homeserverAddress: String = "matrix.org") async {
private func setupViewModel(homeserverAddress: String = "example.com") async {
clientBuilderFactory = AuthenticationClientBuilderFactoryMock(configuration: .init())
service = AuthenticationService(userSessionStore: UserSessionStoreMock(configuration: .init()),
encryptionKeyProvider: EncryptionKeyProvider(),
@@ -36,18 +36,9 @@ class LoginScreenViewModelTests: XCTestCase {
analytics: ServiceLocator.shared.analytics)
}
func testMatrixDotOrg() async {
// Given the initial view model configured for matrix.org.
await setupViewModel()
// Then the view state should contain a homeserver that matches matrix.org and show the login form.
XCTAssertEqual(context.viewState.homeserver, .mockMatrixDotOrg, "The homeserver data should match the default homeserver.")
XCTAssertEqual(context.viewState.loginMode, .password, "The login form should be shown.")
}
func testBasicServer() async {
// Given the view model configured for a basic server example.com that only supports password authentication.
await setupViewModel(homeserverAddress: "example.com")
await setupViewModel()
// Then the view state should be updated with the homeserver and show the login form.
XCTAssertEqual(context.viewState.homeserver, .mockBasicServer, "The homeserver data should should match the new homeserver.")
@@ -89,9 +89,9 @@ class ServerConfirmationScreenViewModelTests: XCTestCase {
func testRegistrationNotSupportedAlert() async throws {
// Given a view model for registration using a service that hasn't been configured and the default server doesn't support registration.
setupViewModel(authenticationFlow: .register, supportsRegistrationHelper: false)
// Note: We don't currently take the create prompt into account when determining registration support.
setupViewModel(authenticationFlow: .register, supportsOIDC: false, supportsOIDCCreatePrompt: false)
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
XCTAssertFalse(service.homeserver.value.supportsRegistration)
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
XCTAssertNil(context.alertInfo)
@@ -100,16 +100,15 @@ class ServerConfirmationScreenViewModelTests: XCTestCase {
context.send(viewAction: .confirm)
try await deferred.fulfill()
// Then the configured homeserver should be used and no additional call should be made to the service.
// Then the configuration should fail with an alert about not supporting registration.
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 1)
XCTAssertEqual(context.alertInfo?.id, .registration)
}
func testLoginNotSupportedAlert() async throws {
// Given a view model for login using a service that hasn't been configured and the default server doesn't support login.
setupViewModel(authenticationFlow: .login, supportsRegistrationHelper: false, supportsPasswordLogin: false)
setupViewModel(authenticationFlow: .login, supportsOIDC: false, supportsOIDCCreatePrompt: false, supportsPasswordLogin: false)
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
XCTAssertFalse(service.homeserver.value.supportsRegistration)
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
XCTAssertNil(context.alertInfo)
@@ -125,14 +124,11 @@ class ServerConfirmationScreenViewModelTests: XCTestCase {
// MARK: - Helpers
private func setupViewModel(authenticationFlow: AuthenticationFlow, supportsRegistrationHelper: Bool = true, supportsPasswordLogin: Bool = true) {
private func setupViewModel(authenticationFlow: AuthenticationFlow, supportsOIDC: Bool = true, supportsOIDCCreatePrompt: Bool = true, supportsPasswordLogin: Bool = true) {
// Manually create a configuration as the default homeserver address setting is immutable.
let clientConfiguration: ClientSDKMock.Configuration = if supportsRegistrationHelper {
.init(supportsPasswordLogin: supportsPasswordLogin)
} else {
.init(supportsPasswordLogin: supportsPasswordLogin, elementWellKnown: "")
}
let client = ClientSDKMock(configuration: clientConfiguration)
let client = ClientSDKMock(configuration: .init(oidcLoginURL: supportsOIDC ? "https://account.matrix.org/authorize" : nil,
supportsOIDCCreatePrompt: supportsOIDCCreatePrompt,
supportsPasswordLogin: supportsPasswordLogin))
let configuration = AuthenticationClientBuilderMock.Configuration(homeserverClients: ["matrix.org": client],
qrCodeClient: client)
@@ -21,7 +21,6 @@ class ServerSelectionScreenViewModelTests: XCTestCase {
// Given a view model for login.
setupViewModel(authenticationFlow: .login)
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
XCTAssertFalse(service.homeserver.value.supportsRegistration)
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
// When selecting matrix.org.
@@ -39,7 +38,6 @@ class ServerSelectionScreenViewModelTests: XCTestCase {
// Given a view model for login.
setupViewModel(authenticationFlow: .login)
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
XCTAssertFalse(service.homeserver.value.supportsRegistration)
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
XCTAssertNil(context.alertInfo)
@@ -58,7 +56,6 @@ class ServerSelectionScreenViewModelTests: XCTestCase {
// Given a view model for registration.
setupViewModel(authenticationFlow: .register)
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
XCTAssertFalse(service.homeserver.value.supportsRegistration)
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
// When selecting matrix.org.
@@ -76,7 +73,6 @@ class ServerSelectionScreenViewModelTests: XCTestCase {
// Given a view model for registration.
setupViewModel(authenticationFlow: .register)
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
XCTAssertFalse(service.homeserver.value.supportsRegistration)
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
XCTAssertNil(context.alertInfo)
@@ -9,39 +9,14 @@ import XCTest
@testable import ElementX
@MainActor
class SoftLogoutViewModelTests: XCTestCase {
let credentials = SoftLogoutScreenCredentials(userID: "mock_user_id",
homeserverName: "https://matrix.org",
homeserverName: "https://example.com",
userDisplayName: "mock_username",
deviceID: "ABCDEFGH")
@MainActor func testInitialStateForMatrixOrg() {
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true)
let context = viewModel.context
// Given a view model where the user hasn't yet sent the verification email.
XCTAssert(context.password.isEmpty, "The view model should start with an empty password.")
XCTAssertFalse(context.viewState.canSubmit, "The view model should start with an invalid password.")
XCTAssertEqual(context.viewState.loginMode, .password, "The view model should show login form for the given homeserver.")
XCTAssert(context.viewState.showRecoverEncryptionKeysMessage, "The view model should show recover encryption keys message.")
}
@MainActor func testInitialStateForMatrixOrgPasswordEntered() {
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true,
password: "12345678")
let context = viewModel.context
// Given a view model where the user hasn't yet sent the verification email.
XCTAssertTrue(context.viewState.canSubmit, "The view model should start with a valid password.")
XCTAssertEqual(context.viewState.loginMode, .password, "The view model should show login form for the given homeserver.")
XCTAssert(context.viewState.showRecoverEncryptionKeysMessage, "The view model should show recover encryption keys message.")
}
@MainActor func testInitialStateForBasicServer() {
func testInitialStateForBasicServer() {
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockBasicServer,
keyBackupNeeded: false)
@@ -53,10 +28,23 @@ class SoftLogoutViewModelTests: XCTestCase {
XCTAssertEqual(context.viewState.loginMode, .password, "The view model should show login form for the given homeserver.")
XCTAssertFalse(context.viewState.showRecoverEncryptionKeysMessage, "The view model should not show recover encryption keys message.")
}
@MainActor func testInitialStateForOIDC() {
func testInitialStateForBasicServerPasswordEntered() {
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockOIDC,
homeserver: .mockBasicServer,
keyBackupNeeded: true,
password: "12345678")
let context = viewModel.context
// Given a view model where the user hasn't yet sent the verification email.
XCTAssertTrue(context.viewState.canSubmit, "The view model should start with a valid password.")
XCTAssertEqual(context.viewState.loginMode, .password, "The view model should show login form for the given homeserver.")
XCTAssert(context.viewState.showRecoverEncryptionKeysMessage, "The view model should show recover encryption keys message.")
}
func testInitialStateForOIDC() {
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: false)
let context = viewModel.context
@@ -67,7 +55,7 @@ class SoftLogoutViewModelTests: XCTestCase {
XCTAssertFalse(context.viewState.showRecoverEncryptionKeysMessage, "The view model should not show recover encryption keys message.")
}
@MainActor func testInitialStateForUnsupported() {
func testInitialStateForUnsupported() {
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockUnsupported,
keyBackupNeeded: false)