diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa1691e..04c1413 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,9 @@
-## 0.1.3
+## 0.1.4 (2021-08-15)
+
+- Add multi-factor authentication
+- Fix third party provider support
+
+## 0.1.3 (2021-07-16)
- Add PasswordChangePage
- Fix code formatting
diff --git a/README.md b/README.md
index 7ce1a8b..6cff233 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Flutter plugin that automatically generates authentication widget templates based on your Amplify CLI Authentication configuration.
-
+
## Features
@@ -16,6 +16,7 @@ Flutter plugin that automatically generates authentication widget templates base
- Facebook
- Google
- Amazon
+- Sign in with multi-factor authentication (SMS)
### SignUpPage
- Sign up via password and one of the following (plus confirmation code):
@@ -58,7 +59,7 @@ Flutter plugin that automatically generates authentication widget templates base
To use this plugin, add `flutter_amplify_auth_ui` as a `dev_dependency` in your pubspec.yaml:
```
dev_dependencies:
- flutter_amplify_auth_ui: ^0.1.3
+ flutter_amplify_auth_ui: ^0.1.4
```
Run `flutter pub get` to install the plugin.
diff --git a/example/screenshots/Demo.gif b/example/screenshots/Demo.gif
new file mode 100644
index 0000000..7dff295
Binary files /dev/null and b/example/screenshots/Demo.gif differ
diff --git a/lib/src/auth_config/auth_config.dart b/lib/src/auth_config/auth_config.dart
index 41dcbb3..247a0b2 100644
--- a/lib/src/auth_config/auth_config.dart
+++ b/lib/src/auth_config/auth_config.dart
@@ -2,13 +2,17 @@ class AuthConfig {
final bool allowUnauthenticatedIdentities;
final List requiredAttributes;
final List usernameAttributes;
- final List authProvidersUserPool;
+ final List authProviders;
+ final String mfaConfiguration;
+ final List mfaTypes;
const AuthConfig({
required this.allowUnauthenticatedIdentities,
required this.requiredAttributes,
required this.usernameAttributes,
- required this.authProvidersUserPool,
+ required this.authProviders,
+ required this.mfaConfiguration,
+ required this.mfaTypes,
});
static AuthConfig fromJson(dynamic json) {
@@ -16,7 +20,9 @@ class AuthConfig {
allowUnauthenticatedIdentities: json['allowUnauthenticatedIdentities'],
requiredAttributes: _getAsList(json, 'requiredAttributes'),
usernameAttributes: _getAsList(json, 'usernameAttributes'),
- authProvidersUserPool: _getAsList(json, 'authProvidersUserPool'),
+ authProviders: _getAsList(json, 'authProviders'),
+ mfaConfiguration: json['mfaConfiguration'],
+ mfaTypes: _getAsList(json, 'mfaTypes'),
);
}
@@ -30,7 +36,9 @@ class AuthConfig {
"allowUnauthenticatedIdentities": $allowUnauthenticatedIdentities,
"requiredAttributes": $requiredAttributes,
"usernameAttributes": $usernameAttributes,
- "authProvidersUserPool": $authProvidersUserPool,
+ "authProviders": $authProviders,
+ "mfaConfiguration": $mfaConfiguration,
+ "mfaTypes": $mfaTypes,
''';
}
}
diff --git a/lib/src/flutter_amplify_auth_ui_generator.dart b/lib/src/flutter_amplify_auth_ui_generator.dart
index ab3e2f0..768ac28 100644
--- a/lib/src/flutter_amplify_auth_ui_generator.dart
+++ b/lib/src/flutter_amplify_auth_ui_generator.dart
@@ -17,6 +17,12 @@ class FlutterAmplifyAuthUIGenerator {
Directory(targetDir).createSync();
CommandLine.printMessage('Generating classes...');
+ _generateClassFromTemplate(
+ targetDir: targetDir,
+ templateName: 'sign_in/confirmation_code_dialog.dart',
+ templateHandler: null,
+ authConfig: authConfig,
+ );
_generateClassFromTemplate(
targetDir: targetDir,
templateName: 'sign_in/sign_in_page.dart',
diff --git a/lib/src/template_handlers/impl/sign_in_page_template_handler.dart b/lib/src/template_handlers/impl/sign_in_page_template_handler.dart
index 36dde90..c033982 100644
--- a/lib/src/template_handlers/impl/sign_in_page_template_handler.dart
+++ b/lib/src/template_handlers/impl/sign_in_page_template_handler.dart
@@ -8,9 +8,9 @@ class SignInPageTemplateHandler extends TemplateHandler {
'phone_number',
];
static const List configurableAuthProviders = [
- 'Facebook',
- 'Google',
- 'LoginWithAmazon',
+ 'graph.facebook.com',
+ 'accounts.google.com',
+ 'www.amazon.com',
];
@override
@@ -19,6 +19,7 @@ class SignInPageTemplateHandler extends TemplateHandler {
_handleUsernameAttributes(template: template, authConfig: authConfig);
_handleGuestSignIn(template: template, authConfig: authConfig);
_handleSignInWithWebUI(template: template, authConfig: authConfig);
+ _handleMfa(template: template, authConfig: authConfig);
}
void _handleUsernameAttributes(
@@ -47,12 +48,18 @@ class SignInPageTemplateHandler extends TemplateHandler {
void _handleSignInWithWebUI(
{required Template template, required AuthConfig authConfig}) {
configurableAuthProviders.forEach((provider) {
- if (!authConfig.authProvidersUserPool.contains(provider)) {
+ if (!authConfig.authProviders.contains(provider)) {
template.remove(identifier: 'authProvidersUserPool[$provider]');
}
});
- if (authConfig.authProvidersUserPool.isEmpty) {
+ if (authConfig.authProviders.isEmpty) {
template.remove(identifier: 'authProvidersUserPool[any]');
}
}
+
+ void _handleMfa(
+ {required Template template, required AuthConfig authConfig}) {
+ var reverseFlag = authConfig.mfaConfiguration == 'ON' ? 'OFF' : 'ON';
+ template.remove(identifier: 'mfaConfiguration[$reverseFlag]');
+ }
}
diff --git a/lib/src/template_handlers/impl/sign_up_page_template_handler.dart b/lib/src/template_handlers/impl/sign_up_page_template_handler.dart
index 193440e..f59579d 100644
--- a/lib/src/template_handlers/impl/sign_up_page_template_handler.dart
+++ b/lib/src/template_handlers/impl/sign_up_page_template_handler.dart
@@ -34,8 +34,13 @@ class SignUpPageTemplateHandler extends TemplateHandler {
void _handleRequiredAttributes(
{required Template template, required AuthConfig authConfig}) {
+ var requiredAttributes = authConfig.requiredAttributes;
+ if (authConfig.mfaConfiguration == 'ON' &&
+ authConfig.mfaTypes.contains('SMS Text Message')) {
+ requiredAttributes.add('phone_number');
+ }
configurableRequiredAttributes.forEach((attribute) {
- if (!authConfig.requiredAttributes.contains(attribute)) {
+ if (!requiredAttributes.contains(attribute)) {
template.remove(identifier: 'requiredAttributes[$attribute]');
}
});
diff --git a/lib/templates/sign_in/confirmation_code_dialog.dart b/lib/templates/sign_in/confirmation_code_dialog.dart
new file mode 100644
index 0000000..2f7817b
--- /dev/null
+++ b/lib/templates/sign_in/confirmation_code_dialog.dart
@@ -0,0 +1,75 @@
+import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
+import 'package:amplify_flutter/amplify.dart';
+import 'package:flutter/material.dart';
+
+class ConfirmationCodeDialog extends StatefulWidget {
+ final void Function(BuildContext) onSignIn;
+
+ const ConfirmationCodeDialog({Key? key, required this.onSignIn})
+ : super(key: key);
+
+ @override
+ _ConfirmationCodeDialogState createState() => _ConfirmationCodeDialogState();
+}
+
+class _ConfirmationCodeDialogState extends State {
+ final TextEditingController _confirmationCodeController =
+ TextEditingController();
+
+ @override
+ void dispose() {
+ _confirmationCodeController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: const Text('Confirmation Code'),
+ content: SingleChildScrollView(
+ child: Column(
+ children: [
+ Text('Please enter the confirmation code we sent you.'),
+ TextFormField(
+ controller: _confirmationCodeController,
+ keyboardType: TextInputType.number,
+ decoration: InputDecoration(
+ icon: Icon(Icons.check_outlined),
+ hintText: 'Enter your confirmation code',
+ labelText: 'Confirmation code'),
+ ),
+ ],
+ ),
+ ),
+ actions: [
+ TextButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ child: const Text('Cancel'),
+ ),
+ ElevatedButton(
+ onPressed: () {
+ _confirm();
+ },
+ child: const Text('Sign In'),
+ ),
+ ],
+ );
+ }
+
+ void _confirm() async {
+ try {
+ SignInResult result = await Amplify.Auth.confirmSignIn(
+ confirmationValue: _confirmationCodeController.text.trim(),
+ );
+ if (result.isSignedIn) {
+ Navigator.of(context).pop();
+ widget.onSignIn(context);
+ }
+ } on AuthException catch (e) {
+ ScaffoldMessenger.of(context)
+ .showSnackBar(SnackBar(content: Text(e.message)));
+ }
+ }
+}
diff --git a/lib/templates/sign_in/sign_in_page.dart b/lib/templates/sign_in/sign_in_page.dart
index 8293a2a..ecf5864 100644
--- a/lib/templates/sign_in/sign_in_page.dart
+++ b/lib/templates/sign_in/sign_in_page.dart
@@ -3,6 +3,7 @@ import 'package:amplify_flutter/amplify.dart';
import 'package:flutter/material.dart';
import '../password_management/password_reset_page.dart';
import '../sign_up/sign_up_page.dart';
+import 'confirmation_code_dialog.dart';
class SignInPage extends StatefulWidget {
final void Function(BuildContext) onSignIn;
@@ -138,7 +139,7 @@ class _SignInPageState extends State {
ButtonBar(
alignment: MainAxisAlignment.center,
children: [
- /*+++START authProvidersUserPool[Facebook]+++*/
+ /*+++START authProvidersUserPool[graph.facebook.com]+++*/
ElevatedButton(
onPressed: () =>
_signInWithWebUI(provider: AuthProvider.facebook),
@@ -149,8 +150,8 @@ class _SignInPageState extends State {
),
child: Text('Sign in with Facebook'),
),
- /*+++END authProvidersUserPool[Facebook]+++*/
- /*+++START authProvidersUserPool[Google]+++*/
+ /*+++END authProvidersUserPool[graph.facebook.com]+++*/
+ /*+++START authProvidersUserPool[accounts.google.com]+++*/
ElevatedButton(
onPressed: () =>
_signInWithWebUI(provider: AuthProvider.google),
@@ -163,8 +164,8 @@ class _SignInPageState extends State {
),
child: Text('Sign in with Google'),
),
- /*+++END authProvidersUserPool[Google]+++*/
- /*+++START authProvidersUserPool[LoginWithAmazon]+++*/
+ /*+++END authProvidersUserPool[accounts.google.com]+++*/
+ /*+++START authProvidersUserPool[www.amazon.com]+++*/
ElevatedButton(
onPressed: () =>
_signInWithWebUI(provider: AuthProvider.amazon),
@@ -175,7 +176,7 @@ class _SignInPageState extends State {
),
child: Text('Sign in with Amazon'),
),
- /*+++END authProvidersUserPool[LoginWithAmazon]+++*/
+ /*+++END authProvidersUserPool[www.amazon.com]+++*/
],
),
],
@@ -189,7 +190,7 @@ class _SignInPageState extends State {
void _signIn() async {
try {
await Amplify.Auth.signOut();
- SignInResult result = await Amplify.Auth.signIn(
+ await Amplify.Auth.signIn(
/*+++START usernameAttributes[username]+++*/
username: _usernameController.text.trim(),
/*+++END usernameAttributes[username]+++*/
@@ -201,9 +202,17 @@ class _SignInPageState extends State {
+++END usernameAttributes[phone_number]+++*/
password: _passwordController.text.trim(),
);
- if (result.isSignedIn) {
- widget.onSignIn(context);
- }
+ /*+++START mfaConfiguration[OFF]+++*/
+ widget.onSignIn(context);
+ /*+++END mfaConfiguration[OFF]+++*/
+ /*+++START mfaConfiguration[ON]+++*/
+ await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (BuildContext context) {
+ return ConfirmationCodeDialog(onSignIn: widget.onSignIn);
+ });
+ /*+++END mfaConfiguration[ON]+++*/
} on AuthException catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(e.message)));
diff --git a/lib/templates/sign_up/sign_up_page.dart b/lib/templates/sign_up/sign_up_page.dart
index 2b309ff..3847011 100644
--- a/lib/templates/sign_up/sign_up_page.dart
+++ b/lib/templates/sign_up/sign_up_page.dart
@@ -586,9 +586,6 @@ class _SignUpPageState extends State {
}),
),
);
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
- content: Text('Successfully signed up'),
- ));
setState(() {
_stepIndex = 1;
});
diff --git a/pubspec.yaml b/pubspec.yaml
index b530568..3fbb223 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: flutter_amplify_auth_ui
description: Flutter plugin that automatically generates authentication widget templates based on your Amplify CLI Authentication configuration.
-version: 0.1.3
+version: 0.1.4
homepage: https://github.com/finkmoritz/flutter_amplify_auth_ui
repository: https://github.com/finkmoritz/flutter_amplify_auth_ui
issue_tracker: https://github.com/finkmoritz/flutter_amplify_auth_ui/issues