Add 2FA code entry screen
This commit is contained in:
parent
b07817c620
commit
e8f3d7bcce
5 changed files with 205 additions and 4 deletions
|
@ -13,6 +13,7 @@ import 'package:photos/models/set_recovery_key_request.dart';
|
||||||
import 'package:photos/ui/ott_verification_page.dart';
|
import 'package:photos/ui/ott_verification_page.dart';
|
||||||
import 'package:photos/ui/password_entry_page.dart';
|
import 'package:photos/ui/password_entry_page.dart';
|
||||||
import 'package:photos/ui/password_reentry_page.dart';
|
import 'package:photos/ui/password_reentry_page.dart';
|
||||||
|
import 'package:photos/ui/two_factor_authentication_page.dart';
|
||||||
import 'package:photos/utils/dialog_util.dart';
|
import 'package:photos/utils/dialog_util.dart';
|
||||||
import 'package:photos/utils/toast_util.dart';
|
import 'package:photos/utils/toast_util.dart';
|
||||||
|
|
||||||
|
@ -92,13 +93,18 @@ class UserService {
|
||||||
);
|
);
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
if (response != null && response.statusCode == 200) {
|
if (response != null && response.statusCode == 200) {
|
||||||
await _saveConfiguration(response);
|
|
||||||
showToast("email verification successful!");
|
showToast("email verification successful!");
|
||||||
var page;
|
var page;
|
||||||
if (Configuration.instance.getEncryptedToken() != null) {
|
final String twoFASessionID = response.data["twoFactorSessionID"];
|
||||||
page = PasswordReentryPage();
|
if (twoFASessionID != null && twoFASessionID.isNotEmpty) {
|
||||||
|
page = TwoFactorAuthenticationPage(twoFASessionID);
|
||||||
} else {
|
} else {
|
||||||
page = PasswordEntryPage();
|
await _saveConfiguration(response);
|
||||||
|
if (Configuration.instance.getEncryptedToken() != null) {
|
||||||
|
page = PasswordReentryPage();
|
||||||
|
} else {
|
||||||
|
page = PasswordEntryPage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
@ -192,6 +198,42 @@ class UserService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> verifyTwoFactor(
|
||||||
|
BuildContext context, String sessionID, String code) async {
|
||||||
|
final dialog = createProgressDialog(context, "authenticating...");
|
||||||
|
await dialog.show();
|
||||||
|
try {
|
||||||
|
final response = await _dio.post(
|
||||||
|
_config.getHttpEndpoint() + "/users/two-factor/verify",
|
||||||
|
data: {
|
||||||
|
"sessionID": sessionID,
|
||||||
|
"code": code,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await dialog.hide();
|
||||||
|
if (response != null && response.statusCode == 200) {
|
||||||
|
showToast("authentication successful!");
|
||||||
|
await _saveConfiguration(response);
|
||||||
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return PasswordReentryPage();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(route) => route.isFirst,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showErrorDialog(
|
||||||
|
context, "oops", "authentication failed, please try again");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
await dialog.hide();
|
||||||
|
_logger.severe(e);
|
||||||
|
showErrorDialog(
|
||||||
|
context, "oops", "authentication failed, please try again");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _saveConfiguration(Response response) async {
|
Future<void> _saveConfiguration(Response response) async {
|
||||||
await Configuration.instance.setUserID(response.data["id"]);
|
await Configuration.instance.setUserID(response.data["id"]);
|
||||||
if (response.data["encryptedToken"] != null) {
|
if (response.data["encryptedToken"] != null) {
|
||||||
|
|
134
lib/ui/two_factor_authentication_page.dart
Normal file
134
lib/ui/two_factor_authentication_page.dart
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:photos/services/user_service.dart';
|
||||||
|
import 'package:photos/ui/common_elements.dart';
|
||||||
|
import 'package:photos/ui/two_factor_recovery_page.dart';
|
||||||
|
import 'package:photos/utils/dialog_util.dart';
|
||||||
|
import 'package:pinput/pin_put/pin_put.dart';
|
||||||
|
|
||||||
|
class TwoFactorAuthenticationPage extends StatefulWidget {
|
||||||
|
final String sessionID;
|
||||||
|
|
||||||
|
const TwoFactorAuthenticationPage(this.sessionID, {Key key})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_TwoFactorAuthenticationPageState createState() =>
|
||||||
|
_TwoFactorAuthenticationPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TwoFactorAuthenticationPageState
|
||||||
|
extends State<TwoFactorAuthenticationPage> {
|
||||||
|
final _pinController = TextEditingController();
|
||||||
|
final _pinPutDecoration = BoxDecoration(
|
||||||
|
border: Border.all(color: Color.fromRGBO(45, 194, 98, 1.0)),
|
||||||
|
borderRadius: BorderRadius.circular(15.0),
|
||||||
|
);
|
||||||
|
String _code = "";
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
"two-factor authentication",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: _getBody(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _getBody() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"enter the 6-digit code from\nyour authenticator app",
|
||||||
|
style: TextStyle(
|
||||||
|
height: 1.4,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Padding(padding: EdgeInsets.all(32)),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
|
||||||
|
child: PinPut(
|
||||||
|
fieldsCount: 6,
|
||||||
|
onSubmit: (String code) {
|
||||||
|
_verifyTwoFactorCode(code);
|
||||||
|
},
|
||||||
|
onChanged: (String pin) {
|
||||||
|
setState(() {
|
||||||
|
_code = pin;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
controller: _pinController,
|
||||||
|
submittedFieldDecoration: _pinPutDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(20.0),
|
||||||
|
),
|
||||||
|
selectedFieldDecoration: _pinPutDecoration,
|
||||||
|
followingFieldDecoration: _pinPutDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(5.0),
|
||||||
|
border: Border.all(
|
||||||
|
color: Color.fromRGBO(45, 194, 98, 0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
inputDecoration: InputDecoration(
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
border: InputBorder.none,
|
||||||
|
counterText: '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(padding: EdgeInsets.all(24)),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
|
||||||
|
width: double.infinity,
|
||||||
|
height: 64,
|
||||||
|
child: button(
|
||||||
|
"authenticate",
|
||||||
|
fontSize: 18,
|
||||||
|
onPressed: _code.length == 6
|
||||||
|
? () async {
|
||||||
|
_verifyTwoFactorCode(_code);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(padding: EdgeInsets.all(30)),
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return TwoFactorRecoveryPage();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"lost device?",
|
||||||
|
style: TextStyle(
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _verifyTwoFactorCode(String code) async {
|
||||||
|
await UserService.instance.verifyTwoFactor(context, widget.sessionID, code);
|
||||||
|
}
|
||||||
|
}
|
17
lib/ui/two_factor_recovery_page.dart
Normal file
17
lib/ui/two_factor_recovery_page.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TwoFactorRecoveryPage extends StatefulWidget {
|
||||||
|
TwoFactorRecoveryPage({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_TwoFactorRecoveryPageState createState() => _TwoFactorRecoveryPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
child: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -641,6 +641,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.11.1"
|
||||||
|
pinput:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: pinput
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -88,6 +88,7 @@ dependencies:
|
||||||
image_editor: ^1.0.0
|
image_editor: ^1.0.0
|
||||||
syncfusion_flutter_sliders: ^19.1.67-beta
|
syncfusion_flutter_sliders: ^19.1.67-beta
|
||||||
syncfusion_flutter_core: ^19.1.67
|
syncfusion_flutter_core: ^19.1.67
|
||||||
|
pinput: ^1.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Add table
Reference in a new issue