Hello Stack Overflow!
This is my first time posting on the site so please bare with me and my question. My class was tasked with individually creating a password generator using JavaScript. Thankfully I had gotten most of the application operating correctly, but I've gotten stuck on a problem.
Example: The user chooses to have 8 characters in their password and chooses to include special, lowercase, and uppercase characters. When the password is generated sometimes it won't include all of the character selections. (Sometimes it'll generate a password with both special and uppercase characters, but not have a single lowercase character).
I've been finished with this assignment for a minute now, but my goal is to understand what I can do to fix this problem and complete this app anyway. I was thinking of potentially removing the passwordOptions object and turning each option into an array of their own, what are your thoughts?
Thank you so much for any suggestions! :D
// passwordOptions contains all necessary string data needed to generate the password
const passwordOptions = {
num: "1234567890",
specialChar: "!@#$%&'()*+,^-./:;<=>?[]_`{~}|",
lowerCase: "abcdefghijklmnopqrstuvwxyz",
upperCase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};
document.getElementById('generate').addEventListener('click', function() {
alert(generatePassword());
});
// Executes when button is clicked
let generatePassword = function() {
// initial state for password information
let passInfo = "";
// ask user for the length of their password
let characterAmount = window.prompt("Enter the amount of characters you want for your password. NOTE: Must be between 8-128 characters");
// If the character length doesn't match requirements, alert the user
if (characterAmount >= 8 && characterAmount < 129) {
// ask if user wants to include integers
let getInteger = window.confirm("Would you like to include NUMBERS?");
// if user wants to include numbers
if (getInteger) {
// add numerical characters to password data
passInfo = passInfo + passwordOptions.num;
};
// ask if user wants to include special characters
let getSpecialCharacters = window.confirm("Would you like to include SPECIAL characters?");
// if user wants to include special characters
if (getSpecialCharacters) {
// add special characters to password data
passInfo = passInfo + passwordOptions.specialChar;
};
// ask if user wants to include lowercase characters
let getLowerCase = window.confirm("Would you like to include LOWERCASE characters?");
// if user wants to include lowercase characters
if (getLowerCase) {
// add lowercase characters to password data
passInfo = passInfo + passwordOptions.lowerCase;
};
// ask if user wants to include uppercase characters
let getUpperCase = window.confirm("Would you like to include UPPERCASE characters?");
// if user wants to include uppercase characters
if (getUpperCase) {
// add uppercase characters to password data
passInfo = passInfo + passwordOptions.upperCase;
};
// ensure user chooses at least one option
if (getInteger !=true && getSpecialCharacters !=true && getLowerCase !=true && getUpperCase !=true) {
// notify user needs to select at least one option
window.alert("You need to select at least one option, please try again!");
// return user back to their questions
return generatePassword();
};
// randomPassword is an empty string that the for loop will pass information in
let randomPassword = "";
// for loop grabs characterAmount to use
for (let i = 0; i < characterAmount; i++) {
//passInfo connects to charAt that uses both Math.floor and random to take the length of passInfo and randomize the results
randomPassword += passInfo[Math.floor(Math.random() * passInfo.length)];
};
// return password results
return randomPassword;
}
// if user's response is invalid
else {
// alert user
window.alert("You need to provide a valid length!");
// return user back to their questions
/* Removed for testing purposes to break the endless loop. */
// return generatePassword();
}
};
<button id="generate">Run</button>
The best way I can imagine is to pick one character from each selected category, then select the remaining characters randomly, finally, shuffle the selected characters.
You can do it like this:
// passwordOptions contains all necessary string data needed to generate the password
const passwordOptions = {
num: "1234567890",
specialChar: "!@#$%&'()*+,^-./:;<=>?[]_`{~}|",
lowerCase: "abcdefghijklmnopqrstuvwxyz",
upperCase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};
document.getElementById('generate').addEventListener('click', function() {
alert(generatePassword());
});
// Executes when button is clicked
let generatePassword = function() {
// initial state for password information
let passInfo = "";
// list of chosen characters
const passChars = [];
// ask user for the length of their password
let characterAmount = window.prompt("Enter the amount of characters you want for your password. NOTE: Must be between 8-128 characters");
// If the character length doesn't match requirements, alert the user
if (characterAmount >= 8 && characterAmount <= 128) {
// ask if user wants to include integers
const getInteger = window.confirm("Would you like to include NUMBERS?");
// if user wants to include numbers
if (getInteger) {
// add numerical characters to password data
passInfo += passwordOptions.num;
// add a number to the list of chosen characters
passChars.push(getRandomChar(passwordOptions.num));
};
// ask if user wants to include special characters
const getSpecialCharacters = window.confirm("Would you like to include SPECIAL characters?");
// if user wants to include special characters
if (getSpecialCharacters) {
// add special characters to password data
passInfo += passwordOptions.specialChar;
// add a special character to the list of chosen characters
passChars.push(getRandomChar(passwordOptions.specialChar));
};
// ask if user wants to include lowercase characters
const getLowerCase = window.confirm("Would you like to include LOWERCASE characters?");
// if user wants to include lowercase characters
if (getLowerCase) {
// add lowercase characters to password data
passInfo += passwordOptions.lowerCase;
// add a lowercase character to the list of chosen characters
passChars.push(getRandomChar(passwordOptions.lowerCase));
};
// ask if user wants to include uppercase characters
const getUpperCase = window.confirm("Would you like to include UPPERCASE characters?");
// if user wants to include uppercase characters
if (getUpperCase) {
// add uppercase characters to password data
passInfo += passwordOptions.upperCase;
// add an uppercase character to the list of chosen characters
passChars.push(getRandomChar(passwordOptions.upperCase));
};
// ensure user chooses at least one option -- passInfo will be empty if they don't
if (!passInfo) {
// notify user needs to select at least one option
window.alert("You need to select at least one option, please try again!");
// return user back to their questions
return generatePassword();
};
// while there aren't enough characters
while(passChars.length < characterAmount) {
// choose a random char from charInfo
passChars.push(getRandomChar(passInfo));
};
// shuffle the list of characters using Fisher-Yates algorithm
// see https://stackoverflow.com/a/2450976/8376184
for(let i = passChars.length - 1; i > 0; i--){
const swapIndex = Math.floor(Math.random() * (i + 1));
const temp = passChars[i];
passChars[i] = passChars[swapIndex];
passChars[swapIndex] = temp;
};
// return the password character list concatenated to a string
return passChars.join("");
}
// if user's response is invalid
else {
// alert user
window.alert("You need to provide a valid length!");
// return user back to their questions
/* Removed for testing purposes to break the endless loop. */
// return generatePassword();
}
};
function getRandomChar(fromString){
return fromString[Math.floor(Math.random() * fromString.length)];
}
<button id="generate">Run</button>
But, as password generators should be cryptographically random, you should use crypto.getRandomValues()
instead of Math.random()
. You can use this algorithm to convert it to a range of values:
// Generate a random integer r with equal chance in 0 <= r < max.
function randRange(max) {
const requestBytes = Math.ceil(Math.log2(max) / 8);
if (!requestBytes) { // No randomness required
return 0;
};
const maxNum = Math.pow(256, requestBytes);
const ar = new Uint8Array(requestBytes);
while (true) {
window.crypto.getRandomValues(ar);
let val = 0;
for (let i = 0; i < requestBytes;i++) {
val = (val << 8) + ar[i];
};
if (val < maxNum - maxNum % max) {
return val % max;
};
};
};
You can combine this with the code above like this:
// passwordOptions contains all necessary string data needed to generate the password
const passwordOptions = {
num: "1234567890",
specialChar: "!@#$%&'()*+,^-./:;<=>?[]_`{~}|",
lowerCase: "abcdefghijklmnopqrstuvwxyz",
upperCase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};
document.getElementById('generate').addEventListener('click', function() {
alert(generatePassword());
});
// Executes when button is clicked
let generatePassword = function() {
// initial state for password information
let passInfo = "";
// list of chosen characters
const passChars = [];
// ask user for the length of their password
let characterAmount = window.prompt("Enter the amount of characters you want for your password. NOTE: Must be between 8-128 characters");
// If the character length doesn't match requirements, alert the user
if (characterAmount >= 8 && characterAmount <= 128) {
// ask if user wants to include integers
const getInteger = window.confirm("Would you like to include NUMBERS?");
// if user wants to include numbers
if (getInteger) {
// add numerical characters to password data
passInfo += passwordOptions.num;
// add a number to the list of chosen characters
passChars.push(getRandomChar(passwordOptions.num));
};
// ask if user wants to include special characters
const getSpecialCharacters = window.confirm("Would you like to include SPECIAL characters?");
// if user wants to include special characters
if (getSpecialCharacters) {
// add special characters to password data
passInfo += passwordOptions.specialChar;
// add a special character to the list of chosen characters
passChars.push(getRandomChar(passwordOptions.specialChar));
};
// ask if user wants to include lowercase characters
const getLowerCase = window.confirm("Would you like to include LOWERCASE characters?");
// if user wants to include lowercase characters
if (getLowerCase) {
// add lowercase characters to password data
passInfo += passwordOptions.lowerCase;
// add a lowercase character to the list of chosen characters
passChars.push(getRandomChar(passwordOptions.lowerCase));
};
// ask if user wants to include uppercase characters
const getUpperCase = window.confirm("Would you like to include UPPERCASE characters?");
// if user wants to include uppercase characters
if (getUpperCase) {
// add uppercase characters to password data
passInfo += passwordOptions.upperCase;
// add an uppercase character to the list of chosen characters
passChars.push(getRandomChar(passwordOptions.upperCase));
};
// ensure user chooses at least one option -- passInfo will be empty if they don't
if (!passInfo) {
// notify user needs to select at least one option
window.alert("You need to select at least one option, please try again!");
// return user back to their questions
return generatePassword();
};
// while there aren't enough characters
while(passChars.length < characterAmount) {
// choose a random char from charInfo
passChars.push(getRandomChar(passInfo));
};
// shuffle the list of characters using Fisher-Yates algorithm
// see https://stackoverflow.com/a/2450976/8376184
for(let i = passChars.length - 1; i > 0; i--){
const swapIndex = randRange(i + 1);
const temp = passChars[i];
passChars[i] = passChars[swapIndex];
passChars[swapIndex] = temp;
};
// return the password character list concatenated to a string
return passChars.join("");
}
// if user's response is invalid
else {
// alert user
window.alert("You need to provide a valid length!");
// return user back to their questions
/* Removed for testing purposes to break the endless loop. */
// return generatePassword();
}
};
function getRandomChar(fromString){
return fromString[randRange(fromString.length)];
};
// Generate a random integer r with equal chance in 0 <= r < max.
function randRange(max) {
const requestBytes = Math.ceil(Math.log2(max) / 8);
if (!requestBytes) { // No randomness required
return 0;
};
const maxNum = Math.pow(256, requestBytes);
const ar = new Uint8Array(requestBytes);
while (true) {
window.crypto.getRandomValues(ar);
let val = 0;
for (let i = 0; i < requestBytes;i++) {
val = (val << 8) + ar[i];
};
if (val < maxNum - maxNum % max) {
return val % max;
};
};
};
<button id="generate">Run</button>