playbook_ui 11.12.1.pre.alpha.passphrase1 → 11.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_passphrase/_passphrase.jsx +97 -56
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.html.erb +1 -145
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.jsx +3 -127
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.md +2 -11
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.jsx +8 -90
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.html.erb +2 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx +20 -6
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.md +1 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.html.erb +2 -2
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.jsx +1 -1
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.html.erb +5 -318
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx +48 -134
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.md +5 -11
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.jsx +20 -96
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.md +2 -6
- data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +0 -4
- data/app/pb_kits/playbook/pb_passphrase/docs/index.js +0 -1
- data/app/pb_kits/playbook/pb_passphrase/passphrase.html.erb +1 -1
- data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +9 -5
- data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +47 -0
- data/app/pb_kits/playbook/pb_passphrase/passwordStrength.js +55 -0
- data/app/pb_kits/playbook/pb_passphrase/useHaveIBeenPwned.js +52 -0
- data/app/pb_kits/playbook/pb_passphrase/useZxcvbn.js +58 -0
- data/lib/playbook/version.rb +2 -2
- metadata +8 -9
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.html.erb +0 -136
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.md +0 -5
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.html.erb +0 -51
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.jsx +0 -39
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.html.erb +0 -123
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3257a43d2c224d202dcfa36f64ae30921ed80d752b8db6927059ebbe7032d6e6
|
4
|
+
data.tar.gz: 573bb60fedf4fc904f3832026d46ac23ee22dc965792801565c9006449dbdcc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38e8c49e2e18ae015912af7ac41da7c6a9fc57b7e201f4ca55c9edb2ea93d0114c917963c98f67972aa4cf50dda11cb4ec40f494e4da260403d63fa9e5920e4a
|
7
|
+
data.tar.gz: 9421ac89e00399825a91ed280324cd1b1a4a43aa8310b88c2143f21724d818c26aeca9af36e82cc9cf55e2e254a059d03d6c356364228631e82a4b8589d0a35e
|
@@ -1,10 +1,14 @@
|
|
1
1
|
/* @flow */
|
2
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
2
3
|
|
3
|
-
import React, { useCallback, useMemo, useState } from
|
4
|
-
import classnames from
|
4
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
5
|
+
import classnames from 'classnames'
|
5
6
|
|
6
|
-
import { buildAriaProps, buildCss, buildDataProps } from
|
7
|
-
import { globalProps } from
|
7
|
+
import { buildAriaProps, buildCss, buildDataProps } from '../utilities/props'
|
8
|
+
import { globalProps } from '../utilities/globalProps'
|
9
|
+
|
10
|
+
import useZxcvbn from './useZxcvbn'
|
11
|
+
import useHaveIBeenPwned from './useHaveIBeenPwned'
|
8
12
|
|
9
13
|
import Body from '../pb_body/_body'
|
10
14
|
import Caption from '../pb_caption/_caption'
|
@@ -12,10 +16,14 @@ import CircleIconButton from '../pb_circle_icon_button/_circle_icon_button'
|
|
12
16
|
import Flex from '../pb_flex/_flex'
|
13
17
|
import Icon from '../pb_icon/_icon'
|
14
18
|
import PbReactPopover from '../pb_popover/_popover'
|
19
|
+
import ProgressSimple from '../pb_progress_simple/_progress_simple'
|
15
20
|
import TextInput from '../pb_text_input/_text_input'
|
16
21
|
|
17
22
|
type PassphraseProps = {
|
18
23
|
aria?: object,
|
24
|
+
averageThreshold?: number,
|
25
|
+
checkPwned?: boolean,
|
26
|
+
common?: boolean,
|
19
27
|
confirmation?: boolean,
|
20
28
|
className?: string,
|
21
29
|
data?: object,
|
@@ -23,8 +31,11 @@ type PassphraseProps = {
|
|
23
31
|
id?: string,
|
24
32
|
inputProps?: {},
|
25
33
|
label?: string,
|
34
|
+
minLength?: number,
|
26
35
|
onChange: (String) => void,
|
27
|
-
showTipsBelow?:
|
36
|
+
showTipsBelow?: 'always' | 'xs' | 'sm' | 'md' | 'lg' | 'xl',
|
37
|
+
onStrengthChange?: (number) => void,
|
38
|
+
strongThreshold?: number,
|
28
39
|
tips?: Array<string>,
|
29
40
|
uncontrolled?: boolean,
|
30
41
|
value: string,
|
@@ -33,56 +44,71 @@ type PassphraseProps = {
|
|
33
44
|
const Passphrase = (props: PassphraseProps) => {
|
34
45
|
const {
|
35
46
|
aria = {},
|
47
|
+
averageThreshold = 2,
|
48
|
+
checkPwned = false,
|
36
49
|
className,
|
50
|
+
common = false,
|
37
51
|
confirmation = false,
|
38
|
-
data = {},
|
39
52
|
dark = false,
|
53
|
+
data = {},
|
40
54
|
id,
|
41
55
|
inputProps = {},
|
42
|
-
label = confirmation ?
|
56
|
+
label = confirmation ? 'Confirm Passphrase' : 'Passphrase',
|
57
|
+
minLength = 12,
|
43
58
|
onChange = () => {},
|
44
|
-
showTipsBelow =
|
59
|
+
showTipsBelow = 'always',
|
60
|
+
onStrengthChange,
|
61
|
+
strongThreshold = 3,
|
45
62
|
tips = [],
|
46
63
|
uncontrolled = false,
|
47
|
-
value =
|
64
|
+
value = '',
|
48
65
|
} = props
|
66
|
+
const ariaProps = buildAriaProps(aria)
|
49
67
|
|
50
|
-
const [uncontrolledValue, setUncontrolledValue] = useState(
|
51
|
-
const [showPopover, setShowPopover] = useState(false)
|
52
|
-
const [showPassphrase, setShowPassphrase] = useState(false)
|
68
|
+
const [uncontrolledValue, setUncontrolledValue] = useState('')
|
53
69
|
|
54
70
|
const handleChange = useCallback(
|
55
|
-
(e) =>
|
71
|
+
(e) => uncontrolled ? setUncontrolledValue(e.target.value) : onChange(e),
|
56
72
|
[uncontrolled, onChange]
|
57
73
|
)
|
58
74
|
|
59
75
|
const displayValue = useMemo(
|
60
76
|
() => (uncontrolled ? uncontrolledValue : value),
|
61
|
-
[value, uncontrolledValue, uncontrolled]
|
77
|
+
[value, uncontrolledValue, uncontrolled],
|
62
78
|
)
|
63
79
|
|
80
|
+
const [showPopover, setShowPopover] = useState(false)
|
64
81
|
const toggleShowPopover = () => setShowPopover(!showPopover)
|
65
82
|
const handleShouldClosePopover = (shouldClosePopover) => {
|
66
83
|
setShowPopover(!shouldClosePopover)
|
67
84
|
}
|
68
85
|
|
86
|
+
const [showPassphrase, setShowPassphrase] = useState(false)
|
69
87
|
const toggleShowPassphrase = (e) => {
|
70
88
|
e.preventDefault()
|
71
89
|
setShowPassphrase(!showPassphrase)
|
72
90
|
}
|
73
91
|
|
92
|
+
const classes = classnames(buildCss('pb_passphrase'), globalProps(props), className)
|
93
|
+
|
94
|
+
const isPwned = checkPwned ? useHaveIBeenPwned(displayValue, minLength) : false
|
95
|
+
|
96
|
+
const { percent: progressPercent, variant: progressVariant, text: strengthLabel, strength } = useZxcvbn({ passphrase: displayValue, common, isPwned, confirmation, averageThreshold, minLength, strongThreshold })
|
97
|
+
|
98
|
+
useEffect(() => {
|
99
|
+
if (typeof onStrengthChange === 'function') {
|
100
|
+
onStrengthChange(strength)
|
101
|
+
}
|
102
|
+
}, [strength])
|
103
|
+
|
74
104
|
const tipClass = classnames(
|
75
|
-
|
76
|
-
showTipsBelow ===
|
105
|
+
'passphrase-popover',
|
106
|
+
(showTipsBelow === 'always' ? null : `show-below-${showTipsBelow}`),
|
77
107
|
)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
buildCss("pb_passphrase"),
|
82
|
-
globalProps(props),
|
83
|
-
className
|
108
|
+
const dataProps = useMemo(
|
109
|
+
() => (buildDataProps(Object.assign({}, data, { strength }))),
|
110
|
+
[data, strength]
|
84
111
|
)
|
85
|
-
const dataProps = buildDataProps(data)
|
86
112
|
|
87
113
|
const popoverReference = (
|
88
114
|
<CircleIconButton
|
@@ -95,17 +121,17 @@ const Passphrase = (props: PassphraseProps) => {
|
|
95
121
|
)
|
96
122
|
|
97
123
|
return (
|
98
|
-
<div
|
99
|
-
{...ariaProps}
|
100
|
-
{...dataProps}
|
101
|
-
className={classes}
|
124
|
+
<div
|
125
|
+
{...ariaProps}
|
126
|
+
{...dataProps}
|
127
|
+
className={classes}
|
102
128
|
id={id}
|
103
129
|
>
|
104
130
|
<label>
|
105
131
|
<Flex align="baseline">
|
106
|
-
<Caption
|
107
|
-
className="passphrase-label"
|
108
|
-
text={label}
|
132
|
+
<Caption
|
133
|
+
className="passphrase-label"
|
134
|
+
text={label}
|
109
135
|
/>
|
110
136
|
<If condition={tips.length > 0 && !confirmation}>
|
111
137
|
<PbReactPopover
|
@@ -116,28 +142,30 @@ const Passphrase = (props: PassphraseProps) => {
|
|
116
142
|
shouldClosePopover={handleShouldClosePopover}
|
117
143
|
show={showPopover}
|
118
144
|
>
|
119
|
-
<Flex
|
120
|
-
align="center"
|
145
|
+
<Flex
|
146
|
+
align="center"
|
121
147
|
orientation="column"
|
122
148
|
>
|
123
|
-
<Caption
|
124
|
-
marginBottom="xs"
|
125
|
-
text="Tips for a good passphrase"
|
149
|
+
<Caption
|
150
|
+
marginBottom="xs"
|
151
|
+
text="Tips for a good passphrase"
|
126
152
|
/>
|
127
153
|
<div>
|
128
|
-
{
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
154
|
+
{
|
155
|
+
tips.map((tip, i) => (
|
156
|
+
<Caption
|
157
|
+
key={i}
|
158
|
+
marginBottom="xs"
|
159
|
+
size="xs"
|
160
|
+
>
|
161
|
+
<Icon
|
162
|
+
icon="shield-check"
|
163
|
+
marginRight="xs"
|
164
|
+
/>
|
165
|
+
{tip}
|
166
|
+
</Caption>
|
167
|
+
))
|
168
|
+
}
|
141
169
|
</div>
|
142
170
|
</Flex>
|
143
171
|
</PbReactPopover>
|
@@ -150,23 +178,23 @@ const Passphrase = (props: PassphraseProps) => {
|
|
150
178
|
marginBottom="xs"
|
151
179
|
onChange={handleChange}
|
152
180
|
placeholder="Enter a passphrase..."
|
153
|
-
type={showPassphrase ?
|
181
|
+
type={showPassphrase ? 'text' : 'password'}
|
154
182
|
value={displayValue}
|
155
183
|
{...inputProps}
|
156
184
|
/>
|
157
|
-
<span
|
158
|
-
className="show-passphrase-icon"
|
185
|
+
<span
|
186
|
+
className="show-passphrase-icon"
|
159
187
|
onClick={toggleShowPassphrase}
|
160
188
|
>
|
161
189
|
<Body
|
162
|
-
className={showPassphrase ?
|
190
|
+
className={showPassphrase ? 'hide-icon' : ''}
|
163
191
|
color="light"
|
164
192
|
dark={dark}
|
165
193
|
>
|
166
194
|
<Icon icon="eye-slash" />
|
167
195
|
</Body>
|
168
196
|
<Body
|
169
|
-
className={showPassphrase ?
|
197
|
+
className={showPassphrase ? '' : 'hide-icon'}
|
170
198
|
color="light"
|
171
199
|
dark={dark}
|
172
200
|
>
|
@@ -175,8 +203,21 @@ const Passphrase = (props: PassphraseProps) => {
|
|
175
203
|
</span>
|
176
204
|
</div>
|
177
205
|
</label>
|
206
|
+
<If condition={!confirmation}>
|
207
|
+
<ProgressSimple
|
208
|
+
className={displayValue.length === 0 ? 'progress-empty-input' : null}
|
209
|
+
dark={dark}
|
210
|
+
percent={progressPercent}
|
211
|
+
variant={progressVariant}
|
212
|
+
/>
|
213
|
+
<Caption
|
214
|
+
dark={dark}
|
215
|
+
size="xs"
|
216
|
+
text={strengthLabel}
|
217
|
+
/>
|
218
|
+
</If>
|
178
219
|
</div>
|
179
|
-
)
|
180
|
-
}
|
220
|
+
)
|
221
|
+
}
|
181
222
|
|
182
|
-
export default Passphrase
|
223
|
+
export default Passphrase
|
@@ -1,145 +1 @@
|
|
1
|
-
<%= pb_rails("passphrase", props: {
|
2
|
-
|
3
|
-
<%= pb_rails("progress_simple", props: { percent: 0, id: "bar_breached" }) %>
|
4
|
-
|
5
|
-
<%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "caption_breached" }) %>
|
6
|
-
|
7
|
-
<%= javascript_tag do %>
|
8
|
-
window.addEventListener("DOMContentLoaded", () => {
|
9
|
-
|
10
|
-
// variables for the kits you are targeting
|
11
|
-
const passphrase = document.querySelector(".passphrase_breached").querySelector("input")
|
12
|
-
const barVariant = document.getElementById("bar_breached")
|
13
|
-
const barPercent = document.getElementById("bar_breached").querySelector("div")
|
14
|
-
const caption = document.getElementById("caption_breached")
|
15
|
-
|
16
|
-
// hide the bar and captions
|
17
|
-
barVariant.style.display = 'none';
|
18
|
-
barPercent.style.display = 'none';
|
19
|
-
caption.style.display = 'none';
|
20
|
-
|
21
|
-
|
22
|
-
const handleStrengthCalculation = (settings) => {
|
23
|
-
const {
|
24
|
-
passphrase = "",
|
25
|
-
common = false,
|
26
|
-
isPwned = false,
|
27
|
-
averageThreshold = 2,
|
28
|
-
minLength = 12,
|
29
|
-
strongThreshold = 3,
|
30
|
-
} = settings
|
31
|
-
|
32
|
-
const resultByScore = {
|
33
|
-
0: {
|
34
|
-
variant: 'negative',
|
35
|
-
label: '',
|
36
|
-
percent: 0,
|
37
|
-
},
|
38
|
-
1: {
|
39
|
-
variant: 'negative',
|
40
|
-
label: 'This passphrase is too common',
|
41
|
-
percent: 25,
|
42
|
-
},
|
43
|
-
2: {
|
44
|
-
variant: 'negative',
|
45
|
-
label: 'Too weak',
|
46
|
-
percent: 25,
|
47
|
-
},
|
48
|
-
3: {
|
49
|
-
variant: 'warning',
|
50
|
-
label: 'Almost there, keep going!',
|
51
|
-
percent: 50,
|
52
|
-
},
|
53
|
-
4: {
|
54
|
-
variant: 'positive',
|
55
|
-
label: 'Success! Strong passphrase',
|
56
|
-
percent: 100,
|
57
|
-
}
|
58
|
-
}
|
59
|
-
|
60
|
-
const { score } = zxcvbn(passphrase);
|
61
|
-
|
62
|
-
const noPassphrase = passphrase.length <= 0
|
63
|
-
const commonPassphrase = common || isPwned
|
64
|
-
const weakPassphrase = passphrase.length < minLength || score < averageThreshold
|
65
|
-
const averagePassphrase = score < strongThreshold
|
66
|
-
const strongPassphrase = score >= strongThreshold
|
67
|
-
|
68
|
-
if (noPassphrase) {
|
69
|
-
return {...resultByScore[0], score}
|
70
|
-
} else if (commonPassphrase) {
|
71
|
-
return {...resultByScore[1], score}
|
72
|
-
} else if (weakPassphrase) {
|
73
|
-
return {...resultByScore[2], score}
|
74
|
-
} else if (averagePassphrase){
|
75
|
-
return {...resultByScore[3], score}
|
76
|
-
} else if (strongPassphrase) {
|
77
|
-
return {...resultByScore[4], score}
|
78
|
-
}
|
79
|
-
}
|
80
|
-
|
81
|
-
// event listeners attached to the input field
|
82
|
-
passphrase.addEventListener('input', (e) => {
|
83
|
-
const passphrase = e.target.value;
|
84
|
-
|
85
|
-
let pwndMatch = false
|
86
|
-
|
87
|
-
const checkHaveIBeenPwned = async function (passphrase) {
|
88
|
-
const buffer = new TextEncoder('utf-8').encode(passphrase)
|
89
|
-
const digest = await crypto.subtle.digest('SHA-1', buffer)
|
90
|
-
const hashArray = Array.from(new Uint8Array(digest))
|
91
|
-
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
|
92
|
-
|
93
|
-
const firstFive = hashHex.slice(0, 5)
|
94
|
-
const endOfHash = hashHex.slice(5)
|
95
|
-
|
96
|
-
const resp = await fetch(`https://api.pwnedpasswords.com/range/${firstFive}`)
|
97
|
-
const text = await resp.text()
|
98
|
-
|
99
|
-
if (passphrase.length < 5) {
|
100
|
-
pwndMatch = false
|
101
|
-
}
|
102
|
-
else {
|
103
|
-
pwndMatch = text.split('\n').some((line) => {
|
104
|
-
return line.split(':')[0] === endOfHash.toUpperCase()
|
105
|
-
})
|
106
|
-
}
|
107
|
-
|
108
|
-
// pass in passphrase and isPwnd match to the handleStrengthCalculation and set that equal to result variable
|
109
|
-
const result = handleStrengthCalculation({ passphrase: passphrase, isPwned: pwndMatch });
|
110
|
-
|
111
|
-
|
112
|
-
// conditional statment to show or hide progress_simple bar and caption if user has entered a password
|
113
|
-
if (passphrase) {
|
114
|
-
barVariant.style.display = 'block';
|
115
|
-
|
116
|
-
barPercent.style.display = 'block';
|
117
|
-
|
118
|
-
caption.style.display = 'block';
|
119
|
-
} else {
|
120
|
-
barVariant.style.display = 'none';
|
121
|
-
|
122
|
-
barPercent.style.display = 'none';
|
123
|
-
|
124
|
-
caption.style.display = 'none';
|
125
|
-
}
|
126
|
-
|
127
|
-
// set the width of the progress_simple kit
|
128
|
-
barPercent.style.width = result.percent.toString()+ "%"
|
129
|
-
|
130
|
-
|
131
|
-
// set the variant of the progress_simple kit
|
132
|
-
barVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left");
|
133
|
-
|
134
|
-
|
135
|
-
// set the text of the caption kit
|
136
|
-
caption.textContent = result.label
|
137
|
-
}
|
138
|
-
|
139
|
-
checkHaveIBeenPwned(passphrase)
|
140
|
-
|
141
|
-
|
142
|
-
});
|
143
|
-
|
144
|
-
})
|
145
|
-
<% end %>
|
1
|
+
<%= pb_rails("passphrase", props: { check_pwned: true }) %>
|
@@ -1,146 +1,22 @@
|
|
1
|
-
import React, { useState
|
1
|
+
import React, { useState } from 'react'
|
2
2
|
|
3
|
-
import
|
4
|
-
import zxcvbn from 'zxcvbn'
|
3
|
+
import Passphrase from '../_passphrase'
|
5
4
|
|
6
5
|
const PassphraseBreached = (props) => {
|
7
6
|
const [input, setInput] = useState('')
|
8
|
-
const [isPwned, setIsPwned] = useState(false)
|
9
|
-
const [checkStrength, setCheckStrength] = useState({
|
10
|
-
label: '',
|
11
|
-
percent: 0,
|
12
|
-
score: 0,
|
13
|
-
variant: ''
|
14
|
-
})
|
15
|
-
|
16
7
|
|
17
8
|
const handleChange = (e) => setInput(e.target.value)
|
18
9
|
|
19
|
-
const checkHaveIBeenPwned = async function (passphrase) {
|
20
|
-
const buffer = new TextEncoder('utf-8').encode(passphrase)
|
21
|
-
const digest = await crypto.subtle.digest('SHA-1', buffer)
|
22
|
-
const hashArray = Array.from(new Uint8Array(digest))
|
23
|
-
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
|
24
|
-
|
25
|
-
const firstFive = hashHex.slice(0, 5)
|
26
|
-
const endOfHash = hashHex.slice(5)
|
27
|
-
|
28
|
-
const resp = await fetch(`https://api.pwnedpasswords.com/range/${firstFive}`)
|
29
|
-
const text = await resp.text()
|
30
|
-
|
31
|
-
const match = text.split('\n').some((line) => {
|
32
|
-
//Each line is <sha-1-hash-suffix>:<count of incidents>
|
33
|
-
return line.split(':')[0] === endOfHash.toUpperCase()
|
34
|
-
})
|
35
|
-
return match
|
36
|
-
}
|
37
|
-
|
38
|
-
const handleStrengthCalculation = (settings) => {
|
39
|
-
const {
|
40
|
-
passphrase = "",
|
41
|
-
common = false,
|
42
|
-
isPwned = false,
|
43
|
-
averageThreshold = 2,
|
44
|
-
minLength = 12,
|
45
|
-
strongThreshold = 3,
|
46
|
-
} = settings
|
47
|
-
|
48
|
-
const resultByScore = {
|
49
|
-
0: {
|
50
|
-
variant: 'negative',
|
51
|
-
label: '',
|
52
|
-
percent: 0,
|
53
|
-
},
|
54
|
-
1: {
|
55
|
-
variant: 'negative',
|
56
|
-
label: 'This passphrase is too common',
|
57
|
-
percent: 25,
|
58
|
-
},
|
59
|
-
2: {
|
60
|
-
variant: 'negative',
|
61
|
-
label: 'Too weak',
|
62
|
-
percent: 25,
|
63
|
-
},
|
64
|
-
3: {
|
65
|
-
variant: 'warning',
|
66
|
-
label: 'Almost there, keep going!',
|
67
|
-
percent: 50,
|
68
|
-
},
|
69
|
-
4: {
|
70
|
-
variant: 'positive',
|
71
|
-
label: 'Success! Strong passphrase',
|
72
|
-
percent: 100,
|
73
|
-
}
|
74
|
-
}
|
75
|
-
|
76
|
-
const { score } = zxcvbn(passphrase);
|
77
|
-
|
78
|
-
const noPassphrase = passphrase.length <= 0
|
79
|
-
const commonPassphrase = common || isPwned
|
80
|
-
const weakPassphrase = passphrase.length < minLength || score < averageThreshold
|
81
|
-
const averagePassphrase = score < strongThreshold
|
82
|
-
const strongPassphrase = score >= strongThreshold
|
83
|
-
|
84
|
-
if (noPassphrase) {
|
85
|
-
return {...resultByScore[0], score}
|
86
|
-
} else if (commonPassphrase) {
|
87
|
-
return {...resultByScore[1], score}
|
88
|
-
} else if (weakPassphrase) {
|
89
|
-
return {...resultByScore[2], score}
|
90
|
-
} else if (averagePassphrase){
|
91
|
-
return {...resultByScore[3], score}
|
92
|
-
} else if (strongPassphrase) {
|
93
|
-
return {...resultByScore[4], score}
|
94
|
-
}
|
95
|
-
}
|
96
|
-
|
97
|
-
useEffect(
|
98
|
-
() => {
|
99
|
-
const delay = 400
|
100
|
-
const result = handleStrengthCalculation({ passphrase: input, isPwned: isPwned });
|
101
|
-
|
102
|
-
setCheckStrength({ ...result })
|
103
|
-
|
104
|
-
// only check the API for passphrases above the minimum size
|
105
|
-
if (input.length < 5) {
|
106
|
-
setIsPwned(false)
|
107
|
-
return
|
108
|
-
}
|
109
|
-
|
110
|
-
const handler = setTimeout(() => {
|
111
|
-
checkHaveIBeenPwned(input)
|
112
|
-
.then((pwned) => setIsPwned(pwned))
|
113
|
-
.catch(() => setIsPwned(false))
|
114
|
-
}, delay)
|
115
|
-
|
116
|
-
return () => {
|
117
|
-
clearTimeout(handler)
|
118
|
-
}
|
119
|
-
},
|
120
|
-
[input, isPwned]
|
121
|
-
)
|
122
|
-
|
123
10
|
return (
|
124
11
|
<>
|
125
12
|
<div>
|
126
13
|
<br />
|
127
14
|
<Passphrase
|
15
|
+
checkPwned
|
128
16
|
onChange={handleChange}
|
129
17
|
value={input}
|
130
18
|
{...props}
|
131
19
|
/>
|
132
|
-
{checkStrength.percent > 0 ?
|
133
|
-
<>
|
134
|
-
<ProgressSimple
|
135
|
-
className={input.length === 0 ? "progress-empty-input" : null}
|
136
|
-
percent={checkStrength.percent}
|
137
|
-
variant={checkStrength.variant}
|
138
|
-
/>
|
139
|
-
<Caption size='xs'
|
140
|
-
text={checkStrength.label}
|
141
|
-
/>
|
142
|
-
</>
|
143
|
-
: null}
|
144
20
|
</div>
|
145
21
|
</>
|
146
22
|
)
|
@@ -1,12 +1,3 @@
|
|
1
|
-
Use <a href='https://haveibeenpwned.com/Passwords'>HaveIBeenPwned's</a> API
|
2
|
-
|
3
|
-
As the passphrase is typed, it is checked against more than half a billion breached passwords, to help ensure its not compromised.
|
1
|
+
Use `checkPwned | checked_pwned` prop to enable checking against <a href='https://haveibeenpwned.com/Passwords'>HaveIBeenPwned's</a> API. As the passphrase is typed, it is checked against more than half a billion breached passwords, to help ensure its not compromised.
|
4
2
|
Should it fail, the feedback will express the passphrase is too common, prompting the user to change.
|
5
|
-
|
6
|
-
This uses their k-Anonymity model, so only the first 5 characters of a hashed copy of the passphrase are sent.
|
7
|
-
|
8
|
-
<div class="pb_pill_kit_warning"><div class="pb_title_kit_size_4 pb_pill_text">Disclaimer</div></div>
|
9
|
-
|
10
|
-
This example depends on the `zxcvbn` library and `haveibeenpwned` API.
|
11
|
-
|
12
|
-
You can use any library to achieve the same result, this example only intends to show how to add more features to the `Passphrase` kit.
|
3
|
+
This uses their k-Anonymity model, so only the first 5 characters of a hashed copy of the passphrase are sent.
|