From 9202ccccf78258a061b5349f2e030de5c890ecbd Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 11 Dec 2017 16:50:20 +0100 Subject: [PATCH] UI redirect to 127.0.0.1 when localhost requested (#7236) * JS redirect from localhost -> 127.0.0.1 * ui-no-validation for 127.0.0.1 * Update with tests --- js/src/index.parity.js | 78 ++++++++++++++++++++------------------- js/src/util/host.js | 40 ++++++++++++++++++++ js/src/util/host.spec.js | 49 ++++++++++++++++++++++++ js/src/util/token.js | 54 +++++++++++++++++++++++++++ js/src/util/token.spec.js | 55 +++++++++++++++++++++++++++ parity/configuration.rs | 4 +- 6 files changed, 240 insertions(+), 40 deletions(-) create mode 100644 js/src/util/host.js create mode 100644 js/src/util/host.spec.js create mode 100644 js/src/util/token.js create mode 100644 js/src/util/token.spec.js diff --git a/js/src/index.parity.js b/js/src/index.parity.js index e9059a0ea..82459e826 100644 --- a/js/src/index.parity.js +++ b/js/src/index.parity.js @@ -21,7 +21,6 @@ import ReactDOM from 'react-dom'; import injectTapEventPlugin from 'react-tap-event-plugin'; import { IndexRoute, Redirect, Route, Router, hashHistory } from 'react-router'; -import qs from 'querystring'; import ContractInstances from '@parity/shared/lib/contracts'; import { initStore } from '@parity/shared/lib/redux'; @@ -29,6 +28,9 @@ import ContextProvider from '@parity/ui/lib/ContextProvider'; import '@parity/shared/lib/environment'; +import { redirectLocalhost } from './util/host'; +import { retrieveToken } from './util/token'; + import Application from './Application'; import Dapp from './Dapp'; import Dapps from './Dapps'; @@ -52,45 +54,45 @@ if (process.env.NODE_ENV === 'development') { } */ -const AUTH_HASH = '#/auth?'; +function renderUI (token) { + const api = new SecureApi(window.location.host, token); -let token = null; + api.parity.registryAddress().then((address) => console.log('registryAddress', address)).catch((error) => console.error('registryAddress', error)); -if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) { - token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token; + ContractInstances.get(api); + + setupProviderFilters(api.provider); + + const store = initStore(api, hashHistory); + + console.log('UI version', process.env.UI_VERSION); + + ReactDOM.render( + + + + + + + + + + , + document.querySelector('#container') + ); + + // testing, priceTicker gist + // injectExternalScript('https://cdn.rawgit.com/jacogr/396fc583e81b9404e21195a48dc862ca/raw/33e5058a4c0028cf9acf4b0662d75298e41ca6fa/priceTicker.js'); + + // testing, signer plugins + require('@parity/plugin-signer-account'); + require('@parity/plugin-signer-default'); + require('@parity/plugin-signer-hardware'); + require('@parity/plugin-signer-qr'); } -const api = new SecureApi(window.location.host, token); +const token = retrieveToken(); -api.parity.registryAddress().then((address) => console.log('registryAddress', address)).catch((error) => console.error('registryAddress', error)); - -ContractInstances.get(api); - -setupProviderFilters(api.provider); - -const store = initStore(api, hashHistory); - -console.log('UI version', process.env.UI_VERSION); - -ReactDOM.render( - - - - - - - - - - , - document.querySelector('#container') -); - -// testing, priceTicker gist -// injectExternalScript('https://cdn.rawgit.com/jacogr/396fc583e81b9404e21195a48dc862ca/raw/33e5058a4c0028cf9acf4b0662d75298e41ca6fa/priceTicker.js'); - -// testing, signer plugins -import '@parity/plugin-signer-account'; -import '@parity/plugin-signer-default'; -import '@parity/plugin-signer-hardware'; -import '@parity/plugin-signer-qr'; +if (!redirectLocalhost(token)) { + renderUI(token); +} diff --git a/js/src/util/host.js b/js/src/util/host.js new file mode 100644 index 000000000..09dc7601d --- /dev/null +++ b/js/src/util/host.js @@ -0,0 +1,40 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export function createLocation (token, location = window.location) { + const { hash, port, protocol } = location; + let query = ''; + + if (hash && hash.indexOf('?') !== -1) { + // TODO: currently no app uses query-params visible in the shell, this may need adjustment if they do + query = hash; + } else { + query = `${hash || '#/'}${token ? '?token=' : ''}${token || ''}`; + } + + return `${protocol}//127.0.0.1:${port}/${query}`; +} + +export function redirectLocalhost (token) { + // we don't want localhost, rather we want 127.0.0.1 + if (window.location.hostname !== 'localhost') { + return false; + } + + window.location.assign(createLocation(token, window.location)); + + return true; +} diff --git a/js/src/util/host.spec.js b/js/src/util/host.spec.js new file mode 100644 index 000000000..baa34eed1 --- /dev/null +++ b/js/src/util/host.spec.js @@ -0,0 +1,49 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { createLocation } from './host'; + +describe('createLocation', () => { + it('only changes the host with no token', () => { + expect( + createLocation('', { protocol: 'http:', port: 3000, hostname: 'localhost' }) + ).to.equal('http://127.0.0.1:3000/#/'); + }); + + it('preserves hash when changing the host', () => { + expect( + createLocation('', { protocol: 'http:', port: 3000, hostname: 'localhost', hash: '#/accounts' }) + ).to.equal('http://127.0.0.1:3000/#/accounts'); + }); + + it('adds the token when required', () => { + expect( + createLocation('test', { protocol: 'http:', port: 3000, hostname: 'localhost' }) + ).to.equal('http://127.0.0.1:3000/#/?token=test'); + }); + + it('preserves hash when token adjusted', () => { + expect( + createLocation('test', { protocol: 'http:', port: 3000, hostname: 'localhost', hash: '#/accounts' }) + ).to.equal('http://127.0.0.1:3000/#/accounts?token=test'); + }); + + it('does not override already-passed parameters', () => { + expect( + createLocation('test', { protocol: 'http:', port: 3000, hostname: 'localhost', hash: '#/accounts?token=abc' }) + ).to.equal('http://127.0.0.1:3000/#/accounts?token=abc'); + }); +}); diff --git a/js/src/util/token.js b/js/src/util/token.js new file mode 100644 index 000000000..cf0683f5a --- /dev/null +++ b/js/src/util/token.js @@ -0,0 +1,54 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import qs from 'querystring'; +import store from 'store'; + +const TOKEN_QS = 'token='; + +function parseTokenQuery (query) { + try { + return qs.parse(query).token; + } catch (error) { + return null; + } +} + +export function retrieveToken (location = window.location) { + const hashIndex = location.hash + ? location.hash.indexOf(TOKEN_QS) + : -1; + const searchIndex = location.search + ? location.search.indexOf(TOKEN_QS) + : -1; + + let token = null; + + if (hashIndex !== -1) { + // extract from hash (e.g. http://127.0.0.1:8180/#/auth?token=...) + token = parseTokenQuery(location.hash.substr(hashIndex)); + } else if (searchIndex !== -1) { + // extract from query (e.g. http://127.0.0.1:3000/?token=...) + token = parseTokenQuery(location.search); + } + + if (!token) { + // we don't have a token, attempt from localStorage + token = store.get('sysuiToken'); + } + + return token; +} diff --git a/js/src/util/token.spec.js b/js/src/util/token.spec.js new file mode 100644 index 000000000..06ebe02b6 --- /dev/null +++ b/js/src/util/token.spec.js @@ -0,0 +1,55 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { retrieveToken } from './token'; + +describe('retrieveToken', () => { + it('returns nothing when not found in hash, search or localStorage', () => { + expect(retrieveToken()).not.to.be.ok; + }); + + describe('localStorage', () => { + beforeEach(() => { + localStorage.setItem('sysuiToken', 'yQls-7TX1-t0Jb-0001'); + }); + + afterEach(() => { + localStorage.removeItem('sysuiToken'); + }); + + it('retrieves the token', () => { + expect(retrieveToken()).to.equal('yQls-7TX1-t0Jb-0001'); + }); + }); + + describe('URL', () => { + it('retrieves the token from search', () => { + expect( + retrieveToken({ + search: 'token=yQls-7TX1-t0Jb-0002' + }) + ).to.equal('yQls-7TX1-t0Jb-0002'); + }); + + it('retrieves the token from hash', () => { + expect( + retrieveToken({ + hash: '#/auth?token=yQls-7TX1-t0Jb-0003' + }) + ).to.equal('yQls-7TX1-t0Jb-0003'); + }); + }); +}); diff --git a/parity/configuration.rs b/parity/configuration.rs index 11a869fdc..b912689eb 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -567,7 +567,7 @@ impl Configuration { } fn dapps_config(&self) -> DappsConfiguration { - let dev_ui = if self.args.flag_ui_no_validation { vec![("localhost".to_owned(), 3000)] } else { vec![] }; + let dev_ui = if self.args.flag_ui_no_validation { vec![("127.0.0.1".to_owned(), 3000)] } else { vec![] }; let ui_port = self.ui_port(); DappsConfiguration { @@ -1578,7 +1578,7 @@ mod tests { port: 8180, hosts: Some(vec![]), }); - assert_eq!(conf1.dapps_config().extra_embed_on, vec![("localhost".to_owned(), 3000)]); + assert_eq!(conf1.dapps_config().extra_embed_on, vec![("127.0.0.1".to_owned(), 3000)]); assert_eq!(conf1.ws_config().unwrap().origins, None); assert_eq!(conf2.directories().signer, "signer".to_owned()); assert_eq!(conf2.ui_config(), UiConfiguration {