Skip to content

Instantly share code, notes, and snippets.

@PlugFox
Last active March 17, 2026 12:30
Show Gist options
  • Select an option

  • Save PlugFox/b6aa1d256e6900424c74aa75136c9aef to your computer and use it in GitHub Desktop.

Select an option

Save PlugFox/b6aa1d256e6900424c74aa75136c9aef to your computer and use it in GitHub Desktop.
Password validation
/*
* Password validation
* https://gist.github.com/PlugFox/b6aa1d256e6900424c74aa75136c9aef
* https://dartpad.dev?id=b6aa1d256e6900424c74aa75136c9aef
* Mike Matiunin <plugfox@gmail.com>, 17 March 2026
*/
// ignore_for_file: curly_braces_in_flow_control_structures
void main() {
print(validatePasswords('Aaaa1234', 'Aaaa1234').isValid);
print(validatePasswords('Aaaa1234', 'baaa1234').isValid);
}
/// {@template password_validation_issues}
/// A bitmask representing password validation issues for new password creation.
/// Use [validatePasswords] to obtain this value.
/// {@endtemplate}
extension type const PasswordValidationIssues(int value) implements int {
/// No issues — password passes all validation rules.
static const PasswordValidationIssues none = PasswordValidationIssues(0);
/// Password length is not between 8 and 128 characters.
static const PasswordValidationIssues length = PasswordValidationIssues(
1 << 0,
);
/// Password does not contain at least one digit.
static const PasswordValidationIssues number = PasswordValidationIssues(
1 << 1,
);
/// Password does not contain at least one uppercase letter.
static const PasswordValidationIssues uppercase = PasswordValidationIssues(
1 << 2,
);
/// Password and confirmation do not match.
static const PasswordValidationIssues mismatch = PasswordValidationIssues(
1 << 3,
);
/// Check if the issues contain a specific flag.
bool contains(PasswordValidationIssues flag) => (value & flag.value) != 0;
/// Add an issue flag.
PasswordValidationIssues add(PasswordValidationIssues flag) =>
PasswordValidationIssues(value | flag.value);
/// Remove an issue flag.
PasswordValidationIssues remove(PasswordValidationIssues flag) =>
PasswordValidationIssues(value & ~flag.value);
/// Whether the password length is not between 8 and 128 characters.
bool get hasLengthIssue => contains(length);
/// Whether the password does not contain at least one digit.
bool get hasNumberIssue => contains(number);
/// Whether the password does not contain at least one uppercase letter.
bool get hasUppercaseIssue => contains(uppercase);
/// Whether the password and confirmation do not match.
bool get hasMismatchIssue => contains(mismatch);
/// Check if the password is valid (no issues).
bool get isValid => value == 0;
/// Check if the password is not valid (has issues).
bool get isNotValid => value != 0;
}
/// Validates a new password against creation rules and returns a bitmask of issues.
/// This method is only for password creation (sign-up), not for login.
///
/// Performs a single pass over the password string, starting with all issues set
/// and removing flags as conditions are met.
PasswordValidationIssues validatePasswords(String password, String confirm) {
// Start with all character-level issues set; remove as we find matches.
var issues = const PasswordValidationIssues(
PasswordValidationIssues.number | PasswordValidationIssues.uppercase,
);
for (final r in password.runes) {
if (r >= 0x30 && r <= 0x39) {
issues = issues.remove(.number); // 0-9
if (!issues.hasUppercaseIssue) break;
} else if (_isUppercaseRune(r)) {
issues = issues.remove(.uppercase); // A-Z
if (!issues.hasNumberIssue) break;
}
}
final length = password.length;
if (length < 8 || length > 128) issues = issues.add(.length);
if (password != confirm) issues = issues.add(.mismatch);
return issues;
}
/// Checks if a Unicode code point is an uppercase letter.
/// Covers Latin, Cyrillic, Greek, and extended Latin ranges without allocations.
@pragma('vm:prefer-inline')
bool _isUppercaseRune(int r) =>
(r >= 0x41 && r <= 0x5A) || // A-Z
(r >= 0x410 && r <= 0x42F) || // А-Я (Cyrillic)
r == 0x401 || // Ё
(r >= 0xC0 && r <= 0xD6) || // À-Ö (Latin Extended-A)
(r >= 0xD8 && r <= 0xDE) || // Ø-Þ (Latin Extended-A)
(r >= 0x391 && r <= 0x3A9); // Α-Ω (Greek)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment