unjs / magic-regexp

A compiled-away, type-safe, readable RegExp alternative
https://regexp.dev
MIT License
3.8k stars 52 forks source link

how do i recreate a password requirements regex? #198

Open majkl-zumberi opened 1 year ago

majkl-zumberi commented 1 year ago

📚 Is your documentation request related to a problem?

i'm trying to recreate a password requirements regex like the following: password must contains al least 8 characters, one lowercase character, one uppercase char, one digit and one special character (! @ # $ % ^ & *)

const PASSWORD_RE= createRegExp(
  (
    oneOrMore(letter.lowercase).times.atLeast(1)
  .and(oneOrMore(letter.uppercase).times.atLeast(1))
  .and(oneOrMore(digit))
  .and(
    oneOrMore(
      charIn('!')
      .or(charIn('@'))
      .or(charIn('#'))
      .or(charIn('$'))
      .or(charIn('%'))
      .or(charIn('^'))
      .or(charIn('&'))
      .or(charIn('*'))
      ).times.atLeast(1)
    )
  )
.times.atLeast(8),['g']
)

i tried to paste the generated regex in one of the regex tester like regex101 but seems to not working as expected

the documentation seems lacking in terms of providing a fully understandable examples

can you help me to achieve a working password regex? Where am I doing wrong?

🔍 Where should you find it?

No response

ℹ️ Additional context

No response

didavid61202 commented 1 year ago

You can do something like this:

const PASSWORD_RE = createRegExp(
  exactly('')
    .before(char.times.any().and(letter.lowercase))
    .before(char.times.any().and(letter.uppercase))
    .before(char.times.any().and(digit))
    .before(char.times.any().and(charIn('!@#$%^&*')))
    .and(letter.or(digit.or(charIn('!@#$%^&*'))).times.atLeast(8))
    .at.lineStart()
    .at.lineEnd()
)

charIn can just take all characters in one go as a string, but we do still missing Input helper that create custom range(s) to simplify more, ex: for regexp [b-t3-6]

majkl-zumberi commented 1 year ago

thanks @didavid61202 for your solution, i'll end up closing the issue but let me first ask you to briefly explain how you ended up with this solution why there's the need of exactly('')? and .at.lineStart() .at.lineEnd() what they do?

the documentation as i mentioned before isn't that clear

leofcshen commented 1 year ago

@didavid61202 your solution create regex ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%\^&*])(?:[a-zA-Z]|(?:\d|[!@#$%\^&*])){8,}$ how to create more clear regex like ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%\^&*]).{8,}$

toindev commented 1 year ago

Hi, just as an aside, "hard" password requirements are actually counter-productive, as a security measure.

For the purpose of checking the safety of a password, you would be better served by something like https://github.com/dropbox/zxcvbn

Nonetheless, have fun with regular expressions!

zoeyzhao19 commented 1 year ago

@didavid61202 your solution create regex ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%\^&*])(?:[a-zA-Z]|(?:\d|[!@#$%\^&*])){8,}$ how to create more clear regex like ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%\^&*]).{8,}$

maybe

const regexp = createRegExp(
    exactly(
      exactly('')
        .before(char.times.any().and(letter.lowercase))
        .before(char.times.any().and(letter.uppercase))
        .before(char.times.any().and(digit))
        .before(char.times.any().and(charIn('!@#$%^&*'))),
      char
        .times.atLeast(8),
    )
      .at.lineStart()
      .at.lineEnd()
)