/* Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend Copyright (C) 2024 Christian Fraß This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ namespace _espe.helper.password { /** */ type char = string; /** */ type type_settings = { minimum_length : (null | int); maximum_length : (null | int); must_contain_letter : boolean; must_contain_number : boolean; must_contain_special_character : boolean; }; /** */ enum enum_character_class { number_ = "number", letter = "letter", special = "special", } /** */ const character_classification : Record> = { [enum_character_class.number_]: [ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, ], [enum_character_class.letter]: [ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x56, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, ], [enum_character_class.special]: [ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x7B, 0x7C, 0x7D, 0x7E, ], }; /** */ function character_is_number( character : char ) : boolean { if (character.length !== 1) { throw (new Error("not a character")); } else { const code : int = character.charCodeAt(0); return character_classification[enum_character_class.number_].includes(code); } } /** */ function character_is_letter( character : char ) : boolean { if (character.length !== 1) { throw (new Error("not a character")); } else { const code : int = character.charCodeAt(0); return character_classification[enum_character_class.letter].includes(code); } } /** */ function character_is_special( character : char ) : boolean { if (character.length !== 1) { throw (new Error("not a character")); } else { const code : int = character.charCodeAt(0); return character_classification[enum_character_class.special].includes(code); } } /** */ export function validate( settings : type_settings, password : string ) : Array<{incident : string; details : Record}> { let flaws : Array<{incident : string; details : Record}> = []; const characters : Array = password.split(""); if ( (settings.minimum_length !== null) && (password.length < settings.minimum_length) ) { flaws.push( { "incident": "too_short", "details": { "minimum_length": settings.minimum_length, "actual_length": password.length, } } ); } if ( (settings.maximum_length !== null) && (password.length > settings.maximum_length) ) { flaws.push( { "incident": "too_long", "details": { "maximum_length": settings.maximum_length, "actual_length": password.length, } } ); } if ( settings.must_contain_letter && (! characters.some(character => character_is_letter(character))) ) { flaws.push( { "incident": "lacks_letter", "details": { } } ); } if ( settings.must_contain_number && (! characters.some(character => character_is_number(character))) ) { flaws.push( { "incident": "lacks_number", "details": { } } ); } if ( settings.must_contain_special_character && (! characters.some(character => character_is_special(character))) ) { flaws.push( { "incident": "lacks_special_character", "details": { } } ); } return flaws; } /** */ export function generate( settings : type_settings ) : string { const count_number : int = (settings.must_contain_number ? 1 : 0); const count_special : int = (settings.must_contain_special_character ? 1 : 0); const count_letter_raw : int = ((settings.minimum_length ?? 8) - count_number - count_special); const count_letter : int = ((settings.must_contain_letter && (count_letter_raw <= 0)) ? 1 : count_letter_raw); if ( (settings.maximum_length !== null) && ((count_letter + count_number + count_special) > settings.maximum_length) ) { throw (new Error("impossible")); } else { return lib_plankton.call.convey( ( ([] as Array) .concat( lib_plankton.list.sequence(count_letter) .map(x => lib_plankton.random.choose_uniformly(character_classification[enum_character_class.letter])) ) .concat( lib_plankton.list.sequence(count_number) .map(x => lib_plankton.random.choose_uniformly(character_classification[enum_character_class.number_])) ) .concat( lib_plankton.list.sequence(count_special) .map(x => lib_plankton.random.choose_uniformly(character_classification[enum_character_class.special])) ) ), [ lib_plankton.random.shuffle, (x : Array) => x.map(y => String.fromCharCode(y)), (x : Array) => x.join(""), ] ); } } }