validate method

Validates a password against the configured policy.

Implementation

ValidationResult<PasswordValidationIssue, String> validate(String input) {
  if (input.isEmpty) {
    return const Left(PasswordEmptyIssue());
  }

  if (input.length < config.minLength) {
    return Left(
      PasswordTooShortIssue(
        currentLength: input.length,
        minLength: config.minLength,
      ),
    );
  }

  if (input.length > config.maxLength) {
    return Left(
      PasswordTooLongIssue(
        currentLength: input.length,
        maxLength: config.maxLength,
      ),
    );
  }

  if (config.requireUppercase && !hasUppercase(input)) {
    return const Left(PasswordMissingUppercaseIssue());
  }

  if (config.requireLowercase && !hasLowercase(input)) {
    return const Left(PasswordMissingLowercaseIssue());
  }

  if (config.requireDigit && !hasDigit(input)) {
    return const Left(PasswordMissingDigitIssue());
  }

  if (config.requireSpecialCharacter && !hasSpecialCharacter(input)) {
    return const Left(PasswordMissingSpecialCharacterIssue());
  }

  final lowered = input.toLowerCase();

  if (config.rejectCommonPasswords) {
    if (config.commonPasswords.contains(lowered)) {
      return const Left(PasswordTooCommonIssue());
    }

    final hasCommonPrefix = config.commonPrefixes.any(
      (prefix) =>
          lowered.startsWith(prefix) && lowered.length <= prefix.length + 4,
    );
    if (hasCommonPrefix) {
      return const Left(PasswordTooCommonIssue());
    }
  }

  if (config.rejectRepeatedCharacters && hasOnlyRepeatedCharacters(input)) {
    return const Left(PasswordRepeatedCharacterIssue());
  }

  final sequentialThreshold =
      config.minSequentialRun < 3 ? 3 : config.minSequentialRun;
  if (config.rejectSequentialPatterns &&
      hasSequentialRun(input, minRun: sequentialThreshold)) {
    return const Left(PasswordSequentialPatternIssue());
  }

  return Right(input);
}