Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

[WIP] QR Codes #1620

Merged
merged 7 commits into from
Sep 18, 2018
Merged
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
2 changes: 1 addition & 1 deletion app/js/account/AccountMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class AccountMenu extends Component {
const tabs = [
{ url: '/account/storage', label: 'storage providers' },
{ url: '/account/password', label: 'change password' },
{ url: '/account/backup', label: 'backup keychain' },
{ url: '/account/backup', label: 'backup & restore' },
{ url: '/account/delete', label: 'reset browser' },
{ url: '/account/api', label: 'api settings' }
]
Expand Down
96 changes: 82 additions & 14 deletions app/js/account/BackupAccountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import bip39 from 'bip39'
import { HDNode } from 'bitcoinjs-lib'
import QRCode from 'qrcode.react'

import Alert from '@components/Alert'
import SimpleButton from '@components/SimpleButton'
Expand Down Expand Up @@ -74,7 +75,6 @@ class BackupAccountPage extends Component {
decrypt(dataBuffer, password).then(
plaintextBuffer => {
logger.debug('Keychain phrase successfully decrypted')
this.updateAlert('success', 'Keychain phrase decrypted')
const seed = bip39.mnemonicToSeed(plaintextBuffer.toString())
const keychain = HDNode.fromSeedBuffer(seed)
this.props.displayedRecoveryCode()
Expand All @@ -94,15 +94,49 @@ class BackupAccountPage extends Component {

render() {
const { alerts, keychain, decryptedBackupPhrase, isDecrypting } = this.state
const b64EncryptedBackupPhrase = new Buffer(
this.props.encryptedBackupPhrase,
'hex'
).toString('base64')

return (
<div>
<div className="container-fluid">
<div className="row">
<div className="col">
<h3>Backup Keychain</h3>
<h3>Magic Recovery Code</h3>
<p>
<i>
Scan or enter the recovery code with your password to restore
your account or sign in on other devices.
</i>
</p>
</div>
</div>
<div className="row m-b-50">
<div className="col col-sm-4 col-12">
<QuickQR data={b64EncryptedBackupPhrase} />
</div>
<div className="col col-sm-8 col-12">
<div className="card">
<div className="card-header">Recovery Code</div>
<div className="card-block backup-phrase-container">
<p className="card-text">
<code>{b64EncryptedBackupPhrase}</code>
</p>
</div>
</div>
</div>
</div>
</div>

<div className="container-fluid">
<div className="row">
<div className="col">
{alerts.map((alert, index) => (
<Alert key={index} message={alert.message} status={alert.status} />
))}
<h3>Secret Recovery Key</h3>
</div>
</div>
</div>
Expand All @@ -112,24 +146,40 @@ class BackupAccountPage extends Component {
<div className="col">
<p>
<i>
Write down the keychain phrase below and keep it safe. Anyone who has it will be
able to access to your keychain.
Write down the secret phrase below and keep it safe, or scan
it to recover your account. Anyone who has it will have full
access your Blockstack ID, so keep it safe!
</i>
</p>
</div>
</div>

<div className="row">
<div className="col col-sm-4 col-12">
<QuickQR data={decryptedBackupPhrase} />
</div>
<div className="col col-sm-8 col-12">
<div className="card">
<div className="card-header">Keychain Phrase</div>
<div className="card-header">Secret Recovery Key</div>
<div className="card-block backup-phrase-container">
<p className="card-text">{decryptedBackupPhrase}</p>
<p className="card-text">
<code>{decryptedBackupPhrase}</code>
</p>
</div>
</div>
</div>
</div>

<div className="card m-t-20">
<div className="card-header">Private Key (WIF)</div>
<div className="card-block backup-phrase-container">
<p className="card-text">{keychain.keyPair.toWIF()}</p>
</div>
</div>
<hr className="m-t-40 m-b-50" />

<div className="row">
<div className="col">
<p>
<strong>Info for Developers</strong>
</p>
<p>
Private Key (WIF) — <code>{keychain.keyPair.toWIF()}</code>
</p>
</div>
</div>
</div>
Expand All @@ -139,8 +189,7 @@ class BackupAccountPage extends Component {
<div className="col">
<p className="container-fluid">
<i>
Enter your password to view your keychain phrase and write down your keychain
phrase.
Enter your password to view and backup your secret recovery phrase.
</i>
</p>
<InputGroup
Expand Down Expand Up @@ -170,4 +219,23 @@ class BackupAccountPage extends Component {
}
}

const QuickQR = ({ data }) => (
<div
className="qr-wrap"
style={{
maxWidth: 320,
padding: 20,
margin: '0 auto 20px',
borderRadius: 4,
boxShadow: '0 1px 4px rgba(0, 0, 0, 0.2)'
}}
>
<QRCode value={data} size="256" style={{ width: '100%' }} />
</div>
)

QuickQR.propTypes = {
data: PropTypes.string
}

export default connect(mapStateToProps, mapDispatchToProps)(BackupAccountPage)
3 changes: 1 addition & 2 deletions app/js/account/DeleteAccountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ class DeleteAccountPage extends Component {
<div>
<p className="container-fluid">
<i>
Erase your keychain and settings so you can create a new one or restore another
keychain.
Erase your local data so you can create a new account or restore another.
</i>
</p>
<div className="container-fluid m-t-40">
Expand Down
5 changes: 3 additions & 2 deletions app/js/account/components/DeleteAccountModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ const DeleteAccountModal = props => (
<h3 className="modal-heading">Are you sure you want to reset Blockstack Browser?</h3>
<div className="modal-body">
<p>
Please make sure you have a written copy of your keychain phrase before continuing otherwise
you will lose access to this keychain and any money or IDs associated with it.
Please make sure you have a written copy of your secret recovery phrase before
continuing otherwise you will lose access to this account and any money or IDs
associated with it.
</p>
</div>
<div className="delete-account-buttons">
Expand Down
12 changes: 5 additions & 7 deletions app/js/clear-auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ class ClearAuthPage extends PureComponent {
<div className="container-fluid">
<h3 className="p-t-20">Sign Out</h3>
<p className="p-t-20 alert alert-warning">
<strong>Warning:</strong> This will reset your keychain and settings
on this device. You’ll be able to restore your keychain, or create
a new one afterwards.
<strong>Warning:</strong> This will reset your account on this device.
You’ll be able to restore your account, or create a new one afterwards.
<br />
<br />
If you plan to restore your keychain, <strong>make sure you have
If you plan to restore your account, <strong>make sure you have
recorded your backup information.</strong> You will either need your
12 word secret key, or your magic recovery code and password to
do so.
12 word secret recovery key, or your magic recovery code and password
to do so.
</p>
<div className="m-t-10">
<button
Expand All @@ -65,7 +64,6 @@ class ClearAuthPage extends PureComponent {
) : (
<span>Reset my device {countdown > 0 && `(${countdown})`}</span>
)}

</button>
</div>
<div className="m-t-10">
Expand Down
33 changes: 33 additions & 0 deletions app/js/components/ui/components/qr-scan/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import PropTypes from 'prop-types'
import QrReader from 'react-qr-reader'
import { StyledButton } from '@ui/components/button'
import * as Styled from './styled'

const QrScan = ({ onScan, onError, handleClose, error }) => (
<Styled.Container>
<Styled.Scanner>
<QrReader
onScan={(data) => data && onScan(data)}
onError={onError}
/>
{error &&
<Styled.ErrorMessage>{error}</Styled.ErrorMessage>
}
</Styled.Scanner>
<Styled.ButtonWrap>
<StyledButton height={44} onClick={handleClose}>
<StyledButton.Label>Stop scanning</StyledButton.Label>
</StyledButton>
</Styled.ButtonWrap>
</Styled.Container>
)

QrScan.propTypes = {
onScan: PropTypes.func.isRequired,
onError: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
error: PropTypes.node
}

export default QrScan
52 changes: 52 additions & 0 deletions app/js/components/ui/components/qr-scan/styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import styled, { keyframes } from 'styled-components'

export const Container = styled.div`
position: absolute;
top: 0;
left: -10px;
right: -10px;
bottom: 0;
display: flex;
flex-direction: column;
background: #FFF;
`

export const Scanner = styled.div`
position: relative;
margin: 0 auto 1rem;
width: 75%;
overflow: hidden;
`

export const ButtonWrap = styled.div`
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
`

const ErrorPop = keyframes`
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0%);
opacity: 1;
}
`

export const ErrorMessage = styled.div`
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 0.5rem;
font-size: 0.9rem;
background: #F67B7B;
color: #FFF;
z-index: 1;
animation: ${ErrorPop} 300ms ease;
z-index: 1000;
text-align: center;
`
20 changes: 13 additions & 7 deletions app/js/components/ui/containers/button.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import React from 'react'
import { StyledButton, Buttons } from '@ui/components/button'
import PropTypes from 'prop-types'
import { Spinner } from '@ui/components/spinner'
import { ArrowRightIcon, SearchIcon } from 'mdi-react'
import { ArrowRightIcon, SearchIcon, QrcodeIcon } from 'mdi-react'

const iconMap = {
SearchIcon,
ArrowRightIcon,
QrcodeIcon
}

const Button = ({
label,
Expand All @@ -25,10 +31,7 @@ const Button = ({
}
}

const SearchIconBool = icon === 'SearchIcon' ? SearchIcon : null

const IconComponent =
icon === 'ArrowRightIcon' ? ArrowRightIcon : SearchIconBool
const IconComponent = iconMap[icon]

const content = loading ? (
<React.Fragment>
Expand All @@ -40,10 +43,13 @@ const Button = ({
)

const renderIcon = () =>
icon &&
IconComponent &&
!loading && (
<StyledButton.Icon>
<IconComponent size={18} color="rgba(75, 190, 190, 1)" />
<IconComponent
size={18}
color={rest.primary ? 'rgba(75, 190, 190, 1)' : 'rgba(0, 0, 0, 0.6)'}
/>
</StyledButton.Icon>
)
return (
Expand Down
25 changes: 10 additions & 15 deletions app/js/sign-in/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,22 +134,17 @@ class SignIn extends React.Component {
this.setState({ key })
}
if (this.isKeyEncrypted(key)) {
this.setState(
{
encryptedKey: key,
decrypt: true
},
() => setTimeout(() => this.updateView(nextView), 100)
)
this.setState({
encryptedKey: key,
decrypt: true
})
} else {
this.setState(
{
seed: key,
decrypt: false
},
() => setTimeout(() => this.updateView(nextView), 100)
)
this.setState({
seed: key,
decrypt: false
})
}
this.updateView(nextView)
}

decryptAndContinue = () => {
Expand Down Expand Up @@ -285,7 +280,7 @@ class SignIn extends React.Component {

if (!isValid) {
logger.error('restoreAccount: Invalid keychain phrase entered')
return Promise.reject('Invalid keychain phrase entered')
return Promise.reject('Invalid recovery phrase entered')
}
this.setState({
creatingAccountStatus: CREATE_ACCOUNT_IN_PROCESS
Expand Down
Loading