Skip to content

Instantly share code, notes, and snippets.

@jamiebuilds
Created July 1, 2020 18:27
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jamiebuilds/3aa8e0ca21da63b8845e588f27fdc2a0 to your computer and use it in GitHub Desktop.
Save jamiebuilds/3aa8e0ca21da63b8845e588f27fdc2a0 to your computer and use it in GitHub Desktop.
async function validatePassword(password: string): string[] {
let errors = []
// 1. Don't regex for things you can trivially express in code
// -----------------------------------------------------------
// For example, we could have written this as `/^.{0,7}$/` but that's not
// nearly as clear as checking the length of the string.
if (password.length < 8) {
errors.push("Password must be at least 8 characters long")
}
// 2. Split your regex into smaller and more easily understood chunks whereever possible
// -------------------------------------------------------------------------------------
// For example, we could have written this as `/(?=.*?[a-z])(?=.*?[A-Z])(?=.*?[0-9])/`
// but we've only made the problem more complex for ourselves by trying to do everything
// at once.
if (/[a-z]/.test(password)) { errors.push("Password must contain at least one lowercase letter") }
if (/[A-Z]/.test(password)) { errors.push("Password must contain at least one uppercase letter") }
if (/[0-9]/.test(password)) { errors.push("Password must contain at least one number") }
// 3. Move logical operators out of regex whereever possible
// ---------------------------------------------------------
// For example, we could have written this as `/^\s|\s$/` but again, regexes are
// way easier to understand when they are broken down into smaller chunks that accomplish
// one things.
if (/^\s/.test(password) || /\s$/.test(password)) {
errors.push("Password must not start or end with a whitespace character")
}
// 4. Learn how to selectively apply the more complex regex operators
// ------------------------------------------------------------------
// For example, we could have written something like `/[!@#$%^&*]/` but we definitely
// can't list off every possible special character. So instead we're testing the string
// contains at least one character that is something _other_ than a-z, A-Z, or 0-9.
// Explainer:
// Match a character `[^except]` `a-z`, `A-Z`, or `0-9`
if (/[^a-zA-Z0-9]/.test(password)) {
errors.push("Password must contain at least one special character")
}
// 5. When using complex regex operators, be sure to keep them isolated and well explained
// ---------------------------------------------------------------------------------------
// For example, a couple more complex regex operators can replace a fair bit of stateful code
// logic here. If these complex operators were part of a massive regex, it would make for a
// very hard to read regex. But keeping it small allows you to document what is happening and
// keeps it easy to understand.
// Explainer:
// Match any `(.)` character, then check if that character `\1` is repeated 3 more times `{3,}`
if (/(.)\1{3,}/.test(password)) {
errors.push("Password must not repeat the same character in a row 4 or more times")
}
// 6. Regular Expressions can't solve everything, don't use it in place of what's most important
// ---------------------------------------------------------------------------------------------
// For example, we've written all of the above checks to make sure the user isn't
// shooting themselves in the foot with their password. But that's in service of
// the bigger problem that we want to make sure someone isn't going to break into
// their account. Regex alone is not enough to solve this problem.
if (await checkHaveIBeenPwned(password)) {
errors.push("Password has been previously exposed by data breach according to haveibeenpwned.com")
}
return errors
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment