1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-12-26 12:58:51 +00:00

Apply password policy on all forms

This commit is contained in:
Pierre HUBERT 2021-02-18 18:58:47 +01:00
parent 16ec9a8e00
commit c5d1512375
10 changed files with 135 additions and 50 deletions

View File

@ -4,6 +4,7 @@ import 'package:comunic/helpers/websocket_helper.dart';
import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/authentication_details.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';
/// Account helper
@ -125,6 +126,11 @@ class AccountHelper {
.execWithThrow())
.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
///
/// Throws in case of failure
@ -161,10 +167,19 @@ class AccountHelper {
/// Check a password reset token
///
/// Throws in case failure
static Future<void> validatePasswordResetToken(String token) async =>
await APIRequest.withoutLogin("account/check_password_reset_token")
.addString("token", token)
.execWithThrow();
static Future<ResCheckPasswordToken> validatePasswordResetToken(
String token) async {
final response =
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
///

View File

@ -88,6 +88,10 @@ class APIRequest {
/// Execute the request, throws an exception in case of failure
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
Future<APIResponse> execWithFiles() async => APIHelper().execWithFiles(this);

View 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);
}

View File

@ -1,5 +1,5 @@
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:flutter/material.dart';
@ -8,27 +8,44 @@ import 'package:flutter/material.dart';
/// @author Pierre HUBERT
/// 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(
context: context, builder: (c) => _InputNewPasswordDialog());
context: context,
builder: (c) => _InputNewPasswordDialog(
userInfo: userInfo,
));
}
class _InputNewPasswordDialog extends StatefulWidget {
final UserInfoForPassword userInfo;
const _InputNewPasswordDialog({
Key key,
@required this.userInfo,
}) : assert(userInfo != null),
super(key: key);
@override
__InputNewPasswordDialogState createState() =>
__InputNewPasswordDialogState();
}
class __InputNewPasswordDialogState extends State<_InputNewPasswordDialog> {
final _controller1 = TextEditingController();
final _controller1 = GlobalKey<NewPasswordInputWidgetState>();
final _controller2 = TextEditingController();
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;
@ -65,12 +82,10 @@ class __InputNewPasswordDialogState extends State<_InputNewPasswordDialog> {
child: Column(
children: <Widget>[
// Input 1
_buildPasswordField(
controller: _controller1,
NewPasswordInputWidget(
key: _controller1,
user: widget.userInfo,
label: tr("Your new password"),
errorText: _controller1.text.isNotEmpty && !_input1Valid
? tr("Invalid password!")
: null,
textInputAction: TextInputAction.next,
onSubmitted: () => _focusScopeNode.nextFocus(),
),

View File

@ -30,7 +30,7 @@ class __InputUserPasswordDialogState
String get _currPass => _controller.text;
bool get _canSubmit =>
validatePassword(_controller.text) && _status != _Status.CHECKING;
legacyValidatePassword(_controller.text) && _status != _Status.CHECKING;
void _setStatus(_Status s) => setState(() => _status = s);

View File

@ -139,9 +139,11 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> {
label: tr("Password"),
onEdited: _updateUI,
icon: Icon(Icons.lock),
email: _emailController.text,
firstName: _firstNameController.text,
lastName: _lastNameController.text,
user: UserInfoForPassword(
firstName: _firstNameController.text,
lastName: _lastNameController.text,
email: _emailController.text,
),
),
// Verify password

View File

@ -1,6 +1,8 @@
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/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/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
@ -47,12 +49,13 @@ class __PasswordResetBodyState extends SafeState<_PasswordResetBody> {
final _key = GlobalKey<AsyncScreenWidgetState>();
var _status = _Status.BEFORE_CHANGE;
ResCheckPasswordToken _tokenInfo;
void _setStatus(_Status s) => setState(() => _status = s);
Future<void> _validateToken() async {
_status = _Status.BEFORE_CHANGE;
await AccountHelper.validatePasswordResetToken(widget.token);
_tokenInfo = await AccountHelper.validatePasswordResetToken(widget.token);
}
@override
@ -108,7 +111,14 @@ class __PasswordResetBodyState extends SafeState<_PasswordResetBody> {
void _changePassword() async {
try {
// 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;
_setStatus(_Status.WHILE_CHANGE);

View File

@ -1,10 +1,13 @@
import 'package:comunic/helpers/account_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/ui/dialogs/input_new_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/new_password_input_widget.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/ui_utils.dart';
import 'package:flutter/material.dart';
@ -55,11 +58,21 @@ class _AccountSecuritySettingsScreenState
/// Change current user password
void _changePassword() async {
try {
final currEmail = await AccountHelper.getCurrentAccountEmailAddress();
final currUser = await UsersHelper().getSingleWithThrow(userID());
final currPassword = await showUserPasswordDialog(context);
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;

View File

@ -1,6 +1,5 @@
import 'package:comunic/helpers/server_config_helper.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/ui_utils.dart';
import 'package:flutter/material.dart';
@ -9,26 +8,38 @@ import 'package:flutter/material.dart';
///
/// @author Pierre Hubert
class NewPasswordInputWidget extends StatefulWidget {
final Widget icon;
final VoidCallback onEdited;
final String label;
final User user;
class UserInfoForPassword {
final String firstName;
final String lastName;
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({
Key key,
this.icon,
this.onEdited,
this.onSubmitted,
this.textInputAction,
@required this.label,
this.user,
this.firstName,
this.lastName,
this.email,
}) : super(key: key);
@required this.user,
}) : assert(label != null),
assert(user != null),
super(key: key);
@override
NewPasswordInputWidgetState createState() => NewPasswordInputWidgetState();
@ -73,6 +84,9 @@ class NewPasswordInputWidgetState extends State<NewPasswordInputWidget> {
controller: _controller,
obscureText: true,
onChanged: (s) => _onChanged(),
onSubmitted:
widget.onSubmitted == null ? null : (s) => widget.onSubmitted(),
textInputAction: widget.textInputAction,
decoration: InputDecoration(
errorText: _errorMessage,
errorMaxLines: 3,
@ -92,28 +106,21 @@ class NewPasswordInputWidgetState extends State<NewPasswordInputWidget> {
// Mandatory checks
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!");
}
if (!_policy.allowMailInPassword &&
(widget.email ?? "").isNotEmpty &&
(widget.email.toLowerCase().contains(value.toLowerCase()) ||
value.toLowerCase().contains(widget.email.toLowerCase()))) {
(widget.user.email ?? "").isNotEmpty &&
(widget.user.email.toLowerCase().contains(value.toLowerCase()) ||
value.toLowerCase().contains(widget.user.email.toLowerCase()))) {
return tr("Your password must not contains part of your email address!");
}
if (!_policy.allowNameInPassword &&
(widget.firstName ?? "").isNotEmpty &&
value.toLowerCase().contains(widget.firstName.toLowerCase())) {
(widget.user.firstName ?? "").isNotEmpty &&
value.toLowerCase().contains(widget.user.firstName.toLowerCase())) {
return tr("Your password must not contains your first name!");
}
if (!_policy.allowNameInPassword &&
(widget.lastName ?? "").isNotEmpty &&
value.toLowerCase().contains(widget.lastName.toLowerCase())) {
(widget.user.lastName ?? "").isNotEmpty &&
value.toLowerCase().contains(widget.user.lastName.toLowerCase())) {
return tr("Your password must not contains your last name!");
}

View File

@ -3,7 +3,7 @@
/// @author Pierre HUBERT
/// 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