mirror of
https://github.com/otaviocc/Triton.git
synced 2026-01-29 19:54:27 +00:00
153 lines
5.6 KiB
Swift
153 lines
5.6 KiB
Swift
import AuthNetworkService
|
|
import AuthPersistenceService
|
|
import Foundation
|
|
|
|
/// Errors that can occur during authentication repository operations.
|
|
///
|
|
/// This enumeration defines the specific error cases that can arise when
|
|
/// working with authentication tokens and repository operations.
|
|
public enum AuthRepositoryError: Error {
|
|
|
|
/// Indicates that no access token is available when one is required.
|
|
///
|
|
/// This error is thrown when attempting to retrieve an access token
|
|
/// but none has been stored, typically indicating the user is not
|
|
/// authenticated or the token has been removed/expired.
|
|
case missingToken
|
|
}
|
|
|
|
/// A repository protocol for managing authentication flow and token storage.
|
|
///
|
|
/// This protocol defines the interface for coordinating the complete authentication
|
|
/// process, from handling OAuth callbacks through secure token storage. It manages
|
|
/// the integration between network-based authentication operations and secure local
|
|
/// token persistence.
|
|
///
|
|
/// The repository handles the OAuth "authorization code" flow where users are redirected
|
|
/// to an external authentication provider, then redirected back to the application with
|
|
/// an authorization code that must be exchanged for an access token. It also provides
|
|
/// secure storage and retrieval of access tokens using the device's Keychain.
|
|
///
|
|
/// The repository follows the repository pattern, abstracting the complexities of
|
|
/// OAuth flows and secure storage to provide a clean interface for authentication
|
|
/// management throughout the application.
|
|
public protocol AuthRepositoryProtocol: Sendable {
|
|
|
|
/// Handles OAuth callback URLs containing authorization codes.
|
|
///
|
|
/// This method processes deep link URLs that result from OAuth authentication flows.
|
|
/// When users complete authentication with the external provider, they are redirected
|
|
/// back to the application with a URL containing an authorization code. This method:
|
|
///
|
|
/// 1. Parses the URL to extract the authorization code
|
|
/// 2. Validates that the URL is an authentication callback (host == "authenticate")
|
|
/// 3. Exchanges the code for an access token via the network service
|
|
/// 4. Securely stores the access token using the persistence service
|
|
///
|
|
/// The method silently returns if the URL is not a valid authentication callback,
|
|
/// allowing other URL handlers to process non-authentication deep links.
|
|
///
|
|
/// - Parameter url: The deep link URL containing the OAuth authorization code.
|
|
/// - Throws: Network errors from the token exchange or validation errors if the flow fails.
|
|
func handleDeepLinkURL(
|
|
_ url: URL
|
|
) async throws
|
|
|
|
/// Stores an access token securely.
|
|
///
|
|
/// This method provides a direct way to store an access token, typically used
|
|
/// in scenarios where a token is obtained through means other than the standard
|
|
/// OAuth deep link flow (such as manual token entry or alternative authentication methods).
|
|
///
|
|
/// The token is stored securely using the same mechanism as tokens obtained
|
|
/// through the OAuth flow, ensuring consistent security practices.
|
|
///
|
|
/// - Parameter accessToken: The access token to store securely.
|
|
func storeToken(
|
|
accessToken: String
|
|
) async
|
|
|
|
/// Retrieves the currently stored access token.
|
|
///
|
|
/// This method returns the access token that was previously stored through
|
|
/// authentication flows. The token can be used for authenticated API requests.
|
|
///
|
|
/// - Returns: The stored access token.
|
|
/// - Throws: `AuthRepositoryError.missingToken` if no token is available.
|
|
func accessToken() async throws -> String
|
|
|
|
/// Removes the stored access token and logs out the user.
|
|
///
|
|
/// This method securely removes the access token from storage, effectively
|
|
/// logging out the user. After calling this method, subsequent calls to
|
|
/// `accessToken()` will throw `AuthRepositoryError.missingToken` until
|
|
/// the user authenticates again.
|
|
///
|
|
/// This operation also triggers logout events for components that observe
|
|
/// authentication state changes.
|
|
func removeAccessToken() async
|
|
}
|
|
|
|
actor AuthRepository: AuthRepositoryProtocol {
|
|
|
|
// MARK: - Properties
|
|
|
|
private let networkService: AuthNetworkServiceProtocol
|
|
private let persistenceService: AuthPersistenceServiceProtocol
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
init(
|
|
networkService: AuthNetworkServiceProtocol,
|
|
persistenceService: AuthPersistenceServiceProtocol
|
|
) {
|
|
self.networkService = networkService
|
|
self.persistenceService = persistenceService
|
|
}
|
|
|
|
// MARK: - Public
|
|
|
|
func handleDeepLinkURL(
|
|
_ url: URL
|
|
) async throws {
|
|
let components = URLComponents(
|
|
url: url,
|
|
resolvingAgainstBaseURL: true
|
|
)
|
|
|
|
let host = components?.host
|
|
let code = components?.queryItems?
|
|
.first { $0.name == "code" }?
|
|
.value
|
|
|
|
guard host == "authenticate", let code else { return }
|
|
|
|
let accessToken = try await networkService.accessToken(
|
|
code: code
|
|
)
|
|
|
|
await persistenceService.storeAccessToken(
|
|
value: accessToken
|
|
)
|
|
}
|
|
|
|
func storeToken(
|
|
accessToken: String
|
|
) async {
|
|
await persistenceService.storeAccessToken(
|
|
value: accessToken
|
|
)
|
|
}
|
|
|
|
func accessToken() async throws -> String {
|
|
guard let accessToken = await persistenceService.fetchAccessToken() else {
|
|
throw AuthRepositoryError.missingToken
|
|
}
|
|
|
|
return accessToken
|
|
}
|
|
|
|
func removeAccessToken() async {
|
|
await persistenceService.removeAccessToken()
|
|
}
|
|
}
|