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({ 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) => { 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 => ({ checked: state[key] as boolean, onChange: (_, e) => setState({ [key]: e.checked as boolean }) }), [state]); const minInputControls = useCallback((enabledKey: keyof PasswordSectionState, key: keyof PasswordSectionState): Partial => ({ 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 ( { i18n.t("common.sections.include") } { i18n.t("advanced.password.min_of_type") } <> setState({ customSet: e.value }) } />
{ i18n.t("common.sections.exclude") }
setState({ excludeCustomSet: e.value }) } />
{ i18n.t("advanced.password.separator1") } setState({ separator: e.value ? e.value[e.value.length - 1] : undefined }) } /> { i18n.t("advanced.password.separator2") } state.separatorInterval ? null : setState({ separatorInterval: DEFAULT_PASSWORD_LENGTH / 2 }) } onChange={ setSeparatorInterval } /> { i18n.t("advanced.password.separator3") } } />
); } 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 ( { props.children.map((i, index) => { i } ) } ); } 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; };