feat(01-06): create password update page with validation

- Implemented UpdatePasswordPage with password strength indicator
- Added password validation utility with comprehensive checks
- Enhanced AuthProvider with updatePasswordFromReset method
- Extended AuthRepository interface and implementation for password reset
- Added InvalidTokenException to auth exception hierarchy
- Includes accessibility features and error handling
- Password strength indicator with real-time feedback

Files:
- lib/features/authentication/presentation/pages/update_password_page.dart
- lib/core/utils/password_validator.dart
- lib/providers/auth_provider.dart
- lib/features/authentication/domain/repositories/auth_repository.dart
- lib/features/authentication/data/repositories/auth_repository_impl.dart
- lib/core/errors/auth_exceptions.dart
This commit is contained in:
Dani B
2026-01-28 12:05:15 -05:00
parent a9f1bf75e8
commit e56dd26fef
6 changed files with 616 additions and 1 deletions

View File

@@ -120,6 +120,19 @@ class EmailNotVerifiedException extends AuthException {
);
}
/// Exception thrown when a reset token is invalid or expired
class InvalidTokenException extends AuthException {
const InvalidTokenException({
String message = 'Reset token is invalid or has expired',
String? code,
dynamic originalError,
}) : super(
message: message,
code: code ?? 'INVALID_TOKEN',
originalError: originalError,
);
}
/// Exception thrown when too many login attempts are made
class TooManyRequestsException extends AuthException {
const TooManyRequestsException({
@@ -198,6 +211,17 @@ class AuthExceptionFactory {
);
}
if (lowerMessage.contains('invalid token') ||
lowerMessage.contains('token expired') ||
lowerMessage.contains('reset token') ||
lowerMessage.contains('session has expired')) {
return InvalidTokenException(
message: _extractUserFriendlyMessage(errorMessage) ?? 'Reset token is invalid or has expired',
code: errorCode,
originalError: error,
);
}
if (lowerMessage.contains('user not found') ||
lowerMessage.contains('no user found') ||
lowerMessage.contains('user does not exist')) {

View File

@@ -0,0 +1,92 @@
/// Utility class for password validation and strength checking
class PasswordValidator {
/// Minimum password length
static const int minLength = 8;
/// Validates a password against security requirements
///
/// Returns [PasswordValidationResult] with detailed validation information
static PasswordValidationResult validate(String password) {
final errors = <String>[];
double strength = 0.0;
// Check length
if (password.length < minLength) {
errors.add('Password must be at least $minLength characters');
} else {
strength += 0.25;
}
// Check for uppercase letter
if (!password.contains(RegExp(r'[A-Z]'))) {
errors.add('Password must contain at least one uppercase letter');
} else {
strength += 0.25;
}
// Check for lowercase letter
if (!password.contains(RegExp(r'[a-z]'))) {
errors.add('Password must contain at least one lowercase letter');
} else {
strength += 0.25;
}
// Check for number
if (!password.contains(RegExp(r'[0-9]'))) {
errors.add('Password must contain at least one number');
} else {
strength += 0.25;
}
// Bonus points for special characters
if (password.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]'))) {
strength = (strength + 0.1).clamp(0.0, 1.0);
}
// Bonus points for longer passwords
if (password.length >= 12) {
strength = (strength + 0.1).clamp(0.0, 1.0);
}
return PasswordValidationResult(
isValid: errors.isEmpty,
errors: errors,
strength: strength,
);
}
/// Gets password strength text based on strength value
static String getStrengthText(double strength) {
if (strength <= 0.25) return 'Weak';
if (strength <= 0.5) return 'Fair';
if (strength <= 0.75) return 'Good';
return 'Strong';
}
/// Gets password strength color based on strength value
static String getStrengthColor(double strength) {
if (strength <= 0.25) return 'red';
if (strength <= 0.5) return 'orange';
if (strength <= 0.75) return 'yellow';
return 'green';
}
}
/// Result of password validation
class PasswordValidationResult {
final bool isValid;
final List<String> errors;
final double strength;
const PasswordValidationResult({
required this.isValid,
required this.errors,
required this.strength,
});
/// Gets the strength text for this result
String get strengthText => PasswordValidator.getStrengthText(strength);
/// Gets the strength color name for this result
String get strengthColor => PasswordValidator.getStrengthColor(strength);
}