playbook_ui 11.13.0 → 11.15.0.pre.alpha.dependencies1
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_dialog/_dialog.scss +39 -0
- data/app/pb_kits/playbook/pb_dialog/child_kits/_dialog_header.tsx +3 -2
- data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +1 -0
- data/app/pb_kits/playbook/pb_dialog/dialog_header.rb +4 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_full_height.jsx +2 -2
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_scrollable.html.erb +13 -1
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_scrollable.jsx +32 -8
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_scrollable.md +5 -2
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_sizes.jsx +2 -2
- data/app/pb_kits/playbook/pb_nav/_subtle_mixin.scss +3 -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/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
@@ -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.
|
@@ -0,0 +1,136 @@
|
|
1
|
+
<%= pb_rails("body", props: {
|
2
|
+
margin_bottom: "md",
|
3
|
+
id: "body_common"
|
4
|
+
}) %>
|
5
|
+
|
6
|
+
<%= pb_rails("passphrase", props: { label: "Passphrase", classname: "passphrase_common" }) %>
|
7
|
+
|
8
|
+
<%= pb_rails("progress_simple", props: { percent: 0, id: "bar_common" }) %>
|
9
|
+
|
10
|
+
<%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "caption_common" }) %>
|
11
|
+
|
12
|
+
|
13
|
+
<%= javascript_tag do %>
|
14
|
+
window.addEventListener("DOMContentLoaded", () => {
|
15
|
+
|
16
|
+
const commonText = document.querySelector("#body_common")
|
17
|
+
|
18
|
+
// variables for the kits you are targeting
|
19
|
+
const passphrase = document.querySelector(".passphrase_common").querySelector("input")
|
20
|
+
const barVariant = document.getElementById("bar_common")
|
21
|
+
const barPercent = document.getElementById("bar_common").querySelector("div")
|
22
|
+
const caption = document.getElementById("caption_common")
|
23
|
+
|
24
|
+
// hide the bar and captions
|
25
|
+
barVariant.style.display = 'none';
|
26
|
+
barPercent.style.display = 'none';
|
27
|
+
caption.style.display = 'none';
|
28
|
+
|
29
|
+
|
30
|
+
const handleStrengthCalculation = (settings) => {
|
31
|
+
const {
|
32
|
+
passphrase = "",
|
33
|
+
common = false,
|
34
|
+
isPwned = false,
|
35
|
+
averageThreshold = 2,
|
36
|
+
minLength = 12,
|
37
|
+
strongThreshold = 3,
|
38
|
+
} = settings
|
39
|
+
|
40
|
+
const resultByScore = {
|
41
|
+
0: {
|
42
|
+
variant: 'negative',
|
43
|
+
label: '',
|
44
|
+
percent: 0,
|
45
|
+
},
|
46
|
+
1: {
|
47
|
+
variant: 'negative',
|
48
|
+
label: 'This passphrase is too common',
|
49
|
+
percent: 25,
|
50
|
+
},
|
51
|
+
2: {
|
52
|
+
variant: 'negative',
|
53
|
+
label: 'Too weak',
|
54
|
+
percent: 25,
|
55
|
+
},
|
56
|
+
3: {
|
57
|
+
variant: 'warning',
|
58
|
+
label: 'Almost there, keep going!',
|
59
|
+
percent: 50,
|
60
|
+
},
|
61
|
+
4: {
|
62
|
+
variant: 'positive',
|
63
|
+
label: 'Success! Strong passphrase',
|
64
|
+
percent: 100,
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
const { score } = zxcvbn(passphrase);
|
69
|
+
|
70
|
+
const noPassphrase = passphrase.length <= 0
|
71
|
+
const commonPassphrase = common || isPwned
|
72
|
+
const weakPassphrase = passphrase.length < minLength || score < averageThreshold
|
73
|
+
const averagePassphrase = score < strongThreshold
|
74
|
+
const strongPassphrase = score >= strongThreshold
|
75
|
+
|
76
|
+
if (noPassphrase) {
|
77
|
+
return {...resultByScore[0], score}
|
78
|
+
} else if (commonPassphrase) {
|
79
|
+
return {...resultByScore[1], score}
|
80
|
+
} else if (weakPassphrase) {
|
81
|
+
return {...resultByScore[2], score}
|
82
|
+
} else if (averagePassphrase){
|
83
|
+
return {...resultByScore[3], score}
|
84
|
+
} else if (strongPassphrase) {
|
85
|
+
return {...resultByScore[4], score}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
// array that holds the common passwords you wish to target
|
90
|
+
const COMMON_PASSPHRASES = ['passphrase', 'apple', 'password', 'p@55w0rd']
|
91
|
+
|
92
|
+
commonText.textContent = `Try typing any of the following: ${COMMON_PASSPHRASES.join(', ')}`
|
93
|
+
|
94
|
+
// function that checks if the user password is in the common password list
|
95
|
+
const isCommon = (passphrase) => {
|
96
|
+
if (COMMON_PASSPHRASES.includes(passphrase))
|
97
|
+
return true
|
98
|
+
return false
|
99
|
+
}
|
100
|
+
|
101
|
+
// event listeners attached to the input field
|
102
|
+
passphrase.addEventListener('input', (e) => {
|
103
|
+
const passphrase = e.target.value;
|
104
|
+
|
105
|
+
// pass in passphrase to the handleStrengthCalculation and set that equal to result variable
|
106
|
+
const result = handleStrengthCalculation({ passphrase: passphrase, common: isCommon(passphrase) })
|
107
|
+
|
108
|
+
// conditional statment to show or hide progress_simple bar and caption if user has entered a password
|
109
|
+
if (passphrase) {
|
110
|
+
barVariant.style.display = 'block';
|
111
|
+
|
112
|
+
barPercent.style.display = 'block';
|
113
|
+
|
114
|
+
caption.style.display = 'block';
|
115
|
+
} else {
|
116
|
+
barVariant.style.display = 'none';
|
117
|
+
|
118
|
+
barPercent.style.display = 'none';
|
119
|
+
|
120
|
+
caption.style.display = 'none';
|
121
|
+
}
|
122
|
+
|
123
|
+
// set the width of the progress_simple kit
|
124
|
+
barPercent.style.width = result.percent.toString()+ "%"
|
125
|
+
|
126
|
+
|
127
|
+
// set the variant of the progress_simple kit
|
128
|
+
barVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left");
|
129
|
+
|
130
|
+
|
131
|
+
// set the text of the caption kit
|
132
|
+
caption.textContent = result.label
|
133
|
+
});
|
134
|
+
|
135
|
+
})
|
136
|
+
<% end %>
|
@@ -1,32 +1,114 @@
|
|
1
|
-
import React, { useState } from 'react'
|
1
|
+
import React, { useState, useEffect } from 'react'
|
2
2
|
|
3
|
-
import Passphrase from '
|
4
|
-
import
|
3
|
+
import {Body, Caption, Passphrase, ProgressSimple} from '../..'
|
4
|
+
import zxcvbn from 'zxcvbn'
|
5
5
|
|
6
6
|
const PassphraseCommon = (props) => {
|
7
7
|
const [input, setInput] = useState('')
|
8
|
-
|
8
|
+
const [checkStrength, setCheckStrength] = useState({
|
9
|
+
label: '',
|
10
|
+
percent: 0,
|
11
|
+
score: 0,
|
12
|
+
variant: ''
|
13
|
+
})
|
9
14
|
const handleChange = (e) => setInput(e.target.value)
|
10
15
|
|
16
|
+
const handleStrengthCalculation = (settings) => {
|
17
|
+
const {
|
18
|
+
passphrase = "",
|
19
|
+
common = false,
|
20
|
+
isPwned = false,
|
21
|
+
averageThreshold = 2,
|
22
|
+
minLength = 12,
|
23
|
+
strongThreshold = 3,
|
24
|
+
} = settings
|
25
|
+
|
26
|
+
const resultByScore = {
|
27
|
+
0: {
|
28
|
+
variant: 'negative',
|
29
|
+
label: '',
|
30
|
+
percent: 0,
|
31
|
+
},
|
32
|
+
1: {
|
33
|
+
variant: 'negative',
|
34
|
+
label: 'This passphrase is too common',
|
35
|
+
percent: 25,
|
36
|
+
},
|
37
|
+
2: {
|
38
|
+
variant: 'negative',
|
39
|
+
label: 'Too weak',
|
40
|
+
percent: 25,
|
41
|
+
},
|
42
|
+
3: {
|
43
|
+
variant: 'warning',
|
44
|
+
label: 'Almost there, keep going!',
|
45
|
+
percent: 50,
|
46
|
+
},
|
47
|
+
4: {
|
48
|
+
variant: 'positive',
|
49
|
+
label: 'Success! Strong passphrase',
|
50
|
+
percent: 100,
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
const { score } = zxcvbn(passphrase);
|
55
|
+
|
56
|
+
const noPassphrase = passphrase.length <= 0
|
57
|
+
const commonPassphrase = common || isPwned
|
58
|
+
const weakPassphrase = passphrase.length < minLength || score < averageThreshold
|
59
|
+
const averagePassphrase = score < strongThreshold
|
60
|
+
const strongPassphrase = score >= strongThreshold
|
61
|
+
|
62
|
+
if (noPassphrase) {
|
63
|
+
return {...resultByScore[0], score}
|
64
|
+
} else if (commonPassphrase) {
|
65
|
+
return {...resultByScore[1], score}
|
66
|
+
} else if (weakPassphrase) {
|
67
|
+
return {...resultByScore[2], score}
|
68
|
+
} else if (averagePassphrase){
|
69
|
+
return {...resultByScore[3], score}
|
70
|
+
} else if (strongPassphrase) {
|
71
|
+
return {...resultByScore[4], score}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
11
75
|
const COMMON_PASSPHRASES = ['passphrase', 'apple', 'password', 'p@55w0rd']
|
12
76
|
|
13
|
-
const
|
77
|
+
const isCommon = (passphrase) => {
|
14
78
|
if (COMMON_PASSPHRASES.includes(passphrase))
|
15
79
|
return true
|
16
80
|
return false
|
17
81
|
}
|
18
82
|
|
83
|
+
useEffect(() => {
|
84
|
+
const result = handleStrengthCalculation({ passphrase: input, common: isCommon(input) });
|
85
|
+
setCheckStrength({ ...result })
|
86
|
+
}, [input])
|
87
|
+
|
88
|
+
|
19
89
|
return (
|
20
90
|
<>
|
21
91
|
<div>
|
22
|
-
<Body
|
23
|
-
|
92
|
+
<Body
|
93
|
+
marginBottom='md'
|
94
|
+
text={`Try typing any of the following: ${COMMON_PASSPHRASES.join(', ')}`} />
|
24
95
|
<Passphrase
|
25
|
-
common={commonCheck(input)}
|
26
96
|
onChange={handleChange}
|
27
97
|
value={input}
|
28
98
|
{...props}
|
29
99
|
/>
|
100
|
+
{input.length > 0 && (
|
101
|
+
<>
|
102
|
+
<ProgressSimple
|
103
|
+
className={input.length === 0 ? "progress-empty-input" : null}
|
104
|
+
percent={checkStrength.percent}
|
105
|
+
variant={checkStrength.variant}
|
106
|
+
/>
|
107
|
+
<Caption size='xs'
|
108
|
+
text={checkStrength.label}
|
109
|
+
/>
|
110
|
+
</>
|
111
|
+
)}
|
30
112
|
</div>
|
31
113
|
</>
|
32
114
|
)
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<div class="pb_pill_kit_warning"><div class="pb_title_kit_size_4 pb_pill_text">Disclaimer</div></div>
|
2
|
+
|
3
|
+
This example depends on the `zxcvbn` library.
|
4
|
+
|
5
|
+
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.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<%= pb_rails("passphrase", props: { classname: "pass_input_1" }) %>
|
2
|
+
|
3
|
+
<%= pb_rails("passphrase", props: { confirmation: true, classname: "pass_input_2"}) %>
|
4
|
+
|
5
|
+
<div id="match"> </div>
|
6
|
+
|
7
|
+
<%= javascript_tag do %>
|
8
|
+
window.addEventListener("DOMContentLoaded", () => {
|
9
|
+
|
10
|
+
const useState = (defaultValue) => {
|
11
|
+
let value = defaultValue;
|
12
|
+
const getValue = () => value
|
13
|
+
const setValue = (newValue) => {
|
14
|
+
return value = newValue
|
15
|
+
}
|
16
|
+
return [getValue, setValue];
|
17
|
+
}
|
18
|
+
|
19
|
+
const [input, setInput] = useState('')
|
20
|
+
const [confirmationInput, setConfirmationInput] = useState('')
|
21
|
+
|
22
|
+
const match = document.querySelector("#match")
|
23
|
+
|
24
|
+
const input1 = document.querySelector(".pass_input_1").querySelector("input")
|
25
|
+
const input2 = document.querySelector(".pass_input_2").querySelector("input")
|
26
|
+
|
27
|
+
input1.addEventListener('input', (e) => {
|
28
|
+
setInput(e.target.value)
|
29
|
+
setMatchText()
|
30
|
+
});
|
31
|
+
|
32
|
+
input2.addEventListener('input', (e) => {
|
33
|
+
setConfirmationInput(e.target.value)
|
34
|
+
setMatchText()
|
35
|
+
});
|
36
|
+
|
37
|
+
const setMatchText = () => {
|
38
|
+
|
39
|
+
if (input() && confirmationInput()) {
|
40
|
+
if (input() === confirmationInput()) {
|
41
|
+
match.textContent = "They match!"
|
42
|
+
} else {
|
43
|
+
match.textContent = "They don't match!"
|
44
|
+
}
|
45
|
+
} else {
|
46
|
+
match.textContent = ""
|
47
|
+
}
|
48
|
+
|
49
|
+
}
|
50
|
+
})
|
51
|
+
<% end %>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import React, { useState } from 'react'
|
2
|
+
|
3
|
+
import {Body, Passphrase} from '../..'
|
4
|
+
|
5
|
+
const PassphraseConfirmation = (props) => {
|
6
|
+
const [input, setInput] = useState('')
|
7
|
+
const [confirmationInput, setConfirmationInput] = useState('')
|
8
|
+
|
9
|
+
const handleChange = (e) => setInput(e.target.value)
|
10
|
+
const handleConfirmationChange = (e) => setConfirmationInput(e.target.value)
|
11
|
+
|
12
|
+
return (
|
13
|
+
<>
|
14
|
+
<div>
|
15
|
+
<Passphrase
|
16
|
+
onChange={handleChange}
|
17
|
+
value={input}
|
18
|
+
{...props}
|
19
|
+
/>
|
20
|
+
<Passphrase
|
21
|
+
confirmation
|
22
|
+
onChange={handleConfirmationChange}
|
23
|
+
value={confirmationInput}
|
24
|
+
{...props}
|
25
|
+
/>
|
26
|
+
{input && confirmationInput && (
|
27
|
+
<Body
|
28
|
+
text={
|
29
|
+
input === confirmationInput ? "They match!" : "They don't match!"
|
30
|
+
}
|
31
|
+
{...props}
|
32
|
+
/>
|
33
|
+
)}
|
34
|
+
</div>
|
35
|
+
</>
|
36
|
+
);
|
37
|
+
}
|
38
|
+
|
39
|
+
export default PassphraseConfirmation
|