Skip to content

Commit fb3966f

Browse files
authored
fix(auth): Delete user api get stuck on no network (#2656)
1 parent 9fa45a9 commit fb3966f

File tree

10 files changed

+145
-11
lines changed

10 files changed

+145
-11
lines changed

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/DeleteUser/DeleteUser.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ struct DeleteUser: Action {
4747
_ = try await client.deleteUser(input: input)
4848
event = DeleteUserEvent(eventType: .signOutDeletedUser)
4949
logVerbose("\(#fileID) Delete User succeeded", environment: environment)
50-
} catch let error as DeleteUserOutputError {
50+
} catch let error as AuthErrorConvertible {
5151
event = DeleteUserEvent(eventType: .throwError(error.authError))
5252
logVerbose("\(#fileID) Delete User failed \(error)", environment: environment)
5353
} catch let error {

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/ClientBehavior/AWSCognitoAuthPlugin+ClientBehavior.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ extension AWSCognitoAuthPlugin: AuthCategoryBehavior {
159159
}
160160

161161
public func deleteUser() async throws {
162-
let task = AWSAuthDeleteUserTask(authStateMachine: self.authStateMachine)
162+
let task = AWSAuthDeleteUserTask(
163+
authStateMachine: self.authStateMachine,
164+
authConfiguraiton: authConfiguration)
163165
_ = try await taskQueue.sync {
164166
return try await task.value
165167
}

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,18 @@ class FetchAuthSessionOperationHelper {
174174

175175
switch error {
176176

177-
case .notAuthorized:
177+
case .notAuthorized, .noCredentialsToRefresh:
178178
if !isSignedIn {
179179
return AuthCognitoSignedOutSessionHelper.makeSessionWithNoGuestAccess()
180180
}
181-
case .noCredentialsToRefresh:
182-
if !isSignedIn {
183-
return AuthCognitoSignedOutSessionHelper.makeSessionWithNoGuestAccess()
181+
182+
case .service(let error):
183+
if let authError = (error as? AuthErrorConvertible)?.authError {
184+
let session = AWSAuthCognitoSession(isSignedIn: isSignedIn,
185+
identityIdResult: .failure(authError),
186+
awsCredentialsResult: .failure(authError),
187+
cognitoTokensResult: .failure(authError))
188+
return session
184189
}
185190
default: break
186191

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AuthErrorConvertible.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ protocol AuthErrorConvertible {
1414

1515
var authError: AuthError { get }
1616
}
17+
18+
extension AuthError: AuthErrorConvertible {
19+
var authError: AuthError {
20+
return self
21+
}
22+
}

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/SdkError+AuthError.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,13 @@ extension SdkError: AuthErrorConvertible {
7070
if let authError = error as? AuthErrorConvertible {
7171
return authError.authError
7272
} else {
73-
return AuthError.service(error.localizedDescription,
74-
"",
75-
AWSCognitoAuthError.network)
73+
return AuthError.service(
74+
error.localizedDescription,
75+
"""
76+
Check your network connection, retry when the network is available.
77+
HTTP Response stauts code: \(String(describing: httpResponse?.statusCode))
78+
""",
79+
AWSCognitoAuthError.network)
7680

7781
}
7882

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Events/AuthEvents.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ struct AuthEvent: StateMachineEvent {
2222
case authenticationConfigured(AuthConfiguration, AmplifyCredentials)
2323

2424
case authorizationConfigured
25+
26+
case reconfigure(AuthConfiguration)
2527
}
2628

2729
var id: String
@@ -38,6 +40,7 @@ struct AuthEvent: StateMachineEvent {
3840
case .authenticationConfigured: return "AuthEvent.authenticationConfigured"
3941
case .authorizationConfigured: return "AuthEvent.authorizationConfigured"
4042
case .validateCredentialAndConfiguration: return "AuthEvent.validateCredentialAndConfiguration"
43+
case .reconfigure: return "AuthEvent.reconfigure"
4144
}
4245
}
4346

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/Resolvers/AuthState/AuthState+Resolver.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ extension AuthState {
8080
return .init(newState: newState, actions: authNresolution.actions + authZresolution.actions)
8181

8282
case .configured(let authenticationState, let authorizationState):
83+
if case .reconfigure(let authConfiguration) = isAuthEvent(event)?.eventType {
84+
let newState = AuthState.configuringAuth
85+
let action = InitializeAuthConfiguration(authConfiguration: authConfiguration)
86+
return .init(newState: newState, actions: [action])
87+
}
8388
let authenticationResolver = AuthenticationState.Resolver()
8489
let authorizationResolver = AuthorizationState.Resolver()
8590
let authNresolution = authenticationResolver.resolve(oldState: authenticationState, byApplying: event)

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/Resolvers/RefreshSession/RefreshSessionState+Resolver.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extension RefreshSessionState {
1818
byApplying event: StateMachineEvent) -> StateResolution<RefreshSessionState> {
1919

2020
switch oldState {
21+
2122
case .notStarted:
2223

2324
if case .refreshCognitoUserPool(let signedInData) = event.isRefreshSessionEvent {

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthDeleteUserTask.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,29 @@ import AWSPluginsCore
1212
class AWSAuthDeleteUserTask: AuthDeleteUserTask {
1313
private let authStateMachine: AuthStateMachine
1414
private let taskHelper: AWSAuthTaskHelper
15+
private let configuration: AuthConfiguration
1516

1617
var eventName: HubPayloadEventName {
1718
HubPayload.EventName.Auth.deleteUserAPI
1819
}
1920

20-
init(authStateMachine: AuthStateMachine) {
21+
init(authStateMachine: AuthStateMachine,
22+
authConfiguraiton: AuthConfiguration) {
2123
self.authStateMachine = authStateMachine
24+
self.configuration = authConfiguraiton
2225
self.taskHelper = AWSAuthTaskHelper(authStateMachine: authStateMachine)
2326
}
2427

2528
func execute() async throws {
2629
await taskHelper.didStateMachineConfigured()
2730
let accessToken = try await taskHelper.getAccessToken()
28-
try await deleteUser(with: accessToken)
31+
32+
do {
33+
try await deleteUser(with: accessToken)
34+
} catch {
35+
await waitForReConfigure()
36+
throw error
37+
}
2938
}
3039

3140
private func deleteUser(with token: String) async throws {
@@ -52,4 +61,10 @@ class AWSAuthDeleteUserTask: AuthDeleteUserTask {
5261
}
5362
}
5463
}
64+
65+
private func waitForReConfigure() async {
66+
let event = AuthEvent(eventType: .reconfigure(configuration))
67+
await authStateMachine.send(event)
68+
await taskHelper.didStateMachineConfigured()
69+
}
5570
}

AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthenticationProviderDeleteUserTests.swift

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import XCTest
1212
@testable import Amplify
1313
@testable import AWSCognitoAuthPlugin
1414
import AWSCognitoIdentityProvider
15+
import ClientRuntime
16+
import AwsCommonRuntimeKit
1517

1618
class AuthenticationProviderDeleteUserTests: BasePluginTest {
1719

@@ -34,6 +36,97 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest {
3436
}
3537
}
3638

39+
40+
/// Test a deleteUser with network error from service
41+
///
42+
/// - Given: Given an auth plugin with mocked service. Mocked service should mock a
43+
/// network error
44+
///
45+
/// - When:
46+
/// - I invoke deleteUser
47+
/// - Then:
48+
/// - I should get a .service error with .network as underlying error
49+
///
50+
func testOfflineDeleteUser() async {
51+
mockIdentityProvider = MockIdentityProvider(
52+
mockRevokeTokenResponse: { _ in
53+
try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok))
54+
}, mockGlobalSignOutResponse: { _ in
55+
try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok))
56+
},
57+
mockDeleteUserOutputResponse: { _ in
58+
let crtError = ClientRuntime.ClientError.retryError(
59+
CRTError.crtError(
60+
AWSError(errorCode: 1059)))
61+
throw SdkError<DeleteUserOutputError>.client(crtError)
62+
}
63+
)
64+
do {
65+
try await plugin.deleteUser()
66+
XCTFail("Should not get success")
67+
} catch {
68+
guard case AuthError.service(_, _, let underlyingError) = error,
69+
case .network = (underlyingError as? AWSCognitoAuthError) else {
70+
XCTFail("Should produce network error instead of \(error)")
71+
return
72+
}
73+
}
74+
}
75+
76+
/// Test a deleteUser with network error from service and retry with success on no network error
77+
///
78+
/// - Given: Given an auth plugin with mocked service. Mocked service should mock a
79+
/// network error for the first call and mock an actual result on the second call
80+
///
81+
/// - When:
82+
/// - I invoke deleteUser and retry it
83+
/// - Then:
84+
/// - I should get a .service error with .network as underlying error for the first call
85+
/// - I should get a valid response for the second call
86+
///
87+
func testOfflineDeleteUserAndRetry() async {
88+
mockIdentityProvider = MockIdentityProvider(
89+
mockRevokeTokenResponse: { _ in
90+
try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok))
91+
}, mockGlobalSignOutResponse: { _ in
92+
try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok))
93+
},
94+
mockDeleteUserOutputResponse: { _ in
95+
let crtError = ClientRuntime.ClientError.retryError(
96+
CRTError.crtError(
97+
AWSError(errorCode: 1059)))
98+
throw SdkError<DeleteUserOutputError>.client(crtError)
99+
}
100+
)
101+
do {
102+
try await plugin.deleteUser()
103+
XCTFail("Should not get success")
104+
} catch {
105+
guard case AuthError.service(_, _, let underlyingError) = error,
106+
case .network = (underlyingError as? AWSCognitoAuthError) else {
107+
XCTFail("Should produce network error instead of \(error)")
108+
return
109+
}
110+
}
111+
112+
mockIdentityProvider = MockIdentityProvider(
113+
mockRevokeTokenResponse: { _ in
114+
try RevokeTokenOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok))
115+
}, mockGlobalSignOutResponse: { _ in
116+
try GlobalSignOutOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok))
117+
},
118+
mockDeleteUserOutputResponse: { _ in
119+
try DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok))
120+
}
121+
)
122+
do {
123+
try await plugin.deleteUser()
124+
print("Delete user success")
125+
} catch {
126+
XCTFail("Received failure with error \(error)")
127+
}
128+
}
129+
37130
// MARK: - Service Exceptions
38131

39132
/// Test a deleteUser with `InternalErrorException` from service

0 commit comments

Comments
 (0)