Files
letro-ios/ElementX/Sources/Screens/Settings/AccountSettings/OIDCAccountSettingsPresenter.swift
Doug e989463d91 Restore the .oidcCallback route for external authentication. (#5391)
* Restore the .oidcCallback route (partially reverts #3461) for external authentication.

* Make sure OIDC also works for non-http URLs.

* Remove oidcAuthentication from the state machine.

There isn't a reliable way to detect failure/cancellation when e.g. the user returns from an external app without interacting with the MAS page.
2026-05-05 12:47:07 +01:00

78 lines
2.9 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
// Copyright 2023-2025 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 AuthenticationServices
/// Presents a web authentication session that will display the user's account settings page.
///
/// A web authentication session is used so that the same session used for login is available
/// meaning that the user doesn't need to sign in again. `SFSafariViewController` doesn't
/// have access to this session, and for some reason `prefersEphemeralWebBrowserSession`
/// isn't sharing the session back to Safari.
@MainActor
class OIDCAccountSettingsPresenter: NSObject {
private let accountURL: URL
private let oidcRedirectURL: URL
private let presentationAnchor: UIWindow
private let appMediator: AppMediatorProtocol
typealias Continuation = AsyncStream<Result<Void, OIDCError>>.Continuation
private let continuation: Continuation?
init(accountURL: URL,
presentationAnchor: UIWindow,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
continuation: Continuation? = nil) {
self.accountURL = accountURL
oidcRedirectURL = appSettings.oidcRedirectURL
self.presentationAnchor = presentationAnchor
self.appMediator = appMediator
self.continuation = continuation
super.init()
}
/// Presents a web authentication session for the supplied data.
func start() {
let session = ASWebAuthenticationSession(url: accountURL, callback: .oidcRedirectURL(oidcRedirectURL)) { [continuation] _, error in
guard let continuation else { return }
if error?.isOIDCUserCancellation == true {
continuation.yield(.failure(.userCancellation))
} else {
let errorDescription = error.map(String.init(describing:)) ?? "Unknown error"
MXLog.error("A web authentication session error occurred: \(errorDescription)")
continuation.yield(.failure(.unknown))
}
continuation.finish()
}
session.prefersEphemeralWebBrowserSession = false
session.presentationContextProvider = self
session.additionalHeaderFields = [
"X-Element-User-Agent": UserAgentBuilder.makeASCIIUserAgent()
]
if accountURL.scheme == "https" || accountURL.scheme == "http" {
session.start()
} else {
appMediator.open(accountURL)
}
}
}
// MARK: ASWebAuthenticationPresentationContextProviding
extension OIDCAccountSettingsPresenter: ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
presentationAnchor
}
}