Skip to content

Commit

Permalink
docs: update API documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ColinFrick committed Oct 31, 2024
1 parent cfe1ce1 commit 8d885c5
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ import Foundation
import FusionAuth

extension AuthorizationManager {
/// We use a shared instance of the AuthorizationManager to ensure that we only have one instance of the AuthorizationManager
/// in our application.
///
/// This shared instance is initialized with the configuration for the FusionAuth server and the client ID.
public static let shared: AuthorizationManager = {
let instance = AuthorizationManager.instance
instance.initialize(configuration: AuthorizationConfiguration(
clientId: "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
fusionAuthUrl: "http://localhost:9011",
additionalScopes: ["email", "profile"]))
clientId: "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
fusionAuthUrl: "http://localhost:9011",
additionalScopes: ["email", "profile"])
)
return instance
}()
}
1 change: 1 addition & 0 deletions Sources/FusionAuth/AuthorizationConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public struct AuthorizationConfiguration {
/// The locale to be used for authorization. (Optional)
let locale: String?

/// Creates a new instance of AuthorizationConfiguration.
public init(clientId: String, fusionAuthUrl: String, tenant: String? = nil, additionalScopes: [String] = [], locale: String? = nil) {
self.clientId = clientId
self.fusionAuthUrl = fusionAuthUrl
Expand Down
13 changes: 13 additions & 0 deletions Sources/FusionAuth/AuthorizationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import AppAuth
public class AuthorizationManager {
private static let fusionAuthState = FusionAuthState()

/// The shared instance of the AuthorizationManager
public static let instance = AuthorizationManager()

private var configuration: AuthorizationConfiguration?
private var tokenManager: TokenManager?

private init() {}

/// Initialize the AuthorizationManager with the given configuration
public func initialize(configuration: AuthorizationConfiguration, storage: Storage? = nil) {
self.configuration = configuration
self.tokenManager = TokenManager().withStorage(storage: storage ?? MemoryStorage())
Expand All @@ -26,14 +28,17 @@ public class AuthorizationManager {
}
}

/// Returns the current FusionAuthState
public func fusionAuthState() -> FusionAuthState {
return Self.fusionAuthState
}

/// Returns the current TokenManager
public func getTokenManager() -> TokenManager {
return tokenManager!
}

/// Returns an instance of the OAuthAuthorizationService, configured with the current configuration
public func oauth() -> OAuthAuthorizationService {
return OAuthAuthorizationService(
fusionAuthUrl: configuration!.fusionAuthUrl,
Expand All @@ -44,22 +49,30 @@ public class AuthorizationManager {
)
}

/// Returns a fresh access token
///
/// If the access token is not expired, it will be returned immediately.
/// If the access token is expired or force is `true`, a new access token will be fetched using the refresh token.
public func freshAccessToken(force: Bool = false) async throws -> String? {
if !force && !self.isAccessTokenExpired() {
return self.getAccessToken()
}

// We always use oAuth to get a fresh access token
return try await oauth().freshAccessToken()
}

/// Retrieves the access token, if available
public func getAccessToken() -> String? {
return self.tokenManager?.getAuthState()?.accessToken
}

/// Retrieves the access token expiration, if available
public func getAccessTokenExpirationTime() -> Date? {
return self.tokenManager?.getAuthState()?.accessTokenExpirationTime
}

/// Checks if the stored access token is expired.
public func isAccessTokenExpired() -> Bool {
guard let expiration = self.getAccessTokenExpirationTime() else {
return true
Expand Down
6 changes: 2 additions & 4 deletions Sources/FusionAuth/FusionAuthState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
import Foundation
import AppAuth

/// TODO
/// FusionAuthState is an observable object that represents the authorization state of the user.
/// It provides properties to store and retrieve access tokens, refresh tokens, and ID tokens.
/// It also provides a method to check if the user is authenticated.
/// The data structure that represents the FusionAuth state.
/// - Note: This data structure contains the access token, refresh token, ID token, and access token expiration time.
public class FusionAuthState: ObservableObject {
@Published public var accessToken: String?
@Published public var accessTokenExpirationTime: Date?
Expand Down
17 changes: 13 additions & 4 deletions Sources/FusionAuth/TokenManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,22 @@ enum TokenManagerError: Error {
case noStorage
}

/// TODO
/// TokenManager is a singleton object that manages the access tokens.
/// It provides methods to get and set the access, refresh and ID tokens.
/// It uses one of the configured Storage implementations to store the access tokens.
/// TokenManager is responsible for managing the authorization tokens.
/// It provides methods to get, save, and clear the access, refresh, and ID tokens.
/// The tokens are stored using a configured Storage implementation.
public class TokenManager {
private var storage: Storage?

/// Configures the TokenManager with a specific storage implementation.
/// - Parameter storage: The storage implementation to be used by the TokenManager.
/// - Returns: The TokenManager instance configured with the provided storage.
func withStorage(storage: Storage) -> TokenManager {
self.storage = storage
return self
}

/// Retrieves the current authorization state from the storage.
/// - Returns: The FusionAuthStateData object representing the current authorization state, or nil if not found.
func getAuthState() -> FusionAuthStateData? {
guard let authState = self.storage?.get(key: "authState") else {
return nil
Expand All @@ -33,6 +37,9 @@ public class TokenManager {
}
}

/// Saves the provided authorization state to the storage.
/// - Parameter authState: The FusionAuthStateData object representing the authorization state to be saved.
/// - Throws: TokenManagerError.noStorage if no storage is configured.
func saveAuthState(_ authState: FusionAuthStateData) throws {
guard let storage else {
throw TokenManagerError.noStorage
Expand All @@ -52,6 +59,8 @@ public class TokenManager {
}
}

/// Clears the current authorization state from the storage.
/// - Throws: TokenManagerError.noStorage if no storage is configured.
func clearAuthState() throws {
guard let storage else {
throw TokenManagerError.noStorage
Expand Down
20 changes: 18 additions & 2 deletions Sources/FusionAuth/oauth/OAuthAuthorization.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import Foundation

/// TODO
/// OAuthAuthorization is a utility struct that provides methods to resume and cancel the OAuth authorization flow.
///
/// `resume` should be used in the `onOpenURL` modifier of the base `ContentView` of your app.
///
/// ```
/// @main
/// struct QuickstartApp: App {
/// var body: some Scene {
/// WindowGroup {
/// ContentView()
/// .environmentObject(AuthorizationManager.shared.fusionAuthState())
/// .onOpenURL { url in
/// OAuthAuthorization.resume(with: url)
/// }
/// }
/// }
/// }
/// ```
public struct OAuthAuthorization {
private init() {}

Expand All @@ -14,7 +30,7 @@ public struct OAuthAuthorization {
return OAuthAuthorizationStore.shared.resume(url)
}

/// Cancel the authorization flow
/// Cancel the current authorization flow
public static func cancel() {
OAuthAuthorizationStore.shared.cancel()
}
Expand Down
28 changes: 25 additions & 3 deletions Sources/FusionAuth/oauth/OAuthAuthorizationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public class OAuthAuthorizationService {
}
#endif

/// Authorizes the user using OAuth authorization code flow.
///
/// - Parameter options: The options to configure the OAuth logout request.
/// - Returns: The OAuth authorization state.
@discardableResult
public func authorize(options: OAuthAuthorizeOptions) async throws -> OIDAuthState {
AuthorizationManager.log?.trace("Starting OAuth authorization...")
Expand Down Expand Up @@ -105,6 +109,10 @@ public class OAuthAuthorizationService {
return authState
}

/// Retrieves the user information for the authenticated user.
///
/// - Returns: The user information
/// - Throws: An error if the user information is not found.
public func userInfo() async throws -> UserInfo {
let configuration = try await getConfiguration()

Expand All @@ -119,6 +127,9 @@ public class OAuthAuthorizationService {
return try await getUserInfo(userinfoEndpoint: userinfoEndpoint, accessToken: accessToken)
}

/// Log out the user
///
/// - Parameter options: The options to configure the OAuth logout request.
public func logout(options: OAuthLogoutOptions) async throws {
let idToken = Self.appAuthState?.lastTokenResponse?.idToken

Expand Down Expand Up @@ -176,6 +187,9 @@ public class OAuthAuthorizationService {
}
}

/// Retrieves a fresh access token.
///
/// - Returns: The fresh access token or nil if an error occurs.
public func freshAccessToken() async throws -> String? {
AuthorizationManager.log?.trace("Retrieve fresh token from FusionAuth")

Expand Down Expand Up @@ -204,7 +218,16 @@ public class OAuthAuthorizationService {
return
}

let request = OIDTokenRequest(configuration: configuration, grantType: OIDGrantTypeRefreshToken, authorizationCode: nil, redirectURL: nil, clientID: clientId, clientSecret: nil, scope: nil, refreshToken: refreshToken, codeVerifier: nil, additionalParameters: nil)
let request = OIDTokenRequest(configuration: configuration,
grantType: OIDGrantTypeRefreshToken,
authorizationCode: nil,
redirectURL: nil,
clientID: clientId,
clientSecret: nil,
scope: nil,
refreshToken: refreshToken,
codeVerifier: nil,
additionalParameters: nil)

DispatchQueue.main.async {
OIDAuthorizationService.perform(request) { response, error in
Expand Down Expand Up @@ -263,7 +286,7 @@ public class OAuthAuthorizationService {
}

if response.statusCode != 200 {
print("HTTP: \(response.statusCode), Response: \(String(decoding: data, as: UTF8.self))")
print("HTTP: \(response.statusCode), Response: \(String(bytes: data, encoding: .utf8) ?? "")")

continuation.resume(throwing: OAuthError.accessTokenNil)
return
Expand Down Expand Up @@ -344,7 +367,6 @@ public class OAuthAuthorizationService {
if options.userCode != nil {
additionalParameters.updateValue(options.userCode!, forKey: "user_code")
}

return additionalParameters
}
}
1 change: 1 addition & 0 deletions Sources/FusionAuth/oauth/OAuthAuthorizeOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public struct OAuthAuthorizeOptions {
/// The end-user verification code.
let userCode: String?

/// Creates a new instance of OAuthAuthorizeOptions.
public init(
bundleId: String = Bundle.main.bundleIdentifier ?? "",
redirectUriSuffix: String = ":/oauth2redirect/ios-provider",
Expand Down
1 change: 1 addition & 0 deletions Sources/FusionAuth/oauth/OAuthLogoutOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public struct OAuthLogoutOptions {
/// The authorization server includes this value when redirecting the user-agent back to the client.
let state: String?

/// Creates a new instance of OAuthLogoutOptions.
public init(
bundleId: String = Bundle.main.bundleIdentifier ?? "",
postLogoutRedirectUriSuffix: String = ":/oauth2redirect/ios-provider",
Expand Down
6 changes: 2 additions & 4 deletions Sources/FusionAuth/storage/KeyChainStorage.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import Foundation
import Security

/// TODO
/// Storage that saves data to the KeyChain.
/// This storage is secure and the data is encrypted.
/// The data is persisted even when the app is closed.
/// Implemenation of the `Storage` protocol that uses the iOS KeyChain to store and retrieve sensitive data.
/// - Note: The KeyChain is a secure storage mechanism that is used to store sensitive data such as passwords, keys, and certificates.
public class KeyChainStorage: Storage {
public init() {}

Expand Down
5 changes: 2 additions & 3 deletions Sources/FusionAuth/storage/MemoryStorage.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// TODO
/// Storage that keeps the data in memory.
/// This storage is not persistent and the data is lost when the app is closed.
/// Implementation of the Storage protocol that stores data in memory.
/// - Note: This implementation is useful for testing and development purposes, as it does not persist data across app launches.
public class MemoryStorage: Storage {
public init() {}

Expand Down
3 changes: 2 additions & 1 deletion Sources/FusionAuth/storage/UserDefaultsStorage.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation

/// Storage that saves data to UserDefaults.
/// Implemenation of the `Storage` protocol that uses UserDefaults to store and retrieve data.
/// - Note: UserDefaults is a simple interface for storing key-value pairs persistently across app launches.
public class UserDefaultsStorage: Storage {
public init() {}

Expand Down

0 comments on commit 8d885c5

Please sign in to comment.