Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 39 additions & 42 deletions Sources/OAuthenticator/Authenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public enum AuthenticatorError: Error, Hashable {
case httpResponseExpected
case unauthorizedRefreshFailed
case missingRedirectURI
case missingRefreshToken
case missingScope
case missingRefreshToken
case missingScope
case failingAuthenticatorUsed
case dpopTokenExpected(String)
case parRequestURIMissing
Expand All @@ -31,7 +31,7 @@ public enum AuthenticatorError: Error, Hashable {
/// Manage state required to executed authenticated URLRequests.
public actor Authenticator {
public typealias UserAuthenticator = @Sendable (URL, String) async throws -> URL
public typealias AuthenticationStatusHandler = @Sendable (Result<Login, AuthenticatorError>) async -> Void
public typealias AuthenticationStatusHandler = @Sendable (Result<Login, AuthenticatorError>) async -> Void

/// A `UserAuthenticator` that always fails. Useful as a placeholder
/// for testing and for doing manual authentication with an external
Expand Down Expand Up @@ -73,8 +73,8 @@ public actor Authenticator {
public let userAuthenticator: UserAuthenticator
public let mode: UserAuthenticationMode

// Specify an authenticationResult closure to obtain result and grantedScope
public let authenticationStatusHandler: AuthenticationStatusHandler?
// Specify an authenticationResult closure to obtain result and grantedScope
public let authenticationStatusHandler: AuthenticationStatusHandler?

#if canImport(AuthenticationServices)
@available(tvOS 16.0, macCatalyst 13.0, *)
Expand All @@ -94,7 +94,7 @@ public actor Authenticator {
// ASWebAuthenticationSession.userAuthenticator directly here
// with GlobalActorIsolatedTypesUsability, but it isn't working
self.userAuthenticator = { try await ASWebAuthenticationSession.userAuthenticator(url: $0, scheme: $1) }
self.authenticationStatusHandler = authenticationStatusHandler
self.authenticationStatusHandler = authenticationStatusHandler
}
#endif

Expand All @@ -111,7 +111,7 @@ public actor Authenticator {
self.tokenHandling = tokenHandling
self.mode = mode
self.userAuthenticator = userAuthenticator
self.authenticationStatusHandler = authenticationStatusHandler
self.authenticationStatusHandler = authenticationStatusHandler
}
}

Expand Down Expand Up @@ -214,17 +214,14 @@ extension Authenticator {

try await storage.storeLogin(login)
}

private func clearLogin() async {
guard let storage = config.loginStorage else { return }

let invalidLogin = Login(token: "invalid", validUntilDate: .distantPast)

do {
try await storage.storeLogin(invalidLogin)
} catch {
print("failed to store an invalid login, possibly stuck", error)

private func clearLogin() async throws {
guard let storage = config.loginStorage else {
self.localLogin = nil
return
}

try await storage.clearLogin()
}
}

Expand All @@ -250,25 +247,25 @@ extension Authenticator {
private func loginTaskResult(manual: Bool, userAuthenticator: @escaping UserAuthenticator) async throws -> Login {
let task = activeTokenTask ?? makeLoginTask(manual: manual, userAuthenticator: userAuthenticator)

var login: Login
do {
do {
login = try await loginFromTask(task: task)
} catch AuthenticatorError.tokenInvalid {
let newTask = makeLoginTask(manual: manual, userAuthenticator: userAuthenticator)
login = try await loginFromTask(task: newTask)
}

// Inform authenticationResult closure of new login information
await self.config.authenticationStatusHandler?(.success(login))
}
catch let authenticatorError as AuthenticatorError {
await self.config.authenticationStatusHandler?(.failure(authenticatorError))

// Rethrow error
throw authenticatorError
}
var login: Login
do {
do {
login = try await loginFromTask(task: task)
} catch AuthenticatorError.tokenInvalid {
let newTask = makeLoginTask(manual: manual, userAuthenticator: userAuthenticator)
login = try await loginFromTask(task: newTask)
}

// Inform authenticationResult closure of new login information
await self.config.authenticationStatusHandler?(.success(login))
}
catch let authenticatorError as AuthenticatorError {
await self.config.authenticationStatusHandler?(.failure(authenticatorError))

// Rethrow error
throw authenticatorError
}

return login
}

Expand Down Expand Up @@ -347,13 +344,13 @@ extension Authenticator {

do {
let login = try await refreshProvider(login, config.appCredentials, { try await self.dpopResponse(for: $0, login: nil) })

try await storeLogin(login)

return login
} catch {
await clearLogin()
try await clearLogin()

throw error
}
}
Expand All @@ -362,7 +359,7 @@ extension Authenticator {
guard let pkce = config.tokenHandling.pkce else {
throw AuthenticatorError.pkceRequired
}

let challenge = pkce.challenge
let scopes = config.appCredentials.scopes.joined(separator: " ")
let callbackURI = config.appCredentials.callbackURL
Expand Down Expand Up @@ -415,7 +412,7 @@ extension Authenticator {
guard let generator = config.tokenHandling.dpopJWTGenerator else {
return try await urlLoader(request)
}

guard let pkce = config.tokenHandling.pkce else {
throw AuthenticatorError.pkceRequired
}
Expand Down
15 changes: 11 additions & 4 deletions Sources/OAuthenticator/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ public struct Login: Codable, Hashable, Sendable {
public var accessToken: Token
public var refreshToken: Token?

// User authorized scopes
public var scopes: String?
// User authorized scopes
public var scopes: String?
public var issuingServer: String?

public init(accessToken: Token, refreshToken: Token? = nil, scopes: String? = nil, issuingServer: String? = nil) {
public init(accessToken: Token, refreshToken: Token? = nil, scopes: String? = nil, issuingServer: String? = nil) {
self.accessToken = accessToken
self.refreshToken = refreshToken
self.scopes = scopes
Expand Down Expand Up @@ -88,13 +88,20 @@ public struct AppCredentials: Codable, Hashable, Sendable {
public struct LoginStorage: Sendable {
public typealias RetrieveLogin = @Sendable () async throws -> Login?
public typealias StoreLogin = @Sendable (Login) async throws -> Void
public typealias ClearLogin = @Sendable () async throws -> Void

public let retrieveLogin: RetrieveLogin
public let storeLogin: StoreLogin
public let clearLogin: ClearLogin

public init(retrieveLogin: @escaping RetrieveLogin, storeLogin: @escaping StoreLogin) {
public init(
retrieveLogin: @escaping RetrieveLogin,
storeLogin: @escaping StoreLogin,
clearLogin: @escaping ClearLogin
) {
self.retrieveLogin = retrieveLogin
self.storeLogin = storeLogin
self.clearLogin = clearLogin
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/OAuthenticator/Services/GitHub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public enum GitHub {
urlBuilder.queryItems = [
URLQueryItem(name: "client_id", value: credentials.clientId),
URLQueryItem(name: "redirect_uri", value: credentials.callbackURL.absoluteString),
URLQueryItem(name: "scope", value: credentials.scopeString),
URLQueryItem(name: "scope", value: credentials.scopeString),
]

if let state = parameters.state {
Expand Down
Loading
Loading