2FA
This commit is contained in:
parent
878aea966f
commit
6c7f8902e1
|
|
@ -18,12 +18,14 @@ class AuthRepository {
|
||||||
String password,
|
String password,
|
||||||
String url,
|
String url,
|
||||||
String secret,
|
String secret,
|
||||||
String platform}) async {
|
String platform,
|
||||||
|
String oneTimePassword}) async {
|
||||||
final credentials = {
|
final credentials = {
|
||||||
'token_name': 'invoice-ninja-$platform-app',
|
'token_name': 'invoice-ninja-$platform-app',
|
||||||
'api_secret': secret,
|
'api_secret': secret,
|
||||||
'email': email,
|
'email': email,
|
||||||
'password': password,
|
'password': password,
|
||||||
|
'one_time_password': oneTimePassword,
|
||||||
};
|
};
|
||||||
|
|
||||||
url = formatApiUrlMachine(url) + '/login';
|
url = formatApiUrlMachine(url) + '/login';
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,19 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
|
|
||||||
class LoadStateRequest {
|
class LoadStateRequest {
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
|
|
||||||
LoadStateRequest(this.context);
|
LoadStateRequest(this.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoadStateSuccess {
|
class LoadStateSuccess {
|
||||||
final AppState state;
|
final AppState state;
|
||||||
|
|
||||||
LoadStateSuccess(this.state);
|
LoadStateSuccess(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoadUserLogin {
|
class LoadUserLogin {
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
|
|
||||||
LoadUserLogin(this.context);
|
LoadUserLogin(this.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,7 +35,9 @@ class OAuthLoginRequest implements StartLoading {
|
||||||
final String url;
|
final String url;
|
||||||
final String secret;
|
final String secret;
|
||||||
final String platform;
|
final String platform;
|
||||||
OAuthLoginRequest({this.completer, this.token, this.url, this.secret, this.platform});
|
|
||||||
|
OAuthLoginRequest(
|
||||||
|
{this.completer, this.token, this.url, this.secret, this.platform});
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserLoginRequest implements StartLoading {
|
class UserLoginRequest implements StartLoading {
|
||||||
|
|
@ -41,8 +47,16 @@ class UserLoginRequest implements StartLoading {
|
||||||
final String url;
|
final String url;
|
||||||
final String secret;
|
final String secret;
|
||||||
final String platform;
|
final String platform;
|
||||||
|
final String oneTimePassword;
|
||||||
|
|
||||||
UserLoginRequest({this.completer, this.email, this.password, this.url, this.secret, this.platform});
|
UserLoginRequest(
|
||||||
|
{this.completer,
|
||||||
|
this.email,
|
||||||
|
this.password,
|
||||||
|
this.url,
|
||||||
|
this.secret,
|
||||||
|
this.platform,
|
||||||
|
this.oneTimePassword});
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserLoginSuccess implements StopLoading {}
|
class UserLoginSuccess implements StopLoading {}
|
||||||
|
|
@ -54,4 +68,3 @@ class UserLoginFailure implements StopLoading {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserLogout implements PersistData {}
|
class UserLogout implements PersistData {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ Middleware<AppState> _createLoginRequest(AuthRepository repository) {
|
||||||
password: action.password,
|
password: action.password,
|
||||||
url: action.url,
|
url: action.url,
|
||||||
secret: action.secret,
|
secret: action.secret,
|
||||||
platform: action.platform)
|
platform: action.platform,
|
||||||
|
oneTimePassword: action.oneTimePassword)
|
||||||
.then((data) {
|
.then((data) {
|
||||||
_saveAuthLocal(action);
|
_saveAuthLocal(action);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,16 @@ class _LoginState extends State<LoginView> {
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final _urlController = TextEditingController();
|
final _urlController = TextEditingController();
|
||||||
final _secretController = TextEditingController();
|
final _secretController = TextEditingController();
|
||||||
|
final _oneTimePasswordController = TextEditingController();
|
||||||
|
|
||||||
static final ValueKey _emailKey = Key(LoginKeys.emailKeyString);
|
static final ValueKey _emailKey = Key(LoginKeys.emailKeyString);
|
||||||
static final ValueKey _passwordKey = Key(LoginKeys.passwordKeyString);
|
static final ValueKey _passwordKey = Key(LoginKeys.passwordKeyString);
|
||||||
static final ValueKey _urlKey = Key(LoginKeys.urlKeyString);
|
static final ValueKey _urlKey = Key(LoginKeys.urlKeyString);
|
||||||
static final ValueKey _secretKey = Key(LoginKeys.secretKeyString);
|
static final ValueKey _secretKey = Key(LoginKeys.secretKeyString);
|
||||||
|
static final ValueKey _oneTimePasswordKey =
|
||||||
|
Key(LoginKeys.oneTimePasswordKeyString);
|
||||||
|
|
||||||
FocusNode focusNode1 = new FocusNode();
|
FocusNode focusNode1 = new FocusNode();
|
||||||
FocusNode focusNode2 = new FocusNode();
|
|
||||||
FocusNode focusNode3 = new FocusNode();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
|
|
@ -61,6 +62,7 @@ class _LoginState extends State<LoginView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final localization = AppLocalization.of(context);
|
final localization = AppLocalization.of(context);
|
||||||
final viewModel = widget.viewModel;
|
final viewModel = widget.viewModel;
|
||||||
|
final error = viewModel.authState.error;
|
||||||
|
|
||||||
if (!viewModel.authState.isInitialized) {
|
if (!viewModel.authState.isInitialized) {
|
||||||
return Container();
|
return Container();
|
||||||
|
|
@ -77,12 +79,24 @@ class _LoginState extends State<LoginView> {
|
||||||
Form(
|
Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: FormCard(
|
child: FormCard(
|
||||||
|
children: <Widget>[
|
||||||
|
(error != null && error.contains('2FA')) ||
|
||||||
|
_oneTimePasswordController.text.isNotEmpty
|
||||||
|
? TextFormField(
|
||||||
|
controller: _oneTimePasswordController,
|
||||||
|
key: _oneTimePasswordKey,
|
||||||
|
autocorrect: false,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: localization.oneTimePassword),
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
key: _emailKey,
|
key: _emailKey,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
decoration: InputDecoration(labelText: localization.email),
|
decoration:
|
||||||
|
InputDecoration(labelText: localization.email),
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
validator: (val) => val.isEmpty || val.trim().isEmpty
|
validator: (val) => val.isEmpty || val.trim().isEmpty
|
||||||
? localization.pleaseEnterYourEmail
|
? localization.pleaseEnterYourEmail
|
||||||
|
|
@ -94,37 +108,36 @@ class _LoginState extends State<LoginView> {
|
||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
key: _passwordKey,
|
key: _passwordKey,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
decoration: InputDecoration(labelText: localization.password),
|
decoration:
|
||||||
|
InputDecoration(labelText: localization.password),
|
||||||
validator: (val) => val.isEmpty || val.trim().isEmpty
|
validator: (val) => val.isEmpty || val.trim().isEmpty
|
||||||
? localization.pleaseEnterYourPassword
|
? localization.pleaseEnterYourPassword
|
||||||
: null,
|
: null,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
focusNode: focusNode1,
|
focusNode: focusNode1,
|
||||||
onFieldSubmitted: (String value) =>
|
|
||||||
FocusScope.of(context).requestFocus(focusNode2),
|
|
||||||
),
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _urlController,
|
controller: _urlController,
|
||||||
key: _urlKey,
|
key: _urlKey,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
decoration: InputDecoration(labelText: localization.url),
|
decoration:
|
||||||
|
InputDecoration(labelText: localization.url),
|
||||||
validator: (val) => val.isEmpty || val.trim().isEmpty
|
validator: (val) => val.isEmpty || val.trim().isEmpty
|
||||||
? localization.pleaseEnterYourUrl
|
? localization.pleaseEnterYourUrl
|
||||||
: null,
|
: null,
|
||||||
keyboardType: TextInputType.url,
|
keyboardType: TextInputType.url,
|
||||||
focusNode: focusNode2,
|
|
||||||
onFieldSubmitted: (String value) =>
|
|
||||||
FocusScope.of(context).requestFocus(focusNode3),
|
|
||||||
),
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _secretController,
|
controller: _secretController,
|
||||||
key: _secretKey,
|
key: _secretKey,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
decoration: InputDecoration(labelText: localization.secret),
|
decoration:
|
||||||
|
InputDecoration(labelText: localization.secret),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
focusNode: focusNode3,
|
|
||||||
),
|
),
|
||||||
viewModel.authState.error == null
|
],
|
||||||
|
),
|
||||||
|
viewModel.authState.error == null || error.contains('2FA')
|
||||||
? Container()
|
? Container()
|
||||||
: Container(
|
: Container(
|
||||||
padding: EdgeInsets.only(top: 26.0, bottom: 4.0),
|
padding: EdgeInsets.only(top: 26.0, bottom: 4.0),
|
||||||
|
|
@ -148,12 +161,12 @@ class _LoginState extends State<LoginView> {
|
||||||
if (!_formKey.currentState.validate()) {
|
if (!_formKey.currentState.validate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
viewModel.onLoginPressed(
|
viewModel.onLoginPressed(context,
|
||||||
context,
|
email: _emailController.text,
|
||||||
_emailController.text,
|
password: _passwordController.text,
|
||||||
_passwordController.text,
|
url: _urlController.text,
|
||||||
_urlController.text,
|
secret: _secretController.text,
|
||||||
_secretController.text);
|
oneTimePassword: _oneTimePasswordController.text);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
/*
|
/*
|
||||||
|
|
@ -9,7 +9,7 @@ import 'package:invoiceninja_flutter/utils/platforms.dart';
|
||||||
import 'package:redux/redux.dart';
|
import 'package:redux/redux.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
|
import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/auth/login.dart';
|
import 'package:invoiceninja_flutter/ui/auth/login_view.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/auth/auth_state.dart';
|
import 'package:invoiceninja_flutter/redux/auth/auth_state.dart';
|
||||||
import 'package:google_sign_in/google_sign_in.dart';
|
import 'package:google_sign_in/google_sign_in.dart';
|
||||||
|
|
||||||
|
|
@ -36,7 +36,12 @@ class LoginScreen extends StatelessWidget {
|
||||||
class LoginVM {
|
class LoginVM {
|
||||||
bool isLoading;
|
bool isLoading;
|
||||||
AuthState authState;
|
AuthState authState;
|
||||||
final Function(BuildContext, String, String, String, String) onLoginPressed;
|
final Function(BuildContext,
|
||||||
|
{String email,
|
||||||
|
String password,
|
||||||
|
String url,
|
||||||
|
String secret,
|
||||||
|
String oneTimePassword}) onLoginPressed;
|
||||||
final Function(BuildContext, String, String) onGoogleLoginPressed;
|
final Function(BuildContext, String, String) onGoogleLoginPressed;
|
||||||
|
|
||||||
LoginVM({
|
LoginVM({
|
||||||
|
|
@ -83,8 +88,12 @@ class LoginVM {
|
||||||
print(error);
|
print(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoginPressed: (BuildContext context, String email, String password,
|
onLoginPressed: (BuildContext context,
|
||||||
String url, String secret) async {
|
{String email,
|
||||||
|
String password,
|
||||||
|
String url,
|
||||||
|
String secret,
|
||||||
|
String oneTimePassword}) async {
|
||||||
if (store.state.isLoading) {
|
if (store.state.isLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +106,7 @@ class LoginVM {
|
||||||
url: url.trim(),
|
url: url.trim(),
|
||||||
secret: secret.trim(),
|
secret: secret.trim(),
|
||||||
platform: getPlatform(context),
|
platform: getPlatform(context),
|
||||||
|
oneTimePassword: oneTimePassword.trim(),
|
||||||
));
|
));
|
||||||
completer.future.then((_) => _handleLogin(context));
|
completer.future.then((_) => _handleLogin(context));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ class LoginKeys {
|
||||||
static const String passwordKeyString = 'loginPassword';
|
static const String passwordKeyString = 'loginPassword';
|
||||||
static const String urlKeyString = 'loginUrl';
|
static const String urlKeyString = 'loginUrl';
|
||||||
static const String secretKeyString = 'loginSecret';
|
static const String secretKeyString = 'loginSecret';
|
||||||
|
static const String oneTimePasswordKeyString = 'loginOneTimePassword';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys for Product Screen
|
// Keys for Product Screen
|
||||||
|
|
|
||||||
|
|
@ -262,6 +262,7 @@ class AppLocalization {
|
||||||
'activity_45': ':user deleted task :task',
|
'activity_45': ':user deleted task :task',
|
||||||
'activity_46': ':user restored task :task',
|
'activity_46': ':user restored task :task',
|
||||||
'activity_47': ':user updated expense :expense',
|
'activity_47': ':user updated expense :expense',
|
||||||
|
'one_time_password': 'One Time Password',
|
||||||
},
|
},
|
||||||
'sq': {
|
'sq': {
|
||||||
'billing_address': 'Adresa e faturimit',
|
'billing_address': 'Adresa e faturimit',
|
||||||
|
|
@ -7658,6 +7659,9 @@ class AppLocalization {
|
||||||
String get activity_47 =>
|
String get activity_47 =>
|
||||||
_localizedValues[locale.languageCode]['activity_47'];
|
_localizedValues[locale.languageCode]['activity_47'];
|
||||||
|
|
||||||
|
String get oneTimePassword =>
|
||||||
|
_localizedValues[locale.languageCode]['one_time_password'];
|
||||||
|
|
||||||
String lookup(String key) {
|
String lookup(String key) {
|
||||||
return _localizedValues[locale.languageCode][toSnakeCase(key)] ?? key;
|
return _localizedValues[locale.languageCode][toSnakeCase(key)] ?? key;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_driver/flutter_driver.dart';
|
import 'package:flutter_driver/flutter_driver.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
//import 'package:invoiceninja_flutter/ui/auth/login.dart';
|
//import 'package:invoiceninja_flutter/ui/auth/login_view.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('scrolling performance test', () {
|
group('scrolling performance test', () {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue