325 lines
5.9 KiB
TypeScript
325 lines
5.9 KiB
TypeScript
/*
|
|
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
|
|
<https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
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, Array<int>> = {
|
|
[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<string, any>}>
|
|
{
|
|
let flaws : Array<{incident : string; details : Record<string, any>}> = [];
|
|
const characters : Array<char> = 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<int>)
|
|
.concat(
|
|
lib_plankton.list.sequence(count_letter)
|
|
.map(x => lib_plankton.random.choose_uniformly<int>(character_classification[enum_character_class.letter]))
|
|
)
|
|
.concat(
|
|
lib_plankton.list.sequence(count_number)
|
|
.map(x => lib_plankton.random.choose_uniformly<int>(character_classification[enum_character_class.number_]))
|
|
)
|
|
.concat(
|
|
lib_plankton.list.sequence(count_special)
|
|
.map(x => lib_plankton.random.choose_uniformly<int>(character_classification[enum_character_class.special]))
|
|
)
|
|
),
|
|
[
|
|
lib_plankton.random.shuffle<int>,
|
|
(x : Array<int>) => x.map(y => String.fromCharCode(y)),
|
|
(x : Array<string>) => x.join(""),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
}
|