@virastack/password 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/logic.ts","../src/icons/index.ts","../src/react/useViraPassword.ts"],"names":["args","React"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Generates the input type based on visibility.\n */\nexport const getInputType = (isVisible: boolean): 'text' | 'password' => {\n return isVisible ? 'text' : 'password';\n};\n\n/**\n * Generates ARIA attributes for the toggle button.\n */\nexport const getButtonAriaAttributes = (\n isVisible: boolean,\n inputId: string,\n label: string = 'Toggle password visibility'\n) => {\n return {\n 'aria-label': label,\n 'aria-pressed': isVisible,\n 'aria-controls': inputId,\n type: 'button' as const,\n };\n};\n\n/**\n * Generates ARIA attributes for the input field.\n */\nexport const getInputAriaAttributes = (\n isInvalid?: boolean,\n describedBy?: string\n) => {\n const attrs: Record<string, any> = {};\n \n if (isInvalid !== undefined) {\n attrs['aria-invalid'] = isInvalid;\n }\n \n if (describedBy) {\n attrs['aria-describedby'] = describedBy;\n }\n \n return attrs;\n};\n","import React from 'react';\n\nexport const EyeIcon = (props: React.SVGProps<SVGSVGElement>) => (\n React.createElement('svg', {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: \"2\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n ...props\n },\n React.createElement('path', { d: \"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\" }),\n React.createElement('circle', { cx: \"12\", cy: \"12\", r: \"3\" })\n )\n);\n\nexport const EyeOffIcon = (props: React.SVGProps<SVGSVGElement>) => (\n React.createElement('svg', {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: \"2\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n ...props\n },\n React.createElement('path', { d: \"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\" }),\n React.createElement('path', { d: \"M14.084 14.158a3 3 0 0 1-4.242-4.242\" }),\n React.createElement('path', { d: \"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\" }),\n React.createElement('path', { d: \"m2 2 20 20\" })\n )\n);\n","'use client';\n\nimport React, { useState, useId, useCallback, ReactNode } from 'react';\nimport { EyeIcon, EyeOffIcon } from '../icons';\nimport { getInputType, getButtonAriaAttributes, getInputAriaAttributes } from '../core/logic';\n\nexport interface UseViraPasswordOptions {\n /**\n * Initial visibility state of the password.\n * @default false\n */\n defaultVisible?: boolean;\n \n /**\n * Custom icons for show/hide states.\n */\n icons?: {\n show: ReactNode;\n hide: ReactNode;\n };\n \n /**\n * Whether the input is disabled.\n * If true, the toggle button will also be disabled.\n */\n disabled?: boolean;\n \n /**\n * Whether the input is read-only.\n * If true, the toggle button will be disabled.\n */\n readOnly?: boolean;\n\n /**\n * ID for the input element. If not provided, a random ID will be generated.\n */\n id?: string;\n\n /**\n * Props to be merged with the generated input props.\n */\n inputProps?: React.InputHTMLAttributes<HTMLInputElement>;\n\n /**\n * Props to be merged with the generated button props.\n */\n btnProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;\n}\n\nexport interface UseViraPasswordResult {\n inputProps: React.InputHTMLAttributes<HTMLInputElement>;\n btnProps: React.ButtonHTMLAttributes<HTMLButtonElement>;\n isVisible: boolean;\n toggle: () => void;\n setVisible: (visible: boolean) => void;\n}\n\n/**\n * A utility to merge event handlers and other props.\n */\nconst mergeProps = <T extends Record<string, any>>(\n ...args: (T | undefined)[]\n): T => {\n const result: Record<string, any> = {};\n\n for (const props of args) {\n if (!props) continue;\n\n for (const key in props) {\n const val = props[key];\n const existing = result[key];\n\n if (\n key.startsWith('on') &&\n typeof val === 'function' &&\n typeof existing === 'function'\n ) {\n result[key] = (...args: any[]) => {\n existing(...args);\n val(...args);\n };\n } else if (key === 'className' && typeof val === 'string' && typeof existing === 'string') {\n result[key] = [existing, val].filter(Boolean).join(' ');\n } else {\n result[key] = val;\n }\n }\n }\n\n return result as T;\n};\n\nexport const useViraPassword = (options: UseViraPasswordOptions = {}): UseViraPasswordResult => {\n const {\n defaultVisible = false,\n icons,\n disabled = false,\n readOnly = false,\n id: providedId,\n inputProps: userInputProps,\n btnProps: userBtnProps,\n } = options;\n\n const [isVisible, setIsVisible] = useState(defaultVisible);\n const generatedId = useId();\n const inputId = providedId || `virapassword-input-${generatedId}`;\n\n const toggle = useCallback(() => {\n if (disabled || readOnly) return;\n setIsVisible((prev) => !prev);\n }, [disabled, readOnly]);\n\n const setVisible = useCallback((visible: boolean) => {\n if (disabled || readOnly) return;\n setIsVisible(visible);\n }, [disabled, readOnly]);\n\n const showIcon = icons?.show ?? React.createElement(EyeIcon);\n const hideIcon = icons?.hide ?? React.createElement(EyeOffIcon);\n\n // Base props for the input\n const baseInputProps: React.InputHTMLAttributes<HTMLInputElement> = {\n id: inputId,\n type: getInputType(isVisible),\n disabled,\n readOnly,\n ...getInputAriaAttributes(),\n };\n\n // Base props for the button\n const baseBtnProps: React.ButtonHTMLAttributes<HTMLButtonElement> = {\n onClick: toggle,\n disabled: disabled || readOnly,\n children: isVisible ? hideIcon : showIcon,\n ...getButtonAriaAttributes(isVisible, inputId),\n };\n\n // Merge user props with base props\n const mergedInputProps = mergeProps(baseInputProps, userInputProps);\n const mergedBtnProps = mergeProps(baseBtnProps, userBtnProps);\n\n return {\n inputProps: mergedInputProps,\n btnProps: mergedBtnProps,\n isVisible,\n toggle,\n setVisible,\n };\n};\n\n// Re-export the mergeProps utility if the user needs it manually\nexport { mergeProps };\n"]}
1
+ {"version":3,"sources":["../src/core/logic.ts","../src/react/useViraPassword.ts","../src/icons/index.ts"],"sourcesContent":["/**\n * Generates the input type based on visibility.\n */\nexport const getInputType = (isVisible: boolean): 'text' | 'password' => {\n return isVisible ? 'text' : 'password';\n};\n\n/**\n * Generates ARIA attributes for the toggle button.\n */\nexport const getButtonAriaAttributes = (\n isVisible: boolean,\n inputId: string,\n label: string = 'Toggle password visibility'\n) => {\n return {\n 'aria-label': label,\n 'aria-pressed': isVisible,\n 'aria-controls': inputId,\n type: 'button' as const,\n };\n};\n\n/**\n * Generates ARIA attributes for the input field.\n */\nexport interface InputAriaAttributes {\n 'aria-invalid'?: boolean;\n 'aria-describedby'?: string;\n}\n\nexport const getInputAriaAttributes = (\n isInvalid?: boolean,\n describedBy?: string\n): InputAriaAttributes => {\n const attrs: InputAriaAttributes = {};\n\n if (isInvalid !== undefined) {\n attrs['aria-invalid'] = isInvalid;\n }\n \n if (describedBy) {\n attrs['aria-describedby'] = describedBy;\n }\n \n return attrs;\n};\n","import React, { useState, useId, useCallback, useEffect, useRef, ReactNode } from 'react';\nimport { flushSync } from 'react-dom';\nimport { EyeIcon, EyeOffIcon } from '../icons';\nimport { getInputType, getButtonAriaAttributes, getInputAriaAttributes } from '../core/logic';\nimport type { PasswordIssue, Policy, StrengthResult } from '../strength/types';\n\nexport type ViraPasswordInputProps = React.InputHTMLAttributes<HTMLInputElement> & {\n ref?: React.Ref<HTMLInputElement>;\n};\n\nexport interface UseViraPasswordOptions {\n /**\n * Initial visibility state of the password.\n * @default false\n */\n defaultVisible?: boolean;\n\n /**\n * Controlled visibility. Pair it with `onVisibleChange`.\n */\n visible?: boolean;\n\n /**\n * Called with the next visibility whenever it changes, including automatic hiding.\n */\n onVisibleChange?: (visible: boolean) => void;\n\n /**\n * Hide the password when its form is submitted or reset, so password managers see a password field.\n * @default true\n */\n hideOnSubmit?: boolean;\n\n /**\n * Hide the password when focus leaves the input and the toggle, or the browser tab is hidden.\n * @default false\n */\n hideOnBlur?: boolean;\n\n /**\n * `autoComplete` for the input. `false` leaves the attribute out.\n * @default \"new-password\" with `policy` or `matchWith`, otherwise \"current-password\"\n */\n autoComplete?: string | false;\n\n /**\n * A policy from `definePolicy()` in `@virastack/password/strength`. Enables `strength` and `issues`.\n */\n policy?: Policy;\n\n /**\n * Values the password must not contain, e.g. the user's email and name.\n */\n forbidden?: readonly unknown[];\n\n /**\n * The password this field must repeat, e.g. `watch(\"password\")` on a confirm field.\n * Enables `matches` and the `password.mismatch` key.\n */\n matchWith?: string;\n\n /**\n * The current value, e.g. `watch(\"password\")`. Without it the hook follows typing in the input,\n * which misses `reset(values)` and `setValue()` from a form library.\n */\n value?: string;\n\n /**\n * Custom icons for show/hide states.\n */\n icons?: {\n show: ReactNode;\n hide: ReactNode;\n };\n\n /**\n * Whether the input is disabled.\n * If true, the toggle button will also be disabled.\n */\n disabled?: boolean;\n\n /**\n * Whether the input is read-only.\n * If true, the toggle button will be disabled.\n */\n readOnly?: boolean;\n\n /**\n * ID for the input element. If not provided, a random ID will be generated.\n */\n id?: string;\n\n /**\n * Props to be merged with the generated input props, e.g. `register(\"password\")`.\n */\n inputProps?: ViraPasswordInputProps;\n\n /**\n * Props to be merged with the generated button props.\n */\n btnProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;\n}\n\nexport interface UseViraPasswordResult {\n inputProps: ViraPasswordInputProps;\n btnProps: React.ButtonHTMLAttributes<HTMLButtonElement>;\n isVisible: boolean;\n /** Caps Lock is on while the input has focus. */\n capsLock: boolean;\n toggle: () => void;\n setVisible: (visible: boolean) => void;\n /** Result of the policy for the current value; `null` without a policy. */\n strength: StrengthResult | null;\n /** Spread on the element that shows the strength, so screen readers announce it. */\n strengthProps: ViraPasswordStrengthProps;\n /** With `matchWith`: whether the value repeats it; `null` while the field is empty or without `matchWith`. */\n matches: boolean | null;\n /** Error keys for the current value. */\n issues: PasswordIssue[];\n /** A validation rule for React Hook Form: `register(\"password\", pw.rule)`. Returns the first error key. */\n rule: ViraPasswordRule;\n}\n\nexport interface ViraPasswordStrengthProps {\n id: string;\n role: 'status';\n 'aria-live': 'polite';\n 'aria-atomic': true;\n}\n\nexport interface ViraPasswordRule {\n validate: (value: unknown) => true | PasswordIssue;\n}\n\ntype Handler = (...args: unknown[]) => unknown;\ntype RefCleanup = ReturnType<React.RefCallback<unknown>>;\n\nconst setRef = <T>(ref: React.Ref<T> | undefined, value: T | null): RefCleanup => {\n if (typeof ref === 'function') return ref(value);\n if (ref) ref.current = value;\n};\n\n/**\n * Combines refs into one callback ref. Supports React 19 ref cleanup functions.\n */\nconst composeRefs = <T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> => (node) => {\n const cleanups = refs.map((ref) => setRef(ref, node));\n if (!cleanups.some((cleanup) => typeof cleanup === 'function')) return;\n return () => {\n refs.forEach((ref, i) => {\n const cleanup = cleanups[i];\n if (typeof cleanup === 'function') cleanup();\n else setRef(ref, null);\n });\n };\n};\n\n/**\n * A utility to merge event handlers, class names, `aria-describedby` and refs.\n * Later props win; handlers run in order.\n */\nfunction mergeProps<T extends object>(...args: (T | undefined)[]): T;\nfunction mergeProps<A extends object, B extends object>(a: A | undefined, b: B | undefined): Omit<A, keyof B> & B;\nfunction mergeProps(...args: (object | undefined)[]): object {\n const result: Record<string, unknown> = {};\n\n for (const props of args) {\n if (!props) continue;\n\n for (const [key, val] of Object.entries(props)) {\n const existing = result[key];\n\n if (\n key.startsWith('on') &&\n typeof val === 'function' &&\n typeof existing === 'function'\n ) {\n result[key] = (...args: unknown[]) => {\n (existing as Handler)(...args);\n (val as Handler)(...args);\n };\n } else if ((key === 'className' || key === 'aria-describedby') && typeof val === 'string' && typeof existing === 'string') {\n result[key] = [existing, val].filter(Boolean).join(' ');\n } else if (key === 'ref' && existing && val) {\n result[key] = composeRefs(existing as React.Ref<unknown>, val as React.Ref<unknown>);\n } else {\n result[key] = val;\n }\n }\n }\n\n return result;\n}\n\n// Same comparison as the server: NFKC, so \"é\" typed two ways still matches.\nconst sameText = (a: string, b: string) => a.normalize('NFKC') === b.normalize('NFKC');\n\nexport const useViraPassword = (options: UseViraPasswordOptions = {}): UseViraPasswordResult => {\n const {\n defaultVisible = false,\n visible: controlledVisible,\n onVisibleChange,\n hideOnSubmit = true,\n hideOnBlur = false,\n autoComplete,\n policy,\n forbidden,\n matchWith,\n value: providedValue,\n icons,\n disabled = false,\n readOnly = false,\n id: providedId,\n inputProps: userInputProps,\n btnProps: userBtnProps,\n } = options;\n\n const [uncontrolledVisible, setUncontrolledVisible] = useState(defaultVisible);\n const [capsLock, setCapsLock] = useState(false);\n const userValue = userInputProps?.value;\n const checksValue = policy !== undefined || matchWith !== undefined;\n const tracksValue = checksValue && providedValue === undefined && typeof userValue !== 'string';\n const [typed, setTyped] = useState(() => {\n const initial = userInputProps?.defaultValue;\n return typeof initial === 'string' ? initial : '';\n });\n const value = providedValue ?? (typeof userValue === 'string' ? userValue : typed);\n const isVisible = controlledVisible ?? uncontrolledVisible;\n const generatedId = useId();\n // inputProps.id wins over id, so the button and the listeners follow the id the input really gets.\n const inputId = (typeof userInputProps?.id === 'string' && userInputProps.id) || providedId || `virapassword-input-${generatedId}`;\n\n // Read from event handlers and listeners without re-subscribing on every render.\n const latest = useRef({ isVisible, controlled: controlledVisible !== undefined, onVisibleChange, policy, forbidden, matchWith, hideOnBlur, tracksValue });\n latest.current = { isVisible, controlled: controlledVisible !== undefined, onVisibleChange, policy, forbidden, matchWith, hideOnBlur, tracksValue };\n\n // Set between a pointer press on the toggle and its click.\n const pointerPress = useRef<{ start: number | null; end: number | null } | null>(null);\n\n // Found by id: inputProps has no ref, so it never replaces the ref of register() or a mask.\n const getInput = useCallback((): HTMLInputElement | null => {\n const element = typeof document === 'undefined' ? null : document.getElementById(inputId);\n return element instanceof HTMLInputElement ? element : null;\n }, [inputId]);\n\n const commit = useCallback((next: boolean) => {\n const current = latest.current;\n if (next === current.isVisible) return;\n if (!current.controlled) setUncontrolledVisible(next);\n current.onVisibleChange?.(next);\n }, []);\n\n const toggle = useCallback(() => {\n if (disabled || readOnly) return;\n commit(!latest.current.isVisible);\n }, [disabled, readOnly, commit]);\n\n const setVisible = useCallback((visible: boolean) => {\n if (disabled || readOnly) return;\n commit(visible);\n }, [disabled, readOnly, commit]);\n\n // Hiding is always allowed, even while disabled.\n const hide = useCallback(() => commit(false), [commit]);\n\n useEffect(() => {\n if (!hideOnSubmit) return;\n const isOwnForm = (event: Event) => {\n const form = getInput()?.form;\n return form != null && event.target === form;\n };\n // Capture so the field is a password field again before the browser or a form library reads it.\n const onSubmit = (event: Event) => {\n if (isOwnForm(event) && latest.current.isVisible) flushSync(hide);\n };\n const onReset = (event: Event) => {\n if (isOwnForm(event) && !event.defaultPrevented) hide();\n };\n document.addEventListener('submit', onSubmit, true);\n document.addEventListener('reset', onReset);\n return () => {\n document.removeEventListener('submit', onSubmit, true);\n document.removeEventListener('reset', onReset);\n };\n }, [hideOnSubmit, getInput, hide]);\n\n useEffect(() => {\n if (!tracksValue) return;\n // A reset changes the value without an input event; read it once the browser has applied it.\n const onReset = (event: Event) => {\n const input = getInput();\n if (input?.form && event.target === input.form) setTimeout(() => setTyped(input.value));\n };\n document.addEventListener('reset', onReset);\n return () => document.removeEventListener('reset', onReset);\n }, [tracksValue, getInput]);\n\n useEffect(() => {\n if (!hideOnBlur) return;\n const onVisibilityChange = () => {\n if (document.visibilityState === 'hidden') hide();\n };\n document.addEventListener('visibilitychange', onVisibilityChange);\n return () => document.removeEventListener('visibilitychange', onVisibilityChange);\n }, [hideOnBlur, hide]);\n\n const hideOnFocusLeave = useCallback((next: EventTarget | null) => {\n if (!latest.current.hideOnBlur || pointerPress.current) return;\n if (next instanceof Element && (next.id === inputId || next.getAttribute('aria-controls') === inputId)) return;\n hide();\n }, [inputId, hide]);\n\n // inputProps carries attributes only, as in 1.0, so it can be spread before or after register()\n // or a mask field without replacing their handlers. Keys, blur and typing are read natively.\n useEffect(() => {\n const isInput = (target: EventTarget | null): target is HTMLInputElement =>\n target instanceof HTMLInputElement && target.id === inputId;\n const onKey = (event: KeyboardEvent) => {\n if (isInput(event.target)) setCapsLock(event.getModifierState?.('CapsLock') ?? false);\n };\n const onFocusOut = (event: FocusEvent) => {\n if (!isInput(event.target)) return;\n setCapsLock(false);\n hideOnFocusLeave(event.relatedTarget);\n };\n const onInput = (event: Event) => {\n if (latest.current.tracksValue && isInput(event.target)) setTyped(event.target.value);\n };\n document.addEventListener('keydown', onKey, true);\n document.addEventListener('keyup', onKey, true);\n document.addEventListener('focusout', onFocusOut, true);\n document.addEventListener('input', onInput, true);\n return () => {\n document.removeEventListener('keydown', onKey, true);\n document.removeEventListener('keyup', onKey, true);\n document.removeEventListener('focusout', onFocusOut, true);\n document.removeEventListener('input', onInput, true);\n };\n }, [inputId, hideOnFocusLeave]);\n\n const strength = policy ? policy.evaluate(value, { forbidden }) : null;\n const matches = matchWith === undefined || value === '' ? null : sameText(value, matchWith);\n const issues: PasswordIssue[] = strength ? [...strength.issues] : [];\n if (matches === false) issues.push('password.mismatch');\n const strengthId = `${inputId}-strength`;\n\n // Stable, and always checks the latest policy, so it can go straight into register().\n const [rule] = useState<ViraPasswordRule>(() => ({\n validate: (input) => {\n const { policy: currentPolicy, forbidden: currentForbidden, matchWith: currentMatch } = latest.current;\n const text = typeof input === 'string' ? input : '';\n const result = currentPolicy?.evaluate(text, { forbidden: currentForbidden });\n if (result && !result.valid) return result.issues[0];\n if (currentMatch !== undefined && !sameText(text, currentMatch)) return 'password.mismatch';\n return true;\n },\n }));\n\n const showIcon = icons?.show ?? React.createElement(EyeIcon);\n const hideIcon = icons?.hide ?? React.createElement(EyeOffIcon);\n\n // Base props for the input\n const baseInputProps: ViraPasswordInputProps = {\n id: inputId,\n type: getInputType(isVisible),\n disabled,\n readOnly,\n ...(autoComplete === false ? {} : { autoComplete: autoComplete ?? (checksValue ? 'new-password' : 'current-password') }),\n spellCheck: false,\n autoCorrect: 'off',\n autoCapitalize: 'off',\n ...getInputAriaAttributes(undefined, policy ? strengthId : undefined),\n };\n\n // Base props for the button\n const baseBtnProps: React.ButtonHTMLAttributes<HTMLButtonElement> = {\n onPointerDown: () => {\n // The input still has focus here; keep its caret for when focus comes back.\n const input = getInput();\n const press = { start: input?.selectionStart ?? null, end: input?.selectionEnd ?? null };\n pointerPress.current = press;\n // A press released outside the button never clicks; forget it after the click would have run.\n window.addEventListener('pointerup', () => setTimeout(() => {\n if (pointerPress.current === press) pointerPress.current = null;\n }), { once: true });\n },\n onPointerCancel: () => {\n pointerPress.current = null;\n },\n onClick: () => {\n const press = pointerPress.current;\n pointerPress.current = null;\n flushSync(toggle);\n // A mouse or touch press returns focus to the input; a keyboard press keeps it on the button.\n if (!press || disabled || readOnly) return;\n const input = getInput();\n if (!input) return;\n input.focus();\n if (press.start !== null && press.end !== null) input.setSelectionRange(press.start, press.end);\n },\n onBlur: (event) => hideOnFocusLeave(event.relatedTarget),\n disabled: disabled || readOnly,\n children: isVisible ? hideIcon : showIcon,\n ...getButtonAriaAttributes(isVisible, inputId),\n };\n\n // Merge user props with base props\n const mergedInputProps = mergeProps(baseInputProps, userInputProps);\n const mergedBtnProps = mergeProps(baseBtnProps, userBtnProps);\n\n return {\n inputProps: mergedInputProps,\n btnProps: mergedBtnProps,\n isVisible,\n capsLock,\n toggle,\n setVisible,\n strength,\n matches,\n strengthProps: { id: strengthId, role: 'status', 'aria-live': 'polite', 'aria-atomic': true },\n issues,\n rule,\n };\n};\n\n// Re-export the mergeProps utility if the user needs it manually\nexport { mergeProps, composeRefs };\n","import React from 'react';\n\nexport const EyeIcon = (props: React.SVGProps<SVGSVGElement>) => (\n React.createElement('svg', {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: \"2\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n ...props\n },\n React.createElement('path', { d: \"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\" }),\n React.createElement('circle', { cx: \"12\", cy: \"12\", r: \"3\" })\n )\n);\n\nexport const EyeOffIcon = (props: React.SVGProps<SVGSVGElement>) => (\n React.createElement('svg', {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: \"2\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n ...props\n },\n React.createElement('path', { d: \"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\" }),\n React.createElement('path', { d: \"M14.084 14.158a3 3 0 0 1-4.242-4.242\" }),\n React.createElement('path', { d: \"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\" }),\n React.createElement('path', { d: \"m2 2 20 20\" })\n )\n);\n"],"mappings":"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","names":["React","args","React"]}
package/dist/pwned.cjs ADDED
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/pwned.ts
21
+ var pwned_exports = {};
22
+ __export(pwned_exports, {
23
+ checkPwned: () => checkPwned
24
+ });
25
+ module.exports = __toCommonJS(pwned_exports);
26
+ var ENDPOINT = "https://api.pwnedpasswords.com/range/";
27
+ var sha1Hex = async (password) => {
28
+ const subtle = globalThis.crypto?.subtle;
29
+ if (!subtle) return null;
30
+ try {
31
+ const digest = await subtle.digest("SHA-1", new TextEncoder().encode(password));
32
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("").toUpperCase();
33
+ } catch {
34
+ return null;
35
+ }
36
+ };
37
+ var checkPwned = async (password, options = {}) => {
38
+ const unknown = (reason) => ({ status: "unknown", reason });
39
+ if (typeof password !== "string" || password === "") return unknown("empty");
40
+ const request = options.fetch ?? globalThis.fetch;
41
+ if (typeof request !== "function") return unknown("unsupported");
42
+ if (options.signal?.aborted) return unknown("aborted");
43
+ const hash = await sha1Hex(password);
44
+ if (!hash) return unknown("unsupported");
45
+ if (options.signal?.aborted) return unknown("aborted");
46
+ const prefix = hash.slice(0, 5);
47
+ const suffix = hash.slice(5);
48
+ const controller = new AbortController();
49
+ let timedOut = false;
50
+ const timer = setTimeout(() => {
51
+ timedOut = true;
52
+ controller.abort();
53
+ }, options.timeout ?? 5e3);
54
+ const onAbort = () => controller.abort();
55
+ options.signal?.addEventListener("abort", onAbort, { once: true });
56
+ try {
57
+ const response = await request(`${options.endpoint ?? ENDPOINT}${prefix}`, {
58
+ headers: { "Add-Padding": "true" },
59
+ signal: controller.signal
60
+ });
61
+ if (response.status === 429) return unknown("rate-limited");
62
+ if (!response.ok) return unknown("http");
63
+ const body = await response.text();
64
+ for (const line of body.split("\n")) {
65
+ const [candidate, count] = line.trim().split(":");
66
+ if (candidate?.toUpperCase() !== suffix) continue;
67
+ const times = Number.parseInt(count ?? "", 10);
68
+ return times > 0 ? { status: "pwned", count: times } : { status: "safe" };
69
+ }
70
+ return { status: "safe" };
71
+ } catch {
72
+ if (timedOut) return unknown("timeout");
73
+ if (options.signal?.aborted) return unknown("aborted");
74
+ return unknown("network");
75
+ } finally {
76
+ clearTimeout(timer);
77
+ options.signal?.removeEventListener("abort", onAbort);
78
+ }
79
+ };
80
+ // Annotate the CommonJS export names for ESM import in node:
81
+ 0 && (module.exports = {
82
+ checkPwned
83
+ });
84
+ //# sourceMappingURL=pwned.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pwned.ts"],"sourcesContent":["export type PwnedUnknownReason = \"empty\" | \"unsupported\" | \"network\" | \"timeout\" | \"aborted\" | \"rate-limited\" | \"http\";\n\nexport type PwnedResult =\n | { status: \"pwned\"; count: number }\n | { status: \"safe\" }\n | { status: \"unknown\"; reason: PwnedUnknownReason };\n\nexport interface CheckPwnedOptions {\n signal?: AbortSignal;\n /** Milliseconds before giving up with `unknown`. @default 5000 */\n timeout?: number;\n /** A custom `fetch`, e.g. for tests or a proxy. @default globalThis.fetch */\n fetch?: typeof globalThis.fetch;\n /** @default \"https://api.pwnedpasswords.com/range/\" */\n endpoint?: string;\n}\n\nconst ENDPOINT = \"https://api.pwnedpasswords.com/range/\";\n\nconst sha1Hex = async (password: string): Promise<string | null> => {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) return null;\n try {\n const digest = await subtle.digest(\"SHA-1\", new TextEncoder().encode(password));\n return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\").toUpperCase();\n } catch {\n return null;\n }\n};\n\n/**\n * Looks the password up in Have I Been Pwned's Pwned Passwords (range API, k-anonymity):\n * only the first 5 characters of its SHA-1 hash leave the machine.\n * Never throws and never blocks: on any failure the status is `unknown`.\n * Call it from the server; from a browser the user's IP is visible to HIBP.\n */\nexport const checkPwned = async (password: string, options: CheckPwnedOptions = {}): Promise<PwnedResult> => {\n const unknown = (reason: PwnedUnknownReason): PwnedResult => ({ status: \"unknown\", reason });\n if (typeof password !== \"string\" || password === \"\") return unknown(\"empty\");\n const request = options.fetch ?? globalThis.fetch;\n if (typeof request !== \"function\") return unknown(\"unsupported\");\n if (options.signal?.aborted) return unknown(\"aborted\");\n\n const hash = await sha1Hex(password);\n if (!hash) return unknown(\"unsupported\");\n if (options.signal?.aborted) return unknown(\"aborted\");\n const prefix = hash.slice(0, 5);\n const suffix = hash.slice(5);\n\n const controller = new AbortController();\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, options.timeout ?? 5000);\n const onAbort = () => controller.abort();\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n try {\n const response = await request(`${options.endpoint ?? ENDPOINT}${prefix}`, {\n headers: { \"Add-Padding\": \"true\" },\n signal: controller.signal,\n });\n if (response.status === 429) return unknown(\"rate-limited\");\n if (!response.ok) return unknown(\"http\");\n const body = await response.text();\n for (const line of body.split(\"\\n\")) {\n const [candidate, count] = line.trim().split(\":\");\n if (candidate?.toUpperCase() !== suffix) continue;\n const times = Number.parseInt(count ?? \"\", 10);\n // Padding entries have a count of 0.\n return times > 0 ? { status: \"pwned\", count: times } : { status: \"safe\" };\n }\n return { status: \"safe\" };\n } catch {\n if (timedOut) return unknown(\"timeout\");\n if (options.signal?.aborted) return unknown(\"aborted\");\n return unknown(\"network\");\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener(\"abort\", onAbort);\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBA,IAAM,WAAW;AAEjB,IAAM,UAAU,OAAO,aAA6C;AAClE,QAAM,SAAS,WAAW,QAAQ;AAClC,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,OAAO,SAAS,IAAI,YAAY,EAAE,OAAO,QAAQ,CAAC;AAC9E,WAAO,MAAM,KAAK,IAAI,WAAW,MAAM,GAAG,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,YAAY;AAAA,EAC/G,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,IAAM,aAAa,OAAO,UAAkB,UAA6B,CAAC,MAA4B;AAC3G,QAAM,UAAU,CAAC,YAA6C,EAAE,QAAQ,WAAW,OAAO;AAC1F,MAAI,OAAO,aAAa,YAAY,aAAa,GAAI,QAAO,QAAQ,OAAO;AAC3E,QAAM,UAAU,QAAQ,SAAS,WAAW;AAC5C,MAAI,OAAO,YAAY,WAAY,QAAO,QAAQ,aAAa;AAC/D,MAAI,QAAQ,QAAQ,QAAS,QAAO,QAAQ,SAAS;AAErD,QAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,MAAI,CAAC,KAAM,QAAO,QAAQ,aAAa;AACvC,MAAI,QAAQ,QAAQ,QAAS,QAAO,QAAQ,SAAS;AACrD,QAAM,SAAS,KAAK,MAAM,GAAG,CAAC;AAC9B,QAAM,SAAS,KAAK,MAAM,CAAC;AAE3B,QAAM,aAAa,IAAI,gBAAgB;AACvC,MAAI,WAAW;AACf,QAAM,QAAQ,WAAW,MAAM;AAC7B,eAAW;AACX,eAAW,MAAM;AAAA,EACnB,GAAG,QAAQ,WAAW,GAAI;AAC1B,QAAM,UAAU,MAAM,WAAW,MAAM;AACvC,UAAQ,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAEjE,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,GAAG,QAAQ,YAAY,QAAQ,GAAG,MAAM,IAAI;AAAA,MACzE,SAAS,EAAE,eAAe,OAAO;AAAA,MACjC,QAAQ,WAAW;AAAA,IACrB,CAAC;AACD,QAAI,SAAS,WAAW,IAAK,QAAO,QAAQ,cAAc;AAC1D,QAAI,CAAC,SAAS,GAAI,QAAO,QAAQ,MAAM;AACvC,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,eAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,YAAM,CAAC,WAAW,KAAK,IAAI,KAAK,KAAK,EAAE,MAAM,GAAG;AAChD,UAAI,WAAW,YAAY,MAAM,OAAQ;AACzC,YAAM,QAAQ,OAAO,SAAS,SAAS,IAAI,EAAE;AAE7C,aAAO,QAAQ,IAAI,EAAE,QAAQ,SAAS,OAAO,MAAM,IAAI,EAAE,QAAQ,OAAO;AAAA,IAC1E;AACA,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B,QAAQ;AACN,QAAI,SAAU,QAAO,QAAQ,SAAS;AACtC,QAAI,QAAQ,QAAQ,QAAS,QAAO,QAAQ,SAAS;AACrD,WAAO,QAAQ,SAAS;AAAA,EAC1B,UAAE;AACA,iBAAa,KAAK;AAClB,YAAQ,QAAQ,oBAAoB,SAAS,OAAO;AAAA,EACtD;AACF;","names":[]}
@@ -0,0 +1,29 @@
1
+ type PwnedUnknownReason = "empty" | "unsupported" | "network" | "timeout" | "aborted" | "rate-limited" | "http";
2
+ type PwnedResult = {
3
+ status: "pwned";
4
+ count: number;
5
+ } | {
6
+ status: "safe";
7
+ } | {
8
+ status: "unknown";
9
+ reason: PwnedUnknownReason;
10
+ };
11
+ interface CheckPwnedOptions {
12
+ signal?: AbortSignal;
13
+ /** Milliseconds before giving up with `unknown`. @default 5000 */
14
+ timeout?: number;
15
+ /** A custom `fetch`, e.g. for tests or a proxy. @default globalThis.fetch */
16
+ fetch?: typeof globalThis.fetch;
17
+ /** @default "https://api.pwnedpasswords.com/range/" */
18
+ endpoint?: string;
19
+ }
20
+ /**
21
+ * Looks the password up in Have I Been Pwned's Pwned Passwords (range API, k-anonymity):
22
+ * only the first 5 characters of its SHA-1 hash leave the machine.
23
+ * Never throws and never blocks: on any failure the status is `unknown`.
24
+ * Call it from the server; from a browser the user's IP is visible to HIBP.
25
+ */
26
+ declare const checkPwned: (password: string, options?: CheckPwnedOptions) => Promise<PwnedResult>;
27
+
28
+ export { checkPwned };
29
+ export type { CheckPwnedOptions, PwnedResult, PwnedUnknownReason };
@@ -0,0 +1,29 @@
1
+ type PwnedUnknownReason = "empty" | "unsupported" | "network" | "timeout" | "aborted" | "rate-limited" | "http";
2
+ type PwnedResult = {
3
+ status: "pwned";
4
+ count: number;
5
+ } | {
6
+ status: "safe";
7
+ } | {
8
+ status: "unknown";
9
+ reason: PwnedUnknownReason;
10
+ };
11
+ interface CheckPwnedOptions {
12
+ signal?: AbortSignal;
13
+ /** Milliseconds before giving up with `unknown`. @default 5000 */
14
+ timeout?: number;
15
+ /** A custom `fetch`, e.g. for tests or a proxy. @default globalThis.fetch */
16
+ fetch?: typeof globalThis.fetch;
17
+ /** @default "https://api.pwnedpasswords.com/range/" */
18
+ endpoint?: string;
19
+ }
20
+ /**
21
+ * Looks the password up in Have I Been Pwned's Pwned Passwords (range API, k-anonymity):
22
+ * only the first 5 characters of its SHA-1 hash leave the machine.
23
+ * Never throws and never blocks: on any failure the status is `unknown`.
24
+ * Call it from the server; from a browser the user's IP is visible to HIBP.
25
+ */
26
+ declare const checkPwned: (password: string, options?: CheckPwnedOptions) => Promise<PwnedResult>;
27
+
28
+ export { checkPwned };
29
+ export type { CheckPwnedOptions, PwnedResult, PwnedUnknownReason };
package/dist/pwned.mjs ADDED
@@ -0,0 +1,59 @@
1
+ // src/pwned.ts
2
+ var ENDPOINT = "https://api.pwnedpasswords.com/range/";
3
+ var sha1Hex = async (password) => {
4
+ const subtle = globalThis.crypto?.subtle;
5
+ if (!subtle) return null;
6
+ try {
7
+ const digest = await subtle.digest("SHA-1", new TextEncoder().encode(password));
8
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("").toUpperCase();
9
+ } catch {
10
+ return null;
11
+ }
12
+ };
13
+ var checkPwned = async (password, options = {}) => {
14
+ const unknown = (reason) => ({ status: "unknown", reason });
15
+ if (typeof password !== "string" || password === "") return unknown("empty");
16
+ const request = options.fetch ?? globalThis.fetch;
17
+ if (typeof request !== "function") return unknown("unsupported");
18
+ if (options.signal?.aborted) return unknown("aborted");
19
+ const hash = await sha1Hex(password);
20
+ if (!hash) return unknown("unsupported");
21
+ if (options.signal?.aborted) return unknown("aborted");
22
+ const prefix = hash.slice(0, 5);
23
+ const suffix = hash.slice(5);
24
+ const controller = new AbortController();
25
+ let timedOut = false;
26
+ const timer = setTimeout(() => {
27
+ timedOut = true;
28
+ controller.abort();
29
+ }, options.timeout ?? 5e3);
30
+ const onAbort = () => controller.abort();
31
+ options.signal?.addEventListener("abort", onAbort, { once: true });
32
+ try {
33
+ const response = await request(`${options.endpoint ?? ENDPOINT}${prefix}`, {
34
+ headers: { "Add-Padding": "true" },
35
+ signal: controller.signal
36
+ });
37
+ if (response.status === 429) return unknown("rate-limited");
38
+ if (!response.ok) return unknown("http");
39
+ const body = await response.text();
40
+ for (const line of body.split("\n")) {
41
+ const [candidate, count] = line.trim().split(":");
42
+ if (candidate?.toUpperCase() !== suffix) continue;
43
+ const times = Number.parseInt(count ?? "", 10);
44
+ return times > 0 ? { status: "pwned", count: times } : { status: "safe" };
45
+ }
46
+ return { status: "safe" };
47
+ } catch {
48
+ if (timedOut) return unknown("timeout");
49
+ if (options.signal?.aborted) return unknown("aborted");
50
+ return unknown("network");
51
+ } finally {
52
+ clearTimeout(timer);
53
+ options.signal?.removeEventListener("abort", onAbort);
54
+ }
55
+ };
56
+ export {
57
+ checkPwned
58
+ };
59
+ //# sourceMappingURL=pwned.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pwned.ts"],"sourcesContent":["export type PwnedUnknownReason = \"empty\" | \"unsupported\" | \"network\" | \"timeout\" | \"aborted\" | \"rate-limited\" | \"http\";\n\nexport type PwnedResult =\n | { status: \"pwned\"; count: number }\n | { status: \"safe\" }\n | { status: \"unknown\"; reason: PwnedUnknownReason };\n\nexport interface CheckPwnedOptions {\n signal?: AbortSignal;\n /** Milliseconds before giving up with `unknown`. @default 5000 */\n timeout?: number;\n /** A custom `fetch`, e.g. for tests or a proxy. @default globalThis.fetch */\n fetch?: typeof globalThis.fetch;\n /** @default \"https://api.pwnedpasswords.com/range/\" */\n endpoint?: string;\n}\n\nconst ENDPOINT = \"https://api.pwnedpasswords.com/range/\";\n\nconst sha1Hex = async (password: string): Promise<string | null> => {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) return null;\n try {\n const digest = await subtle.digest(\"SHA-1\", new TextEncoder().encode(password));\n return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\").toUpperCase();\n } catch {\n return null;\n }\n};\n\n/**\n * Looks the password up in Have I Been Pwned's Pwned Passwords (range API, k-anonymity):\n * only the first 5 characters of its SHA-1 hash leave the machine.\n * Never throws and never blocks: on any failure the status is `unknown`.\n * Call it from the server; from a browser the user's IP is visible to HIBP.\n */\nexport const checkPwned = async (password: string, options: CheckPwnedOptions = {}): Promise<PwnedResult> => {\n const unknown = (reason: PwnedUnknownReason): PwnedResult => ({ status: \"unknown\", reason });\n if (typeof password !== \"string\" || password === \"\") return unknown(\"empty\");\n const request = options.fetch ?? globalThis.fetch;\n if (typeof request !== \"function\") return unknown(\"unsupported\");\n if (options.signal?.aborted) return unknown(\"aborted\");\n\n const hash = await sha1Hex(password);\n if (!hash) return unknown(\"unsupported\");\n if (options.signal?.aborted) return unknown(\"aborted\");\n const prefix = hash.slice(0, 5);\n const suffix = hash.slice(5);\n\n const controller = new AbortController();\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, options.timeout ?? 5000);\n const onAbort = () => controller.abort();\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n try {\n const response = await request(`${options.endpoint ?? ENDPOINT}${prefix}`, {\n headers: { \"Add-Padding\": \"true\" },\n signal: controller.signal,\n });\n if (response.status === 429) return unknown(\"rate-limited\");\n if (!response.ok) return unknown(\"http\");\n const body = await response.text();\n for (const line of body.split(\"\\n\")) {\n const [candidate, count] = line.trim().split(\":\");\n if (candidate?.toUpperCase() !== suffix) continue;\n const times = Number.parseInt(count ?? \"\", 10);\n // Padding entries have a count of 0.\n return times > 0 ? { status: \"pwned\", count: times } : { status: \"safe\" };\n }\n return { status: \"safe\" };\n } catch {\n if (timedOut) return unknown(\"timeout\");\n if (options.signal?.aborted) return unknown(\"aborted\");\n return unknown(\"network\");\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener(\"abort\", onAbort);\n }\n};\n"],"mappings":";AAiBA,IAAM,WAAW;AAEjB,IAAM,UAAU,OAAO,aAA6C;AAClE,QAAM,SAAS,WAAW,QAAQ;AAClC,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,OAAO,SAAS,IAAI,YAAY,EAAE,OAAO,QAAQ,CAAC;AAC9E,WAAO,MAAM,KAAK,IAAI,WAAW,MAAM,GAAG,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,YAAY;AAAA,EAC/G,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,IAAM,aAAa,OAAO,UAAkB,UAA6B,CAAC,MAA4B;AAC3G,QAAM,UAAU,CAAC,YAA6C,EAAE,QAAQ,WAAW,OAAO;AAC1F,MAAI,OAAO,aAAa,YAAY,aAAa,GAAI,QAAO,QAAQ,OAAO;AAC3E,QAAM,UAAU,QAAQ,SAAS,WAAW;AAC5C,MAAI,OAAO,YAAY,WAAY,QAAO,QAAQ,aAAa;AAC/D,MAAI,QAAQ,QAAQ,QAAS,QAAO,QAAQ,SAAS;AAErD,QAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,MAAI,CAAC,KAAM,QAAO,QAAQ,aAAa;AACvC,MAAI,QAAQ,QAAQ,QAAS,QAAO,QAAQ,SAAS;AACrD,QAAM,SAAS,KAAK,MAAM,GAAG,CAAC;AAC9B,QAAM,SAAS,KAAK,MAAM,CAAC;AAE3B,QAAM,aAAa,IAAI,gBAAgB;AACvC,MAAI,WAAW;AACf,QAAM,QAAQ,WAAW,MAAM;AAC7B,eAAW;AACX,eAAW,MAAM;AAAA,EACnB,GAAG,QAAQ,WAAW,GAAI;AAC1B,QAAM,UAAU,MAAM,WAAW,MAAM;AACvC,UAAQ,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAEjE,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,GAAG,QAAQ,YAAY,QAAQ,GAAG,MAAM,IAAI;AAAA,MACzE,SAAS,EAAE,eAAe,OAAO;AAAA,MACjC,QAAQ,WAAW;AAAA,IACrB,CAAC;AACD,QAAI,SAAS,WAAW,IAAK,QAAO,QAAQ,cAAc;AAC1D,QAAI,CAAC,SAAS,GAAI,QAAO,QAAQ,MAAM;AACvC,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,eAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,YAAM,CAAC,WAAW,KAAK,IAAI,KAAK,KAAK,EAAE,MAAM,GAAG;AAChD,UAAI,WAAW,YAAY,MAAM,OAAQ;AACzC,YAAM,QAAQ,OAAO,SAAS,SAAS,IAAI,EAAE;AAE7C,aAAO,QAAQ,IAAI,EAAE,QAAQ,SAAS,OAAO,MAAM,IAAI,EAAE,QAAQ,OAAO;AAAA,IAC1E;AACA,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B,QAAQ;AACN,QAAI,SAAU,QAAO,QAAQ,SAAS;AACtC,QAAI,QAAQ,QAAQ,QAAS,QAAO,QAAQ,SAAS;AACrD,WAAO,QAAQ,SAAS;AAAA,EAC1B,UAAE;AACA,iBAAa,KAAK;AAClB,YAAQ,QAAQ,oBAAoB,SAAS,OAAO;AAAA,EACtD;AACF;","names":[]}
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ definePolicy: () => definePolicy,
24
+ evaluate: () => evaluate,
25
+ normalizePassword: () => normalizePassword,
26
+ validatePassword: () => validatePassword
27
+ });
28
+ module.exports = __toCommonJS(server_exports);
29
+
30
+ // src/strength/common.ts
31
+ var COMMON = "password passwort parola sifre sifrem sifreniz parolam qwerty qwertz azerty qwertyuiop asdfgh asdfghjkl zxcvbn zxcvbnm qazwsx qwaszx wsxedc abc abcdef letmein welcome welcom admin administrator root user guest login test tester demo default changeme secret private iloveyou loveyou lovely love monkey dragon master shadow sunshine princess football baseball soccer basketball hockey superman batman spiderman starwars pokemon minecraft trustno whatever freedom flower hello hellothere cookie cheese chocolate summer winter autumn spring michael jessica jennifer charlie thomas jordan hunter ranger buster tigger ginger pepper matrix computer internet google facebook instagram twitter yahoo hotmail gmail apple samsung iphone android microsoft windows linux ubuntu killer access mustang harley ferrari porsche mercedes yankees liverpool chelsea arsenal barcelona realmadrid juventus galatasaray fenerbahce besiktas trabzonspor bursaspor cimbom istanbul ankara izmir antalya bursa turkiye turkey ataturk allah bismillah askim sevgilim bitanem canim hayatim prenses kelebek melek gunes yildiz deniz mehmet ahmet mustafa ali ayse fatma emine hatice zeynep elif omer murat emre burak huseyin hasan ibrahim yusuf merhaba selam kardesim annem babam qwertyasdfgh correcthorsebatterystaple passphrase mypassword newpassword temppassword passwordpassword nopassword sifreyok";
32
+
33
+ // src/strength/core.ts
34
+ var ZERO_WIDTH = /[\u200B-\u200D\u2060\uFEFF]/g;
35
+ var LEET = { "0": "o", "1": "i", "3": "e", "4": "a", "5": "s", "7": "t", "8": "b", "@": "a", "$": "s", "!": "i", "+": "t" };
36
+ var SEQUENCES = [
37
+ "abcdefghijklmnopqrstuvwxyz",
38
+ "0123456789",
39
+ "qwertyuiopasdfghjklzxcvbnm",
40
+ "qwertzuiopasdfghjklyxcvbnm",
41
+ "azertyuiopqsdfghjklmwxcvbn",
42
+ // Turkish Q layout, folded: ı→i, ğ→g, ü→u, ş→s, ö→o, ç→c
43
+ "qwertyuiopguasdfghjklsizxcvbnmoc",
44
+ "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9ol0p"
45
+ ];
46
+ var LEVELS = ["weak", "weak", "fair", "good", "strong"];
47
+ var common;
48
+ var normalizePassword = (password) => password.normalize("NFKC");
49
+ var codePoints = (value) => Array.from(value.replace(ZERO_WIDTH, ""));
50
+ var fold = (value) => value.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/\u0131/g, "i").replace(ZERO_WIDTH, "");
51
+ var deLeet = (value) => value.replace(/[0-9@$!+]/g, (c) => LEET[c] ?? c);
52
+ var period = (value) => {
53
+ const n = value.length;
54
+ const lps = new Array(n).fill(0);
55
+ for (let i = 1, k = 0; i < n; i++) {
56
+ while (k > 0 && value[i] !== value[k]) k = lps[k - 1];
57
+ if (value[i] === value[k]) k++;
58
+ lps[i] = k;
59
+ }
60
+ return n - (lps[n - 1] ?? 0);
61
+ };
62
+ var unit = (value) => {
63
+ const chars = Array.from(value);
64
+ return chars.slice(0, period(chars)).join("");
65
+ };
66
+ var commonCores = (folded) => {
67
+ const trimmed = folded.replace(/^[^\p{L}]+|[^\p{L}]+$/gu, "");
68
+ return [deLeet(trimmed), trimmed].map((core) => unit(core.replace(/[^\p{L}]/gu, "")));
69
+ };
70
+ var isSequence = (folded) => {
71
+ if (folded.length < 3) return false;
72
+ return SEQUENCES.some((sequence) => {
73
+ const forward = sequence.repeat(Math.ceil(folded.length / sequence.length) + 1);
74
+ const backward = Array.from(forward).reverse().join("");
75
+ return forward.includes(folded) || backward.includes(folded);
76
+ });
77
+ };
78
+ var isRepetitive = (chars, folded) => {
79
+ if (new Set(chars).size < Math.min(4, Math.ceil(chars.length / 2))) return true;
80
+ if (period(chars) <= chars.length / 3) return true;
81
+ return isSequence(folded);
82
+ };
83
+ var personalTokens = (forbidden, minTokenLength) => {
84
+ const tokens = /* @__PURE__ */ new Set();
85
+ for (const value of forbidden) {
86
+ if (typeof value !== "string") continue;
87
+ const at = value.lastIndexOf("@");
88
+ const source = fold(at > 0 ? value.slice(0, at) : value).trim();
89
+ const parts = [source, ...source.split(/[^\p{L}\p{N}]+|(?<=\p{L})(?=\p{N})|(?<=\p{N})(?=\p{L})/u)];
90
+ for (const part of parts) {
91
+ if (Array.from(part).length >= minTokenLength) tokens.add(part);
92
+ }
93
+ }
94
+ return [...tokens];
95
+ };
96
+ var containsAny = (folded, words) => {
97
+ if (words.length === 0) return false;
98
+ const variants = [folded, deLeet(folded), folded.replace(/[^\p{L}\p{N}]/gu, "")];
99
+ return words.some((word) => variants.some((variant) => variant.includes(word)));
100
+ };
101
+ var scoreFor = (chars, issues, minLength) => {
102
+ if (chars.length === 0) return 0;
103
+ if (issues.length > 0) {
104
+ const severe = issues.some(
105
+ (issue) => ["password.common", "password.blocked", "password.repetitive", "password.containsPersonalInfo"].includes(issue)
106
+ );
107
+ return severe || chars.length < minLength / 2 ? 0 : 1;
108
+ }
109
+ let pool = 0;
110
+ const text = chars.join("");
111
+ if (/\p{Ll}/u.test(text)) pool += 26;
112
+ if (/\p{Lu}/u.test(text)) pool += 26;
113
+ if (/\p{Nd}/u.test(text)) pool += 10;
114
+ if (/[\p{P}\p{S}\p{Zs}]/u.test(text)) pool += 33;
115
+ if (/[^\p{Ll}\p{Lu}\p{Nd}\p{P}\p{S}\p{Zs}]/u.test(text)) pool += 100;
116
+ const effective = Math.min(chars.length, new Set(chars).size * 2);
117
+ const bits = effective * Math.log2(Math.max(pool, 2));
118
+ return bits >= 100 ? 4 : bits >= 80 ? 3 : 2;
119
+ };
120
+ var evaluateWith = (policy, input, options = {}) => {
121
+ const password = typeof input === "string" ? normalizePassword(input) : "";
122
+ const chars = codePoints(password);
123
+ const folded = fold(password);
124
+ const { require: require2 } = policy;
125
+ const issues = [];
126
+ const checks = {
127
+ minLength: chars.length >= policy.minLength,
128
+ maxLength: chars.length <= policy.maxLength,
129
+ notCommon: true,
130
+ notBlocked: true,
131
+ notRepetitive: true,
132
+ notPersonal: true
133
+ };
134
+ if (!checks.minLength) issues.push("password.tooShort");
135
+ if (!checks.maxLength) issues.push("password.tooLong");
136
+ if (require2.lowercase) checks.lowercase = /\p{Ll}/u.test(password);
137
+ if (require2.uppercase) checks.uppercase = /\p{Lu}/u.test(password);
138
+ if (require2.digit) checks.digit = /\p{Nd}/u.test(password);
139
+ if (require2.symbol) checks.symbol = /[^\p{L}\p{N}]/u.test(password);
140
+ if (checks.lowercase === false) issues.push("password.noLowercase");
141
+ if (checks.uppercase === false) issues.push("password.noUppercase");
142
+ if (checks.digit === false) issues.push("password.noDigit");
143
+ if (checks.symbol === false) issues.push("password.noSymbol");
144
+ const huge = password.length > policy.maxLength * 4;
145
+ if (chars.length >= 4 && !huge) {
146
+ if (policy.blockCommon) {
147
+ const list = common ??= new Set(COMMON.split(" "));
148
+ checks.notCommon = !commonCores(folded).some((core) => list.has(core));
149
+ }
150
+ checks.notBlocked = !containsAny(folded, policy.blocklist);
151
+ if (policy.blockRepetitive) checks.notRepetitive = !isRepetitive(Array.from(folded), folded);
152
+ }
153
+ if (chars.length > 0 && options.forbidden && !huge) {
154
+ checks.notPersonal = !containsAny(folded, personalTokens(options.forbidden, policy.minTokenLength));
155
+ }
156
+ if (!checks.notCommon) issues.push("password.common");
157
+ if (!checks.notBlocked) issues.push("password.blocked");
158
+ if (!checks.notRepetitive) issues.push("password.repetitive");
159
+ if (!checks.notPersonal) issues.push("password.containsPersonalInfo");
160
+ const score = scoreFor(chars, issues, policy.minLength);
161
+ return { score, level: LEVELS[score], length: chars.length, valid: issues.length === 0, checks, issues };
162
+ };
163
+ var PRESETS = {
164
+ nist: { minLength: 15, maxLength: 64, require: {} },
165
+ legacy: { minLength: 8, maxLength: 64, require: { lowercase: true, uppercase: true, digit: true, symbol: true } }
166
+ };
167
+ var assertLength = (name, value) => {
168
+ if (!Number.isInteger(value) || value < 1) throw new RangeError(`definePolicy: ${name} must be a positive integer, got ${value}`);
169
+ };
170
+ var definePolicy = (options = {}) => {
171
+ const preset = options.preset ?? "nist";
172
+ const base = PRESETS[preset];
173
+ if (!base) throw new RangeError(`definePolicy: unknown preset "${String(preset)}"`);
174
+ const minLength = options.minLength ?? base.minLength;
175
+ const maxLength = options.maxLength ?? base.maxLength;
176
+ const minTokenLength = options.minTokenLength ?? 3;
177
+ assertLength("minLength", minLength);
178
+ assertLength("maxLength", maxLength);
179
+ assertLength("minTokenLength", minTokenLength);
180
+ if (minLength > maxLength) throw new RangeError(`definePolicy: minLength (${minLength}) is greater than maxLength (${maxLength})`);
181
+ const req = { ...base.require, ...options.require };
182
+ const require2 = {
183
+ lowercase: req.lowercase === true,
184
+ uppercase: req.uppercase === true,
185
+ digit: req.digit === true,
186
+ symbol: req.symbol === true
187
+ };
188
+ const classes = Object.values(require2).filter(Boolean).length;
189
+ if (classes > maxLength) throw new RangeError(`definePolicy: ${classes} required character classes do not fit in maxLength (${maxLength})`);
190
+ const policy = Object.freeze({
191
+ preset,
192
+ minLength,
193
+ maxLength,
194
+ require: Object.freeze(require2),
195
+ blockCommon: options.blockCommon ?? true,
196
+ blockRepetitive: options.blockRepetitive ?? true,
197
+ blocklist: Object.freeze((options.blocklist ?? []).map((word) => fold(word).replace(/[^\p{L}\p{N}]/gu, "")).filter(Boolean)),
198
+ minTokenLength,
199
+ evaluate: (password, evaluateOptions) => evaluateWith(policy, password, evaluateOptions)
200
+ });
201
+ return policy;
202
+ };
203
+ var evaluate = (password, policy, options) => policy.evaluate(password, options);
204
+
205
+ // src/server.ts
206
+ var validatePassword = (password, policy, options = {}) => {
207
+ const text = typeof password === "string" ? password : "";
208
+ const result = evaluate(text, policy, { forbidden: options.forbidden });
209
+ const issues = [...result.issues];
210
+ if ("confirm" in options) {
211
+ const confirm = typeof options.confirm === "string" ? options.confirm : "";
212
+ if (normalizePassword(confirm) !== normalizePassword(text)) issues.push("password.mismatch");
213
+ }
214
+ return { ...result, valid: issues.length === 0, issues, password: normalizePassword(text) };
215
+ };
216
+ // Annotate the CommonJS export names for ESM import in node:
217
+ 0 && (module.exports = {
218
+ definePolicy,
219
+ evaluate,
220
+ normalizePassword,
221
+ validatePassword
222
+ });
223
+ //# sourceMappingURL=server.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/strength/common.ts","../src/strength/core.ts"],"sourcesContent":["import { evaluate, normalizePassword } from \"./strength/core\";\nimport type { EvaluateOptions, PasswordIssue, Policy, StrengthResult } from \"./strength/types\";\n\nexport { definePolicy, evaluate, normalizePassword } from \"./strength/core\";\nexport type * from \"./strength/types\";\n\nexport interface ValidatePasswordOptions extends EvaluateOptions {\n /** The repeated password, e.g. `formData.get(\"confirm\")`. Adds `password.mismatch` when it differs. */\n confirm?: unknown;\n}\n\nexport interface PasswordValidation extends StrengthResult {\n /** No issues, including `password.mismatch`. */\n valid: boolean;\n /** The NFKC-normalized password. Hash this, so the same password typed on another keyboard still verifies. */\n password: string;\n}\n\n/**\n * Checks a password on the server with the same policy and engine as the form.\n * Accepts `FormData` values directly; anything that is not a string counts as empty.\n */\nexport const validatePassword = (\n password: unknown,\n policy: Policy,\n options: ValidatePasswordOptions = {},\n): PasswordValidation => {\n const text = typeof password === \"string\" ? password : \"\";\n const result = evaluate(text, policy, { forbidden: options.forbidden });\n const issues: PasswordIssue[] = [...result.issues];\n if (\"confirm\" in options) {\n const confirm = typeof options.confirm === \"string\" ? options.confirm : \"\";\n if (normalizePassword(confirm) !== normalizePassword(text)) issues.push(\"password.mismatch\");\n }\n // score and level describe the password; valid covers the confirmation too.\n return { ...result, valid: issues.length === 0, issues, password: normalizePassword(text) };\n};\n","// Cores of common passwords: lowercase letters only, as `commonCore()` reduces them\n// (\"P@ssw0rd123!\" → \"password\"). Includes Turkish favourites. Digits-only and keyboard runs\n// are caught by the repetition and sequence checks instead.\nexport const COMMON =\n \"password passwort parola sifre sifrem sifreniz parolam qwerty qwertz azerty qwertyuiop asdfgh asdfghjkl \" +\n \"zxcvbn zxcvbnm qazwsx qwaszx wsxedc abc abcdef letmein welcome welcom admin administrator root user guest \" +\n \"login test tester demo default changeme secret private iloveyou loveyou lovely love monkey dragon master \" +\n \"shadow sunshine princess football baseball soccer basketball hockey superman batman spiderman starwars \" +\n \"pokemon minecraft trustno whatever freedom flower hello hellothere cookie cheese chocolate summer winter \" +\n \"autumn spring michael jessica jennifer charlie thomas jordan hunter ranger buster tigger ginger pepper \" +\n \"matrix computer internet google facebook instagram twitter yahoo hotmail gmail apple samsung iphone \" +\n \"android microsoft windows linux ubuntu killer access mustang harley ferrari porsche mercedes yankees \" +\n \"liverpool chelsea arsenal barcelona realmadrid juventus galatasaray fenerbahce besiktas trabzonspor \" +\n \"bursaspor cimbom istanbul ankara izmir antalya bursa turkiye turkey ataturk allah bismillah askim \" +\n \"sevgilim bitanem canim hayatim prenses kelebek melek gunes yildiz deniz mehmet ahmet mustafa ali \" +\n \"ayse fatma emine hatice zeynep elif omer murat emre burak huseyin hasan ibrahim yusuf merhaba \" +\n \"selam kardesim annem babam qwertyasdfgh correcthorsebatterystaple passphrase mypassword newpassword \" +\n \"temppassword passwordpassword nopassword sifreyok\";\n","import { COMMON } from \"./common\";\nimport type {\n EvaluateOptions,\n PasswordChecks,\n PasswordIssue,\n Policy,\n PolicyOptions,\n StrengthLevel,\n StrengthResult,\n StrengthScore,\n} from \"./types\";\n\nconst ZERO_WIDTH = /[\\u200B-\\u200D\\u2060\\uFEFF]/g;\nconst LEET: Record<string, string> = { \"0\": \"o\", \"1\": \"i\", \"3\": \"e\", \"4\": \"a\", \"5\": \"s\", \"7\": \"t\", \"8\": \"b\", \"@\": \"a\", \"$\": \"s\", \"!\": \"i\", \"+\": \"t\" };\n// Rows and runs that read as a sequence when typed, forwards or backwards.\nconst SEQUENCES = [\n \"abcdefghijklmnopqrstuvwxyz\",\n \"0123456789\",\n \"qwertyuiopasdfghjklzxcvbnm\",\n \"qwertzuiopasdfghjklyxcvbnm\",\n \"azertyuiopqsdfghjklmwxcvbn\",\n // Turkish Q layout, folded: ı→i, ğ→g, ü→u, ş→s, ö→o, ç→c\n \"qwertyuiopguasdfghjklsizxcvbnmoc\",\n \"1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9ol0p\",\n];\nconst LEVELS: readonly StrengthLevel[] = [\"weak\", \"weak\", \"fair\", \"good\", \"strong\"];\n\nlet common: Set<string> | undefined;\n\n/** NFKC, so visually identical input from different keyboards compares and hashes the same. */\nexport const normalizePassword = (password: string): string => password.normalize(\"NFKC\");\n\nconst codePoints = (value: string): string[] => Array.from(value.replace(ZERO_WIDTH, \"\"));\n\n/** Lowercase without accents, for comparisons only: \"Şifre\" and \"sifre\" match, so do \"İ\" and \"i\". */\nconst fold = (value: string): string =>\n value.normalize(\"NFKD\").replace(/\\p{M}/gu, \"\").toLowerCase().replace(/\\u0131/g, \"i\").replace(ZERO_WIDTH, \"\");\n\nconst deLeet = (value: string): string => value.replace(/[0-9@$!+]/g, (c) => LEET[c] ?? c);\n\n/** Length of the shortest unit the string repeats, e.g. 3 for \"abcabcab\" (the last copy may be cut short). */\nconst period = (value: string[]): number => {\n const n = value.length;\n const lps = new Array<number>(n).fill(0);\n for (let i = 1, k = 0; i < n; i++) {\n while (k > 0 && value[i] !== value[k]) k = lps[k - 1];\n if (value[i] === value[k]) k++;\n lps[i] = k;\n }\n return n - (lps[n - 1] ?? 0);\n};\n\nconst unit = (value: string): string => {\n const chars = Array.from(value);\n return chars.slice(0, period(chars)).join(\"\");\n};\n\n/** \"P@ssw0rd2024!\" → \"password\"; \"summer2024summer\" → \"summer\". Digits inside are leetspeak or separators. */\nconst commonCores = (folded: string): string[] => {\n const trimmed = folded.replace(/^[^\\p{L}]+|[^\\p{L}]+$/gu, \"\");\n return [deLeet(trimmed), trimmed].map((core) => unit(core.replace(/[^\\p{L}]/gu, \"\")));\n};\n\nconst isSequence = (folded: string): boolean => {\n if (folded.length < 3) return false;\n return SEQUENCES.some((sequence) => {\n const forward = sequence.repeat(Math.ceil(folded.length / sequence.length) + 1);\n const backward = Array.from(forward).reverse().join(\"\");\n return forward.includes(folded) || backward.includes(folded);\n });\n};\n\nconst isRepetitive = (chars: string[], folded: string): boolean => {\n // Fewer than 4 distinct characters; short inputs need half of theirs.\n if (new Set(chars).size < Math.min(4, Math.ceil(chars.length / 2))) return true;\n if (period(chars) <= chars.length / 3) return true;\n return isSequence(folded);\n};\n\n/** Splits emails (local part only), names and usernames into lowercase parts. */\nexport const personalTokens = (forbidden: readonly unknown[], minTokenLength: number): string[] => {\n const tokens = new Set<string>();\n for (const value of forbidden) {\n if (typeof value !== \"string\") continue;\n const at = value.lastIndexOf(\"@\");\n const source = fold(at > 0 ? value.slice(0, at) : value).trim();\n const parts = [source, ...source.split(/[^\\p{L}\\p{N}]+|(?<=\\p{L})(?=\\p{N})|(?<=\\p{N})(?=\\p{L})/u)];\n for (const part of parts) {\n if (Array.from(part).length >= minTokenLength) tokens.add(part);\n }\n }\n return [...tokens];\n};\n\nconst containsAny = (folded: string, words: readonly string[]): boolean => {\n if (words.length === 0) return false;\n const variants = [folded, deLeet(folded), folded.replace(/[^\\p{L}\\p{N}]/gu, \"\")];\n return words.some((word) => variants.some((variant) => variant.includes(word)));\n};\n\nconst scoreFor = (chars: string[], issues: PasswordIssue[], minLength: number): StrengthScore => {\n if (chars.length === 0) return 0;\n if (issues.length > 0) {\n const severe = issues.some((issue) =>\n [\"password.common\", \"password.blocked\", \"password.repetitive\", \"password.containsPersonalInfo\"].includes(issue),\n );\n return severe || chars.length < minLength / 2 ? 0 : 1;\n }\n let pool = 0;\n const text = chars.join(\"\");\n if (/\\p{Ll}/u.test(text)) pool += 26;\n if (/\\p{Lu}/u.test(text)) pool += 26;\n if (/\\p{Nd}/u.test(text)) pool += 10;\n if (/[\\p{P}\\p{S}\\p{Zs}]/u.test(text)) pool += 33;\n if (/[^\\p{Ll}\\p{Lu}\\p{Nd}\\p{P}\\p{S}\\p{Zs}]/u.test(text)) pool += 100;\n // Repeated characters add little; count each distinct one at most twice.\n const effective = Math.min(chars.length, new Set(chars).size * 2);\n const bits = effective * Math.log2(Math.max(pool, 2));\n return bits >= 100 ? 4 : bits >= 80 ? 3 : 2;\n};\n\nconst evaluateWith = (policy: Policy, input: unknown, options: EvaluateOptions = {}): StrengthResult => {\n const password = typeof input === \"string\" ? normalizePassword(input) : \"\";\n const chars = codePoints(password);\n const folded = fold(password);\n const { require } = policy;\n const issues: PasswordIssue[] = [];\n const checks: PasswordChecks = {\n minLength: chars.length >= policy.minLength,\n maxLength: chars.length <= policy.maxLength,\n notCommon: true,\n notBlocked: true,\n notRepetitive: true,\n notPersonal: true,\n };\n\n if (!checks.minLength) issues.push(\"password.tooShort\");\n if (!checks.maxLength) issues.push(\"password.tooLong\");\n\n if (require.lowercase) checks.lowercase = /\\p{Ll}/u.test(password);\n if (require.uppercase) checks.uppercase = /\\p{Lu}/u.test(password);\n if (require.digit) checks.digit = /\\p{Nd}/u.test(password);\n if (require.symbol) checks.symbol = /[^\\p{L}\\p{N}]/u.test(password);\n if (checks.lowercase === false) issues.push(\"password.noLowercase\");\n if (checks.uppercase === false) issues.push(\"password.noUppercase\");\n if (checks.digit === false) issues.push(\"password.noDigit\");\n if (checks.symbol === false) issues.push(\"password.noSymbol\");\n\n // Far past the limit the answer is \"too long\" anyway; skip the pattern checks on huge input.\n const huge = password.length > policy.maxLength * 4;\n if (chars.length >= 4 && !huge) {\n if (policy.blockCommon) {\n const list = (common ??= new Set(COMMON.split(\" \")));\n checks.notCommon = !commonCores(folded).some((core) => list.has(core));\n }\n checks.notBlocked = !containsAny(folded, policy.blocklist);\n if (policy.blockRepetitive) checks.notRepetitive = !isRepetitive(Array.from(folded), folded);\n }\n if (chars.length > 0 && options.forbidden && !huge) {\n checks.notPersonal = !containsAny(folded, personalTokens(options.forbidden, policy.minTokenLength));\n }\n\n if (!checks.notCommon) issues.push(\"password.common\");\n if (!checks.notBlocked) issues.push(\"password.blocked\");\n if (!checks.notRepetitive) issues.push(\"password.repetitive\");\n if (!checks.notPersonal) issues.push(\"password.containsPersonalInfo\");\n\n const score = scoreFor(chars, issues, policy.minLength);\n return { score, level: LEVELS[score], length: chars.length, valid: issues.length === 0, checks, issues };\n};\n\nconst PRESETS = {\n nist: { minLength: 15, maxLength: 64, require: {} },\n legacy: { minLength: 8, maxLength: 64, require: { lowercase: true, uppercase: true, digit: true, symbol: true } },\n} as const;\n\nconst assertLength = (name: string, value: number) => {\n if (!Number.isInteger(value) || value < 1) throw new RangeError(`definePolicy: ${name} must be a positive integer, got ${value}`);\n};\n\n/** A password policy. Define it once in a shared module and use it in the form and on the server. */\nexport const definePolicy = (options: PolicyOptions = {}): Policy => {\n const preset = options.preset ?? \"nist\";\n const base = PRESETS[preset];\n if (!base) throw new RangeError(`definePolicy: unknown preset \"${String(preset)}\"`);\n const minLength = options.minLength ?? base.minLength;\n const maxLength = options.maxLength ?? base.maxLength;\n const minTokenLength = options.minTokenLength ?? 3;\n assertLength(\"minLength\", minLength);\n assertLength(\"maxLength\", maxLength);\n assertLength(\"minTokenLength\", minTokenLength);\n if (minLength > maxLength) throw new RangeError(`definePolicy: minLength (${minLength}) is greater than maxLength (${maxLength})`);\n const req = { ...base.require, ...options.require };\n const require = {\n lowercase: req.lowercase === true,\n uppercase: req.uppercase === true,\n digit: req.digit === true,\n symbol: req.symbol === true,\n };\n const classes = Object.values(require).filter(Boolean).length;\n if (classes > maxLength) throw new RangeError(`definePolicy: ${classes} required character classes do not fit in maxLength (${maxLength})`);\n\n const policy: Policy = Object.freeze({\n preset,\n minLength,\n maxLength,\n require: Object.freeze(require),\n blockCommon: options.blockCommon ?? true,\n blockRepetitive: options.blockRepetitive ?? true,\n blocklist: Object.freeze((options.blocklist ?? []).map((word) => fold(word).replace(/[^\\p{L}\\p{N}]/gu, \"\")).filter(Boolean)),\n minTokenLength,\n evaluate: (password: string, evaluateOptions?: EvaluateOptions) => evaluateWith(policy, password, evaluateOptions),\n });\n return policy;\n};\n\n/** Scores a password against a policy. Pure; no UI. */\nexport const evaluate = (password: string, policy: Policy, options?: EvaluateOptions): StrengthResult =>\n policy.evaluate(password, options);\n"],"mappings":"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","names":["require"]}