From fdb390570654ae4f06e579a4f7a69a321a40ab53 Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 13 Aug 2018 15:31:44 +0200 Subject: [PATCH 1/6] Put back QR code address scanning and enforce scan security This commit prevent scanning non-EIP addresses, or more generally prevent scanning address that generate warning for recipient (currently the only possible warning is for non-EIP address for ETH/ETC). If the warning-address is entered with keyboard instead of scanning, it will still display the warning (as now), the behaviour doesnt change. Closes #1412 Closes #1406 --- src/components/QRCodeCameraPickerCanvas.js | 2 +- src/components/RecipientAddress/index.js | 4 ++- .../AddAccounts/steps/04-step-finish.js | 2 +- .../modals/Send/fields/RecipientField.js | 33 +++++++++++++++---- src/config/errors.js | 1 + static/i18n/en/errors.json | 3 ++ 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/components/QRCodeCameraPickerCanvas.js b/src/components/QRCodeCameraPickerCanvas.js index 271f2c4e..5081c2a5 100644 --- a/src/components/QRCodeCameraPickerCanvas.js +++ b/src/components/QRCodeCameraPickerCanvas.js @@ -156,7 +156,7 @@ export default class QRCodeCameraPickerCanvas extends Component< .catch(e => { if (this.unmounted) return this.setState({ - message: String(e.message || e), + message: String(e.message || e.name || e), }) }) } diff --git a/src/components/RecipientAddress/index.js b/src/components/RecipientAddress/index.js index 24f7895d..e825d2d4 100644 --- a/src/components/RecipientAddress/index.js +++ b/src/components/RecipientAddress/index.js @@ -47,7 +47,7 @@ const BackgroundLayer = styled(Box)` type Props = { value: string, // return false if it can't be changed (invalid info) - onChange: (string, ?{ amount?: BigNumber, currency?: CryptoCurrency }) => ?boolean, + onChange: (string, ?{ amount?: BigNumber, currency?: CryptoCurrency }) => Promise, withQrCode: boolean, } @@ -76,6 +76,8 @@ class RecipientAddress extends PureComponent { handleOnPick = (code: string) => { const { address, ...rest } = decodeURIScheme(code) + // $FlowFixMe + Object.assign(rest, { fromQRCode: true }) if (this.props.onChange(address, rest) !== false) { this.setState({ qrReaderOpened: false }) } diff --git a/src/components/modals/AddAccounts/steps/04-step-finish.js b/src/components/modals/AddAccounts/steps/04-step-finish.js index 6170af8e..312c4aea 100644 --- a/src/components/modals/AddAccounts/steps/04-step-finish.js +++ b/src/components/modals/AddAccounts/steps/04-step-finish.js @@ -52,7 +52,7 @@ export default StepFinish export const StepFinishFooter = ({ onGoStep1, t }: StepProps) => ( - diff --git a/src/components/modals/Send/fields/RecipientField.js b/src/components/modals/Send/fields/RecipientField.js index 769e1483..4fef7245 100644 --- a/src/components/modals/Send/fields/RecipientField.js +++ b/src/components/modals/Send/fields/RecipientField.js @@ -10,6 +10,7 @@ import LabelWithExternalIcon from 'components/base/LabelWithExternalIcon' import RecipientAddress from 'components/RecipientAddress' import { track } from 'analytics/segment' import { createCustomErrorClass } from 'helpers/errors' +import { CantScanQRCode } from 'config/errors' type Props = { t: T, @@ -24,11 +25,12 @@ const InvalidAddress = createCustomErrorClass('InvalidAddress') class RecipientField extends Component< Props, - { isValid: boolean, warning: ?Error }, + { isValid: boolean, warning: ?Error, QRCodeRefusedReason: ?Error }, > { state = { isValid: true, warning: null, + QRCodeRefusedReason: null, } componentDidMount() { this.resync() @@ -43,7 +45,9 @@ class RecipientField extends Component< } componentWillUnmount() { this.syncId++ + this.isUnmounted = true } + isUnmounted = false syncId = 0 async resync() { const { account, bridge, transaction } = this.props @@ -52,18 +56,31 @@ class RecipientField extends Component< const isValid = await bridge.isRecipientValid(account.currency, recipient) const warning = await bridge.getRecipientWarning(account.currency, recipient) if (syncId !== this.syncId) return + if (this.isUnmounted) return this.setState({ isValid, warning }) } - onChange = (recipient: string, maybeExtra: ?Object) => { + onChange = async (recipient: string, maybeExtra: ?Object) => { const { bridge, account, transaction, onChangeTransaction } = this.props - const { amount, currency } = maybeExtra || {} + const { QRCodeRefusedReason } = this.state + const { amount, currency, fromQRCode } = maybeExtra || {} if (currency && currency.scheme !== account.currency.scheme) return false let t = transaction if (amount) { t = bridge.editTransactionAmount(account, t, amount) } - t = bridge.editTransactionRecipient(account, t, recipient) + const warning = fromQRCode + ? await bridge.getRecipientWarning(account.currency, recipient) + : null + if (this.isUnmounted) return false + if (warning) { + // clear the input if field has warning AND has a warning + t = bridge.editTransactionRecipient(account, t, '') + this.setState({ QRCodeRefusedReason: new CantScanQRCode() }) + } else { + t = bridge.editTransactionRecipient(account, t, recipient) + if (QRCodeRefusedReason) this.setState({ QRCodeRefusedReason: null }) + } onChangeTransaction(t) return true } @@ -74,11 +91,13 @@ class RecipientField extends Component< } render() { const { bridge, account, transaction, t, autoFocus } = this.props - const { isValid, warning } = this.state + const { isValid, warning, QRCodeRefusedReason } = this.state const value = bridge.getTransactionRecipient(account, transaction) const error = - !value || isValid ? null : new InvalidAddress(null, { currencyName: account.currency.name }) + !value || isValid + ? QRCodeRefusedReason + : new InvalidAddress(null, { currencyName: account.currency.name }) return ( @@ -88,7 +107,7 @@ class RecipientField extends Component< /> Date: Mon, 13 Aug 2018 18:34:00 +0200 Subject: [PATCH 2/6] Add 3 exchanges, update PayBis url, update main sidebar wording closes #1414 --- src/components/ExchangePage/index.js | 19 ++++ src/config/urls.js | 5 +- static/i18n/en/app.json | 7 +- static/images/logos/exchanges/genesis.svg | 6 ++ static/images/logos/exchanges/luno.svg | 14 +++ static/images/logos/exchanges/shapeshift.svg | 93 ++++++++++++++++++++ 6 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 static/images/logos/exchanges/genesis.svg create mode 100644 static/images/logos/exchanges/luno.svg create mode 100644 static/images/logos/exchanges/shapeshift.svg diff --git a/src/components/ExchangePage/index.js b/src/components/ExchangePage/index.js index 6f40ae6c..9e612a78 100644 --- a/src/components/ExchangePage/index.js +++ b/src/components/ExchangePage/index.js @@ -5,6 +5,7 @@ import { translate } from 'react-i18next' import type { T } from 'types/common' import { urls } from 'config/urls' +import { i } from 'helpers/staticPath' import TrackPage from 'analytics/TrackPage' import Box from 'components/base/Box' @@ -51,6 +52,24 @@ const cards = [ url: urls.paybis, logo: , }, + { + key: 'luno', + id: 'luno', + url: urls.luno, + logo: Luno, + }, + { + key: 'shapeshift', + id: 'shapeshift', + url: urls.shapeshift, + logo: Shapeshift, + }, + { + key: 'genesis', + id: 'genesis', + url: urls.genesis, + logo: Genesis, + }, ] class ExchangePage extends PureComponent { diff --git a/src/config/urls.js b/src/config/urls.js index d3635b7a..7ee5caeb 100644 --- a/src/config/urls.js +++ b/src/config/urls.js @@ -26,7 +26,10 @@ export const urls = { changelly: 'https://changelly.com/?ref_id=aac789605a01', coinmama: 'http://go.coinmama.com/visit/?bta=51801&nci=5343', simplex: 'https://partners.simplex.com/?partner=ledger', - paybis: 'https://paybis.idevaffiliate.com/idevaffiliate.php?id=4064', + paybis: 'https://aff-tracking.paybis.com/click?pid=22&offer_id=1', + luno: 'http://luno.go2cloud.org/aff_c?offer_id=4&aff_id=1001&source=ledger', + shapeshift: 'https://shapeshift.io/#/coins?affiliate=ledger', + genesis: 'https://genesistrading.com/ledger-live/', // Errors errors: { diff --git a/static/i18n/en/app.json b/static/i18n/en/app.json index af321c9b..4861f0a8 100644 --- a/static/i18n/en/app.json +++ b/static/i18n/en/app.json @@ -78,7 +78,7 @@ "menu": "Menu", "accounts": "Accounts ({{count}})", "manager": "Manager", - "exchange": "Trade" + "exchange": "Buy/Trade" }, "account": { "lastOperations": "Last operations", @@ -165,7 +165,10 @@ "changelly": "Changelly is a popular instant crypto asset exchange with 100+ coins and tokens listed.", "coinmama": "Coinmama is a financial service that makes it fast, safe and fun to buy digital assets, anywhere in the world.", "simplex": "Simplex is a EU licensed financial institution, providing a fraudless credit card payment solution.", - "paybis": "it is safe and easy to Buy Bitcoin with credit card from PayBis. Service operates in US, Canada, Germany, Russia and Saudi Arabia." + "paybis": "it is safe and easy to Buy Bitcoin with credit card from PayBis. Service operates in US, Canada, Germany, Russia and Saudi Arabia.", + "luno": "Luno makes it safe and easy to buy, store and learn about digital currencies like Bitcoin and Ethreum", + "shapeshift": "ShapeShift is an online marketplace where users can buy and sell digital assets. It is a fast and secure way for the world to buy and sell digital assets, with no lengthy signup process, no counterparty risk, and no friction.", + "genesis": "Genesis is an institutional trading firm offering liquidity and borrow for digital currencies, including bitcoin, bitcoin cash, ethereum, ethereum classic, litecoin, and XRP." }, "genuinecheck": { "modal": { diff --git a/static/images/logos/exchanges/genesis.svg b/static/images/logos/exchanges/genesis.svg new file mode 100644 index 00000000..6aa3ea56 --- /dev/null +++ b/static/images/logos/exchanges/genesis.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/static/images/logos/exchanges/luno.svg b/static/images/logos/exchanges/luno.svg new file mode 100644 index 00000000..e2ee1781 --- /dev/null +++ b/static/images/logos/exchanges/luno.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/static/images/logos/exchanges/shapeshift.svg b/static/images/logos/exchanges/shapeshift.svg new file mode 100644 index 00000000..5efd76eb --- /dev/null +++ b/static/images/logos/exchanges/shapeshift.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f9f7c5150a93ed126605936cf5d64a63cb5f5c1d Mon Sep 17 00:00:00 2001 From: Thibaut Boustany Date: Tue, 21 Aug 2018 15:12:35 +0200 Subject: [PATCH 3/6] Bump live-common to 3.0.2 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index d0f7205f..abfeb2cb 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@ledgerhq/hw-transport": "^4.13.0", "@ledgerhq/hw-transport-node-hid": "4.22.0", "@ledgerhq/ledger-core": "2.0.0-rc.6", - "@ledgerhq/live-common": "3.0.0", + "@ledgerhq/live-common": "3.0.2", "animated": "^0.2.2", "async": "^2.6.1", "axios": "^0.18.0", diff --git a/yarn.lock b/yarn.lock index 02f77182..77668ec1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1542,9 +1542,9 @@ npm "^5.7.1" prebuild-install "^2.2.2" -"@ledgerhq/live-common@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.0.0.tgz#abbd88e351c7d0ffffabf7e8502f5b2fbecdc150" +"@ledgerhq/live-common@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.0.2.tgz#1ee5fcc6044c5a049c067978d81892f79789863c" dependencies: axios "^0.18.0" bignumber.js "^7.2.1" From b91503a315a4bb18f1dff11add0de07461619334 Mon Sep 17 00:00:00 2001 From: KhalilBellakrid Date: Wed, 15 Aug 2018 17:35:33 +0200 Subject: [PATCH 4/6] Avoid stack overflow when calling execute method of NJSExecutionContext --- src/helpers/init-libcore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/helpers/init-libcore.js b/src/helpers/init-libcore.js index c7bd4213..5f9ee7cb 100644 --- a/src/helpers/init-libcore.js +++ b/src/helpers/init-libcore.js @@ -24,7 +24,8 @@ const stringToBytesArray = str => Array.from(Buffer.from(str)) const NJSExecutionContextImpl = { execute: runnable => { try { - runnable.run() + const runFunction = () => runnable.run() + setImmediate(runFunction) } catch (e) { logger.log(e) } From 050ce1c810cceca5a4fe276eeb990a62693c2683 Mon Sep 17 00:00:00 2001 From: Thibaut Boustany Date: Wed, 8 Aug 2018 15:28:38 +0200 Subject: [PATCH 5/6] qrcode-reader replaced with jsQR --- package.json | 2 +- src/components/QRCodeCameraPickerCanvas.js | 15 +++++++-------- yarn.lock | 8 ++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index abfeb2cb..2a4e9414 100644 --- a/package.json +++ b/package.json @@ -61,12 +61,12 @@ "i18next": "^11.2.2", "i18next-node-fs-backend": "^1.0.0", "invariant": "^2.2.4", + "jsqr": "^1.1.1", "lodash": "^4.17.5", "lru-cache": "^4.1.3", "measure-scrollbar": "^1.1.0", "moment": "^2.22.2", "qrcode": "^1.2.0", - "qrcode-reader": "^1.0.4", "qs": "^6.5.1", "raven": "^2.5.0", "raven-js": "^3.24.2", diff --git a/src/components/QRCodeCameraPickerCanvas.js b/src/components/QRCodeCameraPickerCanvas.js index 5081c2a5..c4340e72 100644 --- a/src/components/QRCodeCameraPickerCanvas.js +++ b/src/components/QRCodeCameraPickerCanvas.js @@ -1,7 +1,7 @@ // @flow import React, { Component } from 'react' -import QrCode from 'qrcode-reader' +import jsQR from 'jsqr' import logger from 'logger' export default class QRCodeCameraPickerCanvas extends Component< @@ -60,12 +60,6 @@ export default class QRCodeCameraPickerCanvas extends Component< if (!getUserMedia) { this.setState({ message: 'Incompatible browser' }) // eslint-disable-line } else { - const qr = new QrCode() - qr.callback = (err, value) => { - if (!err) { - this.props.onPick(value.result) - } - } getUserMedia({ video: { facingMode: 'environment' }, }) @@ -146,7 +140,12 @@ export default class QRCodeCameraPickerCanvas extends Component< if (t - lastCheck >= intervalCheck) { lastCheck = t - qr.decode(ctxMain.getImageData(0, 0, width, height)) + const imageData = ctxMain.getImageData(0, 0, width, height) + const code = jsQR(imageData.data, width, height) + + if (code && code.data) { + this.props.onPick(code.data) + } } } raf = requestAnimationFrame(loop) diff --git a/yarn.lock b/yarn.lock index 77668ec1..fad669ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8992,6 +8992,10 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jsqr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.1.1.tgz#a0d7f95e6c3b0bec913dfef2ca64a877f28ed05f" + jsx-ast-utils@^2.0.0, jsx-ast-utils@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" @@ -11565,10 +11569,6 @@ q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" -qrcode-reader@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/qrcode-reader/-/qrcode-reader-1.0.4.tgz#95d9bb9e8130800361a96cb5a43124ad1d9e06b8" - qrcode-terminal@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" From 144d5fb3d9df2141aaf9d4a03b735d8f4212c122 Mon Sep 17 00:00:00 2001 From: Thibaut Boustany Date: Tue, 21 Aug 2018 16:23:51 +0200 Subject: [PATCH 6/6] v1.1.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a4e9414..59394e48 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "Ledger Live", "description": "Ledger Live - Desktop", "repository": "https://github.com/LedgerHQ/ledger-live-desktop", - "version": "1.1.6", + "version": "1.1.7", "author": "Ledger", "license": "MIT", "scripts": {