Skip to content

Commit

Permalink
Testing out refresh doing a new login
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Aug 23, 2021
1 parent 68e1410 commit c5861fb
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 99 deletions.
100 changes: 12 additions & 88 deletions Sources/OAuthenticator/ApplyAuthentationOperation.swift
Original file line number Diff line number Diff line change
@@ -1,111 +1,35 @@
import Foundation
import AuthenticationServices
import OperationPlus

class ApplyAuthentationOperation: AsyncProducerOperation<Result<URLRequest, Error>> {
let request: URLRequest
let config: AuthConfiguration

private var session: ASWebAuthenticationSession?

private lazy var contextProvider: CredentialWindowProvider = {
CredentialWindowProvider()
}()
private let applier: AuthenticationApplier

init(request: URLRequest, config: AuthConfiguration) {
self.request = request
self.config = config
self.applier = AuthenticationApplier(request: request, config: config)
}

override func cancel() {
OperationQueue.main.addOperation {
self.session?.cancel()
self.session = nil
}
applier.cancel()

super.cancel()
}

var config: AuthConfiguration {
return applier.config
}

override func main() {
OperationQueue.preconditionNotMain()

config.loginStorage.retrieveLogin { result in
switch result {
case .success(let login):
self.buildRequest(with: login)
case .failure:
self.beginLogin()
}
}
}

private func buildRequest(with login: OAuthLogin) {
let authedRequest = self.request.authorizedRequest(with: login.accessToken)

self.finish(with: .success(authedRequest))
}

private func beginLogin() {
precondition(self.session == nil)

// ok, we have no token
let url = config.tokenURL
let scheme = config.callbackURLScheme

OperationQueue.main.addOperation {
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: scheme, completionHandler: { result in
switch result {
case .failure(let error):
self.finish(with: .failure(error))
case .success(let callbackURL):
self.handleSessionResult(url: callbackURL)
}
})

session.prefersEphemeralWebBrowserSession = true
session.presentationContextProvider = self.contextProvider

session.start()

self.session = session
}
}

private func handleSessionResult(url: URL) {
do {
let tokenRequest = try self.config.flowHandler.accessRequestFromCallback(url: url)

performAccessRequest(tokenRequest)
} catch {
self.finish(with: .failure(error))
}
}

private func performAccessRequest(_ request: URLRequest) {
config.loader.response(for: request) { result in
switch result {
config.loginStorage.retrieveLogin { loginResult in
switch loginResult {
case .failure(let error):
self.finish(with: .failure(error))
case .success(let response):
self.handleLoginResponse(response)
}
}
}

private func handleLoginResponse(_ response: URLSession.DataTaskResponse) {
do {
let login = try JSONDecoder().decode(LoginResponse.self, from: response.data).oauthLogin

self.config.loginStorage.storeLogin(login) { error in
if let error = error {
self.finish(with: .failure(error))
return
case .success(let login):
self.applier.applyAuthentication(using: login) { result in
self.finish(with: result)
}

self.buildRequest(with: login)
}
} catch {
self.finish(with: .failure(error))
}
}
}
101 changes: 101 additions & 0 deletions Sources/OAuthenticator/AuthenticationApplier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import Foundation
import AuthenticationServices

class AuthenticationApplier {
let request: URLRequest
let config: AuthConfiguration

private var session: ASWebAuthenticationSession?

private lazy var contextProvider: CredentialWindowProvider = {
CredentialWindowProvider()
}()

init(request: URLRequest, config: AuthConfiguration) {
self.request = request
self.config = config
}

func cancel() {
OperationQueue.main.addOperation {
self.session?.cancel()
self.session = nil
}
}

func applyAuthentication(using login: OAuthLogin, completionHandler: @escaping (Result<URLRequest, Error>) -> Void) {
let authedRequest = self.buildRequest(with: login)

completionHandler(.success(authedRequest))
}

private func buildRequest(with login: OAuthLogin) -> URLRequest {
return request.authorizedRequest(with: login.accessToken)
}

func beginLogin(completionHandler: @escaping (Result<URLRequest, Error>) -> Void) {
precondition(self.session == nil)

// ok, we have no token
let url = config.tokenURL
let scheme = config.callbackURLScheme

OperationQueue.main.addOperation {
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: scheme, completionHandler: { result in
switch result {
case .failure(let error):
completionHandler(.failure(error))
case .success(let callbackURL):
self.handleSessionResult(url: callbackURL, completionHandler: completionHandler)
}
})

session.prefersEphemeralWebBrowserSession = true
session.presentationContextProvider = self.contextProvider

session.start()

self.session = session
}
}

private func handleSessionResult(url: URL, completionHandler: @escaping (Result<URLRequest, Error>) -> Void) {
do {
let tokenRequest = try self.config.flowHandler.accessRequestFromCallback(url: url)

performAccessRequest(tokenRequest, completionHandler: completionHandler)
} catch {
completionHandler(.failure(error))
}
}

private func performAccessRequest(_ request: URLRequest, completionHandler: @escaping (Result<URLRequest, Error>) -> Void) {
config.loader.response(for: request) { result in
switch result {
case .failure(let error):
completionHandler(.failure(error))
case .success(let response):
self.handleLoginResponse(response, completionHandler: completionHandler)
}
}
}

private func handleLoginResponse(_ response: URLSession.DataTaskResponse, completionHandler: @escaping (Result<URLRequest, Error>) -> Void) {
do {
let login = try JSONDecoder().decode(LoginResponse.self, from: response.data).oauthLogin

self.config.loginStorage.storeLogin(login) { error in
if let error = error {
completionHandler(.failure(error))
return
}

let authedRequest = self.buildRequest(with: login)

completionHandler(.success(authedRequest))
}
} catch {
completionHandler(.failure(error))
}
}
}
43 changes: 32 additions & 11 deletions Sources/OAuthenticator/HandleAuthenticatedResultOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import Foundation
import OperationPlus

class HandleAuthenticatedResultOperation: AsyncProducerOperation<URLSession.DataTaskResult> {
let config: AuthConfiguration
let request: URLRequest
let loadResult: URLSession.DataTaskResult
private let applier: AuthenticationApplier

init(request: URLRequest, response: URLLoader.LoadResult, config: AuthConfiguration) {
self.request = request
self.loadResult = response
self.config = config
self.applier = AuthenticationApplier(request: request, config: config)
}

var config: AuthConfiguration {
return applier.config
}

var request: URLRequest {
return applier.request
}

override func main() {
Expand All @@ -23,9 +29,7 @@ class HandleAuthenticatedResultOperation: AsyncProducerOperation<URLSession.Data
case .success(let login):
self.checkForRefresh(with: login)
case .failure:
// Here, we could start an entire new login flow, but we'd need
// to pull out functionality from ApplyAuthenticationOperation
self.finish(with: .failure(OAuthenticatorError.loginAfterRefreshNeededNotSupported))
self.beginNewLogin()
}
}
}
Expand All @@ -47,14 +51,14 @@ class HandleAuthenticatedResultOperation: AsyncProducerOperation<URLSession.Data

self.config.loader.response(for: refreshRequest) { result in
switch result {
case .failure(let error):
self.finish(with: .failure(error))
case .failure:
self.beginNewLogin()
case .success(let response):
self.handleRefreshResponse(response, from: login)
}
}
} catch {
self.finish(with: .failure(error))
self.beginNewLogin()
}
}

Expand All @@ -75,7 +79,7 @@ class HandleAuthenticatedResultOperation: AsyncProducerOperation<URLSession.Data
self.retryOriginalRequest(with: login)
}
} catch {
self.finish(with: .failure(error))
self.beginNewLogin()
}
}

Expand All @@ -86,4 +90,21 @@ class HandleAuthenticatedResultOperation: AsyncProducerOperation<URLSession.Data
self.finish(with: result)
}
}

private func beginNewLogin() {
// we want to attempt to re-request credentials from the user
// and use the resulting value to try the request again. We make the
// assumption that any successful login here will result in a token
// that will not immediately then require a refresh
applier.beginLogin() { result in
switch result {
case .failure(let error):
self.finish(with: .failure(error))
case .success(let request):
self.config.loader.response(for: request) { result in
self.finish(with: result)
}
}
}
}
}
6 changes: 6 additions & 0 deletions Sources/OAuthenticator/OAuthenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,9 @@ extension OAuthenticator {
}
}
}

extension OAuthenticator {
static func getUserCredentials() {

}
}

0 comments on commit c5861fb

Please sign in to comment.