117 lines
3.9 KiB
Swift

import BitwardenKit
import BitwardenSdk
import Foundation
/// A protocol for a `TokenService` which manages accessing and updating the active account's tokens.
///
protocol TokenService: AnyObject {
/// Returns the access token for the current account.
///
/// - Returns: The access token for the current account.
///
func getAccessToken() async throws -> String
/// Returns the access token's expiration date for the current account.
///
/// - Returns: The access token's expiration date for the current account.
///
func getAccessTokenExpirationDate() async throws -> Date?
/// Returns whether the user is an external user.
///
/// - Returns: Whether the user is an external user.
///
func getIsExternal() async throws -> Bool
/// Returns the refresh token for the current account.
///
/// - Returns: The refresh token for the current account.
///
func getRefreshToken() async throws -> String
/// Sets a new access and refresh token for the current account.
///
/// - Parameters:
/// - accessToken: The account's updated access token.
/// - refreshToken: The account's updated refresh token.
/// - expirationDate: The access token's expiration date.
///
func setTokens(accessToken: String, refreshToken: String, expirationDate: Date) async throws
}
// MARK: - DefaultTokenService
/// A default implementation of `TokenService`.
///
actor DefaultTokenService: TokenService {
// MARK: Properties
/// The service used by the application to report non-fatal errors.
let errorReporter: ErrorReporter
/// The repository used to manages keychain items.
let keychainRepository: KeychainRepository
/// The service that manages the account state.
let stateService: StateService
// MARK: Initialization
/// Initialize a `DefaultTokenService`.
///
/// - Parameters
/// - errorReporter: The service used by the application to report non-fatal errors.
/// - keychainRepository: The repository used to manages keychain items.
/// - stateService: The service that manages the account state.
///
init(
errorReporter: ErrorReporter,
keychainRepository: KeychainRepository,
stateService: StateService,
) {
self.errorReporter = errorReporter
self.keychainRepository = keychainRepository
self.stateService = stateService
}
// MARK: Methods
func getAccessToken() async throws -> String {
let userId = try await stateService.getActiveAccountId()
return try await keychainRepository.getAccessToken(userId: userId)
}
func getAccessTokenExpirationDate() async throws -> Date? {
try await stateService.getAccessTokenExpirationDate()
}
func getIsExternal() async throws -> Bool {
let accessToken: String = try await getAccessToken()
let tokenPayload = try TokenParser.parseToken(accessToken)
return tokenPayload.isExternal
}
func getRefreshToken() async throws -> String {
let userId = try await stateService.getActiveAccountId()
return try await keychainRepository.getRefreshToken(userId: userId)
}
func setTokens(accessToken: String, refreshToken: String, expirationDate: Date) async throws {
let userId = try await stateService.getActiveAccountId()
try await keychainRepository.setAccessToken(accessToken, userId: userId)
try await keychainRepository.setRefreshToken(refreshToken, userId: userId)
await stateService.setAccessTokenExpirationDate(expirationDate, userId: userId)
}
}
// MARK: ClientManagedTokens (SDK)
extension DefaultTokenService: ClientManagedTokens {
/// Gets the access token for the SDK, nil if any errors are thrown.
func getAccessToken() async -> String? {
// TODO: PM-21846 Returning `nil` temporarily until we add validation
// given that the SDK expects non-expired token.
nil
}
}