Integrate LinkedIn Authentication to Firebase Auth in iOS using Swift - ios

I'm pretty much new here but I've been developing in swift (ver. 4) for approximately 3 months. So, I have this issue on implementing the LinkedIn authentication using a web view since I don't want to utilise the authentication with the need of a LinkedIn app -- UX stuff.
I used the following guides in order to accomplish this, but I was only able to get to the point of getting an access token with a bug of being stuck in the logging in process. When I debugged the application, I was able to get the access token but the view controller for the web view does not dismiss.
These are the guides I used:
LinkedIn Auth: https://www.appcoda.com/linkedin-sign-in/
Custom Firebase Auth: https://firebase.google.com/docs/auth/ios/custom-auth
Issues:
Unable to dismiss the view controller after getting the access
token.
Where to get the needed "custom token" using the access
token I have for the Firebase Custom Authentication.
Code snippet for Issue #1
// Initialize a NSURLSession object.
let session = URLSession(configuration: URLSessionConfiguration.default)
// Make the request.
let task: URLSessionDataTask = session.dataTask(with: request) { (data, response, error) -> Void in
// Get the HTTP status code of the request.
let statusCode = (response as! HTTPURLResponse).statusCode
if statusCode == 200 {
// Convert the received JSON data into a dictionary.
do {
let dataDictionary = try JSONSerialization.jsonObject(with: data!, options: []) as! [String : AnyObject]
let accessToken = dataDictionary["access_token"] as! String
UserDefaults.standard.set(accessToken, forKey: "LIAccessToken")
UserDefaults.standard.synchronize()
// Custom Firebase Auth
// Auth.auth().signIn(withCustomToken: UserDefaults.standard.string(forKey: "LIAccessToken")!) { (user, error) in
// if let error = error {
// print(error)
// return
// }
//
// }
DispatchQueue.main.async {
self.dismiss(animated: true, completion: nil)
}
} catch {
print("Could not convert JSON data into a dictionary.")
}
}
}
task.resume()
Thanks in advance to any help! I hope to be able to help those who have the same issues.

Related

Post to Instagram by opening Instagram app – iOS, Swift

I have an Instagram scheduling app and I am trying to open this (see image below) in Swift 5.x. The goal is simple: save Image to Firebase, once it is time to post, notification!, user clicks on the notification and this (image below) opens up with the appropriate image/video to post. Everything works except for opening Instagram with the appropriate photo/video. I have tried this:
func postToInstagram(image: URL) {
let videoFileUrl: URL = image
var localId: String?
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoFileUrl)
localId = request?.placeholderForCreatedAsset?.localIdentifier
}, completionHandler: { success, error in
// completion handler is called on an arbitrary thread
// but since you (most likely) will perform some UI stuff
// you better move everything to the main thread.
DispatchQueue.main.async {
guard error == nil else {
// handle error
print(error)
return
}
guard let localId = localId else {
// highly unlikely that it'll be nil,
// but you should handle this error just in case
return
}
let url = URL(string: "instagram://library?LocalIdentifier=\(localId)")!
guard UIApplication.shared.canOpenURL(url) else {
// handle this error
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
})
}
and this:
func postToInstagram(image: URL, igURL: String) {
let urlStr: String = "instagram://app"
let url = URL(string: igURL)
if UIApplication.shared.canOpenURL(url!) {
print("can open")
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}
To no avail. The latter code works, but only opens the Instagram app itself, which is fine, but I would like to open the View in the image below rather than Instagram's home screen. I also tried changing the URL to "instagram://share" and this works but goes to publish a regular post, whereas I want the user to decide what they want to do with their image.
This is where I want to go:
Note: For everyone who will be telling me this and whoever will wonder: Yes, my URL schemes (LSApplicationQueriesSchemes) are fine. And, just to clarify, I need to fetch the image/video from Firebase before posting it.

NSURLSession cancelling dataTask sometimes with error message Task finished with error - code: -999

I am uploading png Images->Base64->jsonData in URLRequest.httpBody size around 6MB. I am using a static NSURLSession with default Configuration and uploading using dataTask on urlsession. Sometimes its successfully uploaded to server sometimes its not and getting below error and nothing is printing at server side. I am not making parallel calls. We are using SSL pinning and handling authentication challenges proper so no SSL authentication error.
iOS device 11.3 and XCode 10 we are using.
Task <58BF437E-7388-4AE4-B676-2485A57CB0CD>.<10> finished with error - code: -999
private lazy var configuration: URLSessionConfiguration = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = TimeInterval(120)
configuration.timeoutIntervalForResource = TimeInterval(120)
return configuration
}()
private lazy var urlSession = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
func invokeService(methodName : String, collectionName: String, queryDictionary: [String:AnyObject]! = nil, httpMethod: String! = nil) {
// Set up the URL request
let baseUrl: String = WebServiceConstants.hostUrl.qaUrl + "/\(collectionName)" + "/\(methodName)"
// let baseUrl: String = WebServiceConstants.hostUrl.demoUrl + "/\(collectionName)" + "/\(methodName)"
// let baseUrl: String = WebServiceConstants.hostUrl.prod_Url + "/\(collectionName)" + "/\(methodName)"
guard let url = URL(string: baseUrl) else {
return
}
var urlRequest = URLRequest(url: url)
// set up the session
// let configuration = URLSessionConfiguration.default
// configuration.timeoutIntervalForRequest = TimeInterval(120)
// configuration.timeoutIntervalForResource = TimeInterval(120)
//
// let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
urlRequest.httpMethod = httpMethod
do {
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: queryDictionary, options: []) // pass dictionary to nsdata object and set it as request body
}
catch _ {
}
urlRequest.setValue(WebServiceConstants.HTTPStrings.contentTypeJSON, forHTTPHeaderField: WebServiceConstants.HTTPStrings.contentTypeHeader)
// print(queryDictionary)
if AppController.sharedInstance.isAlreadyLogin() && (KeychainWrapper.standard.string(forKey: Constants.UserDefaultKeys.authorizationHeaderValue) != nil) {
let authorizationHeaderValue = WebServiceConstants.HTTPStrings.authorizationHeaderValue + KeychainWrapper.standard.string(forKey: Constants.UserDefaultKeys.authorizationHeaderValue)!
urlRequest.setValue(authorizationHeaderValue, forHTTPHeaderField: WebServiceConstants.HTTPStrings.authorizationHeader)
}
let _ = urlSession.dataTask(with: urlRequest, completionHandler: { [unowned self]
(data, response, error) in
//print(response)
if error != nil {
if error?._code == NSURLErrorTimedOut {
// print(error?.localizedDescription)
let userInfo = [
NSLocalizedDescriptionKey: BWLocalizer.sharedInstance.localizedStringForKey(key:"App_Timeout_Message")
]
let errorTemp = NSError(domain:"", code:-1001, userInfo:userInfo)
self.delegate?.didFailWithError(errorObject: errorTemp)
} else if error?._code == NSURLErrorNotConnectedToInternet {
let userInfo = [
NSLocalizedDescriptionKey: BWLocalizer.sharedInstance.localizedStringForKey(key:"Internet_Not_Available")
]
let errorTemp = NSError(domain:"", code:-1001, userInfo:userInfo)
self.delegate?.didFailWithError(errorObject: errorTemp)
}
else if error?._code == NSURLErrorCancelled {
// canceled
print("Request is cancelled") // Control reaches here on Finished with Error code = -999
self.delegate?.didFailWithError(errorObject: error!)
}
else {
self.delegate?.didFailWithError(errorObject: error!)
}
} else {
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
{
self.delegate?.didReceiveResponse(responseObject: json as AnyObject)
//Implement your logic
print(json)
}
} catch {
self.delegate?.didFailWithError(errorObject: error)
}
}
}).resume()
}
*Added SSL Certificate pinning code *
extension WebserviceHandler : URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let trust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let credential = URLCredential(trust: trust)
let pinner = setupCertificatePinner() // adding CertificateHash
if (!pinner.validateCertificateTrustChain(trust)) {
challenge.sender?.cancel(challenge)
}
if (pinner.validateTrustPublicKeys(trust)) {
completionHandler(.useCredential, credential)
}
else {
completionHandler(.cancelAuthenticationChallenge, nil)
let popUp = UIAlertController(title: "", message: BWLocalizer.sharedInstance.localizedStringForKey(key:"Certificate_Pining_Fail_Message"), preferredStyle: UIAlertController.Style.alert)
popUp.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: {alertAction in popUp.dismiss(animated: true, completion: nil)}))
popUp.addAction(UIAlertAction(title: BWLocalizer.sharedInstance.localizedStringForKey(key:"Action_Go"), style: UIAlertAction.Style.default, handler: { action in
let urlToOpenAppStorepage = URL(string: WebServiceConstants.hostUrl.appstore_url)
let objApp = UIApplication.shared
objApp.openURL(urlToOpenAppStorepage!)
}))
UIApplication.shared.keyWindow?.rootViewController?.present(popUp, animated: true, completion: nil)
}
}
}
func setupCertificatePinner() -> CertificatePinner {
// let baseUrl: String = WebServiceConstants.hostUrl.dev_URL
let baseUrl: String = WebServiceConstants.hostUrl.qaUrl
// let baseUrl: String = WebServiceConstants.hostUrl.demoUrl
// let baseUrl: String = WebServiceConstants.hostUrl.prod_Url
let pinner = CertificatePinner(baseUrl)
/*
You will see something like this:
being challanged! for www.google.co.nz
hash order is usually most specific to least, so the first one is your domain, the last is the root CA
Production
hash: 8U/k3RvTcMVSafJeS9NGpY4KDFdTLwpQ/GUc+lmPH/s=
hash: 4vkhpuZeIkPQ+6k0lXGi7ywkVNV55LhVgU0GaWWMOdk=
hash: jZzMXbxSnIsuAiEBqDZulZ/wCrrpW9bRLMZ6QYxs0Gk=
hash: uthKDtpuYHgn+wRsokVptSysyqBzmr4RP86mOC703bg=
you might need to change the has below to be the second one in the list for the code to pass
QA
hash: LX6ZGwP3Uii+KCZxDxDWlDWijvNI6K/t2906cUzKYM4=
hash: 4vkhpuZeIkPQ+6k0lXGi7ywkVNV55LhVgU0GaWWMOdk=
hash: jZzMXbxSnIsuAiEBqDZulZ/wCrrpW9bRLMZ6QYxs0Gk=
*/
pinner.debugMode = true
pinner.addCertificateHash(WebServiceConstants.HTTPStrings.hashKey)
return pinner
}
Using pinning library : https://github.com/nicwise/certificatepinner
I see several bugs in that code.
The very first line in your authentication handler is going to cause failures if you're behind an authenticated proxy.
It will also fail if the server wants any sort of HTTP auth password or OAuth credential.
It will also fail in a number of other situations.
You should never cancel an authentication request unless the request is actually bad in some way. Canceling the authentication request also prevents the operating system from handling it transparently for you if you can. The only situation where you should cancel an authentication request is if you check a cert or whatever and it is actually invalid. Otherwise, you should generally trust the OS to do the right thing by default when you request external handling.
So use default handling unless the authentication method is server trust.
The code does not check the authentication method at all. You should not be doing any checks unless the authentication method is server trust, for all of the reasons listed above. If it is anything else, use default handling. Always.
The next if statement has two problems:
It provides a new state for the authentication request without returning. This means you can call the completion handler afterwards, which could cause crashes and other misbehavior.
It is calling methods on the challenge sender that are intended to affect behavior. That's how you used to do it with NSURLConnection, but you should never call any methods on the challenge sender (other than possibly to see if it is an object of your own creation, if you are using custom NSURLProtocol classes) with NSURLSession, because it can cause all sorts of problems, up to and including crashes. (See the giant warning in the documentation for NSURLAuthenticationChallenge's -sender method, or, for that matter, the two paragraphs before that warning.)
I'm not entirely sure I trust the pinning code, either. It looks like it passes if any key within the chain of trust is a trusted key, whereas typically pinning requires that the last (leaf) key in the chain of trust be a trusted key.
The security advice in that pinning code is also dubious. You probably shouldn't be pinning to a certificate, but rather to the key inside the certificate. Pinning the leaf cert's key is entirely appropriate, and is really the only appropriate choice, because it is typically the only one whose key is actually under your direct control. If you reissue the cert, that's no big deal, because you should be reissuing with the same key as before, unless your key has been compromised in some way.
Alternatively, you can add a trust layer if you want, but this either requires running your own custom root (which would require changing the validateCertificateTrustChain method to add your custom root cert while validating the chain of trust) or convincing a CA to sell you a cert that can sign other certs, which costs $$$$. Neither of these options seems very practical.
Those issues make me a little bit concerned about the library as a whole, but I don't have time to audit it. You should probably ask around and see if anybody has done a thorough audit of the library in question, as it is notoriously easy to make mistakes when writing code that works with TLS keys, and I'd hate to see you run into security problems later.
After you fix all of the bugs listed above, if you're still having problems, come back and ask another question with updated code. Also, please also ask the code pinning project in question to fix their code snippets, as they seem to contain the same bugs. :-) Thanks.

iOS Swift 4 OAuth

I writing an app in Swift 4 that uses the Discogs API. As such, I require a user to have access to personal data on their Discogs account, so I am authenticating against their API using OAuthSwift. Currently, I am able to kick off the auth flow, sign in and return the an oauthToken and the oauthTokenSecret
Making a subsequent request to their https://api.discogs.com/oauth/identity I am returned a user object, so I am happy at this point I can sign in and make authenticated requests.
However, I do not understand how I can check if a user is authenticated when the app first starts up. Currently, I am not storing the response, instead I am making a call to the identity endpoint in nested callback
import UIKit
import OAuthSwift
class ViewController: UIViewController {
let oauthSwift = OAuth1Swift(
consumerKey: "foo",
consumerSecret: "bar",
requestTokenUrl: "https://api.discogs.com/oauth/request_token",
authorizeUrl: "https://www.discogs.com/oauth/authorize",
accessTokenUrl: "https://api.discogs.com/oauth/access_token"
)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = .white
kickOffAuthFlow()
}
fileprivate func kickOffAuthFlow() {
oauthSwift.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauthSwift)
guard let callbackURL = URL(string: "foo.bar.boobaz:/oauth_callback") else { return }
oauthSwift.authorize(withCallbackURL: callbackURL, success: { (credential, response, parameters) in
_ = self.oauthSwift.client.get("https://api.discogs.com/oauth/identity", success: { (response) in
guard let dataString = response.string else { return }
print(dataString)
}, failure: { (error) in
print("error")
})
}) { (error) in
print(error.localizedDescription)
}
}
}
What is best practice in this case? How should I store these tokens and how should I ensure once the user is logged in, they aren't forced to log in the next time the app is opened (providing the token hasn't expired, however that is a separate issue I am prepared to handle at a later point)
Coming from a web development background, I was able to just store a token in session storage, on load I would then check the exp on the token and request a new one or take some other action.
I have not quite grasped how this works in iOS development yet.
You have two options to store access token in local.
UserDefault
Keychain
1. UserDefault
Use UserDefault to store token in memory. When the app gets launch, check if the token is stored in userdafault. UserDefault is used as short memory storage where you can store small data. It remains in memory if you kill the app.
let tokenIdentifier = "TokenIdentifier"
func storeAccessToken(token: String) {
UserDefaults.standard.set(token, forKey: tokenIdentifier)
}
func checkUserLogin() {
if UserDefaults.standard.value(forKey: tokenIdentifier) != nil {
print("User is Login")
}
else {
print("User need to login")
}
}
check this for learn more about userdefault
https://swift3tutorials.com/swift-3-user-defaults/
https://www.hackingwithswift.com/example-code/system/how-to-save-user-settings-using-userdefaults
2. Keychain
Userdefault is not secure. An access token is a sensitive information which should be stored in a secure place. So storing the access token in user default is not the correct choice. You must store access token in the keychain. Use SwiftKeychainWrapper pod to store token in Keychain.
let tokenIdentifier = "TokenIdentifier"
func storeAccessToken(token: String) {
KeychainWrapper.standard.set(token, forKey: tokenIdentifier)
}
func checkUserLogin() {
let token: String? = KeychainWrapper.standard.string(forKey: tokenIdentifier)
if token != nil {
print("User is Login")
}
else {
print("User need to login")
}
}

How can I add phone authentication in firebase using swift?

So firebase recently supported phone auth, but the problem is that the documentation is in objective-c. And I have never done push notifications before. this is a link to the objc phone auth docs: https://firebase.google.com/docs/auth/ios/phone-auth
You can see an example in the official sample repo: https://github.com/firebase/quickstart-ios/blob/master/authentication/AuthenticationExampleSwift/MainViewController.swift#L161
The core process is like this:
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber) { (verificationID, error) in
if let error = error {
// TODO: show error
return
}
guard let verificationID = verificationID else { return }
verificationCode = // TODO: get SMS verification code from user.
if let verificationCode = verificationCode {
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID, verificationCode: verificationCode)
Auth.auth().signIn(with: credential) { (user, error) in
// TODO: handle sign in
}
} else {
// Verification code was empty
}
}
}
You'll need to plug in UI to prompt the user (thats in the sample), or take advantage of FirebaseUI to handle it all for you: https://github.com/firebase/FirebaseUI-iOS

URLSessionUploadTask getting automatically cancelled instantly

I'm having this weird issue in which a newly created URLSessionUploadTask gets cancelled instantly. I'm not sure if it's a bug with the current beta of Xcode 8.
I suspect it might be a bug because the code I'm about to post ran fine exactly once. No changes were made to it afterwards and then it simply stopped working. Yes, it literally ran once, and then it stopped working. I will post the error near the end.
I will post the code below, but first I will summarize how the logic here works.
My test, or user-exposed API (IE for use in Playgrounds or directly on apps), calls the authorize method. This authorize method will in turn call buildPOSTTask, which will construct a valid URL and return a URLSessionUploadTask to be used by the authorize method.
With that said, the code is below:
The session:
internal let urlSession = URLSession(configuration: .default)
Function to create an upload task:
internal func buildPOSTTask(onURLSession urlSession: URLSession, appendingPath path: String, withPostParameters postParams: [String : String]?, getParameters getParams: [String : String]?, httpHeaders: [String : String]?, completionHandler completion: URLSessionUploadTaskCompletionHandler) -> URLSessionUploadTask {
let fullURL: URL
if let gets = getParams {
fullURL = buildURL(appendingPath: path, withGetParameters: gets)
} else {
fullURL = URL(string: path, relativeTo: baseURL)!
}
var request = URLRequest(url: fullURL)
request.httpMethod = "POST"
var postParameters: Data? = nil
if let posts = postParams {
do {
postParameters = try JSONSerialization.data(withJSONObject: posts, options: [])
} catch let error as NSError {
fatalError("[\(#function) \(#line)]: Could not build POST task: \(error.localizedDescription)")
}
}
let postTask = urlSession.uploadTask(with: request, from: postParameters, completionHandler: completion)
return postTask
}
The authentication function, which uses a task created by the above function:
public func authorize(withCode code: String?, completion: AccessTokenExchangeCompletionHandler) {
// I have removed a lot of irrelevant code here, such as the dictionary building code, to make this snippet shorter.
let obtainTokenTask = buildPOSTTask(onURLSession: self.urlSession, appendingPath: "auth/access_token", withPostParameters: nil, getParameters: body, httpHeaders: nil) { (data, response, error) in
if let err = error {
completion(error: err)
} else {
print("Response is \(response)")
completion(error: nil)
}
}
obtainTokenTask.resume()
}
I caught this error in a test:
let testUser = Anilist(grantType: grant, name: "Test Session")
let exp = expectation(withDescription: "Waiting for authorization")
testUser.authorize(withCode: "a valid code") { (error) in
if let er = error {
XCTFail("Authentication error: \(er.localizedDescription)")
}
exp.fulfill()
}
self.waitForExpectations(withTimeout: 5) { (err) in
if let error = err {
XCTFail(error.localizedDescription)
}
}
It always fails instantly with this error:
Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://anilist.co/api/auth/access_token?client_secret=REMOVED&grant_type=authorization_code&redirect_uri=genericwebsitethatshouldntexist.bo&client_id=ibanez-hod6w&code=REMOVED,
NSLocalizedDescription=cancelled,
NSErrorFailingURLStringKey=https://anilist.co/api/auth/access_token?client_secret=REMOVED&grant_type=authorization_code&redirect_uri=genericwebsitethatshouldntexist.bo&client_id=ibanez-hod6w&code=REMOVED}
Here's a few things to keep in mind:
The URL used by the session is valid.
All credentials are valid.
It fails instantly with a "cancelled" error, that simply did not happen before. I am not cancelling the task anywhere, so it's being cancelled by the system.
It also fails on Playgrounds with indefinite execution enabled. This is not limited to my tests.
Here's a list of things I have tried:
Because I suspect this is a bug, I first tried to clean my project, delete derived data, and reset all simulators. None of them worked.
Even went as far restarting my Mac...
Under the small suspicion that the upload task was getting deallocated due to it not having any strong pointers, and in turn calling cancel, I also rewrote authorize to return the task created by buildPOSTTask and assigned it to a variable in my test. The task was still getting cancelled.
Things I have yet to try (but I will accept any other ideas as I work through these):
Run it on a physical device. Currently downloading iOS 10 on an iPad as this is an iOS 10 project. EDIT: I just tried and it's not possible to do this.
I'm out of ideas of what to try. The generated logs don't seem to have any useful info.
EDIT:
I have decided to just post the entire project here. The thing will be open source anyway when it is finished, and the API credentials I got are for a test app.
ALCKit
After struggling non-stop with this for 6 days, and after googling non-stop for a solution, I'm really happy to say I have finally figured it out.
Turns out that, for whatever mysterious reason, the from: parameter in uploadTask(with:from:completionHandler) cannot be nil. Despite the fact that the parameter is marked as an optional Data, it gets cancelled instantly when it is missing. This is probably a bug on Apple's side, and I opened a bug when I couldn't get this to work, so I will update my bug report with this new information.
With that said, everything I had to do was to update my buildPOSTTask method to account for the possibility of the passed dictionary to be nil. With that in place, it works fine now:
internal func buildPOSTTask(onURLSession urlSession: URLSession, appendingPath path: String, withPostParameters postParams: [String : String]?, getParameters getParams: [String : String]?, httpHeaders: [String : String]?, completionHandler completion: URLSessionUploadTaskCompletionHandler) -> URLSessionUploadTask {
let fullURL: URL
if let gets = getParams {
fullURL = buildURL(appendingPath: path, withGetParameters: gets)
} else {
fullURL = URL(string: path, relativeTo: baseURL)!
}
var request = URLRequest(url: fullURL)
request.httpMethod = "POST"
var postParameters: Data
if let posts = postParams {
do {
postParameters = try JSONSerialization.data(withJSONObject: posts, options: [])
} catch let error as NSError {
fatalError("[\(#function) \(#line)]: Could not build POST task: \(error.localizedDescription)")
}
} else {
postParameters = Data()
}
let postTask = urlSession.uploadTask(with: request, from: postParameters, completionHandler: completion)
return postTask
}
Are you by any chance using a third party library such as Ensighten? I had the exact same problem in XCode 8 beta (works fine in XCode 7) and all of my blocks with nil parameters were causing crashes. Turns out it was the library doing some encoding causing the issue.
For me, this was a weak reference causing the issue, so I changed
completion: { [weak self] (response: Result<ResponseType, Error>)
to
completion: { [self] (response: Result<ResponseType, Error>)

Resources