mobile-ios/CovidSafe/API/CovidRequestRetrier.swift

111 lines
4.3 KiB
Swift
Raw Permalink Normal View History

2020-05-08 07:49:14 +00:00
//
// CovidRequestInterceptor.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import Foundation
import Alamofire
2021-03-18 03:16:35 +00:00
import KeychainSwift
2020-05-08 07:49:14 +00:00
final class CovidRequestRetrier: Alamofire.RequestInterceptor {
private let numRetries: Int
private var retriesExecuted: Int = 0
2021-03-18 03:16:35 +00:00
private var triedRefresh = false
2020-05-08 07:49:14 +00:00
init(retries: Int) {
self.numRetries = retries
}
2021-03-18 03:16:35 +00:00
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
2021-05-13 00:39:38 +00:00
let keychain = KeychainSwift.shared
2021-03-18 03:16:35 +00:00
let refreshExists = keychain.get("REFRESH_TOKEN") != nil
2021-05-13 00:39:38 +00:00
// turn off geolock error
UserDefaults.standard.setValue(false, forKey: showGeolockErrorKey)
2021-03-18 03:16:35 +00:00
// prevent authenticated api calls if the re-registration flow has been started
if UserDefaults.standard.bool(forKey: "ReauthenticationNeededKey") &&
refreshExists {
completion(.failure(CovidSafeAPIError.TokenExpiredError))
return
}
// check headers an update if needed.
// intercept the first call to the API after app updates to retrieve new tokens
if !refreshExists &&
keychain.get("JWT_TOKEN") != nil {
AuthenticationAPI.issueTokensAPI { (response, error) in
guard let token = response?.token else {
completion(.success(urlRequest))
return
}
// update the token
urlRequest.headers.add(name: "Authorization", value: "Bearer \(token)")
completion(.success(urlRequest))
}
return
}
guard let token = keychain.get("JWT_TOKEN"),
urlRequest.headers["Authorization"] != nil else {
completion(.success(urlRequest))
return
}
// update the token in case is was updated in a retry
urlRequest.headers.add(name: "Authorization", value: "Bearer \(token)")
completion(.success(urlRequest))
}
2020-05-08 07:49:14 +00:00
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
2021-03-18 03:16:35 +00:00
guard retriesExecuted < numRetries else {
completion(.doNotRetryWithError(error))
return
}
if let covidError = error.asAFError?.underlyingError as? CovidSafeAPIError, covidError == .TokenExpiredError {
retriesExecuted = numRetries
// for some reason the retry is getting called even after doNotRetryWithError below.
// set retries to max and the guard above stops it all
completion(.doNotRetryWithError(error))
return
}
guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 403 || response.statusCode == 401 else {
2020-05-08 07:49:14 +00:00
/// The request did not fail due to a 403 Forbidden response.
let isServerTrustEvaluationError = error.asAFError?.isServerTrustEvaluationError ?? false
if ( retriesExecuted >= numRetries || isServerTrustEvaluationError) {
return completion(.doNotRetryWithError(error))
}
retriesExecuted += 1
return completion(.retryWithDelay(1.0))
}
2021-03-18 03:16:35 +00:00
2021-05-13 00:39:38 +00:00
if let serverHeader = response.headers.first(where: { $0.name == "Server" }),
response.statusCode == 403 && serverHeader.value == "CloudFront" {
UserDefaults.standard.setValue(true, forKey: showGeolockErrorKey)
return completion(.doNotRetryWithError(error))
}
2021-03-18 03:16:35 +00:00
if !triedRefresh &&
2021-05-13 00:39:38 +00:00
(response.statusCode == 401 || response.statusCode == 403) {
2021-03-18 03:16:35 +00:00
triedRefresh = true
retriesExecuted += 1
AuthenticationAPI.issueTokensAPI { (response, authError) in
// this will update the tokens automatically
2021-05-13 00:39:38 +00:00
if let respError = authError, respError == .TokenExpiredError {
2021-03-18 03:16:35 +00:00
completion(.doNotRetryWithError(error))
return
}
completion(.retryWithDelay(1.0))
}
return
}
2020-05-08 07:49:14 +00:00
return completion(.doNotRetryWithError(error))
}
}