playbook_ui 11.11.0 → 11.12.1.pre.alpha.passphrase1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/index.js +2 -0
  3. data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.jsx +21 -1
  4. data/app/pb_kits/playbook/pb_bar_graph/bar_graph.rb +17 -1
  5. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_legend_position.html.erb +62 -0
  6. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_legend_position.jsx +86 -0
  7. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_legend_position.md +17 -0
  8. data/app/pb_kits/playbook/pb_bar_graph/docs/example.yml +2 -0
  9. data/app/pb_kits/playbook/pb_bar_graph/docs/index.js +1 -0
  10. data/app/pb_kits/playbook/pb_button/_button.scss +6 -0
  11. data/app/pb_kits/playbook/pb_button/docs/_button_default.html.erb +4 -4
  12. data/app/pb_kits/playbook/pb_button/docs/_button_default.jsx +18 -17
  13. data/app/pb_kits/playbook/pb_circle_chart/_circle_chart.jsx +15 -0
  14. data/app/pb_kits/playbook/pb_circle_chart/circle_chart.rb +16 -0
  15. data/app/pb_kits/playbook/pb_circle_chart/docs/_circle_chart_legend_position.html.erb +86 -0
  16. data/app/pb_kits/playbook/pb_circle_chart/docs/_circle_chart_legend_position.jsx +114 -0
  17. data/app/pb_kits/playbook/pb_circle_chart/docs/_circle_chart_legend_position.md +17 -0
  18. data/app/pb_kits/playbook/pb_circle_chart/docs/example.yml +2 -0
  19. data/app/pb_kits/playbook/pb_circle_chart/docs/index.js +1 -0
  20. data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +50 -50
  21. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_time.jsx +1 -0
  22. data/app/pb_kits/playbook/pb_date_picker/plugins/timeSelect.ts +89 -89
  23. data/app/pb_kits/playbook/pb_date_picker/sass_partials/_time_selection_styles.scss +15 -0
  24. data/app/pb_kits/playbook/pb_dialog/_dialog.scss +30 -1
  25. data/app/pb_kits/playbook/pb_dialog/dialog.html.erb +28 -0
  26. data/app/pb_kits/playbook/pb_dialog/dialog.rb +7 -35
  27. data/app/pb_kits/playbook/pb_dialog/dialogHelper.js +21 -0
  28. data/app/pb_kits/playbook/pb_dialog/dialog_body.html.erb +7 -0
  29. data/app/pb_kits/playbook/pb_dialog/dialog_body.rb +13 -0
  30. data/app/pb_kits/playbook/pb_dialog/dialog_footer.html.erb +13 -0
  31. data/app/pb_kits/playbook/pb_dialog/dialog_footer.rb +16 -0
  32. data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +13 -0
  33. data/app/pb_kits/playbook/pb_dialog/dialog_header.rb +3 -20
  34. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_compound_components.html.erb +12 -0
  35. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_compound_components.md +3 -1
  36. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_default.html.erb +11 -0
  37. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_default.md +3 -0
  38. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_sizes.html.erb +29 -0
  39. data/app/pb_kits/playbook/pb_dialog/docs/example.yml +3 -0
  40. data/app/pb_kits/playbook/pb_icon/_icon.tsx +3 -1
  41. data/app/pb_kits/playbook/pb_icon/icon.rb +8 -1
  42. data/app/pb_kits/playbook/pb_icon/icon.test.js +155 -0
  43. data/app/pb_kits/playbook/pb_legend/{_legend.jsx → _legend.tsx} +6 -6
  44. data/app/pb_kits/playbook/pb_legend/legend.test.js +29 -0
  45. data/app/pb_kits/playbook/pb_line_graph/_line_graph.jsx +20 -0
  46. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_legend_position.html.erb +62 -0
  47. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_legend_position.jsx +83 -0
  48. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_legend_position.md +17 -0
  49. data/app/pb_kits/playbook/pb_line_graph/docs/example.yml +2 -0
  50. data/app/pb_kits/playbook/pb_line_graph/docs/index.js +1 -0
  51. data/app/pb_kits/playbook/pb_line_graph/line_graph.rb +17 -1
  52. data/app/pb_kits/playbook/pb_passphrase/_passphrase.jsx +56 -97
  53. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.html.erb +145 -1
  54. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.jsx +127 -3
  55. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.md +11 -2
  56. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.html.erb +136 -0
  57. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.jsx +90 -8
  58. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.md +5 -0
  59. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.html.erb +51 -0
  60. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.jsx +39 -0
  61. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.html.erb +0 -2
  62. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx +6 -20
  63. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.html.erb +2 -2
  64. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.jsx +1 -1
  65. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.html.erb +318 -5
  66. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx +134 -48
  67. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.md +11 -5
  68. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.html.erb +123 -0
  69. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.jsx +96 -20
  70. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.md +6 -2
  71. data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +4 -0
  72. data/app/pb_kits/playbook/pb_passphrase/docs/index.js +1 -0
  73. data/app/pb_kits/playbook/pb_passphrase/passphrase.html.erb +1 -1
  74. data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +5 -9
  75. data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +0 -47
  76. data/app/pb_kits/playbook/pb_radio/_radio.scss +1 -1
  77. data/app/pb_kits/playbook/pb_title/_title.scss +5 -0
  78. data/app/pb_kits/playbook/pb_title/_title.tsx +4 -1
  79. data/app/pb_kits/playbook/pb_title/docs/{_title_light.html.erb → _title_default.html.erb} +3 -3
  80. data/app/pb_kits/playbook/pb_title/docs/{_title_light.jsx → _title_default.jsx} +14 -14
  81. data/app/pb_kits/playbook/pb_title/docs/{_title_light.md → _title_default.md} +0 -0
  82. data/app/pb_kits/playbook/pb_title/docs/_title_light_weight.html.erb +1 -0
  83. data/app/pb_kits/playbook/pb_title/docs/_title_light_weight.jsx +19 -0
  84. data/app/pb_kits/playbook/pb_title/docs/_title_light_weight.md +4 -0
  85. data/app/pb_kits/playbook/pb_title/docs/example.yml +4 -2
  86. data/app/pb_kits/playbook/pb_title/docs/index.js +2 -1
  87. data/app/pb_kits/playbook/pb_title/title.rb +6 -1
  88. data/app/pb_kits/playbook/pb_title/title.test.js +13 -0
  89. data/app/pb_kits/playbook/playbook-rails.js +4 -0
  90. data/app/pb_kits/playbook/plugins/pb_chart.js +13 -0
  91. data/app/pb_kits/playbook/tokens/_titles.scss +10 -1
  92. data/app/pb_kits/playbook/tokens/_typography.scss +2 -2
  93. data/lib/playbook/version.rb +2 -2
  94. metadata +37 -13
  95. data/app/pb_kits/playbook/pb_dialog/_dialog.html.erb +0 -10
  96. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.md +0 -1
  97. data/app/pb_kits/playbook/pb_passphrase/passwordStrength.js +0 -55
  98. data/app/pb_kits/playbook/pb_passphrase/useHaveIBeenPwned.js +0 -52
  99. data/app/pb_kits/playbook/pb_passphrase/useZxcvbn.js +0 -58
@@ -1,5 +1,6 @@
1
1
  export { default as LineGraphDefault } from './_line_graph_default.jsx'
2
2
  export { default as LineGraphLegend } from './_line_graph_legend.jsx'
3
+ export { default as LineGraphLegendPosition } from './_line_graph_legend_position.jsx'
3
4
  export { default as LineGraphLegendNonclickable } from './_line_graph_legend_nonclickable.jsx'
4
5
  export { default as LineGraphHeight } from './_line_graph_height.jsx'
5
6
  export { default as LineGraphColors } from './_line_graph_colors.jsx'
@@ -3,6 +3,9 @@
3
3
  module Playbook
4
4
  module PbLineGraph
5
5
  class LineGraph < Playbook::KitBase
6
+ prop :align, type: Playbook::Props::Enum,
7
+ values: %w[left right center],
8
+ default: "center"
6
9
  prop :axis_title
7
10
  prop :chart_data, type: Playbook::Props::Array,
8
11
  default: []
@@ -22,6 +25,14 @@ module Playbook
22
25
  prop :height
23
26
  prop :colors, type: Playbook::Props::Array,
24
27
  default: []
28
+ prop :layout, type: Playbook::Props::Enum,
29
+ values: %w[horizontal vertical proximate],
30
+ default: "horizontal"
31
+ prop :vertical_align, type: Playbook::Props::Enum,
32
+ values: %w[top middle bottom],
33
+ default: "bottom"
34
+ prop :x, type: Playbook::Props::Numeric
35
+ prop :y, type: Playbook::Props::Numeric
25
36
 
26
37
  def chart_type
27
38
  gradient ? "area" : "line"
@@ -29,13 +40,14 @@ module Playbook
29
40
 
30
41
  def chart_options
31
42
  {
43
+ align: align,
32
44
  id: id,
33
45
  className: classname,
34
46
  chartData: chart_data,
35
47
  dark: dark ? "dark" : "",
36
48
  type: chart_type,
37
49
  title: title,
38
- subtitle: subtitle,
50
+ subTitle: subtitle,
39
51
  axisTitle: axis_title,
40
52
  pointStart: point_start,
41
53
  xAxisCategories: x_axis_categories,
@@ -45,6 +57,10 @@ module Playbook
45
57
  toggleLegendClick: toggle_legend_click,
46
58
  height: height,
47
59
  colors: colors,
60
+ layout: layout,
61
+ verticalAlign: vertical_align,
62
+ x: x,
63
+ y: y,
48
64
  }
49
65
  end
50
66
 
@@ -1,14 +1,10 @@
1
1
  /* @flow */
2
- /* eslint-disable react-hooks/rules-of-hooks */
3
2
 
4
- import React, { useCallback, useEffect, useMemo, useState } from 'react'
5
- import classnames from 'classnames'
3
+ import React, { useCallback, useMemo, useState } from "react"
4
+ import classnames from "classnames"
6
5
 
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'
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?: 'always' | 'xs' | 'sm' | 'md' | 'lg' | 'xl',
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 ? 'Confirm Passphrase' : 'Passphrase',
57
- minLength = 12,
42
+ label = confirmation ? "Confirm Passphrase" : "Passphrase",
58
43
  onChange = () => {},
59
- showTipsBelow = 'always',
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) : onChange(e),
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
- 'passphrase-popover',
106
- (showTipsBelow === 'always' ? null : `show-below-${showTipsBelow}`),
75
+ "passphrase-popover",
76
+ showTipsBelow === "always" ? null : `show-below-${showTipsBelow}`
107
77
  )
108
- const dataProps = useMemo(
109
- () => (buildDataProps(Object.assign({}, data, { strength }))),
110
- [data, strength]
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
- 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
- }
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 ? 'text' : 'password'}
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 ? 'hide-icon' : ''}
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 ? '' : 'hide-icon'}
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: { check_pwned: true }) %>
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 '../_passphrase'
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 `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.
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
- This uses their k-Anonymity model, so only the first 5 characters of a hashed copy of the passphrase are sent.
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.