Skip to content

Commit 5dcd369

Browse files
authored
fix(auth): Allow retries with verifyTotpSetup() (#4532)
1 parent 1c32bab commit 5dcd369

File tree

3 files changed

+96
-15
lines changed

3 files changed

+96
-15
lines changed

packages/auth/amplify_auth_cognito/example/integration_test/mfa_totp_optional_test.dart

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,55 @@ void main() {
9393
'w/ device name',
9494
(_) => runTest(friendlyDeviceName: friendlyDeviceName),
9595
);
96+
97+
asyncTest('verifyTotpSetup allows retries', (_) async {
98+
final username = generateUsername();
99+
final password = generatePassword();
100+
101+
await adminCreateUser(
102+
username,
103+
password,
104+
verifyAttributes: true,
105+
autoConfirm: true,
106+
);
107+
108+
final signInRes = await Amplify.Auth.signIn(
109+
username: username,
110+
password: password,
111+
);
112+
113+
check(
114+
because: 'TOTP is optional',
115+
signInRes.nextStep.signInStep,
116+
).equals(AuthSignInStep.done);
117+
118+
final totpSetupResult = await Amplify.Auth.setUpTotp();
119+
120+
await Amplify.Auth.verifyTotpSetup(
121+
'555555',
122+
);
123+
124+
check(
125+
await cognitoPlugin.fetchMfaPreference(),
126+
because: 'TOTP should not be enabled',
127+
).equals(
128+
const UserMfaPreference(
129+
enabled: {},
130+
preferred: null,
131+
),
132+
);
133+
134+
await Amplify.Auth.verifyTotpSetup(
135+
await generateTotpCode(totpSetupResult.sharedSecret),
136+
);
137+
138+
check(await cognitoPlugin.fetchMfaPreference()).equals(
139+
const UserMfaPreference(
140+
enabled: {MfaType.totp},
141+
preferred: MfaType.totp,
142+
),
143+
);
144+
});
96145
});
97146
});
98147
});

packages/auth/amplify_auth_cognito_dart/lib/src/auth_plugin_impl.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ class AmplifyAuthCognitoDart extends AuthPluginInterface
932932
defaultPluginOptions: const CognitoVerifyTotpSetupPluginOptions(),
933933
);
934934
final machine = _stateMachine.getOrCreate(TotpSetupStateMachine.type);
935-
await machine.dispatchAndComplete<TotpSetupSuccess>(
935+
await machine.dispatchAndComplete<TotpSetupState>(
936936
TotpSetupEvent.verify(
937937
code: totpCode,
938938
friendlyDeviceName: pluginOptions.friendlyDeviceName,

packages/auth/amplify_auth_cognito_dart/lib/src/state/machines/totp_setup_state_machine.dart

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import 'package:amplify_auth_cognito_dart/src/jwt/src/cognito.dart';
5-
import 'package:amplify_auth_cognito_dart/src/sdk/cognito_identity_provider.dart';
5+
import 'package:amplify_auth_cognito_dart/src/sdk/cognito_identity_provider.dart'
6+
hide EnableSoftwareTokenMfaException;
67
import 'package:amplify_auth_cognito_dart/src/sdk/sdk_bridge.dart';
8+
import 'package:amplify_auth_cognito_dart/src/sdk/sdk_exception.dart';
79
import 'package:amplify_auth_cognito_dart/src/state/cognito_state_machine.dart';
810
import 'package:amplify_auth_cognito_dart/src/state/state.dart';
911
import 'package:amplify_core/amplify_core.dart';
@@ -48,6 +50,7 @@ final class TotpSetupStateMachine
4850
CognitoIdentityProviderClient get _cognitoIdp => expect();
4951

5052
String? _session;
53+
TotpSetupDetails? _details;
5154

5255
Future<void> _onInitiate(TotpSetupInitiate event) async {
5356
final tokens = await manager.getUserPoolTokens();
@@ -59,29 +62,58 @@ final class TotpSetupStateMachine
5962
)
6063
.result;
6164
_session = response.session;
65+
_details = TotpSetupDetails(
66+
username: CognitoIdToken(tokens.idToken).username,
67+
sharedSecret: response.secretCode!,
68+
);
6269
emit(
6370
TotpSetupState.requiresVerification(
64-
TotpSetupDetails(
65-
username: CognitoIdToken(tokens.idToken).username,
66-
sharedSecret: response.secretCode!,
67-
),
71+
_details!,
6872
),
6973
);
7074
}
7175

7276
Future<void> _onVerify(TotpSetupVerify event) async {
7377
final tokens = await manager.getUserPoolTokens();
7478
final accessToken = tokens.accessToken.raw;
75-
await _cognitoIdp
76-
.verifySoftwareToken(
77-
VerifySoftwareTokenRequest(
78-
accessToken: accessToken,
79-
session: _session,
80-
userCode: event.code,
81-
friendlyDeviceName: event.friendlyDeviceName,
79+
try {
80+
await _cognitoIdp
81+
.verifySoftwareToken(
82+
VerifySoftwareTokenRequest(
83+
accessToken: accessToken,
84+
session: _session,
85+
userCode: event.code,
86+
friendlyDeviceName: event.friendlyDeviceName,
87+
),
88+
)
89+
.result;
90+
} on Exception catch (e, st) {
91+
// Handle mismatch code exception that may occur during TOTP verification.
92+
// See: https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_VerifySoftwareToken.html#API_VerifySoftwareToken_Errors
93+
if (e is EnableSoftwareTokenMfaException) {
94+
assert(
95+
_details != null,
96+
'TotpSetupDetails should not be null. Please report this issue.',
97+
);
98+
logger.verbose(
99+
'Failed to verify TOTP code. Retrying...',
100+
e,
101+
);
102+
emit(
103+
TotpSetupState.requiresVerification(
104+
_details!,
82105
),
83-
)
84-
.result;
106+
);
107+
return;
108+
}
109+
logger.error(
110+
'Failed to verify TOTP code. Please try again.',
111+
e,
112+
st,
113+
);
114+
emit(TotpSetupState.failure(e, st));
115+
}
116+
85117
try {
86118
await _cognitoIdp.setMfaSettings(
87119
accessToken: accessToken,

0 commit comments

Comments
 (0)