1
0
mirror of https://github.com/XFox111/PasswordGeneratorExtension.git synced 2026-04-22 08:08:01 +03:00

Major 5.0 (#469)

* Advanced generator, UI overhaul (#449)

* Major overhaul:
- Added advanced generator
- Removed "Insert and copy" feature
- Moved settings to extension options
- General refactoring

* Updated custom character options for default generator (#447)

* Added state save for advanced generator mode

* Fixed state save for advanced password generator

* Updated extension description

* Minor UI fixes:
- Fixed Options UI not displaying in Google Chrome
- Fixed Quick Options menus overflowing on some locales
- Fixed Advanced generator configuration UI clipping when window height is too small
- Fixed divider in Options UI taking up all available space

* Minor UI/UX changes and fixes:
- Fixed locale in Advanced generator toast notifications
- Added toast notification for copying a single password in Advanced generator
- Moved custom characters input lables to placeholders
- Fixed minor type issues
- Removed duplicate "About" text
- Fixed input fields alignment in Options
- Added "disabled" state for "Include Custom" option in Quick settings

* Bump @typescript-eslint/parser from 8.16.0 to 8.19.1 (#468)

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.16.0 to 8.19.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.19.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @eslint/js from 9.16.0 to 9.18.0 (#467)

Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.16.0 to 9.18.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.18.0/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @fluentui/react-components from 9.56.3 to 9.57.0 (#466)

Bumps [@fluentui/react-components](https://github.com/microsoft/fluentui) from 9.56.3 to 9.57.0.
- [Release notes](https://github.com/microsoft/fluentui/releases)
- [Changelog](https://github.com/microsoft/fluentui/blob/master/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/fluentui/compare/@fluentui/react-components_v9.56.3...@fluentui/react-components_v9.57.0)

---
updated-dependencies:
- dependency-name: "@fluentui/react-components"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump wxt from 0.19.17 to 0.19.24 (#465)

Bumps [wxt](https://github.com/wxt-dev/wxt) from 0.19.17 to 0.19.24.
- [Release notes](https://github.com/wxt-dev/wxt/releases)
- [Commits](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.17...wxt-v0.19.24)

---
updated-dependencies:
- dependency-name: wxt
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump eslint from 9.16.0 to 9.18.0 (#464)

Bumps [eslint](https://github.com/eslint/eslint) from 9.16.0 to 9.18.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.16.0...v9.18.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump eslint-plugin-react from 7.37.2 to 7.37.4 (#463)

Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.37.2 to 7.37.4.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.37.2...v7.37.4)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eugene Fox <eugene.xfox@outlook.com>

* Bump typescript from 5.7.2 to 5.7.3 (#462)

Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.7.2 to 5.7.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.2...v5.7.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eugene Fox <eugene.xfox@outlook.com>

* Bump @typescript-eslint/eslint-plugin from 8.16.0 to 8.19.1 (#461)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.16.0 to 8.19.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.19.1/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump wdzeng/chrome-extension from 1.2.4 to 1.3.0 (#450)

* Bump wdzeng/chrome-extension from 1.2.4 to 1.3.0

Bumps [wdzeng/chrome-extension](https://github.com/wdzeng/chrome-extension) from 1.2.4 to 1.3.0.
- [Release notes](https://github.com/wdzeng/chrome-extension/releases)
- [Commits](https://github.com/wdzeng/chrome-extension/compare/v1.2.4...v1.3.0)

---
updated-dependencies:
- dependency-name: wdzeng/chrome-extension
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Optimized CD workflow

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eugene Fox <eugene@xfox111.net>

* Bump @fluentui/react-icons from 2.0.266 to 2.0.270 (#458)

Bumps [@fluentui/react-icons](https://github.com/microsoft/fluentui-system-icons) from 2.0.266 to 2.0.270.
- [Changelog](https://github.com/microsoft/fluentui-system-icons/blob/main/fluentui-android-system-icons-release.yml)
- [Commits](https://github.com/microsoft/fluentui-system-icons/commits)

---
updated-dependencies:
- dependency-name: "@fluentui/react-icons"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @wxt-dev/module-react from 1.1.2 to 1.1.3 (#455)

Bumps [@wxt-dev/module-react](https://github.com/wxt-dev/wxt/tree/HEAD/packages/module-react) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/wxt-dev/wxt/releases)
- [Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/module-react/CHANGELOG.md)
- [Commits](https://github.com/wxt-dev/wxt/commits/module-react-v1.1.3/packages/module-react)

---
updated-dependencies:
- dependency-name: "@wxt-dev/module-react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump globals from 15.12.0 to 15.14.0 (#452)

Bumps [globals](https://github.com/sindresorhus/globals) from 15.12.0 to 15.14.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.12.0...v15.14.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Eugene Fox
2025-01-13 14:08:31 +03:00
committed by GitHub
parent c0cab1c0d1
commit 8126886fb5
62 changed files with 53646 additions and 2560 deletions
-128
View File
@@ -1,128 +0,0 @@
import { GeneratorOptions } from "./storage";
const Characters =
{
Uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
Lowercase: "abcdefghijklmnopqrstuvwxyz",
Numeric: "1234567890",
Special: "!#$%&*+-=?@^_"
};
const Similar: string = "iIl1Lo0O";
const Ambiguous: string = "{}[]()/\\'\"`~,;:.<>";
export const CharacterHints = { ...Characters, Similar, Ambiguous };
/**
* Generates a random password
* @param options Options for password generation
* @returns Randomly generated password
* @throws Error if options are invalid
*/
export function GeneratePassword(options: GeneratorOptions): string
{
ValidateOptions(options);
let password: string = GetRequiredCharacters(options);
const availableCharacters: string = GetAvailableCharacters(options);
for (let i = password.length; i < options.Length; i++)
{
const character: string = PickRandomFromArray(availableCharacters);
if (options.ExcludeRepeating && password.includes(character))
i--;
else
password += character;
}
password = ShuffleString(password);
return password;
}
/**
* Validates options for password generation
* @param options Options for password generation
* @throws Error if options are invalid
*/
export function ValidateOptions(options: GeneratorOptions): void
{
if (options.Length < 4)
throw new Error(i18n.t("errors.too_short"));
const availableCharacters: string = GetAvailableCharacters(options);
if (availableCharacters.length < 1)
throw new Error(i18n.t("errors.no_characters"));
if (options.ExcludeRepeating && options.Length > availableCharacters.length)
throw new Error(i18n.t("errors.too_long"));
}
// Returns a string containing all characters that are available for password generation
function GetAvailableCharacters(options: GeneratorOptions): string
{
let availableCharacters: string = "";
for (const [key, value] of Object.entries(Characters))
if (options[key as keyof GeneratorOptions])
availableCharacters += value;
if (options.ExcludeSimilar)
availableCharacters = availableCharacters.replace(new RegExp(`[${Similar}]`, "g"), "");
if (options.Special && !options.ExcludeAmbiguous)
availableCharacters += Ambiguous;
return availableCharacters;
}
// Returns a string containing all characters from every available set that are required for password generation
function GetRequiredCharacters(options: GeneratorOptions): string
{
let result: string = "";
const characters: Record<string, string> = Object.assign({}, Characters);
if (!options.ExcludeAmbiguous)
characters.Special += Ambiguous;
if (options.ExcludeSimilar)
for (const key of Object.keys(characters))
characters[key] = characters[key].replace(new RegExp(`[${Similar}]`, "g"), "");
for (const [key, value] of Object.entries(characters))
if (options[key as keyof GeneratorOptions])
result += PickRandomFromArray(value);
return result;
}
// Picks a random character from a string
function PickRandomFromArray(array: string): string
{
return array[GetRandomInt(0, array.length)];
}
// See https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/random
// min is inclusive, max is exclusive
function GetRandomInt(min: number, max: number): number
{
const arr = new Uint16Array(1);
crypto.getRandomValues(arr); // Using crypto instead of Math.random() as a CSPRNG
return Math.floor((arr[0] / 65_536) * (max - min)) + min;
}
// Shuffles a string using Fisher-Yates algorithm and CSPRNG
// See https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
function ShuffleString(str: string): string
{
const arr = str.split("");
for (let i = arr.length - 1; i > 0; i--)
{
const j = GetRandomInt(0, i + 1);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr.join("");
}
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
// Based on ealamiLabs - Password generator (https://github.com/ealamiLabs/password-generator)
// licensed under MIT
import dictionary from "./dictionary.json";
import { getBooleanSequence, getRandomInt } from "./randomUtils";
/* MIT License
*
* Copyright (c) 2024 ealamiLabs
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
export default function generatePassphrase(options: PassphraseProps): string
{
const words: string[] = [];
for (let i = 0; i < options.wordCount; i++)
{
const word: string = dictionary[getRandomInt(0, dictionary.length)].word;
if (!options.allowRepeating && words.includes(word))
i--;
else
words.push(word);
}
let result: string = words.join(options.separator).toLocaleLowerCase();
console.log(result);
if (options.swapCharacters)
result = swapCharacters(result);
if (options.randomizeCase)
result = RandomUpperCase(result);
console.log(result);
return result;
}
function RandomUpperCase(passphrase: string): string
{
const sequence: boolean[] = getBooleanSequence(passphrase.length);
let result: string = "";
for (let i = 0; i < passphrase.length; i++)
result += sequence[i] ? passphrase[i].toLocaleUpperCase() : passphrase[i];
return result;
}
function swapCharacters(passphrase: string): string
{
const sequence: boolean[] = getBooleanSequence(passphrase.length);
let result: string = "";
for (let i = 0; i < passphrase.length; i++)
if (sequence[i])
switch (passphrase[i].toLocaleLowerCase())
{
case "a":
result += getRandomInt(0, 100) < 50 ? "@" : "4";
break;
case "e":
result += "3";
break;
case "i":
result += "!";
break;
case "s":
result += getRandomInt(0, 100) < 50 ? "$" : "5";
break;
default:
result += passphrase[i];
break;
}
else
result += passphrase[i];
return result;
}
export type PassphraseProps =
{
wordCount: number;
swapCharacters: boolean;
randomizeCase: boolean;
allowRepeating: boolean;
separator: string;
};
+142
View File
@@ -0,0 +1,142 @@
import { pickRandomFromArray, shuffleString } from "./randomUtils";
const Characters =
{
uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
lowercase: "abcdefghijklmnopqrstuvwxyz",
numeric: "1234567890",
special: "!#$%&*+-=?@^_"
};
const similar: string = "iIl1Lo0O";
const ambiguous: string = "{}[]()/\\'\"`~,;:.<>";
export const CharacterHints = { ...Characters, similar, ambiguous };
/**
* Generates a random password
* @param options Options for password generation
* @returns Randomly generated password
* @throws Error if options are invalid
*/
export function generatePassword(options: PasswordProps): string
{
validateOptions(options);
let password: string = getRequiredCharacters(options);
const availableCharacters: string = getAvailableCharacters(options);
for (let i = password.length; i < options.length; i++)
{
const character: string = pickRandomFromArray(availableCharacters);
if (options.excludeRepeating && password.includes(character))
i--;
else
password += character;
}
password = shuffleString(password);
return password;
}
/**
* Validates options for password generation
* @param options Options for password generation
* @throws Error if options are invalid
*/
export function validateOptions(options: PasswordProps): void
{
if (options.length < 4)
throw new Error(i18n.t("errors.too_short"));
const availableCharacters: string = getAvailableCharacters(options);
if (availableCharacters.length < 1)
throw new Error(i18n.t("errors.no_characters"));
if (options.excludeRepeating && options.length > availableCharacters.length)
throw new Error(i18n.t("errors.too_long"));
}
// Returns a string containing all characters that are available for password generation
function getAvailableCharacters(options: PasswordProps): string
{
let availableCharacters: string = "";
for (const [key, value] of Object.entries(Characters))
if (options[key as keyof PasswordProps])
availableCharacters += value;
if (options.custom && options.customSet.length > 0)
availableCharacters += options.customSet;
if (options.excludeSimilar)
availableCharacters = availableCharacters.replace(new RegExp(`[${similar}]`, "g"), "");
if (options.special && !options.excludeAmbiguous)
availableCharacters += ambiguous;
if (options.excludeCustom.length > 0)
availableCharacters = availableCharacters.replace(new RegExp(`[${options.excludeCustom}]`, "g"), "");
return availableCharacters;
}
// Returns a string containing all characters from every available set that are required for password generation
function getRequiredCharacters(options: PasswordProps): string
{
let result: string = "";
const characters: Record<string, string> = Object.assign({}, Characters);
if (!options.excludeAmbiguous)
characters.special += ambiguous;
if (options.custom && options.customSet.length > 0)
characters.custom = options.customSet;
for (const key of Object.keys(characters))
{
if (options.excludeSimilar)
characters[key] = characters[key].replace(new RegExp(`[${similar}]`, "g"), "");
if (options.excludeCustom.length > 0)
characters[key] = characters[key].replace(new RegExp(`[${options.excludeCustom}]`, "g"), "");
}
for (const [key, value] of Object.entries(characters))
if (options[key as keyof PasswordProps])
for (let i = 0; i < (options[key as keyof PasswordProps] as number); i++)
{
if (value.length < 1)
continue;
const char = pickRandomFromArray(value);
if (options.excludeRepeating && result.includes(char))
i--;
else
result += char;
}
return result;
}
export type PasswordProps =
{
length: number;
special: boolean | number;
numeric: boolean | number;
lowercase: boolean | number;
uppercase: boolean | number;
custom: boolean | number;
customSet: string;
excludeSimilar: boolean;
excludeAmbiguous: boolean;
excludeRepeating: boolean;
excludeCustom: string;
};
+46
View File
@@ -0,0 +1,46 @@
// Picks a random character from a string
export function pickRandomFromArray(array: string): string
{
return array[getRandomInt(0, array.length)];
}
// See https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/random
// min is inclusive, max is exclusive
export function getRandomInt(min: number, max: number): number
{
const arr = new Uint16Array(1);
crypto.getRandomValues(arr); // Using crypto instead of Math.random() as a CSPRNG
return Math.floor((arr[0] / 65_536) * (max - min)) + min;
}
// Shuffles a string using Fisher-Yates algorithm and CSPRNG
// See https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
export function shuffleString(str: string): string
{
const arr = str.split("");
for (let i = arr.length - 1; i > 0; i--)
{
const j = getRandomInt(0, i + 1);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr.join("");
}
export function getBooleanSequence(length: number): boolean[]
{
const arr = new Uint8Array(Math.ceil(length / 8));
crypto.getRandomValues(arr);
const result: boolean[] = [];
for (let i = 0; i < length; i++)
{
const byte = arr[Math.floor(i / 8)];
const bit = byte & (1 << (i % 8));
result.push(bit !== 0);
}
return result;
}
+15
View File
@@ -0,0 +1,15 @@
import { InfoLabel, Label, LabelProps, Slot } from "@fluentui/react-components";
// FIXME: Remove ts-ignore comments once slots override fix is released
// Tracker: https://github.com/microsoft/fluentui/issues/27090
export default function infoLabel(label: string, hint: string): Slot<typeof Label>
{
// @ts-expect-error See FIXME
return {
children: (_: unknown, props: LabelProps) =>
<InfoLabel { ...props } info={ hint }>
{ label }
</InfoLabel>
};
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { Manifest } from "webextension-polyfill";
export const personalLinks =
{
website: "https://xfox111.net",
twitter: "https://twitter.com/xfox111",
social: "https://bsky.app/profile/xfox111.net",
donation: "https://buymeacoffee.com/xfox111"
};
-1
View File
@@ -2,5 +2,4 @@ export default class ExtensionOptions
{
public MinLength: number = 4;
public MaxLength: number = 32;
public ContextMenu: boolean = true;
}
+5 -1
View File
@@ -6,9 +6,13 @@ export default class GeneratorOptions
public Numeric: boolean = true;
public Lowercase: boolean = true;
public Uppercase: boolean = true;
public Custom: boolean = false;
public ExcludeSimilar: boolean = true;
public ExcludeAmbiguous: boolean = true;
public ExcludeRepeating: boolean = false;
public ExcludeCustom: boolean = false;
public ExcludeCustomSet: string = "";
public IncludeCustomSet: string = "";
}