use-password-policy 1.0.5 → 2.0.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.
- package/README.md +187 -58
- package/dist/index.d.mts +43 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +278 -0
- package/dist/index.mjs +240 -0
- package/package.json +35 -18
- package/Type.ts +0 -14
- package/use-password-policy.ts +0 -112
package/README.md
CHANGED
|
@@ -1,75 +1,204 @@
|
|
|
1
|
-
|
|
1
|
+
# use-password-policy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/use-password-policy)
|
|
4
|
+
[](https://www.npmjs.com/package/use-password-policy)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](http://www.typescriptlang.org/)
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
- Built-in Policies: Enforces common password complexity requirements like minimum length, case sensitivity, digit inclusion, and special characters.
|
|
8
|
+
A powerful, lightweight, and fully customizable solution for real-time password strength validation in React. Comes with a flexible hook and a zero-config, all-in-one UI component.
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
---
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
### [➡️ View Live Demo & Playground](https://rahulpatwa1303.github.io/use-password-policy/)
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
*(Replace this with your final GitHub Pages URL)*
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
|
|
17
|
+
*(**Action Needed:** Record a GIF of your awesome demo and replace this link!)*
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## ✨ Why `use-password-policy`?
|
|
22
|
+
|
|
23
|
+
- **🚀 Two Ways to Use:** Get full control with the `usePasswordPolicy` hook, or get running in seconds with the drop-in `<PasswordPolicyInput />` component.
|
|
24
|
+
- **🔧 Fully Customizable:** Easily configure policies like min-length, character requirements, and even add your own complex rules with custom functions or regex.
|
|
25
|
+
- **💅 Zero-Config Styling:** The UI component works out-of-the-box with self-contained styles, but is easily overridable.
|
|
26
|
+
- **✅ Rich & Reactive Feedback:** Provides a simple `isValid` boolean, a detailed `policyState` object, and a `strengthScore` to easily build any UI you can imagine.
|
|
27
|
+
- **♿ Accessibility First:** The component is designed with accessibility in mind, ready to be paired with a `<label>`.
|
|
28
|
+
- **📦 Tiny & Performant:** Zero dependencies and built with performance in mind, using `useMemo` to prevent unnecessary recalculations.
|
|
29
|
+
|
|
30
|
+
## 💾 Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
15
33
|
npm install use-password-policy
|
|
34
|
+
# or
|
|
35
|
+
yarn add use-password-policy
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 🚀 Usage
|
|
39
|
+
|
|
40
|
+
You have two great ways to implement password validation.
|
|
41
|
+
|
|
42
|
+
### 1. The Easy Way: `<PasswordPolicyInput />` Component
|
|
43
|
+
|
|
44
|
+
For maximum speed, drop the component directly into your form. It includes the input, strength meter, and requirements list all-in-one.
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { PasswordPolicyInput } from 'use-password-policy';
|
|
48
|
+
|
|
49
|
+
function MyForm() {
|
|
50
|
+
const [isValid, setIsValid] = useState(false);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<form>
|
|
54
|
+
<label htmlFor="signup-password">Create a Password</label>
|
|
55
|
+
<PasswordPolicyInput
|
|
56
|
+
id="signup-password"
|
|
57
|
+
name="password"
|
|
58
|
+
placeholder="Enter a secure password..."
|
|
59
|
+
onPasswordChange={(_, validation) => {
|
|
60
|
+
setIsValid(validation.isValid);
|
|
61
|
+
}}
|
|
62
|
+
policyOptions={{ minLength: 8, numberCheck: true, specialCharCheck: true }}
|
|
63
|
+
/>
|
|
64
|
+
<button type="submit" disabled={!isValid}>
|
|
65
|
+
Sign Up
|
|
66
|
+
</button>
|
|
67
|
+
</form>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
16
70
|
```
|
|
17
71
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
### 2. The Powerful Way: `usePasswordPolicy` Hook
|
|
73
|
+
|
|
74
|
+
For complete control over your UI, use the hook and build your own components.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { usePasswordPolicy } from 'use-password-policy';
|
|
78
|
+
|
|
79
|
+
function MyCustomForm() {
|
|
80
|
+
const [password, setPassword] = useState('');
|
|
81
|
+
const { isValid, strengthLabel, policyState } = usePasswordPolicy({
|
|
82
|
+
password: password,
|
|
83
|
+
minLength: 10,
|
|
84
|
+
uppercaseCheck: true,
|
|
85
|
+
customRules: [{ name: 'noSpaces', test: (p) => !/\\s/.test(p) }],
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<form>
|
|
90
|
+
<input
|
|
91
|
+
type="password"
|
|
92
|
+
value={password}
|
|
93
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
94
|
+
/>
|
|
95
|
+
<div>Strength: {strengthLabel}</div>
|
|
96
|
+
<ul>
|
|
97
|
+
{Object.entries(policyState).map(([rule, passed]) => (
|
|
98
|
+
<li key={rule} style={{ color: passed ? 'green' : 'red' }}>
|
|
99
|
+
{rule}
|
|
100
|
+
</li>
|
|
101
|
+
))}
|
|
102
|
+
</ul>
|
|
103
|
+
<button type="submit" disabled={!isValid}>
|
|
104
|
+
Submit
|
|
105
|
+
</button>
|
|
106
|
+
</form>
|
|
107
|
+
);
|
|
49
108
|
}
|
|
50
109
|
```
|
|
51
110
|
|
|
52
|
-
|
|
111
|
+
## 📖 API Reference
|
|
112
|
+
|
|
113
|
+
### `<PasswordPolicyInput />` Props
|
|
114
|
+
|
|
115
|
+
| Prop | Type | Default | Description |
|
|
116
|
+
| ---------------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------------------------------- |
|
|
117
|
+
| `policyOptions` | `PasswordPolicyOptions` | `{}` | Same options as the `usePasswordPolicy` hook to control validation logic. |
|
|
118
|
+
| `onPasswordChange` | `(password: string, validation: HookReturnValue) => void` | `null` | Callback fired on change, providing the password and full validation state. |
|
|
119
|
+
| `showStrengthMeter` | `boolean` | `true` | Toggles the visibility of the strength meter bar. |
|
|
120
|
+
| `showRequirementsList` | `boolean` | `true` | Toggles the visibility of the pass/fail requirements list. |
|
|
121
|
+
| `showToggleButton` | `boolean` | `true` | Toggles the visibility of the show/hide password button. |
|
|
122
|
+
| `...restInputProps` | `React.InputHTMLAttributes` | | All other standard input props (`id`, `name`, `placeholder`, etc.) are passed to the `<input>`. |
|
|
53
123
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
| `
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
124
|
+
<br/>
|
|
125
|
+
|
|
126
|
+
### `usePasswordPolicy` Hook
|
|
127
|
+
|
|
128
|
+
#### Options (`PasswordPolicyOptions`)
|
|
129
|
+
|
|
130
|
+
| Prop | Type | Default | Description |
|
|
131
|
+
| ---------------------- | -------------- | ---------- | ----------------------------------------------------------- |
|
|
132
|
+
| `password` | `string` | `''` | The password string to validate. |
|
|
133
|
+
| `minLength` | `number` | `8` | Minimum password length. |
|
|
134
|
+
| `lowercaseCheck` | `boolean` | `true` | Requires at least one lowercase letter. |
|
|
135
|
+
| `uppercaseCheck` | `boolean` | `true` | Requires at least one uppercase letter. |
|
|
136
|
+
| `numberCheck` | `boolean` | `true` | Requires at least one number. |
|
|
137
|
+
| `specialCharCheck` | `boolean` | `true` | Requires at least one special character. |
|
|
138
|
+
| `customRules` | `PolicyRule[]` | `[]` | An array of custom validation rules. |
|
|
139
|
+
|
|
140
|
+
#### Return Value (`HookReturnValue`)
|
|
141
|
+
|
|
142
|
+
| Key | Type | Description |
|
|
143
|
+
| --------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------- |
|
|
144
|
+
| `isValid` | `boolean` | `true` only if all active policies are met. |
|
|
145
|
+
| `strengthScore` | `number` | The number of policies that have passed. |
|
|
146
|
+
| `strengthLabel` | `'Very Weak' \| 'Weak' \| 'Medium' \| 'Strong' \| 'Very Strong'` | A human-readable strength label. |
|
|
147
|
+
| `policyState` | `object` | An object with boolean flags for each active policy (`{ minLength: true, uppercase: false, ... }`). |
|
|
148
|
+
|
|
149
|
+
## 🎨 Customizing Styles
|
|
150
|
+
|
|
151
|
+
The `<PasswordPolicyInput />` component is built with `styled-components` for complete style isolation and easy customization. You have two primary ways to apply your own styles:
|
|
152
|
+
|
|
153
|
+
### 1. Theming with `styled()`
|
|
154
|
+
|
|
155
|
+
For deep customization, wrap the component with `styled()` from `styled-components`. You can easily change the theme by overriding the internal CSS variables, or target any internal element for specific changes.
|
|
156
|
+
|
|
157
|
+
```jsx
|
|
158
|
+
import styled from 'styled-components';
|
|
159
|
+
import { PasswordPolicyInput } from 'use-password-policy';
|
|
160
|
+
|
|
161
|
+
const MyStyledInput = styled(PasswordPolicyInput)`
|
|
162
|
+
/* Override theme variables */
|
|
163
|
+
--rpp-accent: #ff6347; // Use a tomato red accent
|
|
164
|
+
|
|
165
|
+
/* Override specific elements */
|
|
166
|
+
input {
|
|
167
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
// Then use <MyStyledInput /> in your app!
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Available CSS Variables for Theming:**
|
|
175
|
+
|
|
176
|
+
| Variable | Default | Description |
|
|
177
|
+
| ----------------- | ---------- | ---------------------------------- |
|
|
178
|
+
| `--rpp-accent` | `#646cff` | Accent color for focus, buttons. |
|
|
179
|
+
| `--rpp-success` | `#27ae60` | Color for passed requirements. |
|
|
180
|
+
| `--rpp-danger` | `#c0392b` | Color for failed requirements. |
|
|
181
|
+
| `--rpp-weak` | `#f39c12` | Strength meter color for weak. |
|
|
182
|
+
| `--rpp-medium` | `#d35400` | Strength meter color for medium. |
|
|
183
|
+
| `--rpp-bg` | `#f9f9f9` | Component's background color. |
|
|
184
|
+
| `--rpp-border` | `#e0e0e0` | Component's border color. |
|
|
185
|
+
| `--rpp-text` | `#333` | Component's main text color. |
|
|
186
|
+
|
|
187
|
+
### 2. Applying Custom Class Names
|
|
188
|
+
|
|
189
|
+
To apply layout styles (like margins or flex properties), simply pass a `className`. This works perfectly with utility-class frameworks like Tailwind CSS.
|
|
190
|
+
|
|
191
|
+
```jsx
|
|
192
|
+
import { PasswordPolicyInput } from 'use-password-policy';
|
|
193
|
+
|
|
194
|
+
// Example with Tailwind CSS or a custom utility class
|
|
195
|
+
<PasswordPolicyInput className="mb-4 w-full" />
|
|
196
|
+
```
|
|
66
197
|
|
|
67
|
-
|
|
198
|
+
## ❤️ Contributing
|
|
68
199
|
|
|
69
|
-
|
|
200
|
+
Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://github.com/rahulpatwa1303/use-password-policy/issues).
|
|
70
201
|
|
|
71
|
-
|
|
202
|
+
## 📄 License
|
|
72
203
|
|
|
73
|
-
|
|
74
|
-
- **Enhanced User Experience:** Provides clear feedback to users on password strength, guiding them towards creating secure passwords.
|
|
75
|
-
- **Customization:** Adapts to your specific security requirements through configurable defaults and custom policies.
|
|
204
|
+
This project is [MIT](https://github.com/rahulpatwa1303/use-password-policy/blob/main/LICENSE) licensed.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface PolicyRule {
|
|
4
|
+
name: string;
|
|
5
|
+
optionsKey?: keyof PasswordPolicyOptions;
|
|
6
|
+
test: (password: string, options: Required<PasswordPolicyOptions>) => boolean;
|
|
7
|
+
}
|
|
8
|
+
interface PasswordPolicyOptions {
|
|
9
|
+
password?: string;
|
|
10
|
+
minLength?: number;
|
|
11
|
+
lowercaseCheck?: boolean;
|
|
12
|
+
uppercaseCheck?: boolean;
|
|
13
|
+
numberCheck?: boolean;
|
|
14
|
+
specialCharCheck?: boolean;
|
|
15
|
+
customRules?: PolicyRule[];
|
|
16
|
+
lowercaseRegex?: RegExp;
|
|
17
|
+
uppercaseRegex?: RegExp;
|
|
18
|
+
numberRegex?: RegExp;
|
|
19
|
+
specialCharRegex?: RegExp;
|
|
20
|
+
}
|
|
21
|
+
interface PasswordPolicyState {
|
|
22
|
+
[key: string]: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface HookReturnValue {
|
|
25
|
+
password?: string;
|
|
26
|
+
isValid: boolean;
|
|
27
|
+
strengthScore: number;
|
|
28
|
+
strengthLabel: 'Very Weak' | 'Weak' | 'Medium' | 'Strong' | 'Very Strong';
|
|
29
|
+
policyState: PasswordPolicyState;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare const usePasswordPolicy: (options?: PasswordPolicyOptions) => HookReturnValue;
|
|
33
|
+
|
|
34
|
+
interface PasswordPolicyInputProps extends React.ComponentPropsWithoutRef<'input'> {
|
|
35
|
+
policyOptions?: PasswordPolicyOptions;
|
|
36
|
+
onPasswordChange?: (password: string, validation: HookReturnValue) => void;
|
|
37
|
+
showStrengthMeter?: boolean;
|
|
38
|
+
showRequirementsList?: boolean;
|
|
39
|
+
showToggleButton?: boolean;
|
|
40
|
+
}
|
|
41
|
+
declare const PasswordPolicyInput: FC<PasswordPolicyInputProps>;
|
|
42
|
+
|
|
43
|
+
export { type HookReturnValue, PasswordPolicyInput, type PasswordPolicyInputProps, type PasswordPolicyOptions, type PasswordPolicyState, type PolicyRule, usePasswordPolicy };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface PolicyRule {
|
|
4
|
+
name: string;
|
|
5
|
+
optionsKey?: keyof PasswordPolicyOptions;
|
|
6
|
+
test: (password: string, options: Required<PasswordPolicyOptions>) => boolean;
|
|
7
|
+
}
|
|
8
|
+
interface PasswordPolicyOptions {
|
|
9
|
+
password?: string;
|
|
10
|
+
minLength?: number;
|
|
11
|
+
lowercaseCheck?: boolean;
|
|
12
|
+
uppercaseCheck?: boolean;
|
|
13
|
+
numberCheck?: boolean;
|
|
14
|
+
specialCharCheck?: boolean;
|
|
15
|
+
customRules?: PolicyRule[];
|
|
16
|
+
lowercaseRegex?: RegExp;
|
|
17
|
+
uppercaseRegex?: RegExp;
|
|
18
|
+
numberRegex?: RegExp;
|
|
19
|
+
specialCharRegex?: RegExp;
|
|
20
|
+
}
|
|
21
|
+
interface PasswordPolicyState {
|
|
22
|
+
[key: string]: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface HookReturnValue {
|
|
25
|
+
password?: string;
|
|
26
|
+
isValid: boolean;
|
|
27
|
+
strengthScore: number;
|
|
28
|
+
strengthLabel: 'Very Weak' | 'Weak' | 'Medium' | 'Strong' | 'Very Strong';
|
|
29
|
+
policyState: PasswordPolicyState;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare const usePasswordPolicy: (options?: PasswordPolicyOptions) => HookReturnValue;
|
|
33
|
+
|
|
34
|
+
interface PasswordPolicyInputProps extends React.ComponentPropsWithoutRef<'input'> {
|
|
35
|
+
policyOptions?: PasswordPolicyOptions;
|
|
36
|
+
onPasswordChange?: (password: string, validation: HookReturnValue) => void;
|
|
37
|
+
showStrengthMeter?: boolean;
|
|
38
|
+
showRequirementsList?: boolean;
|
|
39
|
+
showToggleButton?: boolean;
|
|
40
|
+
}
|
|
41
|
+
declare const PasswordPolicyInput: FC<PasswordPolicyInputProps>;
|
|
42
|
+
|
|
43
|
+
export { type HookReturnValue, PasswordPolicyInput, type PasswordPolicyInputProps, type PasswordPolicyOptions, type PasswordPolicyState, type PolicyRule, usePasswordPolicy };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
PasswordPolicyInput: () => PasswordPolicyInput,
|
|
34
|
+
usePasswordPolicy: () => usePasswordPolicy
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/use-password-policy.ts
|
|
39
|
+
var import_react = require("react");
|
|
40
|
+
var DEFAULT_POLICIES = [
|
|
41
|
+
{
|
|
42
|
+
name: "minLength",
|
|
43
|
+
optionsKey: "minLength",
|
|
44
|
+
test: (password, options) => password.length >= options.minLength
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "uppercase",
|
|
48
|
+
optionsKey: "uppercaseCheck",
|
|
49
|
+
test: (password, options) => options.uppercaseRegex.test(password)
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "lowercase",
|
|
53
|
+
optionsKey: "lowercaseCheck",
|
|
54
|
+
test: (password, options) => options.lowercaseRegex.test(password)
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "number",
|
|
58
|
+
optionsKey: "numberCheck",
|
|
59
|
+
test: (password, options) => options.numberRegex.test(password)
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "specialChar",
|
|
63
|
+
optionsKey: "specialCharCheck",
|
|
64
|
+
test: (password, options) => options.specialCharRegex.test(password)
|
|
65
|
+
}
|
|
66
|
+
];
|
|
67
|
+
var DEFAULT_OPTIONS = {
|
|
68
|
+
minLength: 8,
|
|
69
|
+
lowercaseCheck: true,
|
|
70
|
+
uppercaseCheck: true,
|
|
71
|
+
numberCheck: true,
|
|
72
|
+
specialCharCheck: true,
|
|
73
|
+
customRules: [],
|
|
74
|
+
lowercaseRegex: /[a-z]/,
|
|
75
|
+
uppercaseRegex: /[A-Z]/,
|
|
76
|
+
numberRegex: /\d/,
|
|
77
|
+
specialCharRegex: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/
|
|
78
|
+
};
|
|
79
|
+
var usePasswordPolicy = (options = {}) => {
|
|
80
|
+
const { password = "" } = options;
|
|
81
|
+
const mergedOptions = (0, import_react.useMemo)(
|
|
82
|
+
() => ({ ...DEFAULT_OPTIONS, ...options }),
|
|
83
|
+
[options]
|
|
84
|
+
);
|
|
85
|
+
const activePolicies = (0, import_react.useMemo)(() => {
|
|
86
|
+
const activeDefaultPolicies = DEFAULT_POLICIES.filter(
|
|
87
|
+
(policy) => policy.optionsKey && mergedOptions[policy.optionsKey]
|
|
88
|
+
);
|
|
89
|
+
return [...activeDefaultPolicies, ...mergedOptions.customRules];
|
|
90
|
+
}, [mergedOptions]);
|
|
91
|
+
const policyState = (0, import_react.useMemo)(() => {
|
|
92
|
+
const state = {};
|
|
93
|
+
for (const policy of activePolicies) {
|
|
94
|
+
state[policy.name] = policy.test(password, mergedOptions);
|
|
95
|
+
}
|
|
96
|
+
return state;
|
|
97
|
+
}, [password, activePolicies, mergedOptions]);
|
|
98
|
+
const { score, label, isValid } = (0, import_react.useMemo)(() => {
|
|
99
|
+
const passedPolicies = Object.values(policyState).filter(Boolean);
|
|
100
|
+
const score2 = passedPolicies.length;
|
|
101
|
+
const totalPolicies = activePolicies.length;
|
|
102
|
+
const strengthPercentage = totalPolicies > 0 ? score2 / totalPolicies : 0;
|
|
103
|
+
let label2 = "Very Weak";
|
|
104
|
+
if (strengthPercentage >= 1) {
|
|
105
|
+
label2 = "Very Strong";
|
|
106
|
+
} else if (strengthPercentage >= 0.75) {
|
|
107
|
+
label2 = "Strong";
|
|
108
|
+
} else if (strengthPercentage >= 0.5) {
|
|
109
|
+
label2 = "Medium";
|
|
110
|
+
} else if (strengthPercentage > 0) {
|
|
111
|
+
label2 = "Weak";
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
score: score2,
|
|
115
|
+
label: label2,
|
|
116
|
+
isValid: score2 === totalPolicies && totalPolicies > 0
|
|
117
|
+
};
|
|
118
|
+
}, [policyState, activePolicies]);
|
|
119
|
+
return {
|
|
120
|
+
password,
|
|
121
|
+
policyState,
|
|
122
|
+
isValid,
|
|
123
|
+
strengthScore: score,
|
|
124
|
+
strengthLabel: label
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/PasswordPolicyInput.tsx
|
|
129
|
+
var import_react2 = require("react");
|
|
130
|
+
var import_styled_components = __toESM(require("styled-components"));
|
|
131
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
132
|
+
var STRENGTH_COLOR_MAP = {
|
|
133
|
+
"Very Weak": "var(--rpp-weak)",
|
|
134
|
+
"Weak": "var(--rpp-weak)",
|
|
135
|
+
"Medium": "var(--rpp-medium)",
|
|
136
|
+
"Strong": "var(--rpp-success)",
|
|
137
|
+
"Very Strong": "var(--rpp-success)"
|
|
138
|
+
};
|
|
139
|
+
var Container = import_styled_components.default.div`
|
|
140
|
+
/* CSS variables define the component's internal theme */
|
|
141
|
+
--rpp-accent: #646cff;
|
|
142
|
+
--rpp-success: #27ae60;
|
|
143
|
+
--rpp-danger: #c0392b;
|
|
144
|
+
--rpp-weak: #f39c12;
|
|
145
|
+
--rpp-medium: #d35400;
|
|
146
|
+
--rpp-bg: #f9f9f9;
|
|
147
|
+
--rpp-border: #e0e0e0;
|
|
148
|
+
--rpp-text: #333;
|
|
149
|
+
|
|
150
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
gap: 0.75rem;
|
|
154
|
+
`;
|
|
155
|
+
var InputWrapper = import_styled_components.default.div`
|
|
156
|
+
position: relative;
|
|
157
|
+
`;
|
|
158
|
+
var Input = import_styled_components.default.input`
|
|
159
|
+
box-sizing: border-box;
|
|
160
|
+
width: 100%;
|
|
161
|
+
padding: 0.75rem;
|
|
162
|
+
border: 1px solid var(--rpp-border);
|
|
163
|
+
border-radius: 6px;
|
|
164
|
+
font-size: 1rem;
|
|
165
|
+
|
|
166
|
+
&:focus {
|
|
167
|
+
border-color: var(--rpp-accent);
|
|
168
|
+
outline: none;
|
|
169
|
+
}
|
|
170
|
+
`;
|
|
171
|
+
var ToggleButton = import_styled_components.default.button`
|
|
172
|
+
position: absolute;
|
|
173
|
+
top: 50%;
|
|
174
|
+
right: 0.5rem;
|
|
175
|
+
transform: translateY(-50%);
|
|
176
|
+
background: none;
|
|
177
|
+
border: none;
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
padding: 0.5rem;
|
|
180
|
+
color: #888;
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
`;
|
|
184
|
+
var StrengthMeter = import_styled_components.default.div`
|
|
185
|
+
display: flex;
|
|
186
|
+
gap: 0.25rem;
|
|
187
|
+
height: 6px;
|
|
188
|
+
`;
|
|
189
|
+
var StrengthMeterSegment = import_styled_components.default.div`
|
|
190
|
+
flex: 1;
|
|
191
|
+
background-color: var(--rpp-border);
|
|
192
|
+
border-radius: 3px;
|
|
193
|
+
transition: background-color 0.3s;
|
|
194
|
+
|
|
195
|
+
${({ isFilled, strengthLabel }) => (
|
|
196
|
+
// FIX: Explicitly type props
|
|
197
|
+
isFilled && import_styled_components.css`
|
|
198
|
+
background-color: ${STRENGTH_COLOR_MAP[strengthLabel]};
|
|
199
|
+
`
|
|
200
|
+
)}
|
|
201
|
+
`;
|
|
202
|
+
var RequirementsList = import_styled_components.default.ul`
|
|
203
|
+
list-style: none;
|
|
204
|
+
padding: 0;
|
|
205
|
+
margin: 0;
|
|
206
|
+
display: flex;
|
|
207
|
+
flex-direction: column;
|
|
208
|
+
gap: 0.5rem;
|
|
209
|
+
font-size: 0.875rem;
|
|
210
|
+
`;
|
|
211
|
+
var RequirementItem = import_styled_components.default.li`
|
|
212
|
+
display: flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
gap: 0.5rem;
|
|
215
|
+
transition: color 0.2s;
|
|
216
|
+
/* FIX: Explicitly type props */
|
|
217
|
+
color: ${({ passed }) => passed ? "var(--rpp-success)" : "var(--rpp-danger)"};
|
|
218
|
+
`;
|
|
219
|
+
var EyeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
220
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
|
|
221
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "3" })
|
|
222
|
+
] });
|
|
223
|
+
var EyeOffIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
224
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" }),
|
|
225
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "1", y1: "1", x2: "23", y2: "23" })
|
|
226
|
+
] });
|
|
227
|
+
var PasswordPolicyInput = ({
|
|
228
|
+
policyOptions,
|
|
229
|
+
onPasswordChange,
|
|
230
|
+
showStrengthMeter = true,
|
|
231
|
+
showRequirementsList = true,
|
|
232
|
+
showToggleButton = true,
|
|
233
|
+
className,
|
|
234
|
+
...restInputProps
|
|
235
|
+
}) => {
|
|
236
|
+
const [password, setPassword] = (0, import_react2.useState)("");
|
|
237
|
+
const [showPassword, setShowPassword] = (0, import_react2.useState)(false);
|
|
238
|
+
const validation = usePasswordPolicy({ ...policyOptions, password });
|
|
239
|
+
const { policyState, strengthScore, strengthLabel } = validation;
|
|
240
|
+
(0, import_react2.useEffect)(() => {
|
|
241
|
+
onPasswordChange?.(password, validation);
|
|
242
|
+
}, [password, validation, onPasswordChange]);
|
|
243
|
+
const formatPolicyName = (name) => name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
|
|
244
|
+
const handlePasswordChange = (e) => {
|
|
245
|
+
setPassword(e.target.value);
|
|
246
|
+
};
|
|
247
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Container, { className, children: [
|
|
248
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InputWrapper, { children: [
|
|
249
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
250
|
+
Input,
|
|
251
|
+
{
|
|
252
|
+
type: showPassword ? "text" : "password",
|
|
253
|
+
value: password,
|
|
254
|
+
onChange: handlePasswordChange,
|
|
255
|
+
...restInputProps
|
|
256
|
+
}
|
|
257
|
+
),
|
|
258
|
+
showToggleButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToggleButton, { type: "button", onClick: () => setShowPassword(!showPassword), children: showPassword ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EyeOffIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EyeIcon, {}) })
|
|
259
|
+
] }),
|
|
260
|
+
showStrengthMeter && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StrengthMeter, { children: Array.from({ length: 5 }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
261
|
+
StrengthMeterSegment,
|
|
262
|
+
{
|
|
263
|
+
isFilled: strengthScore > index,
|
|
264
|
+
strengthLabel
|
|
265
|
+
},
|
|
266
|
+
index
|
|
267
|
+
)) }),
|
|
268
|
+
showRequirementsList && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequirementsList, { children: Object.entries(policyState).map(([name, passed]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RequirementItem, { passed, children: [
|
|
269
|
+
passed ? "\u2713" : "\u2717",
|
|
270
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatPolicyName(name) })
|
|
271
|
+
] }, name)) })
|
|
272
|
+
] });
|
|
273
|
+
};
|
|
274
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
275
|
+
0 && (module.exports = {
|
|
276
|
+
PasswordPolicyInput,
|
|
277
|
+
usePasswordPolicy
|
|
278
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// src/use-password-policy.ts
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
var DEFAULT_POLICIES = [
|
|
4
|
+
{
|
|
5
|
+
name: "minLength",
|
|
6
|
+
optionsKey: "minLength",
|
|
7
|
+
test: (password, options) => password.length >= options.minLength
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
name: "uppercase",
|
|
11
|
+
optionsKey: "uppercaseCheck",
|
|
12
|
+
test: (password, options) => options.uppercaseRegex.test(password)
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "lowercase",
|
|
16
|
+
optionsKey: "lowercaseCheck",
|
|
17
|
+
test: (password, options) => options.lowercaseRegex.test(password)
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "number",
|
|
21
|
+
optionsKey: "numberCheck",
|
|
22
|
+
test: (password, options) => options.numberRegex.test(password)
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "specialChar",
|
|
26
|
+
optionsKey: "specialCharCheck",
|
|
27
|
+
test: (password, options) => options.specialCharRegex.test(password)
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
var DEFAULT_OPTIONS = {
|
|
31
|
+
minLength: 8,
|
|
32
|
+
lowercaseCheck: true,
|
|
33
|
+
uppercaseCheck: true,
|
|
34
|
+
numberCheck: true,
|
|
35
|
+
specialCharCheck: true,
|
|
36
|
+
customRules: [],
|
|
37
|
+
lowercaseRegex: /[a-z]/,
|
|
38
|
+
uppercaseRegex: /[A-Z]/,
|
|
39
|
+
numberRegex: /\d/,
|
|
40
|
+
specialCharRegex: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/
|
|
41
|
+
};
|
|
42
|
+
var usePasswordPolicy = (options = {}) => {
|
|
43
|
+
const { password = "" } = options;
|
|
44
|
+
const mergedOptions = useMemo(
|
|
45
|
+
() => ({ ...DEFAULT_OPTIONS, ...options }),
|
|
46
|
+
[options]
|
|
47
|
+
);
|
|
48
|
+
const activePolicies = useMemo(() => {
|
|
49
|
+
const activeDefaultPolicies = DEFAULT_POLICIES.filter(
|
|
50
|
+
(policy) => policy.optionsKey && mergedOptions[policy.optionsKey]
|
|
51
|
+
);
|
|
52
|
+
return [...activeDefaultPolicies, ...mergedOptions.customRules];
|
|
53
|
+
}, [mergedOptions]);
|
|
54
|
+
const policyState = useMemo(() => {
|
|
55
|
+
const state = {};
|
|
56
|
+
for (const policy of activePolicies) {
|
|
57
|
+
state[policy.name] = policy.test(password, mergedOptions);
|
|
58
|
+
}
|
|
59
|
+
return state;
|
|
60
|
+
}, [password, activePolicies, mergedOptions]);
|
|
61
|
+
const { score, label, isValid } = useMemo(() => {
|
|
62
|
+
const passedPolicies = Object.values(policyState).filter(Boolean);
|
|
63
|
+
const score2 = passedPolicies.length;
|
|
64
|
+
const totalPolicies = activePolicies.length;
|
|
65
|
+
const strengthPercentage = totalPolicies > 0 ? score2 / totalPolicies : 0;
|
|
66
|
+
let label2 = "Very Weak";
|
|
67
|
+
if (strengthPercentage >= 1) {
|
|
68
|
+
label2 = "Very Strong";
|
|
69
|
+
} else if (strengthPercentage >= 0.75) {
|
|
70
|
+
label2 = "Strong";
|
|
71
|
+
} else if (strengthPercentage >= 0.5) {
|
|
72
|
+
label2 = "Medium";
|
|
73
|
+
} else if (strengthPercentage > 0) {
|
|
74
|
+
label2 = "Weak";
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
score: score2,
|
|
78
|
+
label: label2,
|
|
79
|
+
isValid: score2 === totalPolicies && totalPolicies > 0
|
|
80
|
+
};
|
|
81
|
+
}, [policyState, activePolicies]);
|
|
82
|
+
return {
|
|
83
|
+
password,
|
|
84
|
+
policyState,
|
|
85
|
+
isValid,
|
|
86
|
+
strengthScore: score,
|
|
87
|
+
strengthLabel: label
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/PasswordPolicyInput.tsx
|
|
92
|
+
import { useState, useEffect } from "react";
|
|
93
|
+
import styled, { css } from "styled-components";
|
|
94
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
95
|
+
var STRENGTH_COLOR_MAP = {
|
|
96
|
+
"Very Weak": "var(--rpp-weak)",
|
|
97
|
+
"Weak": "var(--rpp-weak)",
|
|
98
|
+
"Medium": "var(--rpp-medium)",
|
|
99
|
+
"Strong": "var(--rpp-success)",
|
|
100
|
+
"Very Strong": "var(--rpp-success)"
|
|
101
|
+
};
|
|
102
|
+
var Container = styled.div`
|
|
103
|
+
/* CSS variables define the component's internal theme */
|
|
104
|
+
--rpp-accent: #646cff;
|
|
105
|
+
--rpp-success: #27ae60;
|
|
106
|
+
--rpp-danger: #c0392b;
|
|
107
|
+
--rpp-weak: #f39c12;
|
|
108
|
+
--rpp-medium: #d35400;
|
|
109
|
+
--rpp-bg: #f9f9f9;
|
|
110
|
+
--rpp-border: #e0e0e0;
|
|
111
|
+
--rpp-text: #333;
|
|
112
|
+
|
|
113
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-direction: column;
|
|
116
|
+
gap: 0.75rem;
|
|
117
|
+
`;
|
|
118
|
+
var InputWrapper = styled.div`
|
|
119
|
+
position: relative;
|
|
120
|
+
`;
|
|
121
|
+
var Input = styled.input`
|
|
122
|
+
box-sizing: border-box;
|
|
123
|
+
width: 100%;
|
|
124
|
+
padding: 0.75rem;
|
|
125
|
+
border: 1px solid var(--rpp-border);
|
|
126
|
+
border-radius: 6px;
|
|
127
|
+
font-size: 1rem;
|
|
128
|
+
|
|
129
|
+
&:focus {
|
|
130
|
+
border-color: var(--rpp-accent);
|
|
131
|
+
outline: none;
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
var ToggleButton = styled.button`
|
|
135
|
+
position: absolute;
|
|
136
|
+
top: 50%;
|
|
137
|
+
right: 0.5rem;
|
|
138
|
+
transform: translateY(-50%);
|
|
139
|
+
background: none;
|
|
140
|
+
border: none;
|
|
141
|
+
cursor: pointer;
|
|
142
|
+
padding: 0.5rem;
|
|
143
|
+
color: #888;
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
`;
|
|
147
|
+
var StrengthMeter = styled.div`
|
|
148
|
+
display: flex;
|
|
149
|
+
gap: 0.25rem;
|
|
150
|
+
height: 6px;
|
|
151
|
+
`;
|
|
152
|
+
var StrengthMeterSegment = styled.div`
|
|
153
|
+
flex: 1;
|
|
154
|
+
background-color: var(--rpp-border);
|
|
155
|
+
border-radius: 3px;
|
|
156
|
+
transition: background-color 0.3s;
|
|
157
|
+
|
|
158
|
+
${({ isFilled, strengthLabel }) => (
|
|
159
|
+
// FIX: Explicitly type props
|
|
160
|
+
isFilled && css`
|
|
161
|
+
background-color: ${STRENGTH_COLOR_MAP[strengthLabel]};
|
|
162
|
+
`
|
|
163
|
+
)}
|
|
164
|
+
`;
|
|
165
|
+
var RequirementsList = styled.ul`
|
|
166
|
+
list-style: none;
|
|
167
|
+
padding: 0;
|
|
168
|
+
margin: 0;
|
|
169
|
+
display: flex;
|
|
170
|
+
flex-direction: column;
|
|
171
|
+
gap: 0.5rem;
|
|
172
|
+
font-size: 0.875rem;
|
|
173
|
+
`;
|
|
174
|
+
var RequirementItem = styled.li`
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
gap: 0.5rem;
|
|
178
|
+
transition: color 0.2s;
|
|
179
|
+
/* FIX: Explicitly type props */
|
|
180
|
+
color: ${({ passed }) => passed ? "var(--rpp-success)" : "var(--rpp-danger)"};
|
|
181
|
+
`;
|
|
182
|
+
var EyeIcon = () => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
183
|
+
/* @__PURE__ */ jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
|
|
184
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
|
|
185
|
+
] });
|
|
186
|
+
var EyeOffIcon = () => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
187
|
+
/* @__PURE__ */ jsx("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" }),
|
|
188
|
+
/* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23" })
|
|
189
|
+
] });
|
|
190
|
+
var PasswordPolicyInput = ({
|
|
191
|
+
policyOptions,
|
|
192
|
+
onPasswordChange,
|
|
193
|
+
showStrengthMeter = true,
|
|
194
|
+
showRequirementsList = true,
|
|
195
|
+
showToggleButton = true,
|
|
196
|
+
className,
|
|
197
|
+
...restInputProps
|
|
198
|
+
}) => {
|
|
199
|
+
const [password, setPassword] = useState("");
|
|
200
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
201
|
+
const validation = usePasswordPolicy({ ...policyOptions, password });
|
|
202
|
+
const { policyState, strengthScore, strengthLabel } = validation;
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
onPasswordChange?.(password, validation);
|
|
205
|
+
}, [password, validation, onPasswordChange]);
|
|
206
|
+
const formatPolicyName = (name) => name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
|
|
207
|
+
const handlePasswordChange = (e) => {
|
|
208
|
+
setPassword(e.target.value);
|
|
209
|
+
};
|
|
210
|
+
return /* @__PURE__ */ jsxs(Container, { className, children: [
|
|
211
|
+
/* @__PURE__ */ jsxs(InputWrapper, { children: [
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
Input,
|
|
214
|
+
{
|
|
215
|
+
type: showPassword ? "text" : "password",
|
|
216
|
+
value: password,
|
|
217
|
+
onChange: handlePasswordChange,
|
|
218
|
+
...restInputProps
|
|
219
|
+
}
|
|
220
|
+
),
|
|
221
|
+
showToggleButton && /* @__PURE__ */ jsx(ToggleButton, { type: "button", onClick: () => setShowPassword(!showPassword), children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {}) })
|
|
222
|
+
] }),
|
|
223
|
+
showStrengthMeter && /* @__PURE__ */ jsx(StrengthMeter, { children: Array.from({ length: 5 }).map((_, index) => /* @__PURE__ */ jsx(
|
|
224
|
+
StrengthMeterSegment,
|
|
225
|
+
{
|
|
226
|
+
isFilled: strengthScore > index,
|
|
227
|
+
strengthLabel
|
|
228
|
+
},
|
|
229
|
+
index
|
|
230
|
+
)) }),
|
|
231
|
+
showRequirementsList && /* @__PURE__ */ jsx(RequirementsList, { children: Object.entries(policyState).map(([name, passed]) => /* @__PURE__ */ jsxs(RequirementItem, { passed, children: [
|
|
232
|
+
passed ? "\u2713" : "\u2717",
|
|
233
|
+
/* @__PURE__ */ jsx("span", { children: formatPolicyName(name) })
|
|
234
|
+
] }, name)) })
|
|
235
|
+
] });
|
|
236
|
+
};
|
|
237
|
+
export {
|
|
238
|
+
PasswordPolicyInput,
|
|
239
|
+
usePasswordPolicy
|
|
240
|
+
};
|
package/package.json
CHANGED
|
@@ -1,29 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-password-policy",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
},
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "A simple, lightweight, and customizable React hook for real-time password strength validation.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Rahul Patwa <rahul.y2j@gmail.com>",
|
|
9
8
|
"repository": {
|
|
10
9
|
"type": "git",
|
|
11
10
|
"url": "git+https://github.com/rahulpatwa1303/use-password-policy.git"
|
|
12
11
|
},
|
|
13
12
|
"keywords": [
|
|
14
|
-
"password",
|
|
15
|
-
"password-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
"react", "hook", "password", "password-policy", "validation", "form",
|
|
14
|
+
"typescript", "password-strength", "component", "react-component"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://rahulpatwa1303.github.io/use-password-policy/",
|
|
17
|
+
"bugs": { "url": "https://github.com/rahulpatwa1303/use-password-policy/issues" },
|
|
18
|
+
|
|
19
|
+
"workspaces": [
|
|
20
|
+
"demo"
|
|
18
21
|
],
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
"main": "dist/index.js",
|
|
24
|
+
"module": "dist/index.mjs",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"files": [ "dist" ],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"build": "npm run clean && npx tsup src/index.ts --format cjs,esm --dts",
|
|
30
|
+
"dev": "npm run build -- --watch",
|
|
31
|
+
"prepublishOnly": "npm run build",
|
|
32
|
+
"deploy:demo": "npm run build && cd demo && npm install && npm run deploy"
|
|
23
33
|
},
|
|
24
|
-
"homepage": "https://github.com/rahulpatwa1303/use-password-policy#readme",
|
|
25
34
|
"peerDependencies": {
|
|
26
|
-
"react": "
|
|
27
|
-
"
|
|
35
|
+
"react": ">=16.8.0",
|
|
36
|
+
"styled-components": ">=5"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/react": "^18.2.66",
|
|
40
|
+
"@types/styled-components": "^5.1.34",
|
|
41
|
+
"react": "^18.2.0",
|
|
42
|
+
"styled-components": "^6.1.11",
|
|
43
|
+
"tsup": "^8.0.2",
|
|
44
|
+
"typescript": "^5.4.5"
|
|
28
45
|
}
|
|
29
|
-
}
|
|
46
|
+
}
|
package/Type.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export interface CustomPolicy {
|
|
2
|
-
name: string; // Name of the policy (e.g., "SymbolCheck")
|
|
3
|
-
regex?: RegExp; // Regular expression for the check (optional)
|
|
4
|
-
checkFunction?: (password: string) => boolean; // Custom check function (optional)
|
|
5
|
-
message?: string; // Error message if the check fails (optional)
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface StateObjects {
|
|
9
|
-
[key: string]: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface DefaultConfig {
|
|
13
|
-
[key: string]: boolean | RegExp | string | number;
|
|
14
|
-
}
|
package/use-password-policy.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo } from "react";
|
|
2
|
-
import { CustomPolicy, StateObjects, DefaultConfig } from "./Type";
|
|
3
|
-
|
|
4
|
-
export const usePasswordPolicy = ({
|
|
5
|
-
password,
|
|
6
|
-
config,
|
|
7
|
-
customPolicies = [],
|
|
8
|
-
useDefaultConfig = true,
|
|
9
|
-
}: {
|
|
10
|
-
password: string;
|
|
11
|
-
config?: {};
|
|
12
|
-
customPolicies?: CustomPolicy[];
|
|
13
|
-
useDefaultConfig?: boolean;
|
|
14
|
-
}) => {
|
|
15
|
-
const defaultConfig: DefaultConfig = {
|
|
16
|
-
caseCheck: true,
|
|
17
|
-
lengthCheck: true,
|
|
18
|
-
digitCheck: true,
|
|
19
|
-
specialCharCheck: true,
|
|
20
|
-
minLength: 8,
|
|
21
|
-
uppercaseCharRegex: /[A-Z]/,
|
|
22
|
-
digitRegex: /\d/,
|
|
23
|
-
specialCharRegex: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const merged = useMemo(() => mergedConfig(defaultConfig, config), []);
|
|
27
|
-
|
|
28
|
-
function mergedConfig(defaultConfig: {}, config: any) {
|
|
29
|
-
if (useDefaultConfig) {
|
|
30
|
-
return { ...defaultConfig, ...config };
|
|
31
|
-
} else {
|
|
32
|
-
return { ...config };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const [policy, setPolicy] = useState<StateObjects>(() => {
|
|
37
|
-
if (useDefaultConfig) {
|
|
38
|
-
const defaultStates: StateObjects = {
|
|
39
|
-
caseCheck: false,
|
|
40
|
-
lengthCheck: false,
|
|
41
|
-
digitCheck: false,
|
|
42
|
-
specialCharCheck: false,
|
|
43
|
-
};
|
|
44
|
-
const safeConfig: StateObjects = config || {};
|
|
45
|
-
|
|
46
|
-
Object.entries(defaultStates).map(([key]) => {
|
|
47
|
-
if (safeConfig.hasOwnProperty(key) && safeConfig[key] === false) {
|
|
48
|
-
delete defaultStates[key];
|
|
49
|
-
delete defaultConfig[key];
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
return {
|
|
53
|
-
...defaultStates,
|
|
54
|
-
...customPolicies.reduce(
|
|
55
|
-
(acc, policy: { name: string }) => ({
|
|
56
|
-
...acc,
|
|
57
|
-
[policy?.name]: false,
|
|
58
|
-
}),
|
|
59
|
-
{}
|
|
60
|
-
),
|
|
61
|
-
};
|
|
62
|
-
} else
|
|
63
|
-
return {
|
|
64
|
-
...customPolicies.reduce(
|
|
65
|
-
(acc, policy: { name: string }) => ({
|
|
66
|
-
...acc,
|
|
67
|
-
[policy?.name]: false,
|
|
68
|
-
}),
|
|
69
|
-
{}
|
|
70
|
-
),
|
|
71
|
-
};
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const evaluateChecks = () => {
|
|
75
|
-
const newPolicy: StateObjects = { ...policy };
|
|
76
|
-
const policyKeys = Object.keys(policy);
|
|
77
|
-
|
|
78
|
-
if (useDefaultConfig) {
|
|
79
|
-
policyKeys.forEach((key) => {
|
|
80
|
-
if (merged.hasOwnProperty(key) && policy.hasOwnProperty(key)) {
|
|
81
|
-
const checkFunction = {
|
|
82
|
-
caseCheck: () => RegExp(merged.uppercaseCharRegex).test(password),
|
|
83
|
-
lengthCheck: () => password.length >= merged.minLength,
|
|
84
|
-
digitCheck: () => RegExp(merged.digitRegex).test(password),
|
|
85
|
-
specialCharCheck: () =>
|
|
86
|
-
RegExp(merged.specialCharRegex).test(password),
|
|
87
|
-
}[key];
|
|
88
|
-
|
|
89
|
-
if (checkFunction) {
|
|
90
|
-
newPolicy[key] = merged[key] && checkFunction();
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
customPolicies.forEach((customPolicy: CustomPolicy) => {
|
|
97
|
-
const name: string = customPolicy?.name;
|
|
98
|
-
if (customPolicy?.regex) {
|
|
99
|
-
newPolicy[name] = customPolicy.regex.test(password);
|
|
100
|
-
} else if (customPolicy?.checkFunction) {
|
|
101
|
-
newPolicy[name] = customPolicy.checkFunction(password);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
setPolicy(newPolicy);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
evaluateChecks();
|
|
110
|
-
}, [password]);
|
|
111
|
-
return policy;
|
|
112
|
-
};
|