My goal is to create a regex generator function where I pass a policy criteria object as a parameter and it generates regex according to that.
My attempts so far:
function generateRegexExpression(policy) {
let pattern = "";
if (policy.UppercaseLength > 0) pattern += `A-Z{${policy.UppercaseLength},}`;
if (policy.LowercaseLength > 0) pattern += `a-z{${policy.LowercaseLength},}`;
if (policy.NonAlphaLength > 0) pattern += `[!@#$%^&*()_+-={}\[\]\|;:'",.<>/?`~]{${policy.NonAlphaLength},}`;
if (policy.NumericLength > 0) pattern += `\d{${policy.NumericLength},}`;
if (policy.AlphaLength > 0) pattern += `[A-Za-z]{${policy.AlphaLength},}`;
pattern = `^[${pattern}]{${policy.MinimumLength},${policy.MaximumLength}}$`;
return new RegExp(pattern);
}
const policy = {
"MinimumLength": 8,
"MaximumLength": 20,
"UppercaseLength": 0,
"LowercaseLength": 0,
"NonAlphaLength": 0,
"NumericLength": 1,
"AlphaLength": 1
};
"PolicyRules": [
"Please choose a new password that is between 8 and 20 characters in length.",
"Must have at least 1 letter.",
"Must have at least 1 number.",
"Your new password should not be same as your username or as your last password.",
"Choose a password that is different from your last 5 passwords.",
"Do NOT share your password with anyone."
]
2
Answers
This isn’t a direct answer to your question, but might help solve the overarching issue.
Instead of building a complex regex, you’ll probably be better of if you validate each criteria separately. This allows you to provide the user with a relevant error message. But also gives you the freedom to not use regex if you don’t need to.
For example to validate
MinimumLength
there is no need to use regex at all. You could just useinput.length >= MinimumLength
.Here is an example that uses the following
validate
function:This function expects
criteria
, which is a list of[isValid, errorMsg]
combinations. WhereisValid
is a function that accepts theinput
as argument. Then returns a truthy value if the input is valid, or a falsy value if the input is not valid.errorMsg
is the error that will be returned in the scenario thatinput
is invalid.With this function you can build your criteria from your policy fairly simple:
With the criteria build you validate your input using:
Where
isValid
is atrue
/false
value that tells you if the input is valid. Anderrors
are the error messages incriteria
that are relevant to the current input.First off, a disclaimer: I’m not convinced that using a single regular expression is the best tool for this task. We can make it work anyway, but now we all know that I know that we’re abusing regexps 🙂
Your current code will produce strange patterns that won’t do anything close to what you want. Since they’re all along the same lines let’s look at one example and see what’s going wrong.
Suppose we only have an uppercase policy to apply (say,
policy.UpperCaseLength = 4
). With a bit of whitespace to help see what’s going on, this results in:Everything between those square brackets is treated as part of a character class. Although your intention was for
{4,}
to say that there should be at least four characters matching[A-Z]
,[A-Z{4,}]
is interpreted as "an uppercase character, or{
, or4
, or,
, or}
".What you really want to test is "disregarding other characters in the input, are there 4 separate instances of an uppercase letter?"
This is actually a pretty easy pattern: we can search for anything (
.*
) followed by[A-Z]
, at least four times, i.e.(.*[A-Z]){4,}
. In fact we can be slightly more efficient about this – as long as there are four matches we don’t need to keep matching, and while we’re at it we can use a non-greedy quantifier, and a non-capturing group. So, to match a string that contains at leastpolicy.UpperCaseLength
instances of an uppercase letter, we could use a pattern:We can use this same shape of thinking to make patterns for the other character classes e.g.
We can combine the patterns into one regular expression using look-ahead assertions. An assertion effectively checks the pattern against the input string without "using up" the characters, so we can apply several patterns to the string at once.
So, if we add in the rules wrapped in the look-ahead assertion syntax (
(?= ... )
) then our pattern from before looks like:This still isn’t quite right (in fact it won’t match anything – the look-ahead group matches zero characters, so it could only match an empty string that had at least four uppercase characters!) – what we really want to do with the
MinimumLength
andMaximumLength
policies is check that there are between 8 and 20 instances of any character, i.e..
. So, add that to the pattern:Now the pattern checks that there are at least four uppercase characters, and then that the whole input is between 8 and 20 characters. We can construct other patterns based on the
policy
in the same way: