import 'package:comunic/helpers/server_config_helper.dart'; import 'package:comunic/models/server_config.dart'; import 'package:comunic/utils/intl_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, }) : super(key: key); @override NewPasswordInputWidgetState createState() => NewPasswordInputWidgetState(); } class NewPasswordInputWidgetState extends State { final TextEditingController _controller = TextEditingController(); String get value => _controller.text; bool get valid => value.isNotEmpty && (_errorMessage ?? "").isEmpty; PasswordPolicy get _policy => ServerConfigurationHelper.config!.passwordPolicy; @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 (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; } }