mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-11-22 12:59: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/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 =>
|
||||
static Future<ResCheckPasswordToken> validatePasswordResetToken(
|
||||
String token) async {
|
||||
final response =
|
||||
await APIRequest.withoutLogin("account/check_password_reset_token")
|
||||
.addString("token", token)
|
||||
.execWithThrow();
|
||||
.execWithThrowGetObject();
|
||||
|
||||
return ResCheckPasswordToken(
|
||||
firstName: response["first_name"],
|
||||
lastName: response["last_name"],
|
||||
email: response["mail"],
|
||||
);
|
||||
}
|
||||
|
||||
/// Change account password using password reset token
|
||||
///
|
||||
|
@ -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);
|
||||
|
||||
|
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/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(),
|
||||
),
|
||||
|
@ -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);
|
||||
|
||||
|
@ -139,9 +139,11 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> {
|
||||
label: tr("Password"),
|
||||
onEdited: _updateUI,
|
||||
icon: Icon(Icons.lock),
|
||||
email: _emailController.text,
|
||||
user: UserInfoForPassword(
|
||||
firstName: _firstNameController.text,
|
||||
lastName: _lastNameController.text,
|
||||
email: _emailController.text,
|
||||
),
|
||||
),
|
||||
|
||||
// Verify password
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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!");
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user