Search code examples
javascriptalgorithmformsvalidationpasswords

Check for range of characters in a random string


I have this function to generate a random password.

My question is How do I ensure that a selected range of letters/sign/numbers is included in the result?

As an example, if I select that there must be numbers or signs in the result string, how do I check if there is at least one number or sign in the result. I have toughed about looping through numbers or signs for each member of the result, and then break the loop if I meet a character I am testing for.

The code snippet works, but without validating the result

function generatePassword() {
    var base = "";
    var result = "";
    
    var latinLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    var cyrillicLetters = "БбВвГгДдЕеЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЬьЮюЯя"
    var greekLetters = "ΑαΒβΓγΔδΕεΖζΗηΘθΙιΚκΛλΜμΝνΞξΟοΠπΡρΣσςΤτΥυΦφΧχΨψΩω"
    var numbersLetters = "1234567890"
    var signsLetters = "/|()1{}[]?-_+~<>!I;:,^`.$@B%&WM*"
    var armenianLetters = "աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆուև"
    var hangulLetters = "ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎㅏㅑㅓㅕㅗㅛㅜㅠㅡㅣ"
    var nordicLetters = "AaÁáBbCcDdÐðEeÉéFfGgHhIiÍíJjKkLlMmNnOoÓóPpRrSsTtUuÚúVvWwXxYyÝýZzÞþÆæÖöZzÄäØøÅå"
    var arabicLetters = "ءي و ه ن م لك ق ف غ ع ظ ط ض ص ش س ز ر ذ د خ ح ج ث ت ب ا"
    var georgianLetters = "აბგდევზთიკლმნოპჟრსტუფქღყშჩცძწჭხჯჰ"
    var ethiopianLetters = "ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጸፀፈጰፐ"
    var thaanaLetters = "ހށނރބޅކއވމފދތލގސޑޒޓޔޕޖޗޘޙޚޛޜޝޞޟޠޡޢޣޤޥަ ީ   ު   ޫ   ެ   ޭ   ޮ   ޯޱ"
    var hanziLetters = "ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖ ゙ ゛゜ゝゞゟ"
    
    // input
    var length = document.getElementById('pwLength').value
    var latin = document.getElementById('latinChecked')
    var numbers = document.getElementById('numbersChecked')
    var signs = document.getElementById('signsChecked')
    var nordic = document.getElementById('nordicChecked')
    var cyrillic = document.getElementById('cyrillicChecked')
    var greek = document.getElementById('greekChecked')
    var armenian = document.getElementById('armenianChecked')
    var hangul = document.getElementById('hangulChecked')
    var arabic = document.getElementById('arabicChecked')
    var georgian = document.getElementById('georgianChecked')
    var ethiopian = document.getElementById('ethiopianChecked')
    var thaana = document.getElementById('thaanaChecked')
    var hanzi = document.getElementById('hanziChecked')
    
    // build source string
    if (arabic.checked) { base = base + arabicLetters; }
    if (latin.checked) { base = base + latinLetters; }
    if (signs.checked) { base = base + signsLetters; }
    if (greek.checked) { base = base + greekLetters; }
    if (numbers.checked) { base = base + numbersLetters; }
    if (armenian.checked) { base = base + armenianLetters; }
    if (hangul.checked) { base = base + hangulLetters; }
    if (numbers.checked) { base = base + numbersLetters; }
    if (nordic.checked) { base = base + nordicLetters; }
    if (cyrillic.checked) { base = base + cyrillicLetters; }
    if (numbers.checked) { base = base + numbersLetters; }
    if (georgian.checked) { base = base + georgianLetters; }
    if (ethiopian.checked) { base = base + ethiopianLetters; }
    if (thaana.checked) { base = base + thaanaLetters; }
    if (hanzi.checked) { base = base + hanziLetters; }   
    
    // fill result
    for (let i = 0; i < length; i++) {
        var rand = Math.floor(Math.random() * base.length);
        result = result + base.charAt(rand);
    }
       
    // write to html
    document.getElementById("productX").innerHTML = result;
}

Solution

  • You can use this process:

    • Select exactly one random character from each character set that was selected
    • Select the remaining needed characters from all available characters
    • Concatenate all of the above and shuffle randomly

    Here is an implementation:

    // Some utility functions on Math.random
    const randInt = end => Math.floor(Math.random() * end);
    const randomOf = (array, length=1) => Array.from({length}, () =>
        array[randInt(array.length)]
    );
    
    function shuffle(array) {
        for (let i = array.length - 1; i > 0; i--) {
            let j = randInt(i + 1);
            let temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
        return array;
    }
    
    const groups = {
        latin: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
        cyrillic: "БбВвГгДдЕеЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЬьЮюЯя",
        greek: "ΑαΒβΓγΔδΕεΖζΗηΘθΙιΚκΛλΜμΝνΞξΟοΠπΡρΣσςΤτΥυΦφΧχΨψΩω",
        numbers: "1234567890",
        signs: "/|()1{}[]?-_+~<>!I;:,^`.$@B%&WM*",
        armenian: "աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆուև",
        hangul: "ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎㅏㅑㅓㅕㅗㅛㅜㅠㅡㅣ",
        nordic: "AaÁáBbCcDdÐðEeÉéFfGgHhIiÍíJjKkLlMmNnOoÓóPpRrSsTtUuÚúVvWwXxYyÝýZzÞþÆæÖöZzÄäØøÅå",
        arabic: "ءي و ه ن م لك ق ف غ ع ظ ط ض ص ش س ز ر ذ د خ ح ج ث ت ب ا",
        georgian: "აბგდევზთიკლმნოპჟრსტუფქღყშჩცძწჭხჯჰ",
        ethiopian: "ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጸፀፈጰፐ",
        thaana: "ހށނރބޅކއވމފދތލގސޑޒޓޔޕޖޗޘޙޚޛޜޝޞޟޠޡޢޣޤޥަ ީ   ު   ޫ   ެ   ޭ   ޮ   ޯޱ",
        hanzi: "ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖ ゙ ゛゜ゝゞゟ",
    };
    
    function generatePassword(length, charGroups) {
        if (charGroups.length > length) {
            throw "Required length incompatible with number of character groups";
        }
        // Select one from each set, and take the rest from any in those sets. Then shuffle.
        return shuffle([
            ...charGroups.flatMap(group => randomOf(groups[group])),
            ...randomOf(charGroups.map(group => groups[group]).join(""), length - charGroups.length)
         ]).join("");
    }
    
    // I/O handing:
    
    function clickHandler() {
        // Get inputs
        const length = +document.getElementById('pwLength').value;
        const selected = Object.keys(groups).filter(group => document.getElementById(group + "Checked").checked);
        // Apply random generation based on these parameters
        const result = generatePassword(length, selected);
        // Output:
        document.getElementById("productX").textContent = result;
    }
    
    document.getElementById("generate").addEventListener("click", clickHandler);
    #pwLength { width: 4em }
    #productX { font-family: monospace }
    Length: <input id="pwLength" type="number" value="16">
    <table><tr>
        <td><input id="latinChecked" type="checkbox" checked>Latin</td>
        <td><input id="cyrillicChecked" type="checkbox">Cyrillic</td>
        <td><input id="greekChecked" type="checkbox">Greek</td>
        <td><input id="numbersChecked" type="checkbox" checked>Numbers</td>
    </tr><tr>
        <td><input id="signsChecked" type="checkbox" checked>Signs</td>
        <td><input id="armenianChecked" type="checkbox">Armenian</td>
        <td><input id="hangulChecked" type="checkbox">Hangul</td>
    </tr><tr>
        <td><input id="nordicChecked" type="checkbox">Nordic</td>
        <td><input id="arabicChecked" type="checkbox">Arabic</td>
        <td><input id="georgianChecked" type="checkbox">Georgian</td>
    </tr><tr>
        <td><input id="ethiopianChecked" type="checkbox">Etheopian</td>
        <td><input id="thaanaChecked" type="checkbox">Thaana</td>
        <td><input id="hanziChecked" type="checkbox">Hanzi</td>
    </tr></table>
    <button id="generate">Generate password</button><br>
    Password: <span id="productX"></span>