Add an AccountProvider type and use it within the authentication flow. (#6103)

* Add an AccountProvider type and use it within AppSettings.

* Use the AccountProvider type within the Authentication flow too.

* Fix unit test compilation with Swift 6.4

* Make sure defaultAccountProvider takes allowOtherAccountProviders into account.

* Document a caveat on using AuthenticationService.homeserver.value.accountProvider.
This commit is contained in:
Doug
2026-09-14 15:25:33 +01:00
committed by GitHub
parent 26fddb8aa2
commit 64aae09c6d
29 changed files with 442 additions and 265 deletions
@@ -0,0 +1,65 @@
//
// Copyright 2026 Element Creations 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
nonisolated enum AccountProvider: Equatable {
/// An account provider that was input/chosen by the user.
case generic(String)
/// An account provider as provided by the app.
case managed(serverName: String, baseURL: URL, canUseServerName: Bool = true)
/// The account provider's string value, shown in the UI and used to build a `Client` with.
var serverNameOrBaseURL: String {
let serverNameOrBaseURL = switch self {
case .generic(let serverNameOrBaseURL):
serverNameOrBaseURL
case .managed(let serverName, let baseURL, let canUseServerName):
canUseServerName ? serverName : baseURL.absoluteString
}
return sanitize(serverNameOrBaseURL)
}
var isGeneric: Bool {
switch self {
case .generic: true
case .managed: false
}
}
/// Sanitizes an address with the following rules:
/// - Trim any whitespace.
/// - Lowercase the address.
/// - Removes an https scheme (treating it as implicit).
/// - Remove any trailing slashes.
private func sanitize(_ serverNameOrBaseURL: String) -> String {
serverNameOrBaseURL
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased()
.trimmingPrefix("https://") // Intentionally continue to show http:// as an indicator (and for history).
.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
}
}
// MARK: - Array
extension [AccountProvider] {
/// Whether the collection includes a provider matching the given server name or base URL.
func contains(serverName: String?, orBaseURL baseURL: URL?) -> Bool {
contains { accountProvider in
switch accountProvider {
case .generic(let serverNameOrBaseURL):
// Lower case potential user input to be safe. Full sanitisation is wrong given the scheme would be ignored.
serverNameOrBaseURL.lowercased() == serverName || serverNameOrBaseURL.lowercased() == baseURL?.absoluteString
case .managed(let providerServerName, let providerBaseURL, _):
// We can ignore canUseServerName here, it's about constructing a client rather than matching.
providerServerName == serverName || providerBaseURL == baseURL
}
}
}
}
@@ -78,7 +78,7 @@ final nonisolated class AppSettings: @unchecked Sendable {
// MARK: - Hooks
// swiftlint:disable:next function_parameter_count
func override(accountProviders: [String],
func override(accountProviders: [AccountProvider],
allowOtherAccountProviders: Bool,
hideBrandChrome: Bool,
pushGatewayBaseURL: URL,
@@ -143,7 +143,7 @@ final nonisolated class AppSettings: @unchecked Sendable {
///
/// Account provider is the friendly term for the server name. It should not contain an `https` prefix and should
/// match the last part of the user ID. For example `example.com` and not `https://matrix.example.com`.
private(set) var accountProviders = ["matrix.org"]
private(set) var accountProviders: [AccountProvider] = [.managed(serverName: "matrix.org", baseURL: "https://matrix-client.matrix.org")]
/// Whether or not the user is allowed to manually enter their own account provider or must select from one of `defaultAccountProviders`.
private(set) var allowOtherAccountProviders = true
/// Whether the components surrounding the app brand/logo should be hidden or not
@@ -188,8 +188,12 @@ final nonisolated class AppSettings: @unchecked Sendable {
@UserPreference(key: "previousServers", defaultValue: [])
var previousServers: [String]
var defaultServer: String {
previousServers.first ?? accountProviders[0]
var defaultAccountProvider: AccountProvider {
if allowOtherAccountProviders {
previousServers.first.map { .generic($0) } ?? accountProviders[0]
} else {
accountProviders[0]
}
}
// MARK: - Security