Sources/UberAuth/UberAuth.swift (66 lines of code) (raw):
//
// UberAuth.swift
// UberAuth
//
// Copyright © 2024 Uber Technologies, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
public typealias AuthCompletion = (Result<Client, UberAuthError>) -> ()
/// @mockable
public protocol UberAuthInterface {
/// Executes a single login session using the provided context
///
/// - Parameters:
/// - context: An `AuthContext` instance providing all information needed to execute authentication
/// - completion: A closure to be called upon completion
static func login(context: AuthContext, completion: @escaping AuthCompletion)
/// Clears any saved auth information from the keychain
/// If `currentAuthContext` exists, logs out using the stored auth context
/// Otherwise, attempts to delete the saved auth token directly using the internal TokenManager
static func logout()
/// Attempts to extract auth information from the provided URL.
/// This method should be called from the implemeting application's openURL function.
///
/// - Parameter url: The URL that was passed into the implementing app
/// - Returns: A boolean indicating if the URL was handled or not
static func handle(_ url: URL) -> Bool
}
///
/// An internal protocol that translates the class -> instance methods for UberAuth
///
/// @mockable
protocol AuthManaging {
func login(context: AuthContext, completion: @escaping AuthCompletion)
func logout()
func handle(_ url: URL) -> Bool
var isLoggedIn: Bool { get }
}
/// Public interface for the uber-auth-ios library
public final class UberAuth: UberAuthInterface, AuthManaging {
// MARK: Public
/// Executes a single login session using the provided context
///
/// - Parameters:
/// - context: An `AuthContext` instance providing all information needed to execute authentication
/// - completion: A closure to be called upon completion
public static func login(context: AuthContext = .init(),
completion: @escaping AuthCompletion) {
auth.login(
context: context,
completion: completion
)
}
/// Clears any saved auth information from the keychain
/// If `currentAuthContext` exists, logs out using the stored auth context
/// Otherwise, attempts to delete the saved auth token directly using the internal TokenManager
public static func logout() {
auth.logout()
}
/// Attempts to extract auth information from the provided URL.
/// This method should be called from the implemeting application's openURL function.
///
/// - Parameter url: The URL that was passed into the implementing app
/// - Returns: A boolean indicating if the URL was handled or not
@discardableResult
public static func handle(_ url: URL) -> Bool {
auth.handle(url)
}
/// A computed property that indicates if auth information is saved in the keychain
/// First checks for saved token information using the current auth provider
/// If no auth provider exists, falls back to the default token identifier
public static var isLoggedIn: Bool {
auth.isLoggedIn
}
// MARK: Internal
// MARK: AuthManaging
init(currentContext: AuthContext? = nil,
tokenManager: TokenManaging = TokenManager()) {
self.currentContext = currentContext
self.tokenManager = tokenManager
}
func login(context: AuthContext = .init(),
completion: @escaping AuthCompletion) {
context.authProvider.execute(
authDestination: context.authDestination,
prefill: context.prefill,
completion: completion
)
currentContext = context
}
func logout() {
guard let currentContext else {
tokenManager.deleteToken(identifier: TokenManager.defaultAccessTokenIdentifier)
return
}
currentContext.authProvider.logout()
self.currentContext = nil
}
func handle(_ url: URL) -> Bool {
guard let currentContext else {
return false
}
return currentContext.authProvider.handle(response: url)
}
var isLoggedIn: Bool {
currentContext?.authProvider.isLoggedIn ?? (tokenManager.getToken(identifier: TokenManager.defaultAccessTokenIdentifier) != nil)
}
private static let auth = UberAuth()
var currentContext: AuthContext?
var tokenManager: TokenManaging = TokenManager()
}