playbook_ui 11.12.0 → 11.12.1.pre.alpha.passphrase1
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_button/_button.scss +1 -1
- data/app/pb_kits/playbook/pb_passphrase/_passphrase.jsx +56 -97
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.html.erb +145 -1
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.jsx +127 -3
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.md +11 -2
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.html.erb +136 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.jsx +90 -8
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.md +5 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.html.erb +51 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.jsx +39 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.html.erb +0 -2
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx +6 -20
- 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 +318 -5
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx +134 -48
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.md +11 -5
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.html.erb +123 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.jsx +96 -20
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.md +6 -2
- data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +4 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_passphrase/passphrase.html.erb +1 -1
- data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +5 -9
- data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +0 -47
- data/lib/playbook/version.rb +2 -2
- metadata +9 -8
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.md +0 -1
- data/app/pb_kits/playbook/pb_passphrase/passwordStrength.js +0 -55
- data/app/pb_kits/playbook/pb_passphrase/useHaveIBeenPwned.js +0 -52
- data/app/pb_kits/playbook/pb_passphrase/useZxcvbn.js +0 -58
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a2d4fa765df74ba9a5c6abd8ec9bd670371bb4b752bbdec10225d4d35d66293
|
4
|
+
data.tar.gz: ce21ba1dc523fc7875b39a604a0b63f7c54b5f62cd27297a2209f3cfa1b42dc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dace3a848d70cbc4cc6e854aa8bd0c687c8acc6485f37235daabbd18acab8b6393e8e4e2a4c021deb64811303c60364ac8fb43e00201bdd9f0e3f1c59f205d5c
|
7
|
+
data.tar.gz: 7fb618f8c14835809a4b2d02d0afac8476048a5c2209e846466999263a68021ec0ba069ad40bab7b8d7ff72c21952c37d4bc1a887c5e6e93e11214298ae93e7d
|
@@ -1,14 +1,10 @@
|
|
1
1
|
/* @flow */
|
2
|
-
/* eslint-disable react-hooks/rules-of-hooks */
|
3
2
|
|
4
|
-
import React, { useCallback,
|
5
|
-
import classnames from
|
3
|
+
import React, { useCallback, useMemo, useState } from "react"
|
4
|
+
import classnames from "classnames"
|
6
5
|
|
7
|
-
import { buildAriaProps, buildCss, buildDataProps } from
|
8
|
-
import { globalProps } from
|
9
|
-
|
10
|
-
import useZxcvbn from './useZxcvbn'
|
11
|
-
import useHaveIBeenPwned from './useHaveIBeenPwned'
|
6
|
+
import { buildAriaProps, buildCss, buildDataProps } from "../utilities/props"
|
7
|
+
import { globalProps } from "../utilities/globalProps"
|
12
8
|
|
13
9
|
import Body from '../pb_body/_body'
|
14
10
|
import Caption from '../pb_caption/_caption'
|
@@ -16,14 +12,10 @@ import CircleIconButton from '../pb_circle_icon_button/_circle_icon_button'
|
|
16
12
|
import Flex from '../pb_flex/_flex'
|
17
13
|
import Icon from '../pb_icon/_icon'
|
18
14
|
import PbReactPopover from '../pb_popover/_popover'
|
19
|
-
import ProgressSimple from '../pb_progress_simple/_progress_simple'
|
20
15
|
import TextInput from '../pb_text_input/_text_input'
|
21
16
|
|
22
17
|
type PassphraseProps = {
|
23
18
|
aria?: object,
|
24
|
-
averageThreshold?: number,
|
25
|
-
checkPwned?: boolean,
|
26
|
-
common?: boolean,
|
27
19
|
confirmation?: boolean,
|
28
20
|
className?: string,
|
29
21
|
data?: object,
|
@@ -31,11 +23,8 @@ type PassphraseProps = {
|
|
31
23
|
id?: string,
|
32
24
|
inputProps?: {},
|
33
25
|
label?: string,
|
34
|
-
minLength?: number,
|
35
26
|
onChange: (String) => void,
|
36
|
-
showTipsBelow?:
|
37
|
-
onStrengthChange?: (number) => void,
|
38
|
-
strongThreshold?: number,
|
27
|
+
showTipsBelow?: "always" | "xs" | "sm" | "md" | "lg" | "xl",
|
39
28
|
tips?: Array<string>,
|
40
29
|
uncontrolled?: boolean,
|
41
30
|
value: string,
|
@@ -44,71 +33,56 @@ type PassphraseProps = {
|
|
44
33
|
const Passphrase = (props: PassphraseProps) => {
|
45
34
|
const {
|
46
35
|
aria = {},
|
47
|
-
averageThreshold = 2,
|
48
|
-
checkPwned = false,
|
49
36
|
className,
|
50
|
-
common = false,
|
51
37
|
confirmation = false,
|
52
|
-
dark = false,
|
53
38
|
data = {},
|
39
|
+
dark = false,
|
54
40
|
id,
|
55
41
|
inputProps = {},
|
56
|
-
label = confirmation ?
|
57
|
-
minLength = 12,
|
42
|
+
label = confirmation ? "Confirm Passphrase" : "Passphrase",
|
58
43
|
onChange = () => {},
|
59
|
-
showTipsBelow =
|
60
|
-
onStrengthChange,
|
61
|
-
strongThreshold = 3,
|
44
|
+
showTipsBelow = "always",
|
62
45
|
tips = [],
|
63
46
|
uncontrolled = false,
|
64
|
-
value =
|
47
|
+
value = "",
|
65
48
|
} = props
|
66
|
-
const ariaProps = buildAriaProps(aria)
|
67
49
|
|
68
|
-
const [uncontrolledValue, setUncontrolledValue] = useState(
|
50
|
+
const [uncontrolledValue, setUncontrolledValue] = useState("")
|
51
|
+
const [showPopover, setShowPopover] = useState(false)
|
52
|
+
const [showPassphrase, setShowPassphrase] = useState(false)
|
69
53
|
|
70
54
|
const handleChange = useCallback(
|
71
|
-
(e) => uncontrolled ? setUncontrolledValue(e.target.value) :
|
55
|
+
(e) => (uncontrolled ? setUncontrolledValue(e.target.value) : onChange(e)),
|
72
56
|
[uncontrolled, onChange]
|
73
57
|
)
|
74
58
|
|
75
59
|
const displayValue = useMemo(
|
76
60
|
() => (uncontrolled ? uncontrolledValue : value),
|
77
|
-
[value, uncontrolledValue, uncontrolled]
|
61
|
+
[value, uncontrolledValue, uncontrolled]
|
78
62
|
)
|
79
63
|
|
80
|
-
const [showPopover, setShowPopover] = useState(false)
|
81
64
|
const toggleShowPopover = () => setShowPopover(!showPopover)
|
82
65
|
const handleShouldClosePopover = (shouldClosePopover) => {
|
83
66
|
setShowPopover(!shouldClosePopover)
|
84
67
|
}
|
85
68
|
|
86
|
-
const [showPassphrase, setShowPassphrase] = useState(false)
|
87
69
|
const toggleShowPassphrase = (e) => {
|
88
70
|
e.preventDefault()
|
89
71
|
setShowPassphrase(!showPassphrase)
|
90
72
|
}
|
91
73
|
|
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
|
-
|
104
74
|
const tipClass = classnames(
|
105
|
-
|
106
|
-
|
75
|
+
"passphrase-popover",
|
76
|
+
showTipsBelow === "always" ? null : `show-below-${showTipsBelow}`
|
107
77
|
)
|
108
|
-
|
109
|
-
|
110
|
-
|
78
|
+
|
79
|
+
const ariaProps = buildAriaProps(aria)
|
80
|
+
const classes = classnames(
|
81
|
+
buildCss("pb_passphrase"),
|
82
|
+
globalProps(props),
|
83
|
+
className
|
111
84
|
)
|
85
|
+
const dataProps = buildDataProps(data)
|
112
86
|
|
113
87
|
const popoverReference = (
|
114
88
|
<CircleIconButton
|
@@ -121,17 +95,17 @@ const Passphrase = (props: PassphraseProps) => {
|
|
121
95
|
)
|
122
96
|
|
123
97
|
return (
|
124
|
-
<div
|
125
|
-
{...ariaProps}
|
126
|
-
{...dataProps}
|
127
|
-
className={classes}
|
98
|
+
<div
|
99
|
+
{...ariaProps}
|
100
|
+
{...dataProps}
|
101
|
+
className={classes}
|
128
102
|
id={id}
|
129
103
|
>
|
130
104
|
<label>
|
131
105
|
<Flex align="baseline">
|
132
|
-
<Caption
|
133
|
-
className="passphrase-label"
|
134
|
-
text={label}
|
106
|
+
<Caption
|
107
|
+
className="passphrase-label"
|
108
|
+
text={label}
|
135
109
|
/>
|
136
110
|
<If condition={tips.length > 0 && !confirmation}>
|
137
111
|
<PbReactPopover
|
@@ -142,30 +116,28 @@ const Passphrase = (props: PassphraseProps) => {
|
|
142
116
|
shouldClosePopover={handleShouldClosePopover}
|
143
117
|
show={showPopover}
|
144
118
|
>
|
145
|
-
<Flex
|
146
|
-
align="center"
|
119
|
+
<Flex
|
120
|
+
align="center"
|
147
121
|
orientation="column"
|
148
122
|
>
|
149
|
-
<Caption
|
150
|
-
marginBottom="xs"
|
151
|
-
text="Tips for a good passphrase"
|
123
|
+
<Caption
|
124
|
+
marginBottom="xs"
|
125
|
+
text="Tips for a good passphrase"
|
152
126
|
/>
|
153
127
|
<div>
|
154
|
-
{
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
))
|
168
|
-
}
|
128
|
+
{tips.map((tip, i) => (
|
129
|
+
<Caption
|
130
|
+
key={i}
|
131
|
+
marginBottom="xs"
|
132
|
+
size="xs"
|
133
|
+
>
|
134
|
+
<Icon
|
135
|
+
icon="shield-check"
|
136
|
+
marginRight="xs"
|
137
|
+
/>
|
138
|
+
{tip}
|
139
|
+
</Caption>
|
140
|
+
))}
|
169
141
|
</div>
|
170
142
|
</Flex>
|
171
143
|
</PbReactPopover>
|
@@ -178,23 +150,23 @@ const Passphrase = (props: PassphraseProps) => {
|
|
178
150
|
marginBottom="xs"
|
179
151
|
onChange={handleChange}
|
180
152
|
placeholder="Enter a passphrase..."
|
181
|
-
type={showPassphrase ?
|
153
|
+
type={showPassphrase ? "text" : "password"}
|
182
154
|
value={displayValue}
|
183
155
|
{...inputProps}
|
184
156
|
/>
|
185
|
-
<span
|
186
|
-
className="show-passphrase-icon"
|
157
|
+
<span
|
158
|
+
className="show-passphrase-icon"
|
187
159
|
onClick={toggleShowPassphrase}
|
188
160
|
>
|
189
161
|
<Body
|
190
|
-
className={showPassphrase ?
|
162
|
+
className={showPassphrase ? "hide-icon" : ""}
|
191
163
|
color="light"
|
192
164
|
dark={dark}
|
193
165
|
>
|
194
166
|
<Icon icon="eye-slash" />
|
195
167
|
</Body>
|
196
168
|
<Body
|
197
|
-
className={showPassphrase ?
|
169
|
+
className={showPassphrase ? "" : "hide-icon"}
|
198
170
|
color="light"
|
199
171
|
dark={dark}
|
200
172
|
>
|
@@ -203,21 +175,8 @@ const Passphrase = (props: PassphraseProps) => {
|
|
203
175
|
</span>
|
204
176
|
</div>
|
205
177
|
</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>
|
219
178
|
</div>
|
220
|
-
)
|
221
|
-
}
|
179
|
+
);
|
180
|
+
};
|
222
181
|
|
223
|
-
export default Passphrase
|
182
|
+
export default Passphrase;
|
@@ -1 +1,145 @@
|
|
1
|
-
<%= pb_rails("passphrase", props: {
|
1
|
+
<%= pb_rails("passphrase", props: { label: "Passphrase", classname: "passphrase_breached" }) %>
|
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,22 +1,146 @@
|
|
1
|
-
import React, { useState } from 'react'
|
1
|
+
import React, { useState, useEffect } from 'react'
|
2
2
|
|
3
|
-
import Passphrase from '
|
3
|
+
import { Caption, Passphrase, ProgressSimple } from '../..'
|
4
|
+
import zxcvbn from 'zxcvbn'
|
4
5
|
|
5
6
|
const PassphraseBreached = (props) => {
|
6
7
|
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
|
+
|
7
16
|
|
8
17
|
const handleChange = (e) => setInput(e.target.value)
|
9
18
|
|
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
|
+
|
10
123
|
return (
|
11
124
|
<>
|
12
125
|
<div>
|
13
126
|
<br />
|
14
127
|
<Passphrase
|
15
|
-
checkPwned
|
16
128
|
onChange={handleChange}
|
17
129
|
value={input}
|
18
130
|
{...props}
|
19
131
|
/>
|
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}
|
20
144
|
</div>
|
21
145
|
</>
|
22
146
|
)
|
@@ -1,3 +1,12 @@
|
|
1
|
-
Use
|
1
|
+
Use <a href='https://haveibeenpwned.com/Passwords'>HaveIBeenPwned's</a> API to check for breached passwords.
|
2
|
+
|
3
|
+
As the passphrase is typed, it is checked against more than half a billion breached passwords, to help ensure its not compromised.
|
2
4
|
Should it fail, the feedback will express the passphrase is too common, prompting the user to change.
|
3
|
-
|
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.
|