Merge branch 'main' into pr/SandipGyawali/397

This commit is contained in:
Chesterkxng
2026-07-14 17:26:17 +02:00
13 changed files with 90 additions and 41 deletions
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "Zahlen einschließen (0-9)",
"includeSymbols": "Sonderzeichen einschließen",
"includeUppercase": "Großbuchstaben einschließen (A-Z)",
"lengthDesc": "Länge des Passwortes",
"lengthDesc": "Länge des Passwortes (>3 & <257)",
"lengthPlaceholder": "z.B. 12",
"optionsTitle": "Passwortoptionen",
"resultTitle": "Generiertes Passwort",
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "Include numbers (0-9)",
"includeSymbols": "Include special characters",
"includeUppercase": "Include uppercase letters (A-Z)",
"lengthDesc": "Length of the password",
"lengthDesc": "Length of the password (>3 & <257)",
"lengthPlaceholder": "e.g. 12",
"optionsTitle": "Password Options",
"resultTitle": "Generated Password",
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "Incluir números (0-9)",
"includeSymbols": "Incluir caracteres especiales",
"includeUppercase": "Incluir letras mayúsculas (A-Z)",
"lengthDesc": "Longitud de la contraseña",
"lengthDesc": "Longitud de la contraseña (>3 & <257)",
"lengthPlaceholder": "p. ej. 12",
"optionsTitle": "Opciones de contraseña",
"resultTitle": "Contraseña generada",
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "Inclure des chiffres (0-9)",
"includeSymbols": "Inclure des caractères spéciaux",
"includeUppercase": "Inclure des lettres majuscules (A-Z)",
"lengthDesc": "Longueur du mot de passe",
"lengthDesc": "Longueur du mot de passe (>3 & <257)",
"lengthPlaceholder": "par exemple 12",
"optionsTitle": "Options de mot de passe",
"resultTitle": "Mot de passe généré",
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "संख्याएँ शामिल करें (0-9)",
"includeSymbols": "विशेष वर्ण शामिल करें",
"includeUppercase": "बड़े अक्षर (A-Z) शामिल करें",
"lengthDesc": "पासवर्ड की लंबाई",
"lengthDesc": "पासवर्ड की लंबाई (>3 & <257)",
"lengthPlaceholder": "उदाहरणार्थ 12",
"optionsTitle": "पासवर्ड विकल्प",
"resultTitle": "जनरेट किया गया पासवर्ड",
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "数字を含める(0~9)",
"includeSymbols": "特殊文字を含める",
"includeUppercase": "大文字(A~Z)を含める",
"lengthDesc": "パスワードの長さ",
"lengthDesc": "パスワードの長さ (>3 & <257)",
"lengthPlaceholder": "例:12",
"optionsTitle": "パスワードオプション",
"resultTitle": "生成されたパスワード",
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "Inclusief cijfers (0-9)",
"includeSymbols": "Speciale tekens toevoegen",
"includeUppercase": "Inclusief hoofdletters (A-Z)",
"lengthDesc": "Lengte van het wachtwoord",
"lengthDesc": "Lengte van het wachtwoord (>3 & <257)",
"lengthPlaceholder": "bijv. 12",
"optionsTitle": "Wachtwoordopties",
"resultTitle": "gegenereerd wachtwoord",
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "Incluir números (0-9)",
"includeSymbols": "Incluir caracteres especiais",
"includeUppercase": "Incluir letras maiúsculas (A-Z)",
"lengthDesc": "Comprimento da senha",
"lengthDesc": "Comprimento da senha (>3 & <257)",
"lengthPlaceholder": "por exemplo 12",
"optionsTitle": "Opções de senha",
"resultTitle": "Senha gerada",
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "Включить цифры (0-9)",
"includeSymbols": "Включить специальные символы",
"includeUppercase": "Включайте заглавные буквы (A–Z)",
"lengthDesc": "Длина пароля",
"lengthDesc": "Длина пароля (>3 & <257)",
"lengthPlaceholder": "например, 12",
"optionsTitle": "Параметры пароля",
"resultTitle": "Сгенерированный пароль",
+1 -1
View File
@@ -84,7 +84,7 @@
"includeNumbers": "包含数字 (0-9)",
"includeSymbols": "包含特殊字符",
"includeUppercase": "包含大写字母(A-Z)",
"lengthDesc": "密码长度",
"lengthDesc": "密码长度 (>3 & <257)",
"lengthPlaceholder": "例如 12",
"optionsTitle": "密码选项",
"resultTitle": "生成的密码",
@@ -77,8 +77,26 @@ export default function PasswordGenerator({ title }: ToolComponentProps) {
description={t('passwordGenerator.lengthDesc')}
placeholder={t('passwordGenerator.lengthPlaceholder')}
value={values.length}
onOwnChange={(val) => updateField('length', val)}
onOwnChange={(val) => {
const length = Number(val);
if (length > 256) {
updateField('length', '256');
return;
}
if (length < 4 && val !== '') {
updateField('length', '4');
return;
}
updateField('length', val);
}}
type="number"
inputProps={{
min: 4,
max: 256
}}
/>
<FormGroup>
@@ -129,12 +129,6 @@ describe('generatePassword', () => {
expect(result).toHaveLength(50);
});
it('should handle edge case with very short length', () => {
const options = { ...initialValues, length: '1' };
const result = generatePassword(options);
expect(result).toHaveLength(1);
});
it('should handle negative length', () => {
const options = { ...initialValues, length: '-5' };
const result = generatePassword(options);
@@ -1,38 +1,75 @@
import type { InitialValuesType } from './initialValues';
const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
const UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const NUMBERS = '0123456789';
const SYMBOLS = '!@#$%^&*()_+~`|}{[]:;?><,./-=';
const AMBIGUOUS = new Set(['i', 'I', 'l', '0', 'O']);
const MAX_PASSWORD_LENGTH = 256;
function randomChar(chars: string): string {
const array = new Uint32Array(1);
globalThis.crypto.getRandomValues(array);
return chars[array[0] % chars.length];
}
function shuffle(array: string[]): void {
for (let i = array.length - 1; i > 0; i--) {
const random = new Uint32Array(1);
globalThis.crypto.getRandomValues(random);
const j = random[0] % (i + 1);
[array[i], array[j]] = [array[j], array[i]];
}
}
function removeAmbiguous(chars: string): string {
return [...chars].filter((char) => !AMBIGUOUS.has(char)).join('');
}
export function generatePassword(options: InitialValuesType): string {
const length = parseInt(options.length || '', 10);
if (isNaN(length) || length <= 0) {
const length = Number.parseInt(options.length || '', 10);
if (Number.isNaN(length) || length <= 0 || length > MAX_PASSWORD_LENGTH) {
return '';
}
let charset = '';
const lower = 'abcdefghijklmnopqrstuvwxyz';
const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const numbers = '0123456789';
const symbols = '!@#$%^&*()_+~`|}{[]:;?><,./-=';
if (options.includeLowercase) charset += lower;
if (options.includeUppercase) charset += upper;
if (options.includeNumbers) charset += numbers;
if (options.includeSymbols) charset += symbols;
let pools = [
options.includeLowercase ? LOWERCASE : '',
options.includeUppercase ? UPPERCASE : '',
options.includeNumbers ? NUMBERS : '',
options.includeSymbols ? SYMBOLS : ''
].filter(Boolean);
if (options.avoidAmbiguous) {
// ambiguous set = i, I, l, 0, O
const ambig = new Set(['i', 'I', 'l', '0', 'O']);
charset = Array.from(charset)
.filter((c) => !ambig.has(c))
.join('');
pools = pools.map(removeAmbiguous);
}
if (!charset) {
return ''; // nothing to pick from
// Remove empty pools after filtering
pools = pools.filter(Boolean);
if (pools.length === 0 || length < pools.length) {
return '';
}
let pwd = '';
for (let i = 0; i < length; i++) {
const idx = Math.floor(Math.random() * charset.length);
pwd += charset[idx];
const password: string[] = [];
// Guarantee each selected category appears once
for (const pool of pools) {
password.push(randomChar(pool));
}
return pwd;
const charset = pools.join('');
while (password.length < length) {
password.push(randomChar(charset));
}
shuffle(password);
return password.join('');
}