1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-11-30 12:18:09 +00:00

Feature/limit passphrase length to 50 bytes (#542)

Feature/limit passphrase length to 50 bytes
This commit is contained in:
martin 2019-05-02 11:49:50 +02:00 committed by GitHub
commit f7a8fbeb3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 50 additions and 10 deletions

View File

@ -9,6 +9,7 @@ __changed__
- static (without animation) active tab indicator - static (without animation) active tab indicator
- input validation - mandatory leading 0 for float numbers - input validation - mandatory leading 0 for float numbers
- regexps refactored to functions, added unit tests - regexps refactored to functions, added unit tests
- limit passphrase length to 50 bytes
__removed__ __removed__
- Text "already used" from token select in case of already added tokens - Text "already used" from token select in case of already added tokens

View File

@ -2,6 +2,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'styled-components'; import styled from 'styled-components';
import { byteLength } from 'utils/formatUtils';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Button, Input, Checkbox, P, H5, colors } from 'trezor-ui-components'; import { Button, Input, Checkbox, P, H5, colors } from 'trezor-ui-components';
import { FONT_SIZE } from 'config/variables'; import { FONT_SIZE } from 'config/variables';
@ -24,6 +25,7 @@ type State = {
passphraseCheckInputValue: string, passphraseCheckInputValue: string,
doPassphraseInputsMatch: boolean, doPassphraseInputsMatch: boolean,
isPassphraseHidden: boolean, isPassphraseHidden: boolean,
byteLength: number,
}; };
const Wrapper = styled.div` const Wrapper = styled.div`
@ -91,6 +93,7 @@ class Passphrase extends PureComponent<Props, State> {
passphraseCheckInputValue: '', passphraseCheckInputValue: '',
doPassphraseInputsMatch: true, doPassphraseInputsMatch: true,
isPassphraseHidden: true, isPassphraseHidden: true,
byteLength: 0,
}; };
} }
@ -117,6 +120,9 @@ class Passphrase extends PureComponent<Props, State> {
let doPassphraseInputsMatch = false; let doPassphraseInputsMatch = false;
if (inputName === 'passphraseInputValue') { if (inputName === 'passphraseInputValue') {
this.setState({
byteLength: byteLength(inputValue),
});
// If passphrase is not hidden the second input should get filled automatically // If passphrase is not hidden the second input should get filled automatically
// and should be disabled // and should be disabled
if (this.state.isPassphraseHidden) { if (this.state.isPassphraseHidden) {
@ -181,13 +187,28 @@ class Passphrase extends PureComponent<Props, State> {
handleKeyPress(event: KeyboardEvent) { handleKeyPress(event: KeyboardEvent) {
if (event.key === 'Enter') { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault();
if (this.state.doPassphraseInputsMatch) { if (this.state.doPassphraseInputsMatch && this.state.byteLength <= 50) {
this.submitPassphrase(); this.submitPassphrase();
} }
} }
} }
render() { render() {
let error = null;
if (this.state.byteLength > 50) {
error = (
<PassphraseError>
<FormattedMessage {...l10nMessages.TR_PASSPHRASE_IS_TOO_LONG} />
</PassphraseError>
);
} else if (!this.state.doPassphraseInputsMatch) {
error = (
<PassphraseError>
<FormattedMessage {...l10nMessages.TR_PASSPHRASES_DO_NOT_MATCH} />
</PassphraseError>
);
}
return ( return (
<Wrapper> <Wrapper>
<H5> <H5>
@ -236,11 +257,7 @@ class Passphrase extends PureComponent<Props, State> {
/> />
</Row> </Row>
)} )}
{!this.state.doPassphraseInputsMatch && ( {error}
<PassphraseError>
<FormattedMessage {...l10nMessages.TR_PASSPHRASES_DO_NOT_MATCH} />
</PassphraseError>
)}
<Row> <Row>
<Checkbox <Checkbox
isChecked={!this.state.isPassphraseHidden} isChecked={!this.state.isPassphraseHidden}
@ -250,10 +267,7 @@ class Passphrase extends PureComponent<Props, State> {
</Checkbox> </Checkbox>
</Row> </Row>
<Row> <Row>
<Button <Button isDisabled={!!error} onClick={() => this.submitPassphrase()}>
isDisabled={!this.state.doPassphraseInputsMatch}
onClick={() => this.submitPassphrase()}
>
<FormattedMessage {...l10nMessages.TR_ENTER} /> <FormattedMessage {...l10nMessages.TR_ENTER} />
</Button> </Button>
</Row> </Row>

View File

@ -24,6 +24,10 @@ const definedMessages: Messages = defineMessages({
id: 'TR_PASSPHRASES_DO_NOT_MATCH', id: 'TR_PASSPHRASES_DO_NOT_MATCH',
defaultMessage: 'Passphrases do not match!', defaultMessage: 'Passphrases do not match!',
}, },
TR_PASSPHRASE_IS_TOO_LONG: {
id: 'TR_PASSPHRASE_IS_TOO_LONG',
defaultMessage: 'Passphrase is too long!',
},
TR_SHOW_PASSPHRASE: { TR_SHOW_PASSPHRASE: {
id: 'TR_SHOW_PASSPHRASE', id: 'TR_SHOW_PASSPHRASE',
defaultMessage: 'Show passphrase', defaultMessage: 'Show passphrase',

View File

@ -21,4 +21,14 @@ describe('format utils', () => {
expect(utils.fromDecimalAmount('a', 'a')).toBe('0'); expect(utils.fromDecimalAmount('a', 'a')).toBe('0');
expect(utils.fromDecimalAmount('a', '1')).toBe('0'); expect(utils.fromDecimalAmount('a', '1')).toBe('0');
}); });
describe('byteLength', () => {
it('should return correct byte length for strings with special ASCII characters', () => {
expect(utils.byteLength('testString')).toEqual(10);
expect(utils.byteLength('~!@#$%^&*()_+{}|:?><')).toEqual(20);
expect(utils.byteLength('😀')).toEqual(4);
expect(utils.byteLength('ä')).toEqual(2);
expect(utils.byteLength('áľščť')).toEqual(10);
});
});
}); });

View File

@ -27,3 +27,14 @@ export const fromDecimalAmount = (amount: string | number, decimals: number): st
return '0'; return '0';
} }
}; };
export const byteLength = (text: string): number => {
// returns length of the text in bytes, 0 in case of error.
try {
// regexp is handling cases when encodeURI returns '%uXXXX' or %XX%XX
return encodeURI(text).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1;
} catch (error) {
console.error(error);
return 0;
}
};