playbook_ui 11.12.1.pre.alpha.passphrase1 → 11.12.1
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.
- 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.
|