import React, { useState } from 'react' import Passphrase from 'playbook-ui' const PassphraseDefault = (props) => { const [input, setInput] = useState('') const handleChange = (e) => setInput(e.target.value) return ( <Passphrase id="my-passphrase" onChange={handleChange} value={input} /> ) } export default PassphraseDefault
import React, { useState } from 'react' import {Body, Passphrase} from 'playbook-ui' const PassphraseConfirmation = (props) => { const [input, setInput] = useState('') const [confirmationInput, setConfirmationInput] = useState('') const handleChange = (e) => setInput(e.target.value) const handleConfirmationChange = (e) => setConfirmationInput(e.target.value) return ( <> <div> <Passphrase onChange={handleChange} value={input} /> <Passphrase confirmation onChange={handleConfirmationChange} value={confirmationInput} /> {input && confirmationInput && ( <Body text={ input === confirmationInput ? "They match!" : "They don't match!" } /> )} </div> </> ); } export default PassphraseConfirmation
This example shows how to enhance the passphrase strenght by setting diferent thresholds and lengths.
The meterSettings
array contains different settings for each rendered input. The handleStrengthCalculation
handles the strength calculation using those settings, showing different results for the same passphrase
input.
By default, minLength
is 12. Try typing any value in the Default Example
input. Notice that the bar won't change from red until the minimum is met.
Adjust these props to tune the sensitivity of the bar.
Note: minimum length trumps strength and will set the bar to a red color, despite whatever strength is calculated.
This example depends on the zxcvbn
library.
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.
import React, { useState } from 'react' import { Body, Caption, Passphrase, ProgressSimple, TextInput } from 'playbook-ui' import zxcvbn from 'zxcvbn' const PassphraseMeterSettings = (props) => { const [input, setInput] = useState('') const [result, setResult] = useState({}) const [calculatedStrength, setCalculatedStrength] = useState(0) const meterSettings = [ { label: "Default settings" }, { minLength: 5, label: "Min length = 5", }, { minLength: 30, label: "Min length = 30", }, { label: "Average threshold = 1", averageThreshold: 1, }, { label: "Strong Threshold = 4", strongThreshold: 4, }, ] const handleStrengthCalculation = (settings) => { const { passphrase = "", common = false, isPwned = false, averageThreshold = 2, minLength = 12, strongThreshold = 3, } = settings const resultByScore = { 0: { variant: 'negative', label: '', percent: 0, }, 1: { variant: 'negative', label: 'This passphrase is too common', percent: 25, }, 2: { variant: 'negative', label: 'Too weak', percent: 25, }, 3: { variant: 'warning', label: 'Almost there, keep going!', percent: 50, }, 4: { variant: 'positive', label: 'Success! Strong passphrase', percent: 100, } } const { score } = zxcvbn(passphrase); const noPassphrase = passphrase.length <= 0 const commonPassphrase = common || isPwned const weakPassphrase = passphrase.length < minLength || score < averageThreshold const averagePassphrase = score < strongThreshold const strongPassphrase = score >= strongThreshold if (noPassphrase) { return {...resultByScore[0], score} } else if (commonPassphrase) { return {...resultByScore[1], score} } else if (weakPassphrase) { return {...resultByScore[2], score} } else if (averagePassphrase){ return {...resultByScore[3], score} } else if (strongPassphrase) { return {...resultByScore[4], score} } } const handleChange = (e) => { const passphrase = e.target.value; setInput(passphrase) const calculated = [] meterSettings.forEach((setting, index) => { const results = handleStrengthCalculation({passphrase, ...setting}) if (index == 0) setCalculatedStrength(results.score) calculated.push(results) }) setResult(calculated) } return ( <> <div> <Body> { "These examples will all share the same input value. Type in any of the inputs to see how the strength meter changes in response to different settings." } </Body> <Passphrase label={"Type your passphrase"} onChange={handleChange} value={input} /> <TextInput disabled label="Calculated Strength" readOnly value={calculatedStrength} /> {meterSettings.map((settings, index) => ( <div key={index}> <Passphrase label={settings.label} onChange={handleChange} value={input} /> {input.length > 0 && ( <> <ProgressSimple percent={result[index].percent} variant={result[index].variant} /> <Caption size='xs' text={result[index].label} /> </> )} </div> ))} </div> </> ); } export default PassphraseMeterSettings
inputProps
is passed directly to an underlying Text Input kit. See the specific docs here for more details.
import React, { useState } from 'react' import Passphrase from 'playbook-ui' const PassphraseInputProps = (props) => { const [input, setInput] = useState('') const handleChange = (e) => setInput(e.target.value) return ( <> <div> <Passphrase inputProps={{ name: 'my-disabled-field', id: 'my-value-id', disabled: true, }} label="Pass props directly to input kit" onChange={handleChange} value={input} /> <Passphrase inputProps={{ children: ( <input onChange={handleChange} type="password" value={input} />), }} label="Custom input" onChange={handleChange} value={input} /> <Passphrase inputProps={{ name: 'my-value-name', id: 'my-value-id-2' }} label="Set name and ID for use in form libraries" onChange={handleChange} value={input} /> <Passphrase confirmation inputProps={{ name: 'my-value-confirmation-name', id: 'my-value-confirmation-id' }} onChange={handleChange} value={input} /> </div> </> ) } export default PassphraseInputProps
showTipsBelow
(react) / show_tips_below
(rails) takes 'xs', 'sm', 'md', 'lg', 'xl' and only show the tips below the given screen size. Similar to the responsive table breakpoints. Omit this prop to always show.
import React, { useState } from 'react' import Passphrase from 'playbook-ui' const PassphraseTips = (props) => { const [input, setInput] = useState('') const handleChange = (e) => setInput(e.target.value) return ( <> <div> <Passphrase label="Pass an array of strings to the tips prop" onChange={handleChange} tips={['And the info icon will appear.', 'Each string will be displayed as its own tip']} value={input} /> <Passphrase label="Omit the prop to hide the icon" onChange={handleChange} value={input} /> <Passphrase label="Only show tips at small screen size" onChange={handleChange} showTipsBelow="xs" tips={['Make the password longer', 'Type more things', 'Use something else']} value={input} /> <Passphrase label="Only show tips at medium screen size" onChange={handleChange} showTipsBelow="md" tips={['Make the password longer', 'Type more things', 'Use something else']} value={input} /> <Passphrase label="Only show tips at large screen size" onChange={handleChange} showTipsBelow="lg" tips={['Make the password longer', 'Type more things', 'Use something else']} value={input} /> </div> </> ) } export default PassphraseTips
Strength is calculated on a 0-4 scale by the Zxcvbn package.
This example depends on the zxcvbn
library.
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.
import React, { useState } from 'react' import { useEffect } from 'react' import { Caption, Passphrase, ProgressSimple, TextInput} from 'playbook-ui' import zxcvbn from 'zxcvbn' const PassphraseStrengthChange = (props) => { const [input, setInput] = useState('') const [checkStrength, setCheckStrength] = useState({ label: '', percent: 0, score: 0, variant: '' }) const handleChange = (e) => setInput(e.target.value) const handleStrengthCalculation = (settings) => { const { passphrase = "", common = false, isPwned = false, averageThreshold = 2, minLength = 12, strongThreshold = 3, } = settings const resultByScore = { 0: { variant: 'negative', label: '', percent: 0, }, 1: { variant: 'negative', label: 'This passphrase is too common', percent: 25, }, 2: { variant: 'negative', label: 'Too weak', percent: 25, }, 3: { variant: 'warning', label: 'Almost there, keep going!', percent: 50, }, 4: { variant: 'positive', label: 'Success! Strong passphrase', percent: 100, } } const { score } = zxcvbn(passphrase); const noPassphrase = passphrase.length <= 0 const commonPassphrase = common || isPwned const weakPassphrase = passphrase.length < minLength || score < averageThreshold const averagePassphrase = score < strongThreshold const strongPassphrase = score >= strongThreshold if (noPassphrase) { return {...resultByScore[0], score} } else if (commonPassphrase) { return {...resultByScore[1], score} } else if (weakPassphrase) { return {...resultByScore[2], score} } else if (averagePassphrase){ return {...resultByScore[3], score} } else if (strongPassphrase) { return {...resultByScore[4], score} } } useEffect(() => { const result = handleStrengthCalculation({ passphrase: input }) setCheckStrength({...result}) },[input]) return ( <> <Passphrase label="Passphrase" onChange={handleChange} value={input} /> {input.length > 0 && ( <> <ProgressSimple percent={checkStrength.percent} variant={checkStrength.variant} /> <Caption size='xs' text={checkStrength.label} /> </> )} <TextInput disabled label="Passphrase Strength" marginTop="xl" readOnly value={checkStrength.score} /> </> ) } export default PassphraseStrengthChange
This example depends on the zxcvbn
library.
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.
import React, { useState, useEffect } from 'react' import {Body, Caption, Passphrase, ProgressSimple} from 'playbook-ui' import zxcvbn from 'zxcvbn' const PassphraseCommon = (props) => { const [input, setInput] = useState('') const [checkStrength, setCheckStrength] = useState({ label: '', percent: 0, score: 0, variant: '' }) const handleChange = (e) => setInput(e.target.value) const handleStrengthCalculation = (settings) => { const { passphrase = "", common = false, isPwned = false, averageThreshold = 2, minLength = 12, strongThreshold = 3, } = settings const resultByScore = { 0: { variant: 'negative', label: '', percent: 0, }, 1: { variant: 'negative', label: 'This passphrase is too common', percent: 25, }, 2: { variant: 'negative', label: 'Too weak', percent: 25, }, 3: { variant: 'warning', label: 'Almost there, keep going!', percent: 50, }, 4: { variant: 'positive', label: 'Success! Strong passphrase', percent: 100, } } const { score } = zxcvbn(passphrase); const noPassphrase = passphrase.length <= 0 const commonPassphrase = common || isPwned const weakPassphrase = passphrase.length < minLength || score < averageThreshold const averagePassphrase = score < strongThreshold const strongPassphrase = score >= strongThreshold if (noPassphrase) { return {...resultByScore[0], score} } else if (commonPassphrase) { return {...resultByScore[1], score} } else if (weakPassphrase) { return {...resultByScore[2], score} } else if (averagePassphrase){ return {...resultByScore[3], score} } else if (strongPassphrase) { return {...resultByScore[4], score} } } const COMMON_PASSPHRASES = ['passphrase', 'apple', 'password', 'p@55w0rd'] const isCommon = (passphrase) => { if (COMMON_PASSPHRASES.includes(passphrase)) return true return false } useEffect(() => { const result = handleStrengthCalculation({ passphrase: input, common: isCommon(input) }); setCheckStrength({ ...result }) }, [input]) return ( <> <div> <Body marginBottom='md' text={`Try typing any of the following: ${COMMON_PASSPHRASES.join(', ')}`} /> <Passphrase onChange={handleChange} value={input} /> {input.length > 0 && ( <> <ProgressSimple className={input.length === 0 ? "progress-empty-input" : null} percent={checkStrength.percent} variant={checkStrength.variant} /> <Caption size='xs' text={checkStrength.label} /> </> )} </div> </> ) } export default PassphraseCommon
Use HaveIBeenPwned's API to check for breached passwords.
As the passphrase is typed, it is checked against more than half a billion breached passwords, to help ensure its not compromised.
Should it fail, the feedback will express the passphrase is too common, prompting the user to change.
This uses their k-Anonymity model, so only the first 5 characters of a hashed copy of the passphrase are sent.
This example depends on the zxcvbn
library and haveibeenpwned
API.
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.
import React, { useState, useEffect } from 'react' import { Caption, Passphrase, ProgressSimple } from 'playbook-ui' import zxcvbn from 'zxcvbn' const PassphraseBreached = (props) => { const [input, setInput] = useState('') const [isPwned, setIsPwned] = useState(false) const [checkStrength, setCheckStrength] = useState({ label: '', percent: 0, score: 0, variant: '' }) const handleChange = (e) => setInput(e.target.value) const checkHaveIBeenPwned = async function (passphrase) { const buffer = new TextEncoder('utf-8').encode(passphrase) const digest = await crypto.subtle.digest('SHA-1', buffer) const hashArray = Array.from(new Uint8Array(digest)) const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') const firstFive = hashHex.slice(0, 5) const endOfHash = hashHex.slice(5) const resp = await fetch(`https://api.pwnedpasswords.com/range/${firstFive}`) const text = await resp.text() const match = text.split('\n').some((line) => { //Each line is <sha-1-hash-suffix>:<count of incidents> return line.split(':')[0] === endOfHash.toUpperCase() }) return match } const handleStrengthCalculation = (settings) => { const { passphrase = "", common = false, isPwned = false, averageThreshold = 2, minLength = 12, strongThreshold = 3, } = settings const resultByScore = { 0: { variant: 'negative', label: '', percent: 0, }, 1: { variant: 'negative', label: 'This passphrase is too common', percent: 25, }, 2: { variant: 'negative', label: 'Too weak', percent: 25, }, 3: { variant: 'warning', label: 'Almost there, keep going!', percent: 50, }, 4: { variant: 'positive', label: 'Success! Strong passphrase', percent: 100, } } const { score } = zxcvbn(passphrase); const noPassphrase = passphrase.length <= 0 const commonPassphrase = common || isPwned const weakPassphrase = passphrase.length < minLength || score < averageThreshold const averagePassphrase = score < strongThreshold const strongPassphrase = score >= strongThreshold if (noPassphrase) { return {...resultByScore[0], score} } else if (commonPassphrase) { return {...resultByScore[1], score} } else if (weakPassphrase) { return {...resultByScore[2], score} } else if (averagePassphrase){ return {...resultByScore[3], score} } else if (strongPassphrase) { return {...resultByScore[4], score} } } useEffect( () => { const delay = 400 const result = handleStrengthCalculation({ passphrase: input, isPwned: isPwned }); setCheckStrength({ ...result }) // only check the API for passphrases above the minimum size if (input.length < 5) { setIsPwned(false) return } const handler = setTimeout(() => { checkHaveIBeenPwned(input) .then((pwned) => setIsPwned(pwned)) .catch(() => setIsPwned(false)) }, delay) return () => { clearTimeout(handler) } }, [input, isPwned] ) return ( <> <div> <br /> <Passphrase onChange={handleChange} value={input} /> {checkStrength.percent > 0 ? <> <ProgressSimple className={input.length === 0 ? "progress-empty-input" : null} percent={checkStrength.percent} variant={checkStrength.variant} /> <Caption size='xs' text={checkStrength.label} /> </> : null} </div> </> ) } export default PassphraseBreached