#typescript
    #leetcode
    #challenge-question

Building a Password Generator

A New Technical Challenge Approaches!

If you're going through the process of technical interviews, you've probably encountered a few curveballs in terms of being asked to implement brand new features or logic that you might not have considered before, or simply may not encounter in your day-to-day role.

You might be asked to write the code to validate a TicTacToe board, how to convert a matrix of ASCII art characters to a string representation, and other types of questions that aim to figure out how you go about figuring out a technical challenge.

However, one of the more real-world exemplifying challenges you can be asked, is to write the underlying code for a random password generator.

We'll go through the thought process behind that particular challenge, and a simple and straightforward solution using TypeScript.


Defining the Password Generation Input

If you've used a random password generator before, you'll be familiar with the different inputs you can provide to the password generator. A secure solution will typically allow you to provide desired settings like

  • Minimum characters to include in the generated password
  • Required number of alphabetical characters
  • Required number of numerical characters
  • Required number of special characters
  • etc.

So for now, we'll stick with the above as the input we can provide to our random password generating function. We'll define its shape in TypeScript code like this

type PasswordGenerationInput = {
  /** How many letters our password should contain */
  requiredLetters: number;

  /** How many numbers our password should contain */
  requiredNumbers: number;

  /** How many special characters our password should contain */
  requiredSpecials: number;
};

/** Returns a randomized password that satisfies the requirements indicated by the input
 * @param input - The object containing the requirements for the generated password
 */
function generateRandomizedPassword(input: PasswordGenerationInput): string {
  // Setup an empty string for the generated password
  let generatedPassword = '';

  // TODO - Implement the logic!
  return '';
}

The above defines the input we provide to our function and the function signature. We're indicating this function will return with a value of type string — i.e. the generated password.


Readying the Password Generation Logic

From what we know so far, and what we've identified as the input to our password generation function, it sounds like we'll need to be able to

  • Get a random letter
  • Get a random number
  • Get a random special character

But out-of-the-box, and without any particular library to help, JavaScript doesn't provide a means for providing us a random character of any of the above types. That means we'll have to come up with that part of the logic ourselves.

We'll define a few string constants outside of our password generation function. Each of these strings will hold the pool of characters we'll be able to use to get a randomized character type.

// Pool of character types
const LETTERS = 'abcdefhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const NUMBERS = '1234567890';
const SPECIALS = '!@#$%^&*()-+';

Requirements may vary from implementation to implementation, but for the most part, the above constants contain letters, numbers, and special characters we can use for generating our randomized password.

We'll also define a couple of functions outside of the body of our main generateRandomizedPassword function. These are mainly helpers for

  • getRandomIndex - Generating a random index within the bounds of a strings length
  • getRandomCharacter - Getting a random letter, number, or special character

See the below function definitions and read through the function descriptions. It'll help inform on what purpose each function serves.

/** For the given range, will return a number between 0 and that range
 * @param range - The upper bound from 0 to generate a random index for
 */
function getRandomIndex(range: number): number {
  return Math.floor(Math.random() * range);
}

/** Retrieves a random character from one of our pool of string characters
 * @param poolType - The pool of characters to get a random character from
 */
function getRandomCharacter(
  poolType: 'LETTERS' | 'NUMBERS' | 'SPECIALS'
): string {
  if (poolType === 'LETTERS') {
    const randomIndex = getRandomIndex(LETTERS.length);
    const randomLetter = LETTERS[randomIndex];

    return randomLetter;
  }

  if (poolType === 'NUMBERS') {
    const randomIndex = getRandomIndex(NUMBERS.length);
    const randomNumber = NUMBERS[randomIndex];

    return randomNumber;
  }

  if (poolType === 'SPECIALS') {
    const randomIndex = getRandomIndex(SPECIALS.length);
    const randomSpecial = SPECIALS[randomIndex];

    return randomSpecial;
  }

  throw new Error(`Provided pool type ${poolType} not accepted`);
}

And in the next section, we'll go about using each of these helper functions to generate the password.


Building our Randomized Password

We've got pretty much everything we need to be able to generate the randomized password.

Now, for the most part, we just have to put these pieces together and build our randomized password. Let's do that.

At a high-level, our approach will be the following

  • We'll use a while loop to build our password
  • We'll keep track of how many of each character type we've added into our password, and only add as many of each as we need. Remember, per our PasswordGenerationInput settings, we'll only want as many of each character type as needed
  • Our exit condition for our while loop will be once all three of our required character types have been reached

So let's do that! We'll initialize three variables to keep track of how many letters, numbers, and special characters we've added into our generated password.

  // Keep track of how many of each character type we've added to the generated password
  let lettersCount = 0;
  let numbersCount = 0;
  let specialsCount = 0;

These variables will each start at 0 and keep track of how many characters of each character type we've added into the password. We'll increment each as we add a character into the password.

Here's the logic for our while loop

  // Here's where we'll actually add the characters into our generated password
  while (
    lettersCount < input.requiredLetters ||
    numbersCount < input.requiredNumbers ||
    specialsCount < input.requiredSpecials
  ) {
    // If we've not yet reached the required letters count
    // then get a random letter and add it into the password
    if (lettersCount < input.requiredLetters) {
      const randomLetter = getRandomCharacter('LETTERS');
      generatedPassword += randomLetter;

      lettersCount += 1;
    }

    // If we've not yet reached the required numbers count
    // then get a random number and add it into the password
    if (numbersCount < input.requiredNumbers) {
      const randomNumber = getRandomCharacter('NUMBERS');
      generatedPassword += randomNumber;

      numbersCount += 1;
    }

    // If we've not yet reached the required special characters count
    // then get a random special character and add it into the password
    if (specialsCount < input.requiredSpecials) {
      const randomSpecialCharacter = getRandomCharacter('SPECIALS');
      generatedPassword += randomSpecialCharacter;

      specialsCount += 1;
    }
  }

And there we go! Our exit condition is once all three of our character counts reach the required character count per our password generation input settings.

If the required count for a character type happens to be 0, then essentially nothing is done for that character type, since the starting character count is 0.

We increment each character count as we add it to our string, and this generates a password for us that meets the required criteria! We'll add a console.log to our script just before our return statement so we can see information about the generated password.

...
  console.log(`
        Generated random password with
        Letters: ${lettersCount}
        Numbers: ${numbersCount}
        Special Characters: ${specialsCount}

        Password: ${generatedPassword}
    `);
  return generatedPassword;

And then at the bottom of our file, we'll call this function with some arguments and see the output

generateRandomizedPassword({
  requiredLetters: 3,
  requiredNumbers: 3,
  requiredSpecials: 2,
});

/**
The above should output something like
        Generated random password with
        Letters: 3
        Numbers: 3
        Special Characters: 2

        Password: V4+I0&W5
*/

If basic random password generation functionality is what you're looking for, then the above does a solid job at meeting that need. You can feel free to skip the next section of the post, and use the above as-is, or remix it to suit your needs.

However, there is one slight security implication to be mentioned with the above. That is, that this function does generate a random password that meets our needs. But it does so in a pretty predictable way!

Note how for each character type, the next character type immediately follows it. Such that there is indeed a discernable pattern that a bad actor could potentially take advantage of to try and generate a matching password for the end-user of this functionality.

For example, if we were to pass a requirement of 3 letters, 3 numbers, 3 special characters, to our password generation function, then the format of the resulting password will always follow the format of string, number, special character, much like A1!B2@C3#.

In essence, there's a predictable pattern of what character type follows the prior character type, and this results in a potential security vulnerability. Let's move onto the last and final section to figure out how we can adjust our functionality to mitigate this.


Integrating a Shuffling Solution

The simplest and straightforward way to remove this security risk from our functionality would be to just shuffle our password.

For simplicity, and to demonstrate how to shuffle a given string in JavaScript, we'll be implementing this as a separate function.

There's quite a few ways to go about this, each with their own strengths and weaknesses, but for our approach, we'll be doing the following

  • Create a brand new function called shuffleString which will accept a string and return the shuffled string
  • Within that function, we'll create an array from the string to shuffle using the String.split method and call the variable for it originalStringSplit
  • We'll also create an empty array, where its length will be the size of the provided string, called it shuffledStringArray
  • We'll use our getRandomIndex function to access a random character in the originalStringSplit array and move it into shuffledStringArray
  • We'll do a final shuffledStringArray.join('') to convert it into a string

See below for the function implementation and the comments describing what is being done at each step.

/** This function takes in a string of any size and returns a new
 * string where the characters within it have been shuffled.
 *
 * The size and contents of the original string are preserved in the
 * shuffled string - simply the order of the characters is changed
 *
 * @param originalString - The string to shuffle
 */
function shuffleString(originalString: string): string {
  // We'll split the original string into an array containing the character of the string
  // We'll also create an empty array of the size of the original string
  const originalStringSplit = originalString.split('');
  const shuffledStringArray = new Array(originalString.length);

  // By this point, both our arrays are the same length. Our approach will be to
  // 1. Iterate the length of `shuffledStringArray` array
  // 2. During each iteration, generate a random index using `originalStringSplit` length
  // 3. Extract the character at that random index using `Array.splice`
  // 4. Move that extracted character into the current index of `shuffledStringArray` iteration
  // We are essentially extracting characters out of the original string, at random, and then
  // placing them into the empty `shuffledStringArray` array, resulting a shuffling of the original string
  for (let index = 0; index < shuffledStringArray.length; index++) {
    // Extract a character at a random index from the original string split
    // This modifies the size and contents of original string split in-place, which
    // will prevent us from duplicating or re-using already extracted characters
    const randomIndex = getRandomIndex(originalStringSplit.length);
    const randomCharacter = originalStringSplit.splice(randomIndex, 1);

    // We'll store this random character into the shuffled string array
    // at the current `index` value. This will populate our empty array
    shuffledStringArray[index] = randomCharacter;
  }

  // By this point, our shuffled string array is just an array
  // We'll join it into a string to finalize our shuffled string
  const finalShuffledString = shuffledStringArray.join('');
  console.log(`
    Original String: ${originalString}
    Shuffled String: ${finalShuffledString}
    Length is equal: ${originalString.length === finalShuffledString.length}
    Cardinality is equal: ${originalString.split('').sort().join('') === shuffledStringArray.sort().join('')}
    `);

  return finalShuffledString;
}

And the output of that function should look like the following

     // Output of the `console.log` call
    Original String: o6+u5@Q3
    Shuffled String: @3ou5Q+6
    Length is equal: true
    Cardinality is equal: true

This new piece of logic completes our random password generation functionality. It mitigates the security risk of having a discernable pattern of character types for a stronger and more secure password.

Best of all, this new shuffleString function shows you how to implement a technique for shuffling a string in JavaScript! Since, the language doesn't provide that functionality out-of-the-box.

You can grab the full and complete code from here

Happy coding!


zerochass

practical and goal-oriented resources

learn by doing. enjoy what you do.