mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-11-22 21:09:21 +00:00
Apply password policy on all forms
This commit is contained in:
parent
16ec9a8e00
commit
c5d1512375
@ -4,6 +4,7 @@ import 'package:comunic/helpers/websocket_helper.dart';
|
|||||||
import 'package:comunic/models/api_request.dart';
|
import 'package:comunic/models/api_request.dart';
|
||||||
import 'package:comunic/models/authentication_details.dart';
|
import 'package:comunic/models/authentication_details.dart';
|
||||||
import 'package:comunic/models/new_account.dart';
|
import 'package:comunic/models/new_account.dart';
|
||||||
|
import 'package:comunic/models/res_check_password_reset_token.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
/// Account helper
|
/// Account helper
|
||||||
@ -125,6 +126,11 @@ class AccountHelper {
|
|||||||
.execWithThrow())
|
.execWithThrow())
|
||||||
.getObject()["exists"];
|
.getObject()["exists"];
|
||||||
|
|
||||||
|
/// Get current user email address
|
||||||
|
static Future<String> getCurrentAccountEmailAddress() async =>
|
||||||
|
(await APIRequest.withLogin("account/mail")
|
||||||
|
.execWithThrowGetObject())["mail"];
|
||||||
|
|
||||||
/// Check out whether security questions have been set for an account or not
|
/// Check out whether security questions have been set for an account or not
|
||||||
///
|
///
|
||||||
/// Throws in case of failure
|
/// Throws in case of failure
|
||||||
@ -161,10 +167,19 @@ class AccountHelper {
|
|||||||
/// Check a password reset token
|
/// Check a password reset token
|
||||||
///
|
///
|
||||||
/// Throws in case failure
|
/// Throws in case failure
|
||||||
static Future<void> validatePasswordResetToken(String token) async =>
|
static Future<ResCheckPasswordToken> validatePasswordResetToken(
|
||||||
await APIRequest.withoutLogin("account/check_password_reset_token")
|
String token) async {
|
||||||
.addString("token", token)
|
final response =
|
||||||
.execWithThrow();
|
await APIRequest.withoutLogin("account/check_password_reset_token")
|
||||||
|
.addString("token", token)
|
||||||
|
.execWithThrowGetObject();
|
||||||
|
|
||||||
|
return ResCheckPasswordToken(
|
||||||
|
firstName: response["first_name"],
|
||||||
|
lastName: response["last_name"],
|
||||||
|
email: response["mail"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Change account password using password reset token
|
/// Change account password using password reset token
|
||||||
///
|
///
|
||||||
|
@ -88,6 +88,10 @@ class APIRequest {
|
|||||||
/// Execute the request, throws an exception in case of failure
|
/// Execute the request, throws an exception in case of failure
|
||||||
Future<APIResponse> execWithThrow() async => (await exec()).assertOk();
|
Future<APIResponse> execWithThrow() async => (await exec()).assertOk();
|
||||||
|
|
||||||
|
/// Execute the request, throws an exception in case of failure
|
||||||
|
Future<Map<String, dynamic>> execWithThrowGetObject() async =>
|
||||||
|
(await execWithThrow()).getObject();
|
||||||
|
|
||||||
/// Execute the request with files
|
/// Execute the request with files
|
||||||
Future<APIResponse> execWithFiles() async => APIHelper().execWithFiles(this);
|
Future<APIResponse> execWithFiles() async => APIHelper().execWithFiles(this);
|
||||||
|
|
||||||
|
19
lib/models/res_check_password_reset_token.dart
Normal file
19
lib/models/res_check_password_reset_token.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Check password reset token result
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
class ResCheckPasswordToken {
|
||||||
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final String email;
|
||||||
|
|
||||||
|
const ResCheckPasswordToken({
|
||||||
|
@required this.firstName,
|
||||||
|
@required this.lastName,
|
||||||
|
@required this.email,
|
||||||
|
}) : assert(firstName != null),
|
||||||
|
assert(lastName != null),
|
||||||
|
assert(email != null);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:comunic/ui/widgets/dialogs/auto_sized_dialog_content_widget.dart';
|
import 'package:comunic/ui/widgets/dialogs/auto_sized_dialog_content_widget.dart';
|
||||||
import 'package:comunic/utils/input_utils.dart';
|
import 'package:comunic/ui/widgets/new_password_input_widget.dart';
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -8,27 +8,44 @@ import 'package:flutter/material.dart';
|
|||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
|
|
||||||
/// Ask the user to enter a new password
|
/// Ask the user to enter a new password
|
||||||
Future<String> showInputNewPassword(BuildContext context) async {
|
Future<String> showInputNewPassword({
|
||||||
|
@required BuildContext context,
|
||||||
|
@required UserInfoForPassword userInfo,
|
||||||
|
}) async {
|
||||||
|
assert(context != null);
|
||||||
|
assert(userInfo != null);
|
||||||
return await showDialog(
|
return await showDialog(
|
||||||
context: context, builder: (c) => _InputNewPasswordDialog());
|
context: context,
|
||||||
|
builder: (c) => _InputNewPasswordDialog(
|
||||||
|
userInfo: userInfo,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InputNewPasswordDialog extends StatefulWidget {
|
class _InputNewPasswordDialog extends StatefulWidget {
|
||||||
|
final UserInfoForPassword userInfo;
|
||||||
|
|
||||||
|
const _InputNewPasswordDialog({
|
||||||
|
Key key,
|
||||||
|
@required this.userInfo,
|
||||||
|
}) : assert(userInfo != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
__InputNewPasswordDialogState createState() =>
|
__InputNewPasswordDialogState createState() =>
|
||||||
__InputNewPasswordDialogState();
|
__InputNewPasswordDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class __InputNewPasswordDialogState extends State<_InputNewPasswordDialog> {
|
class __InputNewPasswordDialogState extends State<_InputNewPasswordDialog> {
|
||||||
final _controller1 = TextEditingController();
|
final _controller1 = GlobalKey<NewPasswordInputWidgetState>();
|
||||||
final _controller2 = TextEditingController();
|
final _controller2 = TextEditingController();
|
||||||
final _focusScopeNode = FocusScopeNode();
|
final _focusScopeNode = FocusScopeNode();
|
||||||
|
|
||||||
String get _password => _controller1.text;
|
String get _password => _controller1.currentState.value;
|
||||||
|
|
||||||
bool get _input1Valid => validatePassword(_password);
|
bool get _input1Valid =>
|
||||||
|
_controller1.currentState != null && _controller1.currentState.valid;
|
||||||
|
|
||||||
bool get _input2Valid => _controller1.text == _controller2.text;
|
bool get _input2Valid => _controller1.currentState.value == _controller2.text;
|
||||||
|
|
||||||
bool get _isValid => _input1Valid && _input2Valid;
|
bool get _isValid => _input1Valid && _input2Valid;
|
||||||
|
|
||||||
@ -65,12 +82,10 @@ class __InputNewPasswordDialogState extends State<_InputNewPasswordDialog> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// Input 1
|
// Input 1
|
||||||
_buildPasswordField(
|
NewPasswordInputWidget(
|
||||||
controller: _controller1,
|
key: _controller1,
|
||||||
|
user: widget.userInfo,
|
||||||
label: tr("Your new password"),
|
label: tr("Your new password"),
|
||||||
errorText: _controller1.text.isNotEmpty && !_input1Valid
|
|
||||||
? tr("Invalid password!")
|
|
||||||
: null,
|
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
onSubmitted: () => _focusScopeNode.nextFocus(),
|
onSubmitted: () => _focusScopeNode.nextFocus(),
|
||||||
),
|
),
|
||||||
|
@ -30,7 +30,7 @@ class __InputUserPasswordDialogState
|
|||||||
String get _currPass => _controller.text;
|
String get _currPass => _controller.text;
|
||||||
|
|
||||||
bool get _canSubmit =>
|
bool get _canSubmit =>
|
||||||
validatePassword(_controller.text) && _status != _Status.CHECKING;
|
legacyValidatePassword(_controller.text) && _status != _Status.CHECKING;
|
||||||
|
|
||||||
void _setStatus(_Status s) => setState(() => _status = s);
|
void _setStatus(_Status s) => setState(() => _status = s);
|
||||||
|
|
||||||
|
@ -139,9 +139,11 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> {
|
|||||||
label: tr("Password"),
|
label: tr("Password"),
|
||||||
onEdited: _updateUI,
|
onEdited: _updateUI,
|
||||||
icon: Icon(Icons.lock),
|
icon: Icon(Icons.lock),
|
||||||
email: _emailController.text,
|
user: UserInfoForPassword(
|
||||||
firstName: _firstNameController.text,
|
firstName: _firstNameController.text,
|
||||||
lastName: _lastNameController.text,
|
lastName: _lastNameController.text,
|
||||||
|
email: _emailController.text,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Verify password
|
// Verify password
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:comunic/helpers/account_helper.dart';
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
|
import 'package:comunic/models/res_check_password_reset_token.dart';
|
||||||
import 'package:comunic/ui/dialogs/input_new_password_dialog.dart';
|
import 'package:comunic/ui/dialogs/input_new_password_dialog.dart';
|
||||||
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
||||||
|
import 'package:comunic/ui/widgets/new_password_input_widget.dart';
|
||||||
import 'package:comunic/ui/widgets/safe_state.dart';
|
import 'package:comunic/ui/widgets/safe_state.dart';
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
@ -47,12 +49,13 @@ class __PasswordResetBodyState extends SafeState<_PasswordResetBody> {
|
|||||||
final _key = GlobalKey<AsyncScreenWidgetState>();
|
final _key = GlobalKey<AsyncScreenWidgetState>();
|
||||||
|
|
||||||
var _status = _Status.BEFORE_CHANGE;
|
var _status = _Status.BEFORE_CHANGE;
|
||||||
|
ResCheckPasswordToken _tokenInfo;
|
||||||
|
|
||||||
void _setStatus(_Status s) => setState(() => _status = s);
|
void _setStatus(_Status s) => setState(() => _status = s);
|
||||||
|
|
||||||
Future<void> _validateToken() async {
|
Future<void> _validateToken() async {
|
||||||
_status = _Status.BEFORE_CHANGE;
|
_status = _Status.BEFORE_CHANGE;
|
||||||
await AccountHelper.validatePasswordResetToken(widget.token);
|
_tokenInfo = await AccountHelper.validatePasswordResetToken(widget.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -108,7 +111,14 @@ class __PasswordResetBodyState extends SafeState<_PasswordResetBody> {
|
|||||||
void _changePassword() async {
|
void _changePassword() async {
|
||||||
try {
|
try {
|
||||||
// Ask for new password
|
// Ask for new password
|
||||||
final newPass = await showInputNewPassword(context);
|
final newPass = await showInputNewPassword(
|
||||||
|
context: context,
|
||||||
|
userInfo: UserInfoForPassword(
|
||||||
|
firstName: _tokenInfo.firstName,
|
||||||
|
lastName: _tokenInfo.lastName,
|
||||||
|
email: _tokenInfo.email,
|
||||||
|
),
|
||||||
|
);
|
||||||
if (newPass == null) return;
|
if (newPass == null) return;
|
||||||
_setStatus(_Status.WHILE_CHANGE);
|
_setStatus(_Status.WHILE_CHANGE);
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import 'package:comunic/helpers/account_helper.dart';
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
import 'package:comunic/helpers/settings_helper.dart';
|
import 'package:comunic/helpers/settings_helper.dart';
|
||||||
|
import 'package:comunic/helpers/users_helper.dart';
|
||||||
import 'package:comunic/models/security_settings.dart';
|
import 'package:comunic/models/security_settings.dart';
|
||||||
import 'package:comunic/ui/dialogs/input_new_password_dialog.dart';
|
import 'package:comunic/ui/dialogs/input_new_password_dialog.dart';
|
||||||
import 'package:comunic/ui/dialogs/input_user_password_dialog.dart';
|
import 'package:comunic/ui/dialogs/input_user_password_dialog.dart';
|
||||||
import 'package:comunic/ui/widgets/dialogs/auto_sized_dialog_content_widget.dart';
|
import 'package:comunic/ui/widgets/dialogs/auto_sized_dialog_content_widget.dart';
|
||||||
|
import 'package:comunic/ui/widgets/new_password_input_widget.dart';
|
||||||
import 'package:comunic/ui/widgets/settings/header_spacer_section.dart';
|
import 'package:comunic/ui/widgets/settings/header_spacer_section.dart';
|
||||||
|
import 'package:comunic/utils/account_utils.dart';
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -55,11 +58,21 @@ class _AccountSecuritySettingsScreenState
|
|||||||
/// Change current user password
|
/// Change current user password
|
||||||
void _changePassword() async {
|
void _changePassword() async {
|
||||||
try {
|
try {
|
||||||
|
final currEmail = await AccountHelper.getCurrentAccountEmailAddress();
|
||||||
|
final currUser = await UsersHelper().getSingleWithThrow(userID());
|
||||||
|
|
||||||
final currPassword = await showUserPasswordDialog(context);
|
final currPassword = await showUserPasswordDialog(context);
|
||||||
|
|
||||||
if (currPassword == null) return;
|
if (currPassword == null) return;
|
||||||
|
|
||||||
final newPassword = await showInputNewPassword(context);
|
final newPassword = await showInputNewPassword(
|
||||||
|
context: context,
|
||||||
|
userInfo: UserInfoForPassword(
|
||||||
|
firstName: currUser.firstName,
|
||||||
|
lastName: currUser.lastName,
|
||||||
|
email: currEmail,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (newPassword == null) return;
|
if (newPassword == null) return;
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:comunic/helpers/server_config_helper.dart';
|
import 'package:comunic/helpers/server_config_helper.dart';
|
||||||
import 'package:comunic/models/server_config.dart';
|
import 'package:comunic/models/server_config.dart';
|
||||||
import 'package:comunic/models/user.dart';
|
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -9,26 +8,38 @@ import 'package:flutter/material.dart';
|
|||||||
///
|
///
|
||||||
/// @author Pierre Hubert
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
class NewPasswordInputWidget extends StatefulWidget {
|
class UserInfoForPassword {
|
||||||
final Widget icon;
|
|
||||||
final VoidCallback onEdited;
|
|
||||||
final String label;
|
|
||||||
|
|
||||||
final User user;
|
|
||||||
final String firstName;
|
final String firstName;
|
||||||
final String lastName;
|
final String lastName;
|
||||||
final String email;
|
final String email;
|
||||||
|
|
||||||
|
const UserInfoForPassword({
|
||||||
|
@required this.firstName,
|
||||||
|
@required this.lastName,
|
||||||
|
@required this.email,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewPasswordInputWidget extends StatefulWidget {
|
||||||
|
final Widget icon;
|
||||||
|
final VoidCallback onEdited;
|
||||||
|
final VoidCallback onSubmitted;
|
||||||
|
final TextInputAction textInputAction;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
final UserInfoForPassword user;
|
||||||
|
|
||||||
const NewPasswordInputWidget({
|
const NewPasswordInputWidget({
|
||||||
Key key,
|
Key key,
|
||||||
this.icon,
|
this.icon,
|
||||||
this.onEdited,
|
this.onEdited,
|
||||||
|
this.onSubmitted,
|
||||||
|
this.textInputAction,
|
||||||
@required this.label,
|
@required this.label,
|
||||||
this.user,
|
@required this.user,
|
||||||
this.firstName,
|
}) : assert(label != null),
|
||||||
this.lastName,
|
assert(user != null),
|
||||||
this.email,
|
super(key: key);
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
NewPasswordInputWidgetState createState() => NewPasswordInputWidgetState();
|
NewPasswordInputWidgetState createState() => NewPasswordInputWidgetState();
|
||||||
@ -73,6 +84,9 @@ class NewPasswordInputWidgetState extends State<NewPasswordInputWidget> {
|
|||||||
controller: _controller,
|
controller: _controller,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
onChanged: (s) => _onChanged(),
|
onChanged: (s) => _onChanged(),
|
||||||
|
onSubmitted:
|
||||||
|
widget.onSubmitted == null ? null : (s) => widget.onSubmitted(),
|
||||||
|
textInputAction: widget.textInputAction,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorText: _errorMessage,
|
errorText: _errorMessage,
|
||||||
errorMaxLines: 3,
|
errorMaxLines: 3,
|
||||||
@ -92,28 +106,21 @@ class NewPasswordInputWidgetState extends State<NewPasswordInputWidget> {
|
|||||||
|
|
||||||
// Mandatory checks
|
// Mandatory checks
|
||||||
if (!_policy.allowMailInPassword &&
|
if (!_policy.allowMailInPassword &&
|
||||||
(widget.email ?? "").isNotEmpty &&
|
(widget.user.email ?? "").isNotEmpty &&
|
||||||
(widget.email.toLowerCase().contains(value.toLowerCase()) ||
|
(widget.user.email.toLowerCase().contains(value.toLowerCase()) ||
|
||||||
value.toLowerCase().contains(widget.email.toLowerCase()))) {
|
value.toLowerCase().contains(widget.user.email.toLowerCase()))) {
|
||||||
return tr("Your password must not contains part of your email address!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_policy.allowMailInPassword &&
|
|
||||||
(widget.email ?? "").isNotEmpty &&
|
|
||||||
(widget.email.toLowerCase().contains(value.toLowerCase()) ||
|
|
||||||
value.toLowerCase().contains(widget.email.toLowerCase()))) {
|
|
||||||
return tr("Your password must not contains part of your email address!");
|
return tr("Your password must not contains part of your email address!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_policy.allowNameInPassword &&
|
if (!_policy.allowNameInPassword &&
|
||||||
(widget.firstName ?? "").isNotEmpty &&
|
(widget.user.firstName ?? "").isNotEmpty &&
|
||||||
value.toLowerCase().contains(widget.firstName.toLowerCase())) {
|
value.toLowerCase().contains(widget.user.firstName.toLowerCase())) {
|
||||||
return tr("Your password must not contains your first name!");
|
return tr("Your password must not contains your first name!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_policy.allowNameInPassword &&
|
if (!_policy.allowNameInPassword &&
|
||||||
(widget.lastName ?? "").isNotEmpty &&
|
(widget.user.lastName ?? "").isNotEmpty &&
|
||||||
value.toLowerCase().contains(widget.lastName.toLowerCase())) {
|
value.toLowerCase().contains(widget.user.lastName.toLowerCase())) {
|
||||||
return tr("Your password must not contains your last name!");
|
return tr("Your password must not contains your last name!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
|
|
||||||
/// Check out whether a password is valid or not
|
/// Check out whether a password is valid or not
|
||||||
bool validatePassword(String s) => s.length > 3;
|
bool legacyValidatePassword(String s) => s.length > 3;
|
||||||
|
|
||||||
|
|
||||||
/// Check out whether a given email address is valid or not
|
/// Check out whether a given email address is valid or not
|
||||||
|
Loading…
Reference in New Issue
Block a user