Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions src/components/admin-text-setting/index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,43 @@
import AdminBaseSetting from '../admin-base-setting'
import { createRef } from '@wordpress/element'
import { useRef, useState } from '@wordpress/element'
import { maskSensitiveValue } from '~stackable/util'

const AdminTextSetting = props => {
const ref = createRef()
const ref = useRef()
const [ isEditing, setIsEditing ] = useState( false )
const value = props.maskValue && ! isEditing ? maskSensitiveValue( props.value ) : props.value

return (
<AdminBaseSetting
onClick={ ev => {
ev.preventDefault()
ref.current.focus()
if ( props.maskValue ) {
ref.current.select()
}
} }
{ ...props }
>
<input
ref={ ref }
className="ugb-admin-text-setting"
type={ props.type }
value={ props.value }
value={ value }
placeholder={ props.placeholder }
autoComplete={ props.maskValue ? 'off' : undefined }
onFocus={ () => {
if ( props.maskValue ) {
setIsEditing( true )
setTimeout( () => ref.current?.select() )
}
} }
onBlur={ () => {
setIsEditing( false )
} }
onChange={ event => {
if ( props.maskValue && ! isEditing ) {
return
}
props.onChange( event.target.value )
event.preventDefault()
event.stopPropagation()
Expand All @@ -32,6 +52,7 @@ AdminTextSetting.defaultProps = {
label: '',
type: 'text',
value: '',
maskValue: false,
placeholder: '',
onChange: () => {},
}
Expand Down
15 changes: 14 additions & 1 deletion src/util/__test__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@
* Internal dependencies
*/
import {
hexToRgba, prependCSSClass, compileCSS,
hexToRgba, prependCSSClass, compileCSS, maskSensitiveValue,
} from '../'

describe( 'maskSensitiveValue', () => {
it( 'fully masks values with 12 or fewer characters', () => {
expect( maskSensitiveValue( '' ) ).toBe( '' )
expect( maskSensitiveValue( '123456' ) ).toBe( '******' )
expect( maskSensitiveValue( '123456789012' ) ).toBe( '************' )
} )

it( 'keeps the first 6 and last 6 characters for longer values', () => {
expect( maskSensitiveValue( '1234567890123' ) ).toBe( '123456*890123' )
expect( maskSensitiveValue( 'abcdefghijklmnopqrstuvwxyz' ) ).toBe( 'abcdef**************uvwxyz' )
} )
} )

describe( 'hexToRgba', () => {
it( 'should work', () => {
expect( hexToRgba( '#000000' ) ).toBe( 'rgba(0, 0, 0, 1)' )
Expand Down
22 changes: 22 additions & 0 deletions src/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ import { compare } from 'compare-versions'

export const getUniqueBlockClass = uniqueId => uniqueId ? `stk-${ uniqueId }` : ''

/**
* Masks a sensitive string for display while keeping it recognizable.
*
* Shows the first 6 and last 6 characters, and replaces the middle characters
* with asterisks. Values with 12 or fewer characters are fully masked.
*
* @param {string} value The sensitive value to mask.
*
* @return {string} Masked value for display.
*/
export const maskSensitiveValue = value => {
if ( ! value ) {
return value
}

if ( value.length <= 12 ) {
return '*'.repeat( value.length )
}

return `${ value.slice( 0, 6 ) }${ '*'.repeat( value.length - 12 ) }${ value.slice( -6 ) }`
}

/**
* Returns an array range of numbers.
*
Expand Down
1 change: 1 addition & 0 deletions src/welcome/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -1357,6 +1357,7 @@ const Integrations = props => {
searchedSettings={ propsToPass.integrations.children }
value={ settings.stackable_google_maps_api_key }
type="text"
maskValue={ true }
onChange={ value => {
handleSettingsChange( { stackable_google_maps_api_key: value } ) // eslint-disable-line camelcase
} }
Expand Down
Loading