playbook_ui 11.13.0 → 11.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_passphrase/_passphrase.jsx +56 -97
  3. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.html.erb +145 -1
  4. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.jsx +127 -3
  5. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.md +11 -2
  6. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.html.erb +136 -0
  7. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.jsx +90 -8
  8. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.md +5 -0
  9. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.html.erb +51 -0
  10. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.jsx +39 -0
  11. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.html.erb +0 -2
  12. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx +6 -20
  13. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.html.erb +2 -2
  14. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.jsx +1 -1
  15. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.html.erb +318 -5
  16. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx +134 -48
  17. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.md +11 -5
  18. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.html.erb +123 -0
  19. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.jsx +96 -20
  20. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.md +6 -2
  21. data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +4 -0
  22. data/app/pb_kits/playbook/pb_passphrase/docs/index.js +1 -0
  23. data/app/pb_kits/playbook/pb_passphrase/passphrase.html.erb +1 -1
  24. data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +5 -9
  25. data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +0 -47
  26. data/lib/playbook/version.rb +2 -2
  27. metadata +7 -6
  28. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.md +0 -1
  29. data/app/pb_kits/playbook/pb_passphrase/passwordStrength.js +0 -55
  30. data/app/pb_kits/playbook/pb_passphrase/useHaveIBeenPwned.js +0 -52
  31. data/app/pb_kits/playbook/pb_passphrase/useZxcvbn.js +0 -58
@@ -1,34 +1,110 @@
1
1
  import React, { useState } from 'react'
2
+ import { useEffect } from 'react'
2
3
 
3
- import Passphrase from '../_passphrase'
4
-
5
- import TextInput from '../../pb_text_input/_text_input'
4
+ import { Caption, Passphrase, ProgressSimple, TextInput} from '../..'
5
+ import zxcvbn from 'zxcvbn'
6
6
 
7
7
  const PassphraseStrengthChange = (props) => {
8
8
  const [input, setInput] = useState('')
9
+ const [checkStrength, setCheckStrength] = useState({
10
+ label: '',
11
+ percent: 0,
12
+ score: 0,
13
+ variant: ''
14
+ })
9
15
 
10
16
  const handleChange = (e) => setInput(e.target.value)
11
17
 
12
- const [strength, setStrength] = useState(0)
13
- const handleStrengthChange = (str) => setStrength(str)
18
+ const handleStrengthCalculation = (settings) => {
19
+ const {
20
+ passphrase = "",
21
+ common = false,
22
+ isPwned = false,
23
+ averageThreshold = 2,
24
+ minLength = 12,
25
+ strongThreshold = 3,
26
+ } = settings
27
+
28
+ const resultByScore = {
29
+ 0: {
30
+ variant: 'negative',
31
+ label: '',
32
+ percent: 0,
33
+ },
34
+ 1: {
35
+ variant: 'negative',
36
+ label: 'This passphrase is too common',
37
+ percent: 25,
38
+ },
39
+ 2: {
40
+ variant: 'negative',
41
+ label: 'Too weak',
42
+ percent: 25,
43
+ },
44
+ 3: {
45
+ variant: 'warning',
46
+ label: 'Almost there, keep going!',
47
+ percent: 50,
48
+ },
49
+ 4: {
50
+ variant: 'positive',
51
+ label: 'Success! Strong passphrase',
52
+ percent: 100,
53
+ }
54
+ }
55
+
56
+ const { score } = zxcvbn(passphrase);
57
+
58
+ const noPassphrase = passphrase.length <= 0
59
+ const commonPassphrase = common || isPwned
60
+ const weakPassphrase = passphrase.length < minLength || score < averageThreshold
61
+ const averagePassphrase = score < strongThreshold
62
+ const strongPassphrase = score >= strongThreshold
63
+
64
+ if (noPassphrase) {
65
+ return {...resultByScore[0], score}
66
+ } else if (commonPassphrase) {
67
+ return {...resultByScore[1], score}
68
+ } else if (weakPassphrase) {
69
+ return {...resultByScore[2], score}
70
+ } else if (averagePassphrase){
71
+ return {...resultByScore[3], score}
72
+ } else if (strongPassphrase) {
73
+ return {...resultByScore[4], score}
74
+ }
75
+ }
76
+
77
+ useEffect(() => {
78
+ const result = handleStrengthCalculation({ passphrase: input })
79
+ setCheckStrength({...result})
80
+ },[input])
14
81
 
15
82
  return (
16
83
  <>
17
- <div>
18
- <Passphrase
19
- label="Passphrase"
20
- onChange={handleChange}
21
- onStrengthChange={handleStrengthChange}
22
- value={input}
23
- {...props}
24
- />
25
- <TextInput
26
- disabled
27
- label="Passphrase Strength"
28
- readOnly
29
- value={strength}
30
- />
31
- </div>
84
+ <Passphrase
85
+ label="Passphrase"
86
+ onChange={handleChange}
87
+ value={input}
88
+ {...props}
89
+ />
90
+ {input.length > 0 && (
91
+ <>
92
+ <ProgressSimple
93
+ percent={checkStrength.percent}
94
+ variant={checkStrength.variant}
95
+ />
96
+ <Caption size='xs'
97
+ text={checkStrength.label}
98
+ />
99
+ </>
100
+ )}
101
+ <TextInput
102
+ disabled
103
+ label="Passphrase Strength"
104
+ marginTop="xl"
105
+ readOnly
106
+ value={checkStrength.score}
107
+ />
32
108
  </>
33
109
  )
34
110
  }
@@ -1,3 +1,7 @@
1
- As the strength of the entered passphrase changes, the optional `onStrengthChange` callback is called with the new strength value. This exposes the calculated strength.
1
+ Strength is calculated on a 0-4 scale by the <a href="https://github.com/dropbox/zxcvbn" target="_blank"> Zxcvbn package</a>.
2
2
 
3
- Strength is calculated on a 0-4 scale by the <a href="https://github.com/dropbox/zxcvbn" target="_blank"> Zxcvbn package</a>
3
+ <div class="pb_pill_kit_warning"><div class="pb_title_kit_size_4 pb_pill_text">Disclaimer</div></div>
4
+
5
+ This example depends on the `zxcvbn` library.
6
+
7
+ 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.
@@ -2,13 +2,17 @@ examples:
2
2
 
3
3
  rails:
4
4
  - passphrase_default: Default
5
+ - passphrase_confirmation: Confirmation
5
6
  - passphrase_meter_settings: Meter Settings
6
7
  - passphrase_input_props: Input Props
7
8
  - passphrase_tips: Tips
9
+ - passphrase_strength_change: Strength Change
10
+ - passphrase_common: Common Passphrases
8
11
  - passphrase_breached: Breached Passphrases
9
12
 
10
13
  react:
11
14
  - passphrase_default: Default
15
+ - passphrase_confirmation: Confirmation
12
16
  - passphrase_meter_settings: Meter Settings
13
17
  - passphrase_input_props: Input Props
14
18
  - passphrase_tips: Tips
@@ -1,4 +1,5 @@
1
1
  export { default as PassphraseDefault } from './_passphrase_default.jsx'
2
+ export { default as PassphraseConfirmation } from './_passphrase_confirmation.jsx'
2
3
  export { default as PassphraseMeterSettings } from './_passphrase_meter_settings'
3
4
  export { default as PassphraseInputProps } from './_passphrase_input_props'
4
5
  export { default as PassphraseTips } from './_passphrase_tips'
@@ -1 +1 @@
1
- <%= react_component('Passphrase', object.passphrase_options) %>
1
+ <%= react_component('Passphrase', object.passphrase_options, class: object.classname,) %>
@@ -3,15 +3,14 @@
3
3
  module Playbook
4
4
  module PbPassphrase
5
5
  class Passphrase < Playbook::KitBase
6
- prop :average_threshold
7
- prop :check_pwned
8
6
  prop :confirmation, type: Playbook::Props::Boolean, default: false
9
7
  prop :input_props, type: Playbook::Props::Hash, default: {}
10
8
  prop :label
11
- prop :min_length
12
- prop :show_tips_below
13
- prop :strong_threshold
9
+ prop :show_tips_below, type: Playbook::Props::Enum,
10
+ values: %w[always xs sm md lg xl],
11
+ default: "always"
14
12
  prop :tips, type: Playbook::Props::Array, default: []
13
+ prop :value, type: Playbook::Props::String
15
14
 
16
15
  def classname
17
16
  generate_classname("pb_passphrase")
@@ -19,18 +18,15 @@ module Playbook
19
18
 
20
19
  def passphrase_options
21
20
  {
22
- checkPwned: check_pwned,
23
21
  dark: dark,
24
22
  id: id,
25
- averageThreshold: average_threshold,
26
23
  confirmation: confirmation,
27
24
  inputProps: input_props,
28
25
  label: label,
29
- minLength: min_length,
30
26
  showTipsBelow: show_tips_below,
31
- strongThreshold: strong_threshold,
32
27
  tips: tips,
33
28
  uncontrolled: true,
29
+ value: value,
34
30
  }.compact
35
31
  end
36
32
  end
@@ -64,41 +64,6 @@ test('passes input props to input element', () => {
64
64
  expect(input).toBeDisabled()
65
65
  })
66
66
 
67
- test('progress bar is invisible when value is empty', () => {
68
- render(
69
- <Passphrase
70
- data={{ testid: testId }}
71
- />
72
- )
73
-
74
- const kit = screen.getByTestId(testId)
75
- expect(kit.querySelector('[class^=pb_progress_simple_wrapper]')).toHaveClass('progress-empty-input')
76
- })
77
-
78
- test('progress bar is visible when value is not empty', () => {
79
- render(
80
- <Passphrase
81
- data={{ testid: testId }}
82
- value="test_password_input"
83
- />
84
- )
85
-
86
- const kit = screen.getByTestId(testId)
87
- expect(kit.querySelector('[class^=pb_progress_simple_wrapper]')).not.toHaveClass('progress-empty-input')
88
- })
89
-
90
- test('no progress bar is show when confirmation is true', () => {
91
- render(
92
- <Passphrase
93
- confirmation
94
- data={{ testid: testId }}
95
- />
96
- )
97
-
98
- const kit = screen.getByTestId(testId)
99
- expect(kit.querySelector('[class^=pb_progress_simple_wrapper]')).toBeNull()
100
- })
101
-
102
67
  test('popover target shows when tips are given', () => {
103
68
  render(
104
69
  <Passphrase
@@ -121,15 +86,3 @@ test('popover target does not show when tips are not given', () => {
121
86
  const kit = screen.getByTestId(testId)
122
87
  expect(kit.querySelector('[class^=pb_popover_reference_wrapper]')).toBeNull()
123
88
  })
124
-
125
- test('data-strength attribute exposes strength of password', () => {
126
- render(
127
- <Passphrase
128
- data={{ testid: testId }}
129
- value="correct horse battery staple"
130
- />
131
- )
132
-
133
- const kit = screen.getByTestId(testId)
134
- expect(parseInt(kit.getAttribute('data-strength'))).toBeGreaterThan(0)
135
- })
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playbook
4
- PREVIOUS_VERSION = "11.12.1"
5
- VERSION = "11.13.0"
4
+ PREVIOUS_VERSION = "11.13.0"
5
+ VERSION = "11.14.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: playbook_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.13.0
4
+ version: 11.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Power UX
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-12-06 00:00:00.000000000 Z
12
+ date: 2022-12-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -1465,16 +1465,20 @@ files:
1465
1465
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.html.erb
1466
1466
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.jsx
1467
1467
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.md
1468
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.html.erb
1468
1469
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.jsx
1470
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.md
1471
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.html.erb
1472
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.jsx
1469
1473
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.html.erb
1470
1474
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx
1471
- - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.md
1472
1475
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.html.erb
1473
1476
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.jsx
1474
1477
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.md
1475
1478
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.html.erb
1476
1479
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx
1477
1480
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.md
1481
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.html.erb
1478
1482
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.jsx
1479
1483
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.md
1480
1484
  - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_tips.html.erb
@@ -1485,9 +1489,6 @@ files:
1485
1489
  - app/pb_kits/playbook/pb_passphrase/passphrase.html.erb
1486
1490
  - app/pb_kits/playbook/pb_passphrase/passphrase.rb
1487
1491
  - app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx
1488
- - app/pb_kits/playbook/pb_passphrase/passwordStrength.js
1489
- - app/pb_kits/playbook/pb_passphrase/useHaveIBeenPwned.js
1490
- - app/pb_kits/playbook/pb_passphrase/useZxcvbn.js
1491
1492
  - app/pb_kits/playbook/pb_person/_person.jsx
1492
1493
  - app/pb_kits/playbook/pb_person/_person.scss
1493
1494
  - app/pb_kits/playbook/pb_person/docs/_description.md
@@ -1 +0,0 @@
1
- Use the `confirmation` prop to only include the label and show/hide icon.
@@ -1,55 +0,0 @@
1
- import zxcvbn from 'zxcvbn'
2
-
3
- export const zxcvbnPasswordScore = (options) => {
4
- const {
5
- calculate = zxcvbn,
6
- averageThreshold = 2,
7
- strongThreshold = 3,
8
- minLength = 12,
9
- } = options
10
-
11
- return {
12
- minLength,
13
- averageThreshold,
14
- strongThreshold,
15
- test: function (password = '', common = false) {
16
- const feedbackValues = (str) => {
17
- let percent, variant, text
18
-
19
- if (password.length <= 0) {
20
- percent = '0'
21
- variant = 'negative'
22
- text = '\u00A0' //nbsp to keep form from jumping when typing beings
23
- } else if (common) {
24
- percent = '25'
25
- variant = 'negative'
26
- text = 'This passphrase is too common'
27
- } else if (password.length < this.minLength || str < this.averageThreshold) {
28
- percent = '25'
29
- variant = 'negative'
30
- text = 'Too weak'
31
- } else if (str < this.strongThreshold){
32
- percent = '50'
33
- variant = 'warning'
34
- text = 'Almost there, keep going!'
35
- } else if (str >= this.strongThreshold) {
36
- percent = '100'
37
- variant = 'positive'
38
- text = 'Success! Strong passphrase'
39
- }
40
- return { percent, variant, text }
41
- }
42
-
43
- const result = calculate(password)
44
-
45
- return (
46
- {
47
- suggestions: result.feedback.suggestions,
48
- warning: result.feedback.warning,
49
- strength: result.score,
50
- ...feedbackValues(result.score),
51
- }
52
- )
53
- },
54
- }
55
- }
@@ -1,52 +0,0 @@
1
-
2
- import { useEffect, useState } from 'react'
3
-
4
- const checkHaveIBeenPwned = async function (passphrase) {
5
- const buffer = new TextEncoder('utf-8').encode(passphrase)
6
- const digest = await crypto.subtle.digest('SHA-1', buffer)
7
- const hashArray = Array.from(new Uint8Array(digest))
8
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
9
-
10
- const firstFive = hashHex.slice(0, 5)
11
- const endOfHash = hashHex.slice(5)
12
-
13
- const resp = await fetch(`https://api.pwnedpasswords.com/range/${firstFive}`)
14
- const text = await resp.text()
15
-
16
- const match = text.split('\n').some((line) => {
17
- //Each line is <sha-1-hash-suffix>:<count of incidents>
18
- return line.split(':')[0] === endOfHash.toUpperCase()
19
- })
20
- return match
21
- }
22
-
23
- /**
24
- * If the input hasn't changed in <delay> ms,
25
- * hit the haveibeenpwned api and check if the given passphrase is compromised
26
- */
27
- export default function useHaveIBeenPwned(passphrase, minLength, delay = 400) {
28
- const [isPwned, setIsPwned] = useState(false)
29
-
30
- useEffect(
31
- () => {
32
- // only check the API for passphrases above the minimum size
33
- if (passphrase.length < minLength) {
34
- setIsPwned(false)
35
- return
36
- }
37
-
38
- const handler = setTimeout(() => {
39
- checkHaveIBeenPwned(passphrase)
40
- .then((pwned) => setIsPwned(pwned))
41
- .catch(() => setIsPwned(false))
42
- }, delay)
43
-
44
- return () => {
45
- clearTimeout(handler)
46
- }
47
- },
48
- [passphrase, minLength, delay]
49
- )
50
-
51
- return isPwned
52
- }
@@ -1,58 +0,0 @@
1
- import { useEffect, useMemo, useState } from 'react'
2
- import zxcvbn from 'zxcvbn'
3
-
4
- export default function useZxcvbn(options) {
5
- const { passphrase = '', common, isPwned, confirmation, averageThreshold, minLength, strongThreshold } = options
6
- const calculator = useMemo(
7
- () => confirmation ? () => ({ score: 0 }) : zxcvbn,
8
- [confirmation]
9
- )
10
-
11
- const [percent, setPercent] = useState('0')
12
- const [variant, setVariant] = useState('negative')
13
- const [text, setText] = useState('\u00A0') //nbsp to keep height constant
14
- const [result, setResult] = useState({})
15
-
16
- useEffect(() => {
17
- if (confirmation) return
18
- const newResult = calculator(passphrase)
19
- setResult(newResult)
20
- const str = newResult.score
21
-
22
- const noPassphrase = passphrase.length <= 0
23
- const commonPassphrase = common || isPwned
24
- const weakPassphrase = passphrase.length < minLength || str < averageThreshold
25
- const averagePassphrase = str < strongThreshold
26
- const strongPassphrase = str >= strongThreshold
27
-
28
- if (noPassphrase) {
29
- setPercent('0')
30
- setVariant('negative')
31
- setText('\u00A0') //nbsp to keep height constant
32
- } else if (commonPassphrase) {
33
- setPercent('25')
34
- setVariant('negative')
35
- setText('This passphrase is too common')
36
- } else if (weakPassphrase) {
37
- setPercent('25')
38
- setVariant('negative')
39
- setText('Too weak')
40
- } else if (averagePassphrase){
41
- setPercent('50')
42
- setVariant('warning')
43
- setText('Almost there, keep going!')
44
- } else if (strongPassphrase) {
45
- setPercent('100')
46
- setVariant('positive')
47
- setText('Success! Strong passphrase')
48
- }
49
- }, [passphrase, common, isPwned, averageThreshold, minLength, strongThreshold]
50
- )
51
-
52
- return {
53
- strength: common || isPwned ? 0 : result.score,
54
- percent,
55
- variant,
56
- text,
57
- }
58
- }