Skip to content

Commit

Permalink
Refresh no longer valid token stored in active task (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tunous authored Nov 6, 2023
1 parent 51a4e2d commit 7f4c920
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 1 deletion.
7 changes: 6 additions & 1 deletion Sources/OAuthenticator/Authenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,12 @@ extension Authenticator {

var login: Login
do {
login = try await loginFromTask(task: task)
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
self.config.authenticationStatusHandler?(.success(login))
Expand Down
70 changes: 70 additions & 0 deletions Tests/OAuthenticatorTests/AuthenticatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,74 @@ final class AuthenticatorTests: XCTestCase {
XCTAssertEqual(mockLoader.requests[0].allHTTPHeaderFields!["Authorization"], "Bearer EXPIRED")
XCTAssertEqual(mockLoader.requests[1].allHTTPHeaderFields!["Authorization"], "Bearer REFRESHED")
}

@MainActor
func testTokenExpiredAfterUseRefresh() async throws {
var sentRequests: [URLRequest] = []
let mockLoader: URLResponseProvider = { request in
sentRequests.append(request)
return ("hello".data(using: .utf8)!, URLResponse())
}

var refreshedLogins: [Login] = []
let refreshProvider: TokenHandling.RefreshProvider = { login, _, _ in
refreshedLogins.append(login)
return Login(token: "REFRESHED")
}

let tokenHandling = TokenHandling(
authorizationURLProvider: Self.disabledAuthorizationURLProvider,
loginProvider: Self.disabledLoginProvider,
refreshProvider: refreshProvider,
responseStatusProvider: TokenHandling.allResponsesValid
)

let storedLogin = Login(
accessToken: Token(value: "EXPIRE SOON", expiry: Date().addingTimeInterval(1)),
refreshToken: Token(value: "REFRESH")
)
var loadLoginCount = 0
var savedLogins: [Login] = []
let storage = LoginStorage {
loadLoginCount += 1
return storedLogin
} storeLogin: { login in
savedLogins.append(login)
}

let config = Authenticator.Configuration(appCredentials: Self.mockCredentials,
loginStorage: storage,
tokenHandling: tokenHandling,
userAuthenticator: Self.disabledUserAuthenticator)

let auth = Authenticator(config: config, urlLoader: mockLoader)

let (_, _) = try await auth.response(for: URLRequest(url: URL(string: "https://example.com")!))
XCTAssertEqual(sentRequests.count, 1, "First request should be sent")
XCTAssertEqual(sentRequests.first?.value(forHTTPHeaderField: "Authorization"), "Bearer EXPIRE SOON", "Non expired token should be used for first request")
XCTAssertTrue(refreshedLogins.isEmpty, "Token should not be refreshed after first request")
XCTAssertEqual(loadLoginCount, 1, "Login should be loaded from storage once")
XCTAssertTrue(savedLogins.isEmpty, "Login storage should not be updated after first request")

// Let the token expire
sleep(1)

let (_, _) = try await auth.response(for: URLRequest(url: URL(string: "https://example.com")!))
XCTAssertEqual(refreshedLogins.count, 1, "Token should be refreshed")
XCTAssertEqual(refreshedLogins.first?.accessToken.value, "EXPIRE SOON", "Expired token should be passed to refresh call")
XCTAssertEqual(refreshedLogins.first?.refreshToken?.value, "REFRESH", "Refresh token should be passed to refresh call")
XCTAssertEqual(loadLoginCount, 2, "New login should be loaded from storage")
XCTAssertEqual(sentRequests.count, 2, "Second request should be sent")
let secondRequest = sentRequests.dropFirst().first
XCTAssertEqual(secondRequest?.value(forHTTPHeaderField: "Authorization"), "Bearer REFRESHED", "Refreshed token should be used for second request")
XCTAssertEqual(savedLogins.first?.accessToken.value, "REFRESHED", "Refreshed token should be saved to storage")

let (_, _) = try await auth.response(for: URLRequest(url: URL(string: "https://example.com")!))
XCTAssertEqual(refreshedLogins.count, 1, "No additional refreshes should happen")
XCTAssertEqual(loadLoginCount, 2, "No additional login loads should happen")
XCTAssertEqual(sentRequests.count, 3, "Third request should be sent")
let thirdRequest = sentRequests.dropFirst(2).first
XCTAssertEqual(thirdRequest?.value(forHTTPHeaderField: "Authorization"), "Bearer REFRESHED", "Refreshed token should be used for third request")
XCTAssertEqual(savedLogins.count, 1, "No additional logins should be saved to storage")
}
}

0 comments on commit 7f4c920

Please sign in to comment.