Triton/Packages/Auth/Sources/AuthRepository/AuthRepository.swift
Otávio 3e878667a1 Add Triton App
Signed-off-by: Otavio Cordeiro <otaviocc@users.noreply.github.com>
2025-12-15 20:39:07 +01:00

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()
}
}