Skip to content

Commit 13cdff8

Browse files
authored
fix(Auth): Sign out when user does not exist during delete user task (#2812)
1 parent 7e8bc10 commit 13cdff8

File tree

4 files changed

+92
-36
lines changed

4 files changed

+92
-36
lines changed

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ class AWSAuthDeleteUserTask: AuthDeleteUserTask, DefaultLogger {
3131
let accessToken = try await taskHelper.getAccessToken()
3232

3333
do {
34-
try await deleteUser(with: accessToken)
34+
try await deleteUser(with: accessToken)
3535
log.verbose("Received Success")
3636
} catch {
3737
log.verbose("Delete user failed, reconfiguring auth state. \(error)")
3838
await waitForReConfigure()
39+
await signOutIfUserWasNotFound(with: error)
3940
throw error
4041
}
4142
}
@@ -66,6 +67,18 @@ class AWSAuthDeleteUserTask: AuthDeleteUserTask, DefaultLogger {
6667
}
6768
}
6869

70+
private func signOutIfUserWasNotFound(with error: Error) async {
71+
guard case AuthError.service(_, _, let underlyingError) = error,
72+
case .userNotFound = (underlyingError as? AWSCognitoAuthError) else {
73+
return
74+
}
75+
log.verbose("User not found, signing out")
76+
let signOutData = SignOutEventData(globalSignOut: true)
77+
let event = AuthenticationEvent(eventType: .signOutRequested(signOutData))
78+
await authStateMachine.send(event)
79+
_ = await taskHelper.didSignOut()
80+
}
81+
6982
private func waitForReConfigure() async {
7083

7184
let event = AuthEvent(eventType: .reconfigure(configuration))

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

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class AWSAuthSignOutTask: AuthSignOutTask, DefaultLogger {
3535
if isValidAuthNStateToStart(authNState) {
3636
log.verbose("Sending signOut event")
3737
await sendSignOutEvent()
38-
return await doSignOut()
38+
return await taskHelper.didSignOut()
3939
} else if case .federatedToIdentityPool = authNState {
4040
let invalidStateError = AuthError.invalidState(
4141
"The user is currently federated to identity pool. You must call clearFederationToIdentityPool to clear credentials.",
@@ -47,40 +47,6 @@ class AWSAuthSignOutTask: AuthSignOutTask, DefaultLogger {
4747

4848
}
4949

50-
private func doSignOut() async -> AuthSignOutResult {
51-
52-
let stateSequences = await authStateMachine.listen()
53-
log.verbose("Waiting for signOut completion")
54-
for await state in stateSequences {
55-
guard case .configured(let authNState, _) = state else {
56-
return invalidStateResult()
57-
}
58-
59-
switch authNState {
60-
case .signedOut(let data):
61-
if data.revokeTokenError != nil ||
62-
data.globalSignOutError != nil ||
63-
data.hostedUIError != nil {
64-
return AWSCognitoSignOutResult.partial(
65-
revokeTokenError: data.revokeTokenError,
66-
globalSignOutError: data.globalSignOutError,
67-
hostedUIError: data.hostedUIError)
68-
}
69-
return AWSCognitoSignOutResult.complete
70-
case .signingIn:
71-
log.verbose("Cancel if a signIn is in progress")
72-
await authStateMachine.send(AuthenticationEvent.init(eventType: .cancelSignIn))
73-
case .signingOut(let state):
74-
if case .error(let error) = state {
75-
return AWSCognitoSignOutResult.failed(error.authError)
76-
}
77-
default:
78-
continue
79-
}
80-
}
81-
fatalError()
82-
}
83-
8450
func isValidAuthNStateToStart(_ authNState: AuthenticationState) -> Bool {
8551
switch authNState {
8652
case .signedIn, .signedOut:

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/Helpers/AWSAuthTaskHelper.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,40 @@ class AWSAuthTaskHelper: DefaultLogger {
3131
}
3232
}
3333

34+
func didSignOut() async -> AuthSignOutResult {
35+
let stateSequences = await authStateMachine.listen()
36+
log.verbose("Waiting for signOut completion")
37+
for await state in stateSequences {
38+
guard case .configured(let authNState, _) = state else {
39+
let error = AuthError.invalidState("Auth State not in a valid state", AuthPluginErrorConstants.invalidStateError, nil)
40+
return AWSCognitoSignOutResult.failed(error)
41+
}
42+
43+
switch authNState {
44+
case .signedOut(let data):
45+
if data.revokeTokenError != nil ||
46+
data.globalSignOutError != nil ||
47+
data.hostedUIError != nil {
48+
return AWSCognitoSignOutResult.partial(
49+
revokeTokenError: data.revokeTokenError,
50+
globalSignOutError: data.globalSignOutError,
51+
hostedUIError: data.hostedUIError)
52+
}
53+
return AWSCognitoSignOutResult.complete
54+
case .signingIn:
55+
log.verbose("Cancel if a signIn is in progress")
56+
await authStateMachine.send(AuthenticationEvent.init(eventType: .cancelSignIn))
57+
case .signingOut(let state):
58+
if case .error(let error) = state {
59+
return AWSCognitoSignOutResult.failed(error.authError)
60+
}
61+
default:
62+
continue
63+
}
64+
}
65+
fatalError()
66+
}
67+
3468
func getAccessToken() async throws -> String {
3569

3670
let session = try await fetchAuthSessionHelper.fetch(authStateMachine)

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,35 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest {
3636
}
3737
}
3838

39+
/// Test a deleteUser when sign out failed
40+
///
41+
/// - Given: Given an auth plugin with mocked service. Mocked service should mock a
42+
/// sign out failure
43+
///
44+
/// - When:
45+
/// - I invoke deleteUser
46+
/// - Then:
47+
/// - I should still be able to get a success for delete user
48+
///
49+
func testSignOutFailureWhenDeleteUserIsSuccess() async {
50+
mockIdentityProvider = MockIdentityProvider(
51+
mockRevokeTokenResponse: { _ in
52+
throw RevokeTokenOutputError.unsupportedTokenTypeException(.init())
53+
}, mockGlobalSignOutResponse: { _ in
54+
throw GlobalSignOutOutputError.internalErrorException(.init())
55+
},
56+
mockDeleteUserOutputResponse: { _ in
57+
try DeleteUserOutputResponse(httpResponse: .init(body: .empty, statusCode: .ok))
58+
}
59+
)
60+
do {
61+
try await plugin.deleteUser()
62+
print("Delete user success")
63+
} catch {
64+
XCTFail("Received failure with error \(error)")
65+
}
66+
}
67+
3968

4069
/// Test a deleteUser with network error from service
4170
///
@@ -374,6 +403,7 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest {
374403
/// - I invoke deleteUser
375404
/// - Then:
376405
/// - I should get a .service error with .userNotFound error
406+
/// AuthN should be in signedOut state
377407
///
378408
func testDeleteUserWithUserNotFoundException() async {
379409
mockIdentityProvider = MockIdentityProvider(
@@ -397,6 +427,19 @@ class AuthenticationProviderDeleteUserTests: BasePluginTest {
397427
return
398428
}
399429
}
430+
431+
switch await plugin.authStateMachine.currentState {
432+
case .configured(let authNState, let authZState):
433+
switch (authNState, authZState) {
434+
case (.signedOut, .configured):
435+
print("AuthN and AuthZ are in a valid state")
436+
default:
437+
XCTFail("AuthN should be in signed out state")
438+
}
439+
default:
440+
XCTFail("Auth should be in configured state")
441+
442+
}
400443
}
401444

402445
// TODO: ENABLE TESTS after adding hosted UI feature

0 commit comments

Comments
 (0)