mirror of
https://github.com/XFox111/PasswordGeneratorExtension.git
synced 2026-04-22 08:08:01 +03:00
a4de3139bf
* Force no-wrap for special characters tooltip (resolves #551) (#555) * Increase width of password count field in advanced generator (resolves #548) (#554) * Added password length limit in advanced generator (#552) * Added password length limit in advanced generator (resolves #547) * Add radix to parseInt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove unnecessary callback dependency Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * npm audit fix (#556) * Added length slider range limits (#557) * Added length slider range limits (resolves #546) * Fix validateMaxLimit logic Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Added password length update on input field blur (resolves #549) (#559) * Separator option for advanced password generator (#558) * Added separator option for advanced password generator (resolves #545) * Fix typos * Fixed merge typo * Update package.json * Bump @fluentui/react-components from 9.64.0 to 9.66.5 (#561) Bumps [@fluentui/react-components](https://github.com/microsoft/fluentui) from 9.64.0 to 9.66.5. - [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.64.0...@fluentui/react-components_v9.66.5) --- updated-dependencies: - dependency-name: "@fluentui/react-components" dependency-version: 9.66.5 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 @typescript-eslint/parser from 8.33.1 to 8.35.1 (#562) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.33.1 to 8.35.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.35.1/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-version: 8.35.1 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.28.0 to 9.30.0 (#564) Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.28.0 to 9.30.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.30.0/packages/js) --- updated-dependencies: - dependency-name: "@eslint/js" dependency-version: 9.30.0 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 from 9.28.0 to 9.30.0 (#566) Bumps [eslint](https://github.com/eslint/eslint) from 9.28.0 to 9.30.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.28.0...v9.30.0) --- updated-dependencies: - dependency-name: eslint dependency-version: 9.30.0 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 @typescript-eslint/eslint-plugin from 8.33.1 to 8.35.1 (#563) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.33.1 to 8.35.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.35.1/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.35.1 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-icons from 2.0.302 to 2.0.305 (#565) Bumps [@fluentui/react-icons](https://github.com/microsoft/fluentui-system-icons) from 2.0.302 to 2.0.305. - [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-version: 2.0.305 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> * Moved default password lengths to constants (#553) * Added radix 10 for parseInt (#553) * Removed ts-expect-error microsoft/fluentui#27090 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
260 lines
9.1 KiB
TypeScript
260 lines
9.1 KiB
TypeScript
import { CharacterHints, generatePassword } from "@/utils/generators/generatePassword";
|
|
import infoLabel from "@/utils/infoLabel";
|
|
import * as fui from "@fluentui/react-components";
|
|
import { ReactElement } from "react";
|
|
import { GeneratorProps } from "../Page";
|
|
import GeneratorForm from "../components/GeneratorForm";
|
|
import { DEFAULT_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH } from "@/utils/constants";
|
|
|
|
// TODO: needs refactoring
|
|
export default function PasswordSection(props: GeneratorProps): ReactElement
|
|
{
|
|
const [state, private_setState] = useState<PasswordSectionState>({
|
|
length: DEFAULT_PASSWORD_LENGTH,
|
|
enableUppercase: true, uppercaseCount: 1,
|
|
enableLowercase: true, lowercaseCount: 1,
|
|
enableNumeric: true, numericCount: 1,
|
|
enableSpecial: true, specialCount: 1,
|
|
enableCustom: false, customCount: 1, customSet: "",
|
|
|
|
excludeSimilar: true,
|
|
excludeAmbiguous: true,
|
|
excludeRepeating: false,
|
|
excludeCustom: false, excludeCustomSet: "",
|
|
|
|
enableSeparator: false,
|
|
separator: "-",
|
|
separatorInterval: DEFAULT_PASSWORD_LENGTH / 2
|
|
});
|
|
|
|
const cls = useStyles();
|
|
|
|
const setState = useCallback((newState: Partial<PasswordSectionState>) =>
|
|
{
|
|
private_setState({ ...state, ...newState });
|
|
}, [state]);
|
|
|
|
const setLength = useCallback((_: any, e: fui.InputOnChangeData) =>
|
|
{
|
|
const n = parseInt(e.value ?? "", 10);
|
|
setState({ length: isNaN(n) || n < 1 ? null : Math.min(n, MAX_PASSWORD_LENGTH) });
|
|
}, [setState]);
|
|
|
|
const saveConfiguration = useCallback(
|
|
async () => await browser.storage.sync.set({ AdvancedPasswordOptions: state }),
|
|
[state]
|
|
);
|
|
|
|
const generate = useCallback((count: number) =>
|
|
{
|
|
const passwords: string[] = [];
|
|
|
|
for (let i = 0; i < count; i++)
|
|
passwords.push(generatePassword({
|
|
length: state.length ?? DEFAULT_PASSWORD_LENGTH,
|
|
custom: state.enableCustom ? state.customCount ?? 1 : 0,
|
|
customSet: state.customSet,
|
|
numeric: state.enableNumeric ? state.numericCount ?? 1 : 0,
|
|
special: state.enableSpecial ? state.specialCount ?? 1 : 0,
|
|
uppercase: state.enableUppercase ? state.uppercaseCount ?? 1 : 0,
|
|
lowercase: state.enableLowercase ? state.lowercaseCount ?? 1 : 0,
|
|
excludeAmbiguous: state.excludeAmbiguous,
|
|
excludeCustom: state.excludeCustom ? state.excludeCustomSet : "",
|
|
excludeRepeating: state.excludeRepeating,
|
|
excludeSimilar: state.excludeSimilar,
|
|
separator: state.enableSeparator ? state.separator : undefined,
|
|
separatorInterval: state.separatorInterval ?? (DEFAULT_PASSWORD_LENGTH / 2)
|
|
}));
|
|
|
|
props.onGenerated(passwords);
|
|
}, [state, props.onGenerated]);
|
|
|
|
useEffect(() =>
|
|
{
|
|
browser.storage.sync.get("AdvancedPasswordOptions").then(({ AdvancedPasswordOptions }) =>
|
|
private_setState({ ...state, ...AdvancedPasswordOptions as PasswordSectionState }));
|
|
}, []);
|
|
|
|
const checkboxControls = useCallback((key: keyof PasswordSectionState): Partial<fui.CheckboxProps> => ({
|
|
checked: state[key] as boolean,
|
|
onChange: (_, e) => setState({ [key]: e.checked as boolean })
|
|
}), [state]);
|
|
|
|
const minInputControls = useCallback((enabledKey: keyof PasswordSectionState, key: keyof PasswordSectionState): Partial<fui.InputProps> => ({
|
|
size: "small",
|
|
disabled: !state[enabledKey],
|
|
value: state[key]?.toString() ?? "",
|
|
onChange: (_, e) => setState({ [key]: parseCount(e.value) })
|
|
}), [state]);
|
|
|
|
const setSeparatorInterval = (_: any, e: fui.InputOnChangeData) =>
|
|
{
|
|
if (!e.value)
|
|
{
|
|
setState({ separatorInterval: undefined });
|
|
return;
|
|
}
|
|
|
|
const n = parseInt(e.value, 10);
|
|
|
|
if (!isNaN(n))
|
|
setState({ separatorInterval: n < 1 ? 1 : Math.min(n, state.length ?? DEFAULT_PASSWORD_LENGTH) });
|
|
};
|
|
|
|
const updateLength = (): void =>
|
|
{
|
|
const minLength = Math.max(MIN_PASSWORD_LENGTH,
|
|
(state.enableCustom ? state.customCount ?? 1 : 0) +
|
|
(state.enableNumeric ? state.numericCount ?? 1 : 0) +
|
|
(state.enableSpecial ? state.specialCount ?? 1 : 0) +
|
|
(state.enableUppercase ? state.uppercaseCount ?? 1 : 0) +
|
|
(state.enableLowercase ? state.lowercaseCount ?? 1 : 0)
|
|
);
|
|
|
|
if (!state.length || state.length < minLength)
|
|
setState({ length: minLength });
|
|
};
|
|
|
|
return (
|
|
<GeneratorForm onGenerate={ generate } onSave={ saveConfiguration }>
|
|
<fui.Field label={ i18n.t("advanced.password.length") }>
|
|
<fui.Input value={ state.length?.toString() ?? "" } onChange={ setLength } onBlur={ updateLength } />
|
|
</fui.Field>
|
|
<fui.Table size="small" as="div">
|
|
<fui.TableHeader as="div">
|
|
<fui.TableRow as="div">
|
|
<fui.TableHeaderCell as="div">{ i18n.t("common.sections.include") }</fui.TableHeaderCell>
|
|
<fui.TableHeaderCell as="div">{ i18n.t("advanced.password.min_of_type") }</fui.TableHeaderCell>
|
|
</fui.TableRow>
|
|
</fui.TableHeader>
|
|
<fui.TableBody as="div">
|
|
<Row>
|
|
<fui.Checkbox label={ i18n.t("common.characters.uppercase") } { ...checkboxControls("enableUppercase") } />
|
|
<fui.Input { ...minInputControls("enableUppercase", "uppercaseCount") } onBlur={ updateLength } />
|
|
</Row>
|
|
<Row>
|
|
<fui.Checkbox label={ i18n.t("common.characters.lowercase") } { ...checkboxControls("enableLowercase") } />
|
|
<fui.Input { ...minInputControls("enableLowercase", "lowercaseCount") } onBlur={ updateLength } />
|
|
</Row>
|
|
<Row>
|
|
<fui.Checkbox label={ i18n.t("common.characters.numeric") } { ...checkboxControls("enableNumeric") } />
|
|
<fui.Input { ...minInputControls("enableNumeric", "numericCount") } onBlur={ updateLength } />
|
|
</Row>
|
|
<Row>
|
|
<fui.Checkbox label={ infoLabel(i18n.t("common.characters.special"), CharacterHints.special, true) } { ...checkboxControls("enableSpecial") } />
|
|
<fui.Input { ...minInputControls("enableSpecial", "specialCount") } onBlur={ updateLength } />
|
|
</Row>
|
|
<Row>
|
|
<>
|
|
<fui.Checkbox { ...checkboxControls("enableCustom") } />
|
|
<fui.Input size="small" disabled={ !state.enableCustom }
|
|
placeholder={ i18n.t("common.characters.custom") }
|
|
value={ state.customSet } onChange={ (_, e) => setState({ customSet: e.value }) } />
|
|
</>
|
|
<fui.Input { ...minInputControls("enableCustom", "customCount") } onBlur={ updateLength } />
|
|
</Row>
|
|
</fui.TableBody>
|
|
</fui.Table>
|
|
|
|
<section className={ cls.section }>
|
|
<fui.Text>{ i18n.t("common.sections.exclude") }</fui.Text>
|
|
<fui.Checkbox label={ infoLabel(i18n.t("common.characters.similar"), CharacterHints.similar) } { ...checkboxControls("excludeSimilar") } />
|
|
<fui.Checkbox label={ infoLabel(i18n.t("common.characters.ambiguous"), CharacterHints.ambiguous) } disabled={ !state.enableSpecial } { ...checkboxControls("excludeAmbiguous") } />
|
|
<fui.Checkbox label={ infoLabel(i18n.t("common.characters.repeating.label"), i18n.t("common.characters.repeating.hint")) } { ...checkboxControls("excludeRepeating") } />
|
|
<div>
|
|
<fui.Checkbox { ...checkboxControls("excludeCustom") } />
|
|
<fui.Input size="small" disabled={ !state.excludeCustom }
|
|
placeholder={ i18n.t("common.characters.custom") }
|
|
value={ state.excludeCustomSet } onChange={ (_, e) => setState({ excludeCustomSet: e.value }) } />
|
|
</div>
|
|
</section>
|
|
|
|
<div>
|
|
<fui.Checkbox
|
|
{ ...checkboxControls("enableSeparator") }
|
|
label={
|
|
<span className={ cls.separatorLabel }>
|
|
{ i18n.t("advanced.password.separator1") }
|
|
<fui.Input size="small" className={ cls.separatorInput }
|
|
disabled={ !state.enableSeparator }
|
|
value={ state.separator ?? "" }
|
|
onChange={ (_, e) => setState({ separator: e.value ? e.value[e.value.length - 1] : undefined }) } />
|
|
{ i18n.t("advanced.password.separator2") }
|
|
<fui.Input size="small" className={ cls.separatorInput }
|
|
disabled={ !state.enableSeparator }
|
|
value={ state.separatorInterval?.toString() ?? "" }
|
|
onBlur={ () => state.separatorInterval ? null : setState({ separatorInterval: DEFAULT_PASSWORD_LENGTH / 2 }) }
|
|
onChange={ setSeparatorInterval } />
|
|
{ i18n.t("advanced.password.separator3") }
|
|
</span>
|
|
} />
|
|
</div>
|
|
</GeneratorForm>
|
|
);
|
|
}
|
|
|
|
function parseCount(value: string): number | null
|
|
{
|
|
const n = parseInt(value, 10);
|
|
return isNaN(n) || n < 1 ? null : Math.min(n, 100);
|
|
};
|
|
|
|
function Row(props: { children: ReactElement[]; }): ReactElement
|
|
{
|
|
return (
|
|
<fui.TableRow as="div">
|
|
{ props.children.map((i, index) =>
|
|
<fui.TableCell key={ index } as="div">
|
|
{ i }
|
|
</fui.TableCell>
|
|
) }
|
|
</fui.TableRow>
|
|
);
|
|
}
|
|
|
|
const useStyles = fui.makeStyles({
|
|
section:
|
|
{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
},
|
|
separatorLabel:
|
|
{
|
|
display: "inline-flex",
|
|
flexWrap: "wrap",
|
|
alignItems: "center",
|
|
gap: `${fui.tokens.spacingVerticalXXS} ${fui.tokens.spacingHorizontalS}`,
|
|
},
|
|
separatorInput:
|
|
{
|
|
width: "4em",
|
|
}
|
|
});
|
|
|
|
type PasswordSectionState =
|
|
{
|
|
length: number | null;
|
|
enableUppercase: boolean;
|
|
uppercaseCount: number | null;
|
|
enableLowercase: boolean;
|
|
lowercaseCount: number | null;
|
|
enableNumeric: boolean;
|
|
numericCount: number | null;
|
|
enableSpecial: boolean;
|
|
specialCount: number | null;
|
|
enableCustom: boolean;
|
|
customCount: number | null;
|
|
|
|
excludeSimilar: boolean;
|
|
excludeAmbiguous: boolean;
|
|
excludeRepeating: boolean;
|
|
excludeCustom: boolean;
|
|
|
|
excludeCustomSet: string;
|
|
customSet: string;
|
|
|
|
enableSeparator: boolean;
|
|
separator?: string;
|
|
separatorInterval?: number;
|
|
};
|