Skip to content

Commit b01d22d

Browse files
authored
fix(auth): Enable retrying when confirm signIn fails (#2617)
1 parent dc8ee7a commit b01d22d

File tree

7 files changed

+98
-19
lines changed

7 files changed

+98
-19
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ extension AWSCognitoAuthPlugin: AuthCategoryBehavior {
8989
let options = options ?? AuthConfirmSignInRequest.Options()
9090
let request = AuthConfirmSignInRequest(challengeResponse: challengeResponse,
9191
options: options)
92-
let task = AWSAuthConfirmSignInTask(request, stateMachine: authStateMachine)
92+
let task = AWSAuthConfirmSignInTask(request,
93+
stateMachine: authStateMachine,
94+
configuration: authConfiguration)
9395
return try await taskQueue.sync {
9496
return try await task.value
9597
} as! AuthSignInResult

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInChallengeState+Debug.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ extension SignInChallengeState {
1414
switch self {
1515

1616
case .waitingForAnswer(let respondAuthChallenge, _),
17-
.verifying(let respondAuthChallenge, _):
17+
.verifying(let respondAuthChallenge, _, _):
1818
additionalMetadataDictionary = respondAuthChallenge.debugDictionary
19-
case .error(let respondAuthChallenge, let error):
19+
case .error(let respondAuthChallenge, _, let error):
2020
additionalMetadataDictionary = respondAuthChallenge.debugDictionary
2121
additionalMetadataDictionary["error"] = error
2222
default: additionalMetadataDictionary = [:]

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/SignInChallengeState.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ enum SignInChallengeState: State {
1313

1414
case waitingForAnswer(RespondToAuthChallenge, SignInMethod)
1515

16-
case verifying(RespondToAuthChallenge, String)
16+
case verifying(RespondToAuthChallenge, SignInMethod, String)
1717

1818
case verified
1919

20-
case error(RespondToAuthChallenge, SignInError)
20+
case error(RespondToAuthChallenge, SignInMethod, SignInError)
2121
}
2222

2323
extension SignInChallengeState {

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/Resolvers/SignIn/SignInChallengeState+Resolver.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,40 @@ extension SignInChallengeState {
3333
challenge: challenge,
3434
confirmSignEventData: answerEventData,
3535
signInMethod: signInMethod)
36-
return .init(newState: .verifying(challenge, answerEventData.answer), actions: [action])
36+
return .init(
37+
newState: .verifying(challenge, signInMethod, answerEventData.answer),
38+
actions: [action]
39+
)
3740
}
3841
return .from(oldState)
3942

40-
case .verifying(let challenge, _):
43+
case .verifying(let challenge, let signInMethod, _):
4144

4245
if case .finalizeSignIn(let signedInData) = event.isSignInEvent {
4346
return .init(newState: .verified,
4447
actions: [SignInComplete(signedInData: signedInData)])
4548
}
4649

4750
if case .throwAuthError(let error) = event.isSignInEvent {
48-
return .init(newState: .error(challenge, error))
51+
return .init(newState: .error(challenge, signInMethod, error))
4952
}
5053
return .from(oldState)
5154

52-
case .verified, .error:
55+
case .error(let challenge, let signInMethod, _):
56+
// If a verifyChallengeAnswer is received on error state we allow
57+
// to retry the challenge.
58+
if case .verifyChallengeAnswer(let answerEventData) = event.isChallengeEvent {
59+
let action = VerifySignInChallenge(
60+
challenge: challenge,
61+
confirmSignEventData: answerEventData,
62+
signInMethod: signInMethod)
63+
return .init(
64+
newState: .verifying(challenge, signInMethod, answerEventData.answer),
65+
actions: [action]
66+
)
67+
}
68+
return .from(oldState)
69+
case .verified:
5370

5471
return .from(oldState)
5572
}

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

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,54 @@ class AWSAuthConfirmSignInTask: AuthConfirmSignInTask {
1313
private let request: AuthConfirmSignInRequest
1414
private let authStateMachine: AuthStateMachine
1515
private let taskHelper: AWSAuthTaskHelper
16+
private let authConfiguration: AuthConfiguration
1617

1718
var eventName: HubPayloadEventName {
1819
HubPayload.EventName.Auth.confirmSignInAPI
1920
}
2021

21-
init(_ request: AuthConfirmSignInRequest, stateMachine: AuthStateMachine) {
22+
init(_ request: AuthConfirmSignInRequest,
23+
stateMachine: AuthStateMachine,
24+
configuration: AuthConfiguration) {
2225
self.request = request
2326
self.authStateMachine = stateMachine
2427
self.taskHelper = AWSAuthTaskHelper(authStateMachine: authStateMachine)
28+
self.authConfiguration = configuration
2529
}
2630

2731
func execute() async throws -> AuthSignInResult {
32+
33+
await taskHelper.didStateMachineConfigured()
34+
35+
//Check if we have a user pool configuration
36+
guard authConfiguration.getUserPoolConfiguration() != nil else {
37+
let message = AuthPluginErrorConstants.configurationError
38+
let authError = AuthError.configuration(
39+
"Could not find user pool configuration",
40+
message)
41+
throw authError
42+
}
43+
2844
if let validationError = request.hasError() {
2945
throw validationError
3046
}
3147
let invalidStateError = AuthError.invalidState(
3248
"User is not attempting signIn operation",
3349
AuthPluginErrorConstants.invalidStateError, nil)
3450

35-
await taskHelper.didStateMachineConfigured()
3651

37-
if case .configured(let authNState, _) = await authStateMachine.currentState,
38-
case .signingIn(let signInState) = authNState,
39-
case .resolvingChallenge(let challengeState, _, _) = signInState,
40-
case .waitingForAnswer = challengeState {
52+
53+
guard case .configured(let authNState, _) = await authStateMachine.currentState,
54+
case .signingIn(let signInState) = authNState,
55+
case .resolvingChallenge(let challengeState, _, _) = signInState else {
56+
throw invalidStateError
57+
}
58+
59+
switch challengeState {
60+
case .waitingForAnswer, .error:
4161
await sendConfirmSignInEvent()
62+
default:
63+
throw invalidStateError
4264
}
4365

4466
let stateSequences = await authStateMachine.listen()
@@ -56,7 +78,7 @@ class AWSAuthConfirmSignInTask: AuthConfirmSignInTask {
5678

5779
case .signingIn(let signInState):
5880
if case .resolvingChallenge(let challengeState, _, _) = signInState,
59-
case .error(_, let signInError) = challengeState {
81+
case .error(_, _, let signInError) = challengeState {
6082
let authError = signInError.authError
6183
if case .service(_, _, let serviceError) = authError,
6284
let cognitoError = serviceError as? AWSCognitoAuthError,

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,17 @@ class AWSAuthSignInTask: AuthSignInTask {
2929
}
3030

3131
func execute() async throws -> AuthSignInResult {
32+
await taskHelper.didStateMachineConfigured()
33+
//Check if we have a user pool configuration
3234
guard let userPoolConfiguration = authConfiguration.getUserPoolConfiguration() else {
3335
let message = AuthPluginErrorConstants.configurationError
34-
let authError = AuthenticationError.configuration(message: message)
36+
let authError = AuthError.configuration(
37+
"Could not find user pool configuration",
38+
message)
3539
throw authError
3640
}
3741

38-
await taskHelper.didStateMachineConfigured()
42+
3943
try await validateCurrentState()
4044

4145
let authflowType = authFlowType(userPoolConfiguration: userPoolConfiguration)

AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/AWSAuthConfirmSignInTaskTests.swift

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest {
5959
/// - Then:
6060
/// - I should get an .validation error
6161
///
62-
func testSuccessfullyConfirmSignIn() async {
62+
func testConfirmSignInWithEmptyResponse() async {
6363

6464
self.mockIdentityProvider = MockIdentityProvider(
6565
mockRespondToAuthChallengeResponse: { _ in
@@ -210,6 +210,40 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest {
210210
}
211211
}
212212
}
213+
214+
func testConfirmSignInRetryWithCodeMismatchException() async {
215+
self.mockIdentityProvider = MockIdentityProvider(
216+
mockRespondToAuthChallengeResponse: { _ in
217+
throw RespondToAuthChallengeOutputError.codeMismatchException(
218+
.init(message: "Exception"))
219+
})
220+
221+
do {
222+
_ = try await plugin.confirmSignIn(challengeResponse: "code")
223+
XCTFail("Should return an error if the result from service is invalid")
224+
} catch {
225+
guard case AuthError.service(_, _, let underlyingError) = error else {
226+
XCTFail("Should produce service error instead of \(error)")
227+
return
228+
}
229+
guard case .codeMismatch = (underlyingError as? AWSCognitoAuthError) else {
230+
XCTFail("Underlying error should be codeMismatch \(error)")
231+
return
232+
}
233+
234+
self.mockIdentityProvider = MockIdentityProvider(
235+
mockRespondToAuthChallengeResponse: { _ in
236+
return .testData()
237+
})
238+
do {
239+
let confirmSignInResult = try await plugin.confirmSignIn(challengeResponse: "code")
240+
XCTAssertTrue(confirmSignInResult.isSignedIn, "Signin result should be complete")
241+
} catch {
242+
XCTFail("Received failure with error \(error)")
243+
}
244+
245+
}
246+
}
213247

214248
/// Test a confirmSignIn call with CodeExpiredException response from service
215249
///

0 commit comments

Comments
 (0)