commit
b7c7083a11
@ -0,0 +1,152 @@ |
||||
version: 2 |
||||
|
||||
workflows: |
||||
version: 2 |
||||
full_test: |
||||
jobs: |
||||
- prep-deps-npm |
||||
- prep-deps-firefox |
||||
- prep-scss: |
||||
requires: |
||||
- prep-deps-npm |
||||
- test-lint: |
||||
requires: |
||||
- prep-deps-npm |
||||
- test-unit: |
||||
requires: |
||||
- prep-deps-npm |
||||
- test-integration-mascara: |
||||
requires: |
||||
- prep-deps-npm |
||||
- prep-deps-firefox |
||||
- prep-scss |
||||
- test-integration-flat: |
||||
requires: |
||||
- prep-deps-npm |
||||
- prep-deps-firefox |
||||
- prep-scss |
||||
|
||||
jobs: |
||||
prep-deps-npm: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
- run: |
||||
name: Install deps via npm |
||||
command: npm install |
||||
- save_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
paths: |
||||
- node_modules |
||||
|
||||
prep-deps-firefox: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- run: |
||||
name: Download Firefox |
||||
command: > |
||||
wget https://ftp.mozilla.org/pub/firefox/releases/58.0/linux-x86_64/en-US/firefox-58.0.tar.bz2 |
||||
&& tar xjf firefox-58.0.tar.bz2 |
||||
- save_cache: |
||||
key: dependency-cache-firefox-{{ .Revision }} |
||||
paths: |
||||
- firefox |
||||
|
||||
|
||||
prep-scss: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
- run: |
||||
name: Get Scss Cache key |
||||
# this allows us to checksum against a whole directory |
||||
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum |
||||
- run: |
||||
name: Build for integration tests |
||||
command: npm run test:integration:build |
||||
- save_cache: |
||||
key: scss-cache-{{ checksum "scss_checksum" }} |
||||
paths: |
||||
- ui/app/css/output |
||||
|
||||
test-lint: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
- run: |
||||
name: Test |
||||
command: npm run lint |
||||
|
||||
test-unit: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
- run: |
||||
name: test:coverage |
||||
command: npm run test:coverage |
||||
|
||||
test-integration-flat: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-firefox-{{ .Revision }} |
||||
- run: |
||||
name: Install firefox |
||||
command: > |
||||
sudo rm -r /opt/firefox |
||||
&& sudo mv firefox /opt/firefox58 |
||||
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old |
||||
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox |
||||
- restore_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
- run: |
||||
name: Get Scss Cache key |
||||
# this allows us to checksum against a whole directory |
||||
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum |
||||
- restore_cache: |
||||
key: scss-cache-{{ checksum "scss_checksum" }} |
||||
- run: |
||||
name: test:integration:flat |
||||
command: npm run test:flat |
||||
|
||||
test-integration-mascara: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-firefox-{{ .Revision }} |
||||
- run: |
||||
name: Install firefox |
||||
command: > |
||||
sudo rm -r /opt/firefox |
||||
&& sudo mv firefox /opt/firefox58 |
||||
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old |
||||
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox |
||||
- restore_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
- run: |
||||
name: Get Scss Cache key |
||||
# this allows us to checksum against a whole directory |
||||
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum |
||||
- restore_cache: |
||||
key: scss-cache-{{ checksum "scss_checksum" }} |
||||
- run: |
||||
name: test:integration:mascara |
||||
command: npm run test:mascara |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"exceptions": ["https://nodesecurity.io/advisories/566"] |
||||
} |
@ -0,0 +1,48 @@ |
||||
const KeyringController = require('eth-keyring-controller') |
||||
|
||||
const seedPhraseVerifier = { |
||||
|
||||
// Verifies if the seed words can restore the accounts.
|
||||
//
|
||||
// The seed words can recreate the primary keyring and the accounts belonging to it.
|
||||
// The created accounts in the primary keyring are always the same.
|
||||
// The keyring always creates the accounts in the same sequence.
|
||||
verifyAccounts (createdAccounts, seedWords) { |
||||
|
||||
return new Promise((resolve, reject) => { |
||||
|
||||
if (!createdAccounts || createdAccounts.length < 1) { |
||||
return reject(new Error('No created accounts defined.')) |
||||
} |
||||
|
||||
const keyringController = new KeyringController({}) |
||||
const Keyring = keyringController.getKeyringClassForType('HD Key Tree') |
||||
const opts = { |
||||
mnemonic: seedWords, |
||||
numberOfAccounts: createdAccounts.length, |
||||
} |
||||
|
||||
const keyring = new Keyring(opts) |
||||
keyring.getAccounts() |
||||
.then((restoredAccounts) => { |
||||
|
||||
log.debug('Created accounts: ' + JSON.stringify(createdAccounts)) |
||||
log.debug('Restored accounts: ' + JSON.stringify(restoredAccounts)) |
||||
|
||||
if (restoredAccounts.length !== createdAccounts.length) { |
||||
// this should not happen...
|
||||
return reject(new Error('Wrong number of accounts')) |
||||
} |
||||
|
||||
for (let i = 0; i < restoredAccounts.length; i++) { |
||||
if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) { |
||||
return reject(new Error('Not identical accounts! Original: ' + createdAccounts[i] + ', Restored: ' + restoredAccounts[i])) |
||||
} |
||||
} |
||||
return resolve() |
||||
}) |
||||
}) |
||||
}, |
||||
} |
||||
|
||||
module.exports = seedPhraseVerifier |
@ -1,17 +0,0 @@ |
||||
machine: |
||||
node: |
||||
version: 8.1.4 |
||||
test: |
||||
override: |
||||
- "npm test" |
||||
dependencies: |
||||
pre: |
||||
- sudo apt-get update |
||||
# get latest stable firefox |
||||
- sudo apt-get install firefox |
||||
- firefox_cmd=`which firefox`; sudo rm -f $firefox_cmd; sudo ln -s `which firefox.ubuntu` $firefox_cmd |
||||
# get latest stable chrome |
||||
- wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - |
||||
- sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' |
||||
- sudo apt-get update |
||||
- sudo apt-get install google-chrome-stable |
@ -1,100 +1,122 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../../../ui/app/actions') |
||||
const FileInput = require('react-simple-file-input').default |
||||
const PropTypes = require('prop-types') |
||||
|
||||
const HELP_LINK = 'https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file' |
||||
|
||||
module.exports = connect(mapStateToProps)(JsonImportSubview) |
||||
class JsonImportSubview extends Component { |
||||
constructor (props) { |
||||
super(props) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
error: state.appState.warning, |
||||
this.state = { |
||||
file: null, |
||||
fileContents: '', |
||||
} |
||||
} |
||||
} |
||||
|
||||
inherits(JsonImportSubview, Component) |
||||
function JsonImportSubview () { |
||||
Component.call(this) |
||||
} |
||||
render () { |
||||
const { error } = this.props |
||||
|
||||
JsonImportSubview.prototype.render = function () { |
||||
const { error } = this.props |
||||
|
||||
return ( |
||||
h('div', { |
||||
style: { |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
alignItems: 'center', |
||||
padding: '5px 15px 0px 15px', |
||||
}, |
||||
}, [ |
||||
|
||||
h('p', 'Used by a variety of different clients'), |
||||
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), |
||||
|
||||
h(FileInput, { |
||||
readAs: 'text', |
||||
onLoad: this.onLoad.bind(this), |
||||
return ( |
||||
h('div', { |
||||
style: { |
||||
margin: '20px 0px 12px 20px', |
||||
fontSize: '15px', |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
alignItems: 'center', |
||||
padding: '5px 15px 0px 15px', |
||||
}, |
||||
}), |
||||
}, [ |
||||
|
||||
h('p', 'Used by a variety of different clients'), |
||||
h('a.warning', { |
||||
href: HELP_LINK, |
||||
target: '_blank', |
||||
}, 'File import not working? Click here!'), |
||||
|
||||
h(FileInput, { |
||||
readAs: 'text', |
||||
onLoad: this.onLoad.bind(this), |
||||
style: { |
||||
margin: '20px 0px 12px 20px', |
||||
fontSize: '15px', |
||||
}, |
||||
}), |
||||
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
placeholder: 'Enter password', |
||||
id: 'json-password-box', |
||||
onKeyPress: this.createKeyringOnEnter.bind(this), |
||||
style: { |
||||
width: 260, |
||||
marginTop: 12, |
||||
}, |
||||
}), |
||||
|
||||
h('button.primary', { |
||||
onClick: this.createNewKeychain.bind(this), |
||||
style: { |
||||
margin: 12, |
||||
}, |
||||
}, 'Import'), |
||||
|
||||
error ? h('span.error', error) : null, |
||||
]) |
||||
) |
||||
} |
||||
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
placeholder: 'Enter password', |
||||
id: 'json-password-box', |
||||
onKeyPress: this.createKeyringOnEnter.bind(this), |
||||
style: { |
||||
width: 260, |
||||
marginTop: 12, |
||||
}, |
||||
}), |
||||
onLoad (event, file) { |
||||
this.setState({file: file, fileContents: event.target.result}) |
||||
} |
||||
|
||||
h('button.primary', { |
||||
onClick: this.createNewKeychain.bind(this), |
||||
style: { |
||||
margin: 12, |
||||
}, |
||||
}, 'Import'), |
||||
createKeyringOnEnter (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.createNewKeychain() |
||||
} |
||||
} |
||||
|
||||
error ? h('span.error', error) : null, |
||||
]) |
||||
) |
||||
} |
||||
createNewKeychain () { |
||||
const { fileContents } = this.state |
||||
|
||||
JsonImportSubview.prototype.onLoad = function (event, file) { |
||||
this.setState({file: file, fileContents: event.target.result}) |
||||
} |
||||
if (!fileContents) { |
||||
const message = 'You must select a file to import.' |
||||
return this.props.displayWarning(message) |
||||
} |
||||
|
||||
JsonImportSubview.prototype.createKeyringOnEnter = function (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.createNewKeychain() |
||||
} |
||||
} |
||||
const passwordInput = document.getElementById('json-password-box') |
||||
const password = passwordInput.value |
||||
|
||||
JsonImportSubview.prototype.createNewKeychain = function () { |
||||
const state = this.state |
||||
const { fileContents } = state |
||||
if (!password) { |
||||
const message = 'You must enter a password for the selected file.' |
||||
return this.props.displayWarning(message) |
||||
} |
||||
|
||||
if (!fileContents) { |
||||
const message = 'You must select a file to import.' |
||||
return this.props.dispatch(actions.displayWarning(message)) |
||||
this.props.importNewAccount([ fileContents, password ]) |
||||
} |
||||
} |
||||
|
||||
const passwordInput = document.getElementById('json-password-box') |
||||
const password = passwordInput.value |
||||
JsonImportSubview.propTypes = { |
||||
error: PropTypes.string, |
||||
displayWarning: PropTypes.func, |
||||
importNewAccount: PropTypes.func, |
||||
} |
||||
|
||||
if (!password) { |
||||
const message = 'You must enter a password for the selected file.' |
||||
return this.props.dispatch(actions.displayWarning(message)) |
||||
const mapStateToProps = state => { |
||||
return { |
||||
error: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) |
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
goHome: () => dispatch(actions.goHome()), |
||||
displayWarning: warning => dispatch(actions.displayWarning(warning)), |
||||
importNewAccount: options => dispatch(actions.importNewAccount('JSON File', options)), |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(JsonImportSubview) |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,133 @@ |
||||
const assert = require('assert') |
||||
const clone = require('clone') |
||||
const KeyringController = require('eth-keyring-controller') |
||||
const firstTimeState = require('../../app/scripts/first-time-state') |
||||
const seedPhraseVerifier = require('../../app/scripts/lib/seed-phrase-verifier') |
||||
const mockEncryptor = require('../lib/mock-encryptor') |
||||
|
||||
describe('SeedPhraseVerifier', function () { |
||||
|
||||
describe('verifyAccounts', function () { |
||||
|
||||
let password = 'passw0rd1' |
||||
let hdKeyTree = 'HD Key Tree' |
||||
|
||||
let keyringController |
||||
let vault |
||||
let primaryKeyring |
||||
|
||||
beforeEach(async function () { |
||||
keyringController = new KeyringController({ |
||||
initState: clone(firstTimeState), |
||||
encryptor: mockEncryptor, |
||||
}) |
||||
|
||||
assert(keyringController) |
||||
|
||||
vault = await keyringController.createNewVaultAndKeychain(password) |
||||
primaryKeyring = keyringController.getKeyringsByType(hdKeyTree)[0] |
||||
}) |
||||
|
||||
it('should be able to verify created account with seed words', async function () { |
||||
|
||||
let createdAccounts = await primaryKeyring.getAccounts() |
||||
assert.equal(createdAccounts.length, 1) |
||||
|
||||
let serialized = await primaryKeyring.serialize() |
||||
let seedWords = serialized.mnemonic |
||||
assert.notEqual(seedWords.length, 0) |
||||
|
||||
let result = await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) |
||||
}) |
||||
|
||||
it('should be able to verify created account (upper case) with seed words', async function () { |
||||
|
||||
let createdAccounts = await primaryKeyring.getAccounts() |
||||
assert.equal(createdAccounts.length, 1) |
||||
|
||||
let upperCaseAccounts = [createdAccounts[0].toUpperCase()] |
||||
|
||||
let serialized = await primaryKeyring.serialize() |
||||
let seedWords = serialized.mnemonic |
||||
assert.notEqual(seedWords.length, 0) |
||||
|
||||
let result = await seedPhraseVerifier.verifyAccounts(upperCaseAccounts, seedWords) |
||||
}) |
||||
|
||||
it('should be able to verify created account (lower case) with seed words', async function () { |
||||
|
||||
let createdAccounts = await primaryKeyring.getAccounts() |
||||
assert.equal(createdAccounts.length, 1) |
||||
let lowerCaseAccounts = [createdAccounts[0].toLowerCase()] |
||||
|
||||
let serialized = await primaryKeyring.serialize() |
||||
let seedWords = serialized.mnemonic |
||||
assert.notEqual(seedWords.length, 0) |
||||
|
||||
let result = await seedPhraseVerifier.verifyAccounts(lowerCaseAccounts, seedWords) |
||||
}) |
||||
|
||||
it('should return error with good but different seed words', async function () { |
||||
|
||||
let createdAccounts = await primaryKeyring.getAccounts() |
||||
assert.equal(createdAccounts.length, 1) |
||||
|
||||
let serialized = await primaryKeyring.serialize() |
||||
let seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' |
||||
|
||||
try {
|
||||
let result = await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) |
||||
assert.fail("Should reject") |
||||
} catch (err) { |
||||
assert.ok(err.message.indexOf('Not identical accounts!') >= 0, 'Wrong error message') |
||||
} |
||||
}) |
||||
|
||||
it('should return error with undefined existing accounts', async function () { |
||||
|
||||
let createdAccounts = await primaryKeyring.getAccounts() |
||||
assert.equal(createdAccounts.length, 1) |
||||
|
||||
let serialized = await primaryKeyring.serialize() |
||||
let seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' |
||||
|
||||
try {
|
||||
let result = await seedPhraseVerifier.verifyAccounts(undefined, seedWords) |
||||
assert.fail("Should reject") |
||||
} catch (err) { |
||||
assert.equal(err.message, 'No created accounts defined.') |
||||
} |
||||
}) |
||||
|
||||
it('should return error with empty accounts array', async function () { |
||||
|
||||
let createdAccounts = await primaryKeyring.getAccounts() |
||||
assert.equal(createdAccounts.length, 1) |
||||
|
||||
let serialized = await primaryKeyring.serialize() |
||||
let seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' |
||||
|
||||
try {
|
||||
let result = await seedPhraseVerifier.verifyAccounts([], seedWords) |
||||
assert.fail("Should reject") |
||||
} catch (err) { |
||||
assert.equal(err.message, 'No created accounts defined.') |
||||
} |
||||
}) |
||||
|
||||
it('should be able to verify more than one created account with seed words', async function () { |
||||
|
||||
const keyState = await keyringController.addNewAccount(primaryKeyring) |
||||
const keyState2 = await keyringController.addNewAccount(primaryKeyring) |
||||
|
||||
let createdAccounts = await primaryKeyring.getAccounts() |
||||
assert.equal(createdAccounts.length, 3) |
||||
|
||||
let serialized = await primaryKeyring.serialize() |
||||
let seedWords = serialized.mnemonic |
||||
assert.notEqual(seedWords.length, 0) |
||||
|
||||
let result = await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) |
||||
}) |
||||
}) |
||||
}) |
Loading…
Reference in new issue