Prompt user to re-enter seed phrase during wallet creation and log out

This commit is contained in:
jhl-alameda 2021-04-26 23:32:49 +08:00
parent afd9be9bc1
commit ef996b5265
3 changed files with 112 additions and 62 deletions

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import DialogForm from './DialogForm';
import { forgetWallet } from '../utils/wallet-seed';
import { forgetWallet, normalizeMnemonic, useUnlockedMnemonicAndSeed } from '../utils/wallet-seed';
import DialogTitle from '@material-ui/core/DialogTitle';
import { DialogContentText } from '@material-ui/core';
import DialogActions from '@material-ui/core/DialogActions';
@ -8,7 +8,8 @@ import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
export default function DeleteMnemonicDialog({ open, onClose }) {
const [deleteCheck, setDeleteCheck] = useState('');
const [seedCheck, setSeedCheck] = useState('');
const [mnemKey] = useUnlockedMnemonicAndSeed();
return (
<>
<DialogForm
@ -35,16 +36,17 @@ export default function DeleteMnemonicDialog({ open, onClose }) {
<br />
<strong>
To prevent loss of funds, please ensure you have the seed phrase
and the private key for all current accounts.
and the private key for all current accounts. You can view it by selecting
"Export Mnemonic" in the user menu.
</strong>
</div>
<TextField
label={`Please type "delete" to confirm`}
label={`Please type your seed phrase to confirm`}
fullWidth
variant="outlined"
margin="normal"
value={deleteCheck}
onChange={(e) => setDeleteCheck(e.target.value.trim())}
value={seedCheck}
onChange={(e) => setSeedCheck(e.target.value)}
/>
</DialogContentText>
<DialogActions>
@ -52,7 +54,7 @@ export default function DeleteMnemonicDialog({ open, onClose }) {
<Button
type="submit"
color="secondary"
disabled={deleteCheck !== 'delete'}
disabled={normalizeMnemonic(seedCheck) !== mnemKey.mnemonic}
>
Delete
</Button>

View File

@ -5,6 +5,7 @@ import {
loadMnemonicAndSeed,
mnemonicToSeed,
storeMnemonicAndSeed,
normalizeMnemonic,
} from '../utils/wallet-seed';
import {
getAccountFromSeed,
@ -15,7 +16,7 @@ import LoadingIndicator from '../components/LoadingIndicator';
import { BalanceListItem } from '../components/BalancesList.js';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { Typography } from '@material-ui/core';
import { DialogActions, DialogContentText, DialogTitle, Typography } from '@material-ui/core';
import TextField from '@material-ui/core/TextField';
import Checkbox from '@material-ui/core/Checkbox';
import FormControl from '@material-ui/core/FormControl';
@ -27,6 +28,7 @@ import MenuItem from '@material-ui/core/MenuItem';
import { useCallAsync } from '../utils/notifications';
import Link from '@material-ui/core/Link';
import { validateMnemonic } from 'bip39';
import DialogForm from '../components/DialogForm';
export default function LoginPage() {
const [restore, setRestore] = useState(false);
@ -97,60 +99,100 @@ function CreateWalletForm() {
function SeedWordsForm({ mnemonicAndSeed, goForward }) {
const [confirmed, setConfirmed] = useState(false);
const [showDialog, setShowDialog] = useState(false);
const [seedCheck, setSeedCheck] = useState('');
return (
<Card>
<CardContent>
<Typography variant="h5" gutterBottom>
Create New Wallet
</Typography>
<Typography paragraph>
Create a new wallet to hold Solana and SPL tokens.
</Typography>
<Typography>
Please write down the following twenty four words and keep them in a
safe place:
</Typography>
{mnemonicAndSeed ? (
<TextField
variant="outlined"
fullWidth
multiline
margin="normal"
value={mnemonicAndSeed.mnemonic}
label="Seed Words"
onFocus={(e) => e.currentTarget.select()}
/>
) : (
<LoadingIndicator />
)}
<Typography paragraph>
Your private keys are only stored on your current computer or device.
You will need these words to restore your wallet if your browser's
storage is cleared or your device is damaged or lost.
</Typography>
<Typography paragraph>
By default, sollet will use <code>m/44'/501'/0'/0'</code> as the
derivation path for the main wallet. To use an alternative path, try
restoring an existing wallet.
</Typography>
<FormControlLabel
control={
<Checkbox
checked={confirmed}
disabled={!mnemonicAndSeed}
onChange={(e) => setConfirmed(e.target.checked)}
<>
<Card>
<CardContent>
<Typography variant="h5" gutterBottom>
Create New Wallet
</Typography>
<Typography paragraph>
Create a new wallet to hold Solana and SPL tokens.
</Typography>
<Typography>
Please write down the following twenty four words and keep them in a
safe place:
</Typography>
{mnemonicAndSeed ? (
<TextField
variant="outlined"
fullWidth
multiline
margin="normal"
value={mnemonicAndSeed.mnemonic}
label="Seed Words"
onFocus={(e) => e.currentTarget.select()}
/>
}
label="I have saved these words in a safe place."
/>
</CardContent>
<CardActions style={{ justifyContent: 'flex-end' }}>
<Button color="primary" disabled={!confirmed} onClick={goForward}>
Continue
</Button>
</CardActions>
</Card>
) : (
<LoadingIndicator />
)}
<Typography paragraph>
Your private keys are only stored on your current computer or device.
You will need these words to restore your wallet if your browser's
storage is cleared or your device is damaged or lost.
</Typography>
<Typography paragraph>
By default, sollet will use <code>m/44'/501'/0'/0'</code> as the
derivation path for the main wallet. To use an alternative path, try
restoring an existing wallet.
</Typography>
<FormControlLabel
control={
<Checkbox
checked={confirmed}
disabled={!mnemonicAndSeed}
onChange={(e) => setConfirmed(e.target.checked)}
/>
}
label="I have saved these words in a safe place."
/>
</CardContent>
<CardActions style={{ justifyContent: 'flex-end' }}>
<Button color="primary" disabled={!confirmed} onClick={() => setShowDialog(true)}>
Continue
</Button>
</CardActions>
</Card>
<DialogForm
open={showDialog}
onClose={() => setShowDialog(false)}
onSubmit={goForward}
fullWidth
>
<DialogTitle>{'Confirm Mnemonic'}</DialogTitle>
<DialogContentText style={{ margin: 20 }}>
<div
style={{
display: 'flex',
flexDirection: 'column',
}}
>
Please re-enter your seed phrase to confirm that you have saved it.
</div>
<TextField
label={`Please type your seed phrase to confirm`}
fullWidth
variant="outlined"
margin="normal"
value={seedCheck}
onChange={(e) => setSeedCheck(e.target.value)}
/>
</DialogContentText>
<DialogActions>
<Button onClick={() => setShowDialog(false)}>Close</Button>
<Button
type="submit"
color="secondary"
disabled={normalizeMnemonic(seedCheck) !== mnemonicAndSeed?.mnemonic}
>
Continue
</Button>
</DialogActions>
</DialogForm>
</>
);
}
@ -264,11 +306,13 @@ function LoginForm() {
}
function RestoreWalletForm({ goBack }) {
const [mnemonic, setMnemonic] = useState('');
const [rawMnemonic, setRawMnemonic] = useState('');
const [seed, setSeed] = useState('');
const [password, setPassword] = useState('');
const [passwordConfirm, setPasswordConfirm] = useState('');
const [next, setNext] = useState(false);
const mnemonic = normalizeMnemonic(rawMnemonic);
const isNextBtnEnabled =
password === passwordConfirm && validateMnemonic(mnemonic);
@ -303,8 +347,8 @@ function RestoreWalletForm({ goBack }) {
rows={3}
margin="normal"
label="Seed Words"
value={mnemonic}
onChange={(e) => setMnemonic(e.target.value)}
value={rawMnemonic}
onChange={(e) => setRawMnemonic(e.target.value)}
/>
<TextField
variant="outlined"

View File

@ -6,6 +6,10 @@ import { EventEmitter } from 'events';
import { isExtension } from './utils';
import { useEffect, useState } from 'react';
export function normalizeMnemonic(mnemonic) {
return mnemonic.trim().split(/\s+/g).join(" ");
}
export async function generateMnemonicAndSeed() {
const bip39 = await import('bip39');
const mnemonic = bip39.generateMnemonic(256);