diff --git a/embark-ui/package-lock.json b/embark-ui/package-lock.json index 564e6f8e..02b570c0 100644 --- a/embark-ui/package-lock.json +++ b/embark-ui/package-lock.json @@ -155,6 +155,14 @@ "color-convert": "^1.9.0" } }, + "ansi-to-html": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.6.6.tgz", + "integrity": "sha512-90M/2sZna3OsoOEbSyXK46poFnlClBC53Rx6etNKQK7iShsX5fI5E/M9Ld6FurtLaxAWLuAPi0Jp8p3y5oAkxg==", + "requires": { + "entities": "^1.1.1" + } + }, "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", @@ -434,6 +442,11 @@ "postcss-value-parser": "^3.2.3" } }, + "autoscroll-react": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/autoscroll-react/-/autoscroll-react-3.2.0.tgz", + "integrity": "sha512-HOiwy9GGTSk9WZwEPd+FwNLLZ17o5wkjAtAb+RtQUr/2J1PT2KRG+OI6LoGtfY5EwWvQKAbSsjkgLvLhItscSg==" + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -7106,6 +7119,22 @@ "prepend-http": "^1.0.0", "query-string": "^4.1.0", "sort-keys": "^1.0.0" + }, + "dependencies": { + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + } } }, "npm-run-path": { @@ -8826,12 +8855,12 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.1.0.tgz", + "integrity": "sha512-pNB/Gr8SA8ff8KpUFM36o/WFAlthgaThka5bV19AD9PNTH20Pwq5Zxodif2YyHwrctp6SkL4GqlOot0qR/wGaw==", "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" + "decode-uri-component": "^0.2.0", + "strict-uri-encode": "^2.0.0" } }, "querystring": { @@ -10235,9 +10264,9 @@ } }, "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" }, "string-length": { "version": "1.0.1", diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index 967c1026..ced87b63 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -13,11 +13,32 @@ function action(type, payload = {}) { return {type, ...payload}; } -export const AUTHORIZE = createRequestTypes('AUTHORIZE'); -export const authorize = { - request: (token, callback) => action(AUTHORIZE[REQUEST], {token, callback}), - success: () => action(AUTHORIZE[SUCCESS]), - failure: (error) => action(AUTHORIZE[FAILURE], {error}) +export const AUTHENTICATE = createRequestTypes('AUTHENTICATE'); +export const authenticate = { + request: (token) => action(AUTHENTICATE[REQUEST], {token}), + success: (_result, payload) => action(AUTHENTICATE[SUCCESS], {token: payload.token}), + failure: (error) => action(AUTHENTICATE[FAILURE], {error}) +}; + +export const FETCH_TOKEN = createRequestTypes('FETCH_TOKEN'); +export const fetchToken = { + request: () => action(FETCH_TOKEN[REQUEST]), + success: (token) => action(FETCH_TOKEN[SUCCESS], {token}), + failure: () => action(FETCH_TOKEN[FAILURE]) +}; + +export const POST_TOKEN = createRequestTypes('POST_TOKEN'); +export const postToken = { + request: (token) => action(POST_TOKEN[REQUEST], {token}), + success: (token) => action(POST_TOKEN[SUCCESS], {token}), + failure: () => action(POST_TOKEN[FAILURE]) +}; + +export const LOGOUT = createRequestTypes('LOGOUT'); +export const logout = { + request: () => action(LOGOUT[REQUEST]), + success: () => action(LOGOUT[SUCCESS]), + failure: () => action(LOGOUT[FAILURE]) }; export const ACCOUNTS = createRequestTypes('ACCOUNTS'); @@ -226,20 +247,6 @@ export const saveCurrentFile = { failure: () => action(SAVE_CURRENT_FILE[FAILURE]) }; -export const GET_TOKEN = createRequestTypes('TOKEN'); -export const getToken = { - request: (callback) => action(GET_TOKEN[REQUEST], {callback}), - success: (token) => action(GET_TOKEN[SUCCESS], {token}), - failure: () => action(GET_TOKEN[FAILURE]) -}; - -export const POST_TOKEN = createRequestTypes('POST_TOKEN'); -export const postToken = { - request: (token) => action(POST_TOKEN[REQUEST], {token}), - success: (token) => action(POST_TOKEN[SUCCESS], {token}), - failure: () => action(POST_TOKEN[FAILURE]) -}; - export const GAS_ORACLE = createRequestTypes('GAS_ORACLE'); export const gasOracle = { request: () => action(GAS_ORACLE[REQUEST]), diff --git a/embark-ui/src/components/AuthError.js b/embark-ui/src/components/AuthError.js deleted file mode 100644 index 38eaccce..00000000 --- a/embark-ui/src/components/AuthError.js +++ /dev/null @@ -1,24 +0,0 @@ -import PropTypes from "prop-types"; -import React from 'react'; -import {Page, Alert, Form, Button} from "tabler-react"; - -const AuthError = ({error}) => { - return - - {error} - -
- - - -
; -}; - -AuthError.propTypes = { - error: PropTypes.string.isRequired -}; - -export default AuthError; - diff --git a/embark-ui/src/components/Layout.js b/embark-ui/src/components/Layout.js index 212da010..b76788fd 100644 --- a/embark-ui/src/components/Layout.js +++ b/embark-ui/src/components/Layout.js @@ -13,37 +13,49 @@ const navBarItems = [ {value: "Documentation", to: "/embark/documentation", icon: "file-text", LinkComponent: NavLink} ]; -const Layout = (props) => ( +const Layout = ({children, logout}) => ( - - + + + + + + + + ) }} navProps={{itemsObjects: navBarItems}} > - {props.children} + {children} ); Layout.propTypes = { - children: PropTypes.element + children: PropTypes.element, + logout: PropTypes.func }; export default Layout; diff --git a/embark-ui/src/components/Unauthenticated.js b/embark-ui/src/components/Unauthenticated.js new file mode 100644 index 00000000..c7b5b94d --- /dev/null +++ b/embark-ui/src/components/Unauthenticated.js @@ -0,0 +1,47 @@ +import PropTypes from "prop-types"; +import React from 'react'; +import {Page, Alert, Form, Button} from "tabler-react"; + +class Unauthenticated extends React.Component { + constructor(props){ + super(props); + this.state = { token: '' }; + } + + handleTokenChange(event){ + this.setState({token: event.target.value}); + } + + handleSubmit(event) { + event.preventDefault(); + this.props.authenticate(this.state.token); + } + + render() { + return ( + + {this.props.error && + {this.props.error} + } +
this.handleSubmit(e)}> + this.handleTokenChange(e)} + placeholder="Enter Token"/> + + +
+ ); + } +} + +Unauthenticated.propTypes = { + authenticate: PropTypes.func, + error: PropTypes.string +}; + +export default Unauthenticated; + diff --git a/embark-ui/src/containers/AppContainer.js b/embark-ui/src/containers/AppContainer.js index 3480d532..f66c3c41 100644 --- a/embark-ui/src/containers/AppContainer.js +++ b/embark-ui/src/containers/AppContainer.js @@ -4,64 +4,72 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; import routes from '../routes'; -import AuthError from '../components/AuthError'; +import Unauthenticated from '../components/Unauthenticated'; +import Layout from "../components/Layout"; import queryString from 'query-string'; import { initBlockHeader, - authorize, getToken, postToken, + authenticate, fetchToken, logout, processes as processesAction, versions as versionsAction, plugins as pluginsAction } from '../actions'; +import { getToken, getAuthenticationError } from '../reducers/selectors'; + class AppContainer extends Component { constructor (props) { super(props); - this.state = { - authenticateError: null - }; - this.checkToken(); + this.queryStringAuthenticate(); } - checkToken() { - if (this.props.location.search) { - const token = queryString.parse(this.props.location.search).token; - this.props.postToken(token); - return this.props.authorize(token, this.authCallback.bind(this)); + queryStringAuthenticate() { + if (!this.props.location.search) { + return; } - this.props.getToken((err, token) => { - this.props.authorize(token, this.authCallback.bind(this)); - }); - } - - authCallback(err) { - if (err) { - return this.setState({authenticateError: err}); + const token = queryString.parse(this.props.location.search).token; + if (token === this.props.token) { + return; } - this.setState({authenticateError: null}); + this.props.authenticate(token); } componentDidMount() { - this.props.initBlockHeader(); - this.props.fetchProcesses(); - this.props.fetchVersions(); - this.props.fetchPlugins(); + this.props.fetchToken(); + } + + componentDidUpdate(){ + if (this.props.token) { + this.props.authenticate(this.props.token); + this.props.initBlockHeader(); + this.props.fetchProcesses(); + this.props.fetchVersions(); + this.props.fetchPlugins(); + } + } + + shouldRenderUnauthenticated() { + return this.props.authenticationError || !this.props.token; } render() { - if (this.state.authenticateError) { - return ; - } - return ({routes}); + return ( + + {this.shouldRenderUnauthenticated() ? : {routes}} + + ); } } AppContainer.propTypes = { - authorize: PropTypes.func, - getToken: PropTypes.func, - postToken: PropTypes.func, + token: PropTypes.string, + authenticationError: PropTypes.string, + authenticate: PropTypes.func, + logout: PropTypes.func, + fetchToken: PropTypes.func, initBlockHeader: PropTypes.func, fetchProcesses: PropTypes.func, fetchPlugins: PropTypes.func, @@ -69,13 +77,20 @@ AppContainer.propTypes = { location: PropTypes.object }; +function mapStateToProps(state) { + return { + token: getToken(state), + authenticationError: getAuthenticationError(state) + }; +} + export default withRouter(connect( - null, + mapStateToProps, { initBlockHeader, - authorize: authorize.request, - getToken: getToken.request, - postToken: postToken.request, + authenticate: authenticate.request, + logout: logout.request, + fetchToken: fetchToken.request, fetchProcesses: processesAction.request, fetchVersions: versionsAction.request, fetchPlugins: pluginsAction.request diff --git a/embark-ui/src/index.js b/embark-ui/src/index.js index 7f69eded..3b41ef57 100644 --- a/embark-ui/src/index.js +++ b/embark-ui/src/index.js @@ -6,7 +6,6 @@ import {Provider} from 'react-redux'; import "tabler-react/dist/Tabler.css"; import "./general.css"; import "./slider.css"; -import Layout from "./components/Layout"; import AppContainer from './containers/AppContainer'; import history from "./history"; @@ -18,9 +17,7 @@ const store = configureStore(); ReactDOM.render( - - - + , document.getElementById('root') diff --git a/embark-ui/src/reducers/index.js b/embark-ui/src/reducers/index.js index af2ab2b1..1ad5c799 100644 --- a/embark-ui/src/reducers/index.js +++ b/embark-ui/src/reducers/index.js @@ -1,5 +1,5 @@ import {combineReducers} from 'redux'; -import {REQUEST, SUCCESS, FAILURE, CONTRACT_COMPILE, FILES} from "../actions"; +import {REQUEST, SUCCESS, FAILURE, CONTRACT_COMPILE, FILES, LOGOUT, AUTHENTICATE} from "../actions"; const BN_FACTOR = 10000; const voidAddress = '0x0000000000000000000000000000000000000000'; @@ -83,6 +83,9 @@ const filtrer = { }, gasOracleStats: function(stat, index, _self) { return index === 0; // Only keep last one + }, + versions: function(version, index, self) { + return index === self.findIndex((v) => v.value === version.value && v.name === version.name); } }; @@ -115,8 +118,8 @@ function entities(state = entitiesDefaultState, action) { return state; } -function errorMessage(state = null, action) { - return action.error || state; +function errorMessage(_state = null, action) { + return action.error || null; } function errorEntities(state = {}, action) { @@ -146,8 +149,16 @@ function compilingContract(state = false, action) { return state; } -function token(state = null, action) { - return (action.token) ? action.token : state; +function authentication(state = {}, action) { + if (action.type === LOGOUT[SUCCESS]) { + return {}; + } + + if (action.type === AUTHENTICATE[FAILURE]) { + return {error: action.error}; + } + + return (action.token) ? {token: action.token} : state; } const rootReducer = combineReducers({ @@ -156,7 +167,7 @@ const rootReducer = combineReducers({ compilingContract, errorMessage, errorEntities, - token + authentication }); export default rootReducer; diff --git a/embark-ui/src/reducers/selectors.js b/embark-ui/src/reducers/selectors.js index 95ca30d6..cefcae15 100644 --- a/embark-ui/src/reducers/selectors.js +++ b/embark-ui/src/reducers/selectors.js @@ -1,5 +1,13 @@ import {last} from '../utils/utils'; +export function getToken(state) { + return state.authentication.token; +} + +export function getAuthenticationError(state) { + return state.authentication.error; +} + export function getAccounts(state) { return state.entities.accounts; } diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index f311a0e1..d49fb81f 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -3,11 +3,10 @@ import * as api from '../services/api'; import * as storage from '../services/storage'; import {eventChannel} from 'redux-saga'; import {all, call, fork, put, takeEvery, take, select} from 'redux-saga/effects'; +import {getToken} from '../reducers/selectors'; function *doRequest(entity, serviceFn, payload) { - payload.token = yield select(function (state) { - return state.token; - }); + payload.token = yield select(getToken); const {response, error} = yield call(serviceFn, payload); if(response) { yield put(entity.success(response.data, payload)); @@ -42,13 +41,14 @@ export const fetchFile = doRequest.bind(null, actions.file, api.fetchFile); export const postFile = doRequest.bind(null, actions.saveFile, api.postFile); export const deleteFile = doRequest.bind(null, actions.removeFile, api.deleteFile); export const fetchEthGas = doRequest.bind(null, actions.gasOracle, api.getEthGasAPI); -export const authorize = doRequest.bind(null, actions.authorize, api.authorize); +export const authenticate = doRequest.bind(null, actions.authenticate, api.authenticate); export const fetchCurrentFile = doRequest.bind(null, actions.currentFile, storage.fetchCurrentFile); export const postCurrentFile = doRequest.bind(null, actions.saveCurrentFile, storage.postCurrentFile); export const deleteCurrentFile = doRequest.bind(null, null, storage.deleteCurrentFile); -export const fetchToken = doRequest.bind(null, actions.getToken, storage.fetchToken); +export const fetchToken = doRequest.bind(null, actions.fetchToken, storage.fetchToken); export const postToken = doRequest.bind(null, actions.postToken, storage.postToken); +export const logout = doRequest.bind(null, actions.logout, storage.logout); export function *watchFetchTransaction() { @@ -172,20 +172,24 @@ export function *watchPostCurrentFile() { yield takeEvery(actions.SAVE_CURRENT_FILE[actions.REQUEST], postCurrentFile); } -export function *watchFetchToken() { - yield takeEvery(actions.GET_TOKEN[actions.REQUEST], fetchToken); -} - -export function *watchPostToken() { - yield takeEvery(actions.POST_TOKEN[actions.REQUEST], postToken); -} - export function *watchFetchEthGas() { yield takeEvery(actions.GAS_ORACLE[actions.REQUEST], fetchEthGas); } export function *watchAuthenticate() { - yield takeEvery(actions.AUTHORIZE[actions.REQUEST], authorize); + yield takeEvery(actions.AUTHENTICATE[actions.REQUEST], authenticate); +} + +export function *watchAuthenticateSuccess() { + yield takeEvery(actions.AUTHENTICATE[actions.SUCCESS], postToken); +} + +export function *watchFetchToken() { + yield takeEvery(actions.FETCH_TOKEN[actions.REQUEST], fetchToken); +} + +export function *watchLogout() { + yield takeEvery(actions.LOGOUT[actions.REQUEST], logout); } function createChannel(socket) { @@ -297,9 +301,10 @@ export default function *root() { fork(watchFetchCurrentFile), fork(watchPostCurrentFile), fork(watchFetchToken), - fork(watchPostToken), fork(watchFetchEthGas), fork(watchAuthenticate), + fork(watchAuthenticateSuccess), + fork(watchLogout), fork(watchListenGasOracle) ]); } diff --git a/embark-ui/src/services/api.js b/embark-ui/src/services/api.js index 90ec8681..48f73a7b 100644 --- a/embark-ui/src/services/api.js +++ b/embark-ui/src/services/api.js @@ -137,8 +137,8 @@ export function deleteFile(payload) { return destroy('/file', {params: payload, token: payload.token}); } -export function authorize() { - return post('/authorize', ...arguments); +export function authenticate() { + return post('/authenticate', ...arguments); } // TODO token for WS? diff --git a/embark-ui/src/services/storage.js b/embark-ui/src/services/storage.js index b9080681..a1d9ebdc 100644 --- a/embark-ui/src/services/storage.js +++ b/embark-ui/src/services/storage.js @@ -18,18 +18,23 @@ export function deleteCurrentFile() { }); } -export function postToken(data) { +export function postToken({token}) { return new Promise(function(resolve) { - localStorage.setItem('token', data.token); - resolve({response: {data: data.token}}); - }); -} - -export function fetchToken({callback}) { - callback = callback || function(){}; - return new Promise(function(resolve) { - const token = localStorage.getItem('token'); - callback(null, token); + localStorage.setItem('token', token); resolve({response: {data: token}}); }); } + +export function fetchToken() { + return new Promise(function(resolve) { + const token = localStorage.getItem('token'); + resolve({response: {data: token}}); + }); +} + +export function logout() { + return new Promise(function(resolve) { + localStorage.clear(); + resolve({response: true}); + }); +} diff --git a/lib/modules/authenticator/index.js b/lib/modules/authenticator/index.js index 8a6e6c8f..ac4a6af5 100644 --- a/lib/modules/authenticator/index.js +++ b/lib/modules/authenticator/index.js @@ -16,10 +16,10 @@ class Authenticator { registerCalls() { this.embark.registerAPICall( 'post', - '/embark-api/authorize', + '/embark-api/authenticate', (req, res) => { if (req.body.token !== this.authToken) { - this.logger.warn(__('Someone tried and failed to authorize to the backend')); + this.logger.warn(__('Someone tried and failed to authenticate to the backend')); this.logger.warn(__('- User-Agent: %s', req.headers['user-agent'])); this.logger.warn(__('- Referer: %s', req.headers.referer)); return res.send(ERROR_OBJ); @@ -32,7 +32,7 @@ class Authenticator { return { match: () => cmd === "token", process: (callback) => { - callback(null, __('Your authorisation token: %s', this.authToken)); + callback(null, __('Your authentication token: %s', this.authToken)); } }; });