password-manager-autofill-utilities
Version:
TypeScript utilities to control password manager autofill behavior across different password managers
601 lines (589 loc) • 21.3 kB
JavaScript
;
/**
* 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