playbook_ui 11.12.1.pre.alpha.charts1 → 11.12.1.pre.alpha.passphrase1
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/index.js +1 -0
- data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.jsx +111 -0
- data/app/pb_kits/playbook/pb_circle_chart/_circle_chart.jsx +151 -0
- data/app/pb_kits/playbook/pb_circle_chart/circle_chart.html.erb +21 -9
- data/app/pb_kits/playbook/pb_circle_chart/circle_chart.rb +47 -7
- data/app/pb_kits/playbook/pb_dashboard/{pbChartsDarkTheme.ts → pbChartsDarkTheme.js} +21 -6
- data/app/pb_kits/playbook/pb_dashboard/{pbChartsLightTheme.ts → pbChartsLightTheme.js} +21 -6
- data/app/pb_kits/playbook/pb_gauge/_gauge.jsx +112 -0
- data/app/pb_kits/playbook/pb_gauge/_gauge.scss +0 -4
- data/app/pb_kits/playbook/pb_gauge/docs/_gauge_complex.html.erb +1 -1
- data/app/pb_kits/playbook/pb_gauge/docs/_gauge_complex.jsx +8 -8
- data/app/pb_kits/playbook/pb_gauge/gauge.html.erb +11 -1
- data/app/pb_kits/playbook/pb_gauge/gauge.rb +8 -3
- data/app/pb_kits/playbook/pb_line_graph/_line_graph.jsx +113 -0
- 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/app/pb_kits/playbook/pb_treemap_chart/_treemap_chart.jsx +79 -0
- data/app/pb_kits/playbook/pb_treemap_chart/docs/_treemap_chart_tooltip.jsx +1 -1
- data/app/pb_kits/playbook/playbook-rails-react-bindings.js +0 -4
- data/app/pb_kits/playbook/playbook-rails.js +4 -0
- data/app/pb_kits/playbook/plugins/pb_chart.js +322 -0
- data/lib/playbook/version.rb +1 -1
- metadata +15 -16
- data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.tsx +0 -145
- data/app/pb_kits/playbook/pb_circle_chart/ChartsTypes.ts +0 -2
- data/app/pb_kits/playbook/pb_circle_chart/_circle_chart.tsx +0 -216
- data/app/pb_kits/playbook/pb_dashboard/pbChartsColorsHelper.ts +0 -16
- data/app/pb_kits/playbook/pb_dashboard/themeTypes.ts +0 -16
- data/app/pb_kits/playbook/pb_gauge/_gauge.tsx +0 -213
- data/app/pb_kits/playbook/pb_line_graph/_line_graph.tsx +0 -148
- 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
- data/app/pb_kits/playbook/pb_treemap_chart/_treemap_chart.tsx +0 -111
@@ -22,10 +22,15 @@ module Playbook
|
|
22
22
|
prop :max, type: Playbook::Props::Numeric, default: 100
|
23
23
|
prop :colors, type: Playbook::Props::Array, default: []
|
24
24
|
|
25
|
+
def chart_data_formatted
|
26
|
+
chart_data.map { |hash| hash[:y] = hash.delete :value }
|
27
|
+
chart_data
|
28
|
+
end
|
29
|
+
|
25
30
|
def chart_options
|
26
31
|
{
|
27
32
|
id: id,
|
28
|
-
chartData:
|
33
|
+
chartData: chart_data_formatted,
|
29
34
|
circumference: full_circle ? [0, 360] : [-100, 100],
|
30
35
|
dark: dark ? "dark" : "",
|
31
36
|
disableAnimation: disable_animation,
|
@@ -38,9 +43,9 @@ module Playbook
|
|
38
43
|
showLabels: show_labels,
|
39
44
|
style: style,
|
40
45
|
tooltipHtml: tooltip_html,
|
41
|
-
type:
|
46
|
+
type: "gauge",
|
42
47
|
colors: colors,
|
43
|
-
}
|
48
|
+
}.to_json.html_safe
|
44
49
|
end
|
45
50
|
|
46
51
|
def classname
|
@@ -0,0 +1,113 @@
|
|
1
|
+
/* @flow */
|
2
|
+
|
3
|
+
import React from 'react'
|
4
|
+
import classnames from 'classnames'
|
5
|
+
|
6
|
+
import { globalProps } from '../utilities/globalProps'
|
7
|
+
import pbChart from '../plugins/pb_chart'
|
8
|
+
|
9
|
+
type LineGraphProps = {
|
10
|
+
align?: "left" | "right" | "center",
|
11
|
+
axisTitle?: string,
|
12
|
+
dark?: Boolean,
|
13
|
+
xAxisCategories: array,
|
14
|
+
yAxisMin: number,
|
15
|
+
yAxisMax: number,
|
16
|
+
className?: string,
|
17
|
+
chartData: array<{
|
18
|
+
name: string,
|
19
|
+
data: array<number>,
|
20
|
+
}>,
|
21
|
+
gradient?: boolean,
|
22
|
+
id: string,
|
23
|
+
pointStart: number,
|
24
|
+
subTitle?: string,
|
25
|
+
title: string,
|
26
|
+
type?: string,
|
27
|
+
legend?: boolean,
|
28
|
+
toggleLegendClick?: boolean,
|
29
|
+
height?: string,
|
30
|
+
colors: array,
|
31
|
+
layout?: "horizontal" | "vertical" | "proximate",
|
32
|
+
verticalAlign?: "top" | "middle" | "bottom",
|
33
|
+
x?: number,
|
34
|
+
y?: number,
|
35
|
+
}
|
36
|
+
|
37
|
+
export default class LineGraph extends React.Component<LineGraphProps> {
|
38
|
+
static defaultProps = {
|
39
|
+
align: "center",
|
40
|
+
className: 'pb_bar_graph',
|
41
|
+
dark: false,
|
42
|
+
gradient: false,
|
43
|
+
type: 'line',
|
44
|
+
legend: false,
|
45
|
+
toggleLegendClick: true,
|
46
|
+
layout: "horizontal",
|
47
|
+
verticalAlign: "bottom",
|
48
|
+
x: 0,
|
49
|
+
y: 0,
|
50
|
+
}
|
51
|
+
|
52
|
+
componentDidMount() {
|
53
|
+
const {
|
54
|
+
align,
|
55
|
+
axisTitle,
|
56
|
+
dark,
|
57
|
+
xAxisCategories,
|
58
|
+
yAxisMin,
|
59
|
+
yAxisMax,
|
60
|
+
className,
|
61
|
+
chartData,
|
62
|
+
id,
|
63
|
+
pointStart,
|
64
|
+
subTitle,
|
65
|
+
title,
|
66
|
+
type,
|
67
|
+
legend,
|
68
|
+
toggleLegendClick,
|
69
|
+
height,
|
70
|
+
colors = [],
|
71
|
+
layout,
|
72
|
+
verticalAlign,
|
73
|
+
x,
|
74
|
+
y,
|
75
|
+
} = this.props
|
76
|
+
|
77
|
+
new pbChart(`.${className}`, {
|
78
|
+
align,
|
79
|
+
axisTitle: axisTitle,
|
80
|
+
chartData: chartData,
|
81
|
+
colors: colors,
|
82
|
+
dark,
|
83
|
+
id: id,
|
84
|
+
pointStart: pointStart,
|
85
|
+
subtitle: subTitle,
|
86
|
+
type,
|
87
|
+
title: title,
|
88
|
+
xAxisCategories: xAxisCategories,
|
89
|
+
yAxisMin: yAxisMin,
|
90
|
+
yAxisMax: yAxisMax,
|
91
|
+
legend: legend,
|
92
|
+
toggleLegendClick: toggleLegendClick,
|
93
|
+
height: height,
|
94
|
+
layout,
|
95
|
+
verticalAlign,
|
96
|
+
x,
|
97
|
+
y,
|
98
|
+
})
|
99
|
+
}
|
100
|
+
|
101
|
+
props: LineGraphProps
|
102
|
+
|
103
|
+
render() {
|
104
|
+
const { className, id } = this.props
|
105
|
+
|
106
|
+
return (
|
107
|
+
<div
|
108
|
+
className={classnames(globalProps(this.props), className)}
|
109
|
+
id={id}
|
110
|
+
/>
|
111
|
+
)
|
112
|
+
}
|
113
|
+
}
|
@@ -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
|
)
|