UNPKG

password-manager-autofill-utilities

Version:

TypeScript utilities to control password manager autofill behavior across different password managers

601 lines (589 loc) 21.3 kB
'use strict'; /** * Supported password manager behaviors */ exports.PasswordManagerBehavior = void 0; (function (PasswordManagerBehavior) { /** Prevent password manager from interacting with the field */ PasswordManagerBehavior["IGNORE"] = "ignore"; /** Allow password manager to interact with the field (default behavior) */ PasswordManagerBehavior["ALLOW"] = "allow"; })(exports.PasswordManagerBehavior || (exports.PasswordManagerBehavior = {})); /** * Supported password managers */ exports.PasswordManager = void 0; (function (PasswordManager) { PasswordManager["ONE_PASSWORD"] = "1password"; PasswordManager["LASTPASS"] = "lastpass"; PasswordManager["BITWARDEN"] = "bitwarden"; PasswordManager["DASHLANE"] = "dashlane"; PasswordManager["BROWSER_AUTOCOMPLETE"] = "browser"; })(exports.PasswordManager || (exports.PasswordManager = {})); /** * Error thrown when password manager configuration is invalid */ class PasswordManagerConfigError extends Error { constructor(message) { super(message); this.name = 'PasswordManagerConfigError'; } } /** * Error thrown when a password manager provider is not found */ class PasswordManagerProviderError extends Error { constructor(message) { super(message); this.name = 'PasswordManagerProviderError'; } } /** * Abstract base class for password manager providers * Provides common functionality and enforces the interface contract */ class BasePasswordManagerProvider { /** * Get attributes for the specified behavior * @param behavior - The desired behavior * @returns HTML attributes to apply */ getAttributes(behavior) { if (!this.supportsBehavior(behavior)) { return {}; } switch (behavior) { case exports.PasswordManagerBehavior.IGNORE: return this.getIgnoreAttributes(); case exports.PasswordManagerBehavior.ALLOW: return this.getAllowAttributes(); default: return {}; } } /** * Check if this provider supports the given behavior * @param behavior - The behavior to check * @returns true if supported */ supportsBehavior(behavior) { // By default, all providers support ignore behavior // Allow behavior is supported if the provider has specific allow attributes switch (behavior) { case exports.PasswordManagerBehavior.IGNORE: return true; case exports.PasswordManagerBehavior.ALLOW: return Object.keys(this.getAllowAttributes()).length > 0; default: return false; } } /** * Get attributes that explicitly allow the password manager to interact with the field * Default implementation returns empty object (no special attributes needed) * Can be overridden by providers that have specific allow attributes */ getAllowAttributes() { return {}; } } /** * 1Password provider implementation * * Uses the data-1p-ignore attribute to prevent autofill. * * @see {@link https://developer.1password.com/docs/web/compatible-website-design/} 1Password Developer Documentation * * @example * ```typescript * const provider = new OnePasswordProvider(); * const attrs = provider.getAttributes(PasswordManagerBehavior.IGNORE); * // Returns: { 'data-1p-ignore': '' } * ``` */ class OnePasswordProvider extends BasePasswordManagerProvider { constructor() { super(...arguments); /** The password manager this provider handles */ this.manager = exports.PasswordManager.ONE_PASSWORD; } /** * Get attributes that prevent 1Password from interacting with the field * * Uses the `data-1p-ignore` attribute as specified in 1Password's documentation. * Alternative attribute `data-op-ignore` is also supported by 1Password but not used here. * * @returns HTML attributes for 1Password ignore behavior * @see {@link https://developer.1password.com/docs/web/compatible-website-design/#ignore-offers-to-save-or-fill-specific-fields} */ getIgnoreAttributes() { return { 'data-1p-ignore': '', }; } } /** * LastPass provider implementation * * Uses the data-lpignore attribute to prevent autofill. * * @example * ```typescript * const provider = new LastPassProvider(); * const attrs = provider.getAttributes(PasswordManagerBehavior.IGNORE); * // Returns: { 'data-lpignore': 'true' } * ``` */ class LastPassProvider extends BasePasswordManagerProvider { constructor() { super(...arguments); /** The password manager this provider handles */ this.manager = exports.PasswordManager.LASTPASS; } /** * Get attributes that prevent LastPass from interacting with the field * * Uses the `data-lpignore="true"` attribute as specified in LastPass documentation. * Note that the value must be "true" (string), not a boolean. * * @returns HTML attributes for LastPass ignore behavior */ getIgnoreAttributes() { return { 'data-lpignore': 'true', }; } } /** * Bitwarden provider implementation * Uses the data-bwignore attribute to prevent autofill */ class BitwardenProvider extends BasePasswordManagerProvider { constructor() { super(...arguments); this.manager = exports.PasswordManager.BITWARDEN; } /** * Get attributes that prevent Bitwarden from interacting with the field * @returns HTML attributes for Bitwarden ignore behavior */ getIgnoreAttributes() { return { 'data-bwignore': '', }; } } /** * Dashlane provider implementation * Uses the data-form-type attribute to prevent autofill */ class DashlaneProvider extends BasePasswordManagerProvider { constructor() { super(...arguments); this.manager = exports.PasswordManager.DASHLANE; } /** * Get attributes that prevent Dashlane from interacting with the field * @returns HTML attributes for Dashlane ignore behavior */ getIgnoreAttributes() { return { 'data-form-type': 'other', }; } } /** * Browser autocomplete provider implementation * Uses the autoComplete attribute to control browser autofill behavior * Note: Uses React's camelCase naming convention (autoComplete) rather than HTML's lowercase (autocomplete) */ class BrowserAutocompleteProvider extends BasePasswordManagerProvider { constructor() { super(...arguments); this.manager = exports.PasswordManager.BROWSER_AUTOCOMPLETE; } /** * Get attributes that prevent browser autocomplete from interacting with the field * @returns HTML attributes for browser autocomplete ignore behavior */ getIgnoreAttributes() { return { autoComplete: 'off', }; } /** * Get attributes that explicitly allow browser autocomplete * @returns HTML attributes for browser autocomplete allow behavior */ getAllowAttributes() { return { autoComplete: 'on', }; } } /** * Registry of all available password manager providers */ const PASSWORD_MANAGER_PROVIDERS = { [exports.PasswordManager.ONE_PASSWORD]: new OnePasswordProvider(), [exports.PasswordManager.LASTPASS]: new LastPassProvider(), [exports.PasswordManager.BITWARDEN]: new BitwardenProvider(), [exports.PasswordManager.DASHLANE]: new DashlaneProvider(), [exports.PasswordManager.BROWSER_AUTOCOMPLETE]: new BrowserAutocompleteProvider(), }; /** * Get a password manager provider by its identifier * @param manager - The password manager identifier * @returns The provider instance */ function getPasswordManagerProvider(manager) { const provider = PASSWORD_MANAGER_PROVIDERS[manager]; if (!provider) { throw new PasswordManagerProviderError(`Unknown password manager: ${manager}`); } return provider; } /** * Get all available password manager providers * @returns Array of all provider instances */ function getAllPasswordManagerProviders() { return Object.values(PASSWORD_MANAGER_PROVIDERS); } /** * Validates a password manager configuration * @param config - Configuration to validate * @throws Error if configuration is invalid */ function validatePasswordManagerConfig(config) { if (!config) { throw new PasswordManagerConfigError('Password manager configuration is required'); } if (!Object.values(exports.PasswordManagerBehavior).includes(config.behavior)) { throw new PasswordManagerConfigError(`Invalid password manager behavior: ${config.behavior}. Must be one of: ${Object.values(exports.PasswordManagerBehavior).join(', ')}`); } if (config.managers) { if (!Array.isArray(config.managers)) { throw new PasswordManagerConfigError('Password managers must be an array'); } if (config.managers.length === 0) { throw new PasswordManagerConfigError('Password managers array cannot be empty. Omit the managers property to target all managers.'); } const invalidManagers = config.managers.filter(manager => !Object.values(exports.PasswordManager).includes(manager)); if (invalidManagers.length > 0) { throw new PasswordManagerConfigError(`Invalid password managers: ${invalidManagers.join(', ')}. Must be one of: ${Object.values(exports.PasswordManager).join(', ')}`); } } } /** * Get HTML attributes for controlling password manager behavior * @param config - Configuration specifying the desired behavior * @returns HTML attributes to apply to form elements * * @example * // Prevent all password managers from autofilling * const attrs = getPasswordManagerAttributes({ * behavior: PasswordManagerBehavior.IGNORE * }); * * @example * // Prevent only specific password managers * const attrs = getPasswordManagerAttributes({ * behavior: PasswordManagerBehavior.IGNORE, * managers: [PasswordManager.ONE_PASSWORD, PasswordManager.LASTPASS] * }); */ function getPasswordManagerAttributes(config) { validatePasswordManagerConfig(config); const { behavior, managers } = config; const attributes = {}; try { // If specific managers are provided, only apply to those if (managers && managers.length > 0) { managers.forEach(manager => { try { const provider = getPasswordManagerProvider(manager); const providerAttrs = provider.getAttributes(behavior); Object.assign(attributes, providerAttrs); } catch (error) { // Log warning but continue with other providers console.warn(`Failed to get attributes for ${manager}:`, error); } }); } else { // Apply to all password managers const providers = getAllPasswordManagerProviders(); providers.forEach(provider => { try { const providerAttrs = provider.getAttributes(behavior); Object.assign(attributes, providerAttrs); } catch (error) { // Log warning but continue with other providers console.warn(`Failed to get attributes for ${provider.manager}:`, error); } }); } } catch (error) { throw new Error(`Failed to generate password manager attributes: ${error instanceof Error ? error.message : 'Unknown error'}`); } return attributes; } /** * Get attributes to prevent all password managers from autofilling * Convenience function for the most common use case * @returns HTML attributes to prevent password manager autofill * * @example * <input {...getPasswordManagerPreventionProps()} /> * <textarea {...getPasswordManagerPreventionProps()} /> */ function getPasswordManagerPreventionProps() { return getPasswordManagerAttributes({ behavior: exports.PasswordManagerBehavior.IGNORE, }); } /** * Merge password manager attributes with existing props * Useful when you need to preserve existing attributes while adding password manager control * @param existingProps - Existing props object * @param config - Password manager configuration * @returns Merged props with password manager attributes * * @example * const props = mergeWithPasswordManagerAttributes( * { className: "my-input", placeholder: "Enter text" }, * { behavior: PasswordManagerBehavior.IGNORE } * ); */ function mergeWithPasswordManagerAttributes(existingProps, config) { const passwordManagerAttrs = getPasswordManagerAttributes(config); return { ...existingProps, ...passwordManagerAttrs, }; } /** * Merge password manager prevention attributes with existing props * Convenience function for the most common use case * @param existingProps - Existing props object * @returns Merged props with password manager prevention attributes * * @example * const props = mergeWithPasswordManagerPrevention({ * className: "my-input", * placeholder: "Enter text" * }); */ function mergeWithPasswordManagerPrevention(existingProps = {}) { return mergeWithPasswordManagerAttributes(existingProps, { behavior: exports.PasswordManagerBehavior.IGNORE, }); } /** * Check if a password manager supports a specific behavior * @param manager - The password manager to check * @param behavior - The behavior to check for support * @returns true if the password manager supports the behavior * * @example * const supportsIgnore = supportsPasswordManagerBehavior( * PasswordManager.ONE_PASSWORD, * PasswordManagerBehavior.IGNORE * ); */ function supportsPasswordManagerBehavior(manager, behavior) { try { const provider = getPasswordManagerProvider(manager); return provider.supportsBehavior(behavior); } catch { return false; } } /** * Get all supported password managers * @returns Array of all supported password manager identifiers */ function getSupportedPasswordManagers() { return Object.values(exports.PasswordManager); } /** * Get all supported behaviors for a specific password manager * @param manager - The password manager to check * @returns Array of supported behaviors */ function getSupportedBehaviors(manager) { try { const provider = getPasswordManagerProvider(manager); return Object.values(exports.PasswordManagerBehavior).filter(behavior => provider.supportsBehavior(behavior)); } catch { return []; } } /** * Get password manager attributes for use in React components * This function can be used in React hooks when React is available * @param config - Password manager configuration * @returns HTML attributes to spread onto form elements * * @example * // In a React component: * function MyInput() { * const attrs = getPasswordManagerControlAttributes({ * behavior: PasswordManagerBehavior.IGNORE * }); * return <input {...attrs} />; * } */ function getPasswordManagerControlAttributes(config) { return getPasswordManagerAttributes(config); } /** * Get password manager prevention attributes for use in React components * Convenience function for the most common use case * @returns HTML attributes to prevent password manager autofill * * @example * // In a React component: * function MyInput() { * const preventionProps = getPasswordManagerPreventionAttributes(); * return <input {...preventionProps} />; * } */ function getPasswordManagerPreventionAttributes() { return getPasswordManagerPreventionProps(); } /** * Create configuration object for password manager control HOCs * This can be used with custom HOC implementations * @param config - Password manager configuration * @returns Configuration object with attributes * * @example * const controlConfig = createPasswordManagerControlConfig({ * behavior: PasswordManagerBehavior.IGNORE * }); * // Use controlConfig.attributes in your HOC implementation */ function createPasswordManagerControlConfig(config) { const attributes = getPasswordManagerAttributes(config); return { config, attributes, }; } /** * Create configuration object for password manager prevention HOCs * Convenience function for the most common use case * @returns Configuration object with prevention attributes */ function createPasswordManagerPreventionConfig() { return createPasswordManagerControlConfig({ behavior: exports.PasswordManagerBehavior.IGNORE, }); } /** * Merge props with password manager attributes * Utility function for React component prop merging * @param existingProps - Existing component props * @param config - Password manager configuration * @returns Merged props with password manager attributes * * @example * function MyInput(props) { * const mergedProps = mergePropsWithPasswordManagerControl(props, { * behavior: PasswordManagerBehavior.IGNORE * }); * return <input {...mergedProps} />; * } */ function mergePropsWithPasswordManagerControl(existingProps, config) { const passwordManagerAttrs = getPasswordManagerAttributes(config); return { ...existingProps, ...passwordManagerAttrs, }; } /** * Merge props with password manager prevention attributes * Convenience function for the most common use case * @param existingProps - Existing component props * @returns Merged props with password manager prevention attributes * * @example * function MyInput(props) { * const mergedProps = mergePropsWithPasswordManagerPrevention(props); * return <input {...mergedProps} />; * } */ function mergePropsWithPasswordManagerPrevention(existingProps) { return mergePropsWithPasswordManagerControl(existingProps, { behavior: exports.PasswordManagerBehavior.IGNORE, }); } /** * React hook for controlling password manager behavior * @param config - Password manager configuration * @returns HTML attributes to spread onto form elements * * @example * function MyInput() { * const attrs = usePasswordManagerControl({ * behavior: PasswordManagerBehavior.IGNORE * }); * return <input {...attrs} type="text" />; * } */ function usePasswordManagerControl(config) { // In a real React environment, this would use useMemo for optimization // For now, we'll return the attributes directly since React is a peer dependency return getPasswordManagerAttributes(config); } /** * React hook for the common use case of preventing password manager autofill * @returns HTML attributes to prevent password manager autofill * * @example * function MyInput() { * const preventionProps = usePasswordManagerPrevention(); * return <input {...preventionProps} type="text" />; * } */ function usePasswordManagerPrevention() { // In a real React environment, this would use useMemo for optimization // For now, we'll return the attributes directly since React is a peer dependency return getPasswordManagerPreventionProps(); } exports.BasePasswordManagerProvider = BasePasswordManagerProvider; exports.BitwardenProvider = BitwardenProvider; exports.BrowserAutocompleteProvider = BrowserAutocompleteProvider; exports.DashlaneProvider = DashlaneProvider; exports.LastPassProvider = LastPassProvider; exports.OnePasswordProvider = OnePasswordProvider; exports.PASSWORD_MANAGER_PREVENTION_ATTRS = getPasswordManagerPreventionProps; exports.PASSWORD_MANAGER_PROVIDERS = PASSWORD_MANAGER_PROVIDERS; exports.PasswordManagerConfigError = PasswordManagerConfigError; exports.PasswordManagerProviderError = PasswordManagerProviderError; exports.createPasswordManagerControlConfig = createPasswordManagerControlConfig; exports.createPasswordManagerPreventionConfig = createPasswordManagerPreventionConfig; exports.getAllPasswordManagerProviders = getAllPasswordManagerProviders; exports.getPasswordManagerAttributes = getPasswordManagerAttributes; exports.getPasswordManagerControlAttributes = getPasswordManagerControlAttributes; exports.getPasswordManagerPreventionAttributes = getPasswordManagerPreventionAttributes; exports.getPasswordManagerPreventionProps = getPasswordManagerPreventionProps; exports.getPasswordManagerProvider = getPasswordManagerProvider; exports.getSupportedBehaviors = getSupportedBehaviors; exports.getSupportedPasswordManagers = getSupportedPasswordManagers; exports.mergePropsWithPasswordManagerControl = mergePropsWithPasswordManagerControl; exports.mergePropsWithPasswordManagerPrevention = mergePropsWithPasswordManagerPrevention; exports.mergeWithPasswordManagerAttributes = mergeWithPasswordManagerAttributes; exports.mergeWithPasswordManagerPrevention = mergeWithPasswordManagerPrevention; exports.supportsPasswordManagerBehavior = supportsPasswordManagerBehavior; exports.usePasswordManagerControl = usePasswordManagerControl; exports.usePasswordManagerPrevention = usePasswordManagerPrevention; //# sourceMappingURL=index.js.map