import 'package:comunic/helpers/server_config_helper.dart'; import 'package:comunic/models/server_config.dart'; import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/ui_utils.dart'; import 'package:flutter/material.dart'; /// New password input widget /// /// @author Pierre Hubert 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, @required this.user, }) : assert(label != null), assert(user != null), super(key: key); @override NewPasswordInputWidgetState createState() => NewPasswordInputWidgetState(); } class NewPasswordInputWidgetState extends State { final TextEditingController _controller = TextEditingController(); bool _ready = false; String get value => _controller.text; bool get valid => _ready && value.isNotEmpty && (_errorMessage ?? "").isEmpty; PasswordPolicy get _policy => ServerConfigurationHelper.config.passwordPolicy; @override void initState() { super.initState(); _init(); } /// Make sure server configuration is loaded void _init() async { try { await ServerConfigurationHelper.ensureLoaded(); setState(() => _ready = true); } catch (e, s) { print("Failed to initialize NewPasswordInputWidget! : $e - $s"); showSimpleSnack(context, tr("Failed to retrieve server configuration!")); } } @override void didUpdateWidget(covariant NewPasswordInputWidget oldWidget) { super.didUpdateWidget(oldWidget); setState(() {}); } @override Widget build(BuildContext context) => TextField( controller: _controller, obscureText: true, onChanged: (s) => _onChanged(), onSubmitted: widget.onSubmitted == null ? null : (s) => widget.onSubmitted(), textInputAction: widget.textInputAction, decoration: InputDecoration( errorText: _errorMessage, errorMaxLines: 3, icon: widget.icon, labelText: widget.label, ), ); void _onChanged() { setState(() {}); if (widget.onEdited != null) widget.onEdited(); } /// Generate an error message associated with current password String get _errorMessage { if (!_ready || value.isEmpty) return null; // Mandatory checks if (!_policy.allowMailInPassword && (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.user.firstName ?? "").isNotEmpty && value.toLowerCase().contains(widget.user.firstName.toLowerCase())) { return tr("Your password must not contains your first name!"); } if (!_policy.allowNameInPassword && (widget.user.lastName ?? "").isNotEmpty && value.toLowerCase().contains(widget.user.lastName.toLowerCase())) { return tr("Your password must not contains your last name!"); } if (_policy.minPasswordLength > value.length) { return tr( "Your password must be composed of at least %num% characters!", args: { "num": _policy.minPasswordLength.toString(), }, ); } // Characteristics check var count = 0; if (_hasCharacteristic(RegExp(r'[A-Z]'), _policy.minNumberUpperCaseLetters)) count++; if (_hasCharacteristic(RegExp(r'[a-z]'), _policy.minNumberLowerCaseLetters)) count++; if (_hasCharacteristic(RegExp(r'[0-9]'), _policy.minNumberDigits)) count++; if (_hasCharacteristic( RegExp(r'[^A-Za-z0-9]'), _policy.minNumberSpecialCharacters)) count++; if (count >= _policy.minCategoriesPresence) return null; return tr( "Your password must contains characters of at least %num% of the following categories : %upper% upper case letter, %lower% lowercase letter, %digit% digit, %special% special character.", args: { "num": (_policy.minCategoriesPresence == 4 ? tr("ALL") : _policy.minCategoriesPresence.toString()), "upper": _policy.minNumberUpperCaseLetters.toString(), "lower": _policy.minNumberLowerCaseLetters.toString(), "digit": _policy.minNumberDigits.toString(), "special": _policy.minNumberSpecialCharacters.toString(), }, ); } bool _hasCharacteristic(RegExp exp, int requiredCount) { if (requiredCount < 1) return true; return exp.allMatches(value).length >= requiredCount; } }