Skip to content

Commit 6d6e3ca

Browse files
authored
feat(auth): Fix cancelling hostedUI returning a generic error (#982)
- This is a behavior change and potential break the current customers, please see Before and After effect below in the PR - Cancelling HostedUI signOut now returns userCancelled similar to the cancelling signIn
1 parent 771e35a commit 6d6e3ca

File tree

9 files changed

+161
-47
lines changed

9 files changed

+161
-47
lines changed

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/Dependency/AuthenticationProviderAdapter+SignIn.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ extension AuthenticationProviderAdapter {
148148
guard let self = self else { return }
149149

150150
if let error = error {
151-
let authError = self.convertSignUIErrorToAuthError(error)
151+
let authError = self.convertSignInUIErrorToAuthError(error)
152152
completionHandler(.failure(authError))
153153
return
154154
}
@@ -182,7 +182,13 @@ extension AuthenticationProviderAdapter {
182182
return .failure(authError)
183183
}
184184

185-
private func convertSignUIErrorToAuthError(_ error: Error) -> AuthError {
185+
private func convertSignInUIErrorToAuthError(_ error: Error) -> AuthError {
186+
if AuthErrorHelper.didUserCancelHostedUI(error) {
187+
return AuthError.service(
188+
AuthPluginErrorConstants.hostedUIUserCancelledError.errorDescription,
189+
AuthPluginErrorConstants.hostedUIUserCancelledError.recoverySuggestion,
190+
AWSCognitoAuthError.userCancelled)
191+
}
186192
if let awsMobileClientError = error as? AWSMobileClientError {
187193
switch awsMobileClientError {
188194
case .securityFailed(message: _):
@@ -207,7 +213,6 @@ extension AuthenticationProviderAdapter {
207213
default:
208214
break
209215
}
210-
211216
}
212217
let authError = AuthErrorHelper.toAuthError(error)
213218
return authError

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/Dependency/AuthenticationProviderAdapter+SignOut.swift

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ extension AuthenticationProviderAdapter {
1717
guard let self = self else {
1818
return
1919
}
20-
self.signOutWithUI(isGlobalSignout: request.options.globalSignOut,
21-
completionHandler: completionHandler)
20+
self.signOutWithUI(isGlobalSignout: request.options.globalSignOut, completionHandler: completionHandler)
2221
}
2322
}
2423

@@ -31,19 +30,32 @@ extension AuthenticationProviderAdapter {
3130

3231
let signOutOptions = SignOutOptions(signOutGlobally: isGlobalSignout, invalidateTokens: true)
3332
awsMobileClient.signOut(options: signOutOptions) { [weak self] error in
34-
guard error == nil else {
35-
let authError = AuthErrorHelper.toAuthError(error!)
36-
if case .notAuthorized = authError {
37-
// signOut globally might return notAuthorized when the current token is expired or invalidated
38-
// In this case, we just signOut the user locally and return a success result back.
39-
self?.awsMobileClient.signOutLocally()
40-
completionHandler(.success(()))
41-
} else {
42-
completionHandler(.failure(authError))
43-
}
33+
guard let error = error else {
34+
completionHandler(.success(()))
4435
return
4536
}
46-
completionHandler(.success(()))
37+
38+
// If the user had cancelled the signOut flow by closing the HostedUI,
39+
// return userCancelled error.
40+
if AuthErrorHelper.didUserCancelHostedUI(error) {
41+
let signOutError = AuthError.service(
42+
AuthPluginErrorConstants.hostedUIUserCancelledSignOutError.errorDescription,
43+
AuthPluginErrorConstants.hostedUIUserCancelledSignOutError.recoverySuggestion,
44+
AWSCognitoAuthError.userCancelled)
45+
completionHandler(.failure(signOutError))
46+
return
47+
}
48+
49+
let authError = AuthErrorHelper.toAuthError(error)
50+
if case .notAuthorized = authError {
51+
// signOut globally might return notAuthorized when the current token is expired or invalidated
52+
// In this case, we just signOut the user locally and return a success result back.
53+
self?.awsMobileClient.signOutLocally()
54+
completionHandler(.success(()))
55+
} else {
56+
completionHandler(.failure(authError))
57+
}
58+
return
4759
}
4860
}
4961
}

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/Models/Options/AWSAttributeResendConfirmationCodeOptions.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import Foundation
99

10+
// swiftlint:disable type_name
1011
public struct AWSAttributeResendConfirmationCodeOptions {
1112

1213
public let metadata: [String: String]?

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/Support/Constants/AuthPluginErrorConstants.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ struct AuthPluginErrorConstants {
3636
"User cancelled the signIn flow and could not be completed.",
3737
"Present the signIn UI again for the user to sign in.")
3838

39+
static let hostedUIUserCancelledSignOutError: AuthPluginErrorString = (
40+
"User cancelled the signOut flow and could not be completed.",
41+
"Present the signOut UI again for the user to sign out.")
42+
3943
static let userInvalidError: AuthPluginErrorString = (
4044
"Could not validate the user",
4145
"Get the current user Auth.getCurrentUser() and make the request")

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/Support/Utils/AuthErrorHelper.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import Amplify
99
import AWSMobileClient
10+
import SafariServices
11+
import AuthenticationServices
1012

1113
struct AuthErrorHelper {
1214

@@ -165,4 +167,21 @@ struct AuthErrorHelper {
165167
return AuthError.unknown("An unknown error occurred", error)
166168

167169
}
170+
171+
static func didUserCancelHostedUI(_ error: Error) -> Bool {
172+
if let sfAuthError = error as? SFAuthenticationError,
173+
case SFAuthenticationError.Code.canceledLogin = sfAuthError.code {
174+
return true
175+
}
176+
177+
if #available(iOS 12.0, *) {
178+
179+
if let asWebAuthError = error as? ASWebAuthenticationSessionError,
180+
case ASWebAuthenticationSessionError.Code.canceledLogin = asWebAuthError.code {
181+
return true
182+
}
183+
}
184+
185+
return false
186+
}
168187
}

AmplifyPlugins/Auth/AWSCognitoAuthPluginTests/AuthenticationProviderTests/AuthenticationProviderSigninWithSocialWebUITests.swift

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ class AuthenticationProviderSigninWithSocialWebUITests: BaseAuthenticationProvid
9595
/// - Given: an auth plugin with mocked service.
9696
///
9797
/// - When:
98-
/// - I invoke signInWithWebUI and mock cancel
98+
/// - I invoke signInWithWebUI and mock cancel using SFAuthenticationError
9999
/// - Then:
100-
/// - I should get a SFAuthenticationError.canceledLogin error
100+
/// - I should get a AWSCognitoAuthError.userCancelled error
101101
///
102102
func testCancelSignIn() {
103-
let mockError = NSError(domain: "com.apple.SafariServices.Authentication",
103+
let mockError = NSError(domain: SFAuthenticationErrorDomain,
104104
code: SFAuthenticationError.canceledLogin.rawValue,
105105
userInfo: nil)
106106
mockAWSMobileClient?.showSignInMockResult = .failure(mockError)
@@ -115,9 +115,45 @@ class AuthenticationProviderSigninWithSocialWebUITests: BaseAuthenticationProvid
115115
case .success(let signinResult):
116116
XCTFail("Should throw user cancelled error, instead - \(signinResult)")
117117
case .failure(let error):
118-
guard case .unknown(_, let underlyingError) = error,
119-
case .canceledLogin = (underlyingError as? SFAuthenticationError)?.code else {
120-
XCTFail("Should produce SFAuthenticationError error but instead produced \(error)")
118+
guard case .service(_, _, let underlyingError) = error,
119+
case .userCancelled = (underlyingError as? AWSCognitoAuthError) else {
120+
XCTFail("Should produce AWSCognitoAuthError error but instead produced \(error)")
121+
return
122+
}
123+
}
124+
}
125+
wait(for: [resultExpectation], timeout: apiTimeout)
126+
}
127+
128+
/// Test a signInWithWebUI when the user cancel
129+
///
130+
/// - Given: an auth plugin with mocked service.
131+
///
132+
/// - When:
133+
/// - I invoke signInWithWebUI and mock cancel using ASWebAuthenticationSessionError
134+
/// - Then:
135+
/// - I should get a AWSCognitoAuthError.userCancelled error
136+
///
137+
@available(iOS 12.0, *)
138+
func testASWebAuthenticationSessionError() {
139+
let mockError = NSError(domain: ASWebAuthenticationSessionErrorDomain,
140+
code: ASWebAuthenticationSessionError.canceledLogin.rawValue,
141+
userInfo: nil)
142+
mockAWSMobileClient?.showSignInMockResult = .failure(mockError)
143+
let options = AuthWebUISignInRequest.Options()
144+
145+
let resultExpectation = expectation(description: "Should receive a result")
146+
_ = plugin.signInWithWebUI(for: .amazon, presentationAnchor: window, options: options) { result in
147+
defer {
148+
resultExpectation.fulfill()
149+
}
150+
switch result {
151+
case .success(let signinResult):
152+
XCTFail("Should throw user cancelled error, instead - \(signinResult)")
153+
case .failure(let error):
154+
guard case .service(_, _, let underlyingError) = error,
155+
case .userCancelled = (underlyingError as? AWSCognitoAuthError) else {
156+
XCTFail("Should produce AWSCognitoAuthError error but instead produced \(error)")
121157
return
122158
}
123159
}

AmplifyPlugins/Auth/AWSCognitoAuthPluginTests/AuthenticationProviderTests/AuthenticationProviderSigninWithWebUITests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,9 @@ class AuthenticationProviderSigninWithWebUITests: BaseAuthenticationProviderTest
115115
case .success(let signinResult):
116116
XCTFail("Should throw user cancelled error, instead - \(signinResult)")
117117
case .failure(let error):
118-
guard case .unknown(_, let underlyingError) = error,
119-
case .canceledLogin = (underlyingError as? SFAuthenticationError)?.code else {
120-
XCTFail("Should produce SFAuthenticationError error but instead produced \(error)")
118+
guard case .service(_, _, let underlyingError) = error,
119+
case .userCancelled = (underlyingError as? AWSCognitoAuthError) else {
120+
XCTFail("Should produce userCancelled error but instead produced \(error)")
121121
return
122122
}
123123
}

AmplifyPlugins/Auth/AWSCognitoAuthPluginTests/AuthenticationProviderTests/AuthenticationProviderSignoutTests.swift

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,10 @@ class AuthenticationProviderSignoutTests: BaseAuthenticationProviderTest {
275275
/// - When:
276276
/// - I invoke signOut
277277
/// - Then:
278-
/// - I should get a SFAuthenticationError.canceledLogin error
278+
/// - I should get a AWSCognitoAuthError.userCancelled error
279279
///
280280
func testSignOutWithUserCancel() {
281-
let error = NSError(domain: "com.apple.SafariServices.Authentication",
281+
let error = NSError(domain: SFAuthenticationErrorDomain,
282282
code: SFAuthenticationError.canceledLogin.rawValue,
283283
userInfo: nil)
284284
let options = AuthSignOutRequest.Options()
@@ -293,9 +293,46 @@ class AuthenticationProviderSignoutTests: BaseAuthenticationProviderTest {
293293
case .success:
294294
XCTFail("Should not get success")
295295
case .failure(let error):
296-
guard case .unknown(_, let underlyingError) = error,
297-
case .canceledLogin = (underlyingError as? SFAuthenticationError)?.code else {
298-
XCTFail("Should produce SFAuthenticationError error instead of \(error)")
296+
guard case .service(_, _, let underlyingError) = error,
297+
case .userCancelled = (underlyingError as? AWSCognitoAuthError) else {
298+
XCTFail("Should produce userCancelled error instead of \(error)")
299+
return
300+
}
301+
}
302+
}
303+
wait(for: [resultExpectation], timeout: apiTimeout)
304+
}
305+
306+
/// Test a signOut with userCancelled from service
307+
///
308+
/// - Given: Given an auth plugin with mocked service. Mocked service should mock a
309+
/// ASWeb UserCancelled response
310+
///
311+
/// - When:
312+
/// - I invoke signOut
313+
/// - Then:
314+
/// - I should get a AWSCognitoAuthError.userCancelled error
315+
///
316+
@available(iOS 12.0, *)
317+
func testASWebAuthSignOutWithUserCancel() {
318+
let mockError = NSError(domain: ASWebAuthenticationSessionErrorDomain,
319+
code: ASWebAuthenticationSessionError.canceledLogin.rawValue,
320+
userInfo: nil)
321+
let options = AuthSignOutRequest.Options()
322+
mockAWSMobileClient.signOutMockError = mockError
323+
let resultExpectation = expectation(description: "Should receive a result")
324+
_ = plugin.signOut(options: options) { result in
325+
defer {
326+
resultExpectation.fulfill()
327+
}
328+
329+
switch result {
330+
case .success:
331+
XCTFail("Should not get success")
332+
case .failure(let error):
333+
guard case .service(_, _, let underlyingError) = error,
334+
case .userCancelled = (underlyingError as? AWSCognitoAuthError) else {
335+
XCTFail("Should produce userCancelled error instead of \(error)")
299336
return
300337
}
301338
}

AmplifyPlugins/Auth/Podfile.lock

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ PODS:
1010
- AWSCore (~> 2.22.0)
1111
- AWSMobileClient (~> 2.22.0)
1212
- AWSPluginsCore (= 1.5.5)
13-
- AWSAuthCore (2.22.0):
14-
- AWSCore (= 2.22.0)
15-
- AWSCognitoIdentityProvider (2.22.0):
16-
- AWSCognitoIdentityProviderASF (= 2.22.0)
17-
- AWSCore (= 2.22.0)
18-
- AWSCognitoIdentityProviderASF (2.22.0)
19-
- AWSCore (2.22.0)
20-
- AWSMobileClient (2.22.0):
21-
- AWSAuthCore (= 2.22.0)
22-
- AWSCognitoIdentityProvider (= 2.22.0)
23-
- AWSCognitoIdentityProviderASF (= 2.22.0)
24-
- AWSCore (= 2.22.0)
13+
- AWSAuthCore (2.22.1):
14+
- AWSCore (= 2.22.1)
15+
- AWSCognitoIdentityProvider (2.22.1):
16+
- AWSCognitoIdentityProviderASF (= 2.22.1)
17+
- AWSCore (= 2.22.1)
18+
- AWSCognitoIdentityProviderASF (2.22.1)
19+
- AWSCore (2.22.1)
20+
- AWSMobileClient (2.22.1):
21+
- AWSAuthCore (= 2.22.1)
22+
- AWSCognitoIdentityProvider (= 2.22.1)
23+
- AWSCognitoIdentityProviderASF (= 2.22.1)
24+
- AWSCore (= 2.22.1)
2525
- AWSPluginsCore (1.5.5):
2626
- Amplify (= 1.5.5)
2727
- AWSCore (~> 2.22.0)
@@ -76,11 +76,11 @@ CHECKOUT OPTIONS:
7676
SPEC CHECKSUMS:
7777
Amplify: 975fe3981790cf06e605353d2e704fc0985a45f3
7878
AmplifyTestCommon: 4e33dad284eca69acdb7511418c877967c0fc5da
79-
AWSAuthCore: b54e2008da479e5a6ffdb6b482821d8f5a9afaa4
80-
AWSCognitoIdentityProvider: 32a91ad644e49e40235a408a8fbb96ea74891f19
81-
AWSCognitoIdentityProviderASF: 8b92721c17f2951ecd5bcfb749d5534f2851c2c7
82-
AWSCore: 8bd1def9cb0b6770c65b6d394f1c1cbc0a5562b7
83-
AWSMobileClient: bba690cc09c6569cc1111141beaa4e2f789f536b
79+
AWSAuthCore: ad355f72d2d3e56939fb3b2965b796a93091cdf1
80+
AWSCognitoIdentityProvider: 33e00e930891f899c27c007b33863e79b4b896af
81+
AWSCognitoIdentityProviderASF: ac1574ea9fda4877400ecef9004e4e5759eb4961
82+
AWSCore: f3f2e25cf5b4cb58b9561b48987d4fdd019227c2
83+
AWSMobileClient: 70c694a0399c37084edaaebe8e1f1b6f21fd299f
8484
AWSPluginsCore: 358365037f62d5bc995037d6eb687f39582d90d0
8585
CwlCatchException: 70a52ae44ea5d46db7bd385f801a94942420cd8c
8686
CwlPreconditionTesting: d33a4e4f285c0b885fddcae5dfedfbb34d4f3961

0 commit comments

Comments
 (0)