From 6aa8781ff500f0cc85dadf98526d5f50e525b360 Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Mon, 10 Dec 2018 10:18:12 -0500 Subject: [PATCH] fix(@embark): single use tokens --- embark-ui/src/actions/index.js | 4 +- embark-ui/src/containers/AppContainer.js | 1 - embark-ui/src/services/api.js | 23 ++++++--- src/lib/modules/authenticator/index.js | 65 +++++++++++++++++++----- 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index dfb480380..a17d6ef07 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -19,7 +19,9 @@ function action(type, payload = {}) { export const AUTHENTICATE = createRequestTypes('AUTHENTICATE'); export const authenticate = { request: (host, token) => action(AUTHENTICATE[REQUEST], {host, token}), - success: (_result, payload) => action(AUTHENTICATE[SUCCESS], {host: payload.host, token: payload.token}), + success: (result, payload) => { + return action(AUTHENTICATE[SUCCESS], {host: payload.host, token: result.token}) + }, failure: (error) => action(AUTHENTICATE[FAILURE], {error}) }; diff --git a/embark-ui/src/containers/AppContainer.js b/embark-ui/src/containers/AppContainer.js index 5ead97c60..855aac51f 100644 --- a/embark-ui/src/containers/AppContainer.js +++ b/embark-ui/src/containers/AppContainer.js @@ -18,7 +18,6 @@ import { changeTheme, fetchTheme } from '../actions'; - import {LIGHT_THEME, DARK_THEME} from '../constants'; import { diff --git a/embark-ui/src/services/api.js b/embark-ui/src/services/api.js index 350eab24a..b9d56ce48 100644 --- a/embark-ui/src/services/api.js +++ b/embark-ui/src/services/api.js @@ -3,7 +3,7 @@ import keccak from "keccakjs"; function hash(cnonce, token, type, path, params = {}) { let hash = new keccak(); - hash.update(JSON.stringify(cnonce)); + hash.update(cnonce.toString()); hash.update(token); hash.update(type.toUpperCase()); hash.update(`/embark-api${path}`); @@ -52,6 +52,13 @@ function destroy() { return request('delete', ...arguments); } +function websocket(credentials, path) { + const cnonce = Date.now() + Math.random(); + const requestHash = hash(cnonce, credentials.token, 'ws', '/', {}); + + return new WebSocket(`ws://${credentials.host}/embark-api${path}`, [`${cnonce}|${requestHash}`]); +} + export function postCommand() { return post('/command', ...arguments); } @@ -222,15 +229,15 @@ export function toggleBreakpoint(payload) { } export function listenToDebugger(credentials) { - return new WebSocket(`ws://${credentials.host}/embark-api/debugger`, [credentials.token]); + return websocket(credentials, '/debugger'); } export function listenToChannel(credentials, channel) { - return new WebSocket(`ws://${credentials.host}/embark-api/communication/listenTo/${channel}`, [credentials.token]); + return websocket(credentials, `/communication/listenTo/${channel}`); } export function webSocketProcess(credentials, processName) { - return new WebSocket(`ws://${credentials.host}/embark-api/process-logs/${processName}`, [credentials.token]); + return websocket(credentials, `/process-logs/${processName}`); } export function webSocketServices(credentials) { @@ -238,7 +245,7 @@ export function webSocketServices(credentials) { } export function webSocketContractLogs(credentials) { - return new WebSocket(`ws://${credentials.host}/embark-api/contracts/logs`, [credentials.token]); + return websocket(credentials, `/contracts/logs`); } export function webSocketContracts(credentials) { @@ -246,13 +253,13 @@ export function webSocketContracts(credentials) { } export function webSocketContractEvents(credentials) { - return new WebSocket(`ws://${credentials.host}/embark-api/blockchain/contracts/event`, [credentials.token]); + return websocket(credentials, `/blockchain/contracts/event`); } export function webSocketBlockHeader(credentials) { - return new WebSocket(`ws://${credentials.host}/embark-api/blockchain/blockHeader`, [credentials.token]); + return websocket(credentials, `/blockchain/blockHeader`); } export function websocketGasOracle(credentials) { - return new WebSocket(`ws://${credentials.host}/embark-api/blockchain/gas/oracle`, [credentials.token]); + return websocket(credentials, `/blockchain/gas/oracle`); } diff --git a/src/lib/modules/authenticator/index.js b/src/lib/modules/authenticator/index.js index 4bdc6831c..504aae5b2 100644 --- a/src/lib/modules/authenticator/index.js +++ b/src/lib/modules/authenticator/index.js @@ -1,4 +1,4 @@ -const uuid = require('uuid/v1'); +const uuid = require('uuid/v4'); const utils = require("../../utils/utils.js"); const keccak = require('keccakjs'); @@ -6,25 +6,39 @@ const ERROR_OBJ = {error: __('Wrong authentication token. Get your token from th class Authenticator { constructor(embark, _options) { - this.authToken = uuid(); this.embark = embark; this.logger = embark.logger; this.events = embark.events; + this.authToken = uuid(); + this.emittedTokens = {}; + this.registerCalls(); this.registerEvents(); } - generateRequestHash(req) { - let cnonce = req.headers['x-embark-cnonce']; - let hash = new keccak(); + getRemoteAddress(req) { + return (req.headers && req.headers['x-forwarded-for']) || + (req.connection && req.connection.remoteAddress) || + (req.socket && req.socket.remoteAddress) || + (req._socket && req._socket.remoteAddress); + } + + generateRequestHash(req, token) { + const remoteAddress = this.getRemoteAddress(req); + const cnonce = req.headers['x-embark-cnonce']; + + // We fallback to an empty string so that the hashing won't fail. + token = token || this.emittedTokens[remoteAddress] || ''; + let url = req.url; - let queryParamIndex = url.indexOf('?'); + const queryParamIndex = url.indexOf('?'); url = url.substring(0, queryParamIndex !== -1 ? queryParamIndex : url.length); + let hash = new keccak(); hash.update(cnonce); - hash.update(this.authToken); - hash.update(req.method); + hash.update(token); + hash.update(req.method.toUpperCase()); hash.update(url); return hash.digest('hex'); } @@ -36,14 +50,23 @@ class Authenticator { 'post', '/embark-api/authenticate', (req, res) => { - let hash = self.generateRequestHash(req); + let hash = self.generateRequestHash(req, this.authToken); if(hash !== req.headers['x-embark-request-hash']) { 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); } - res.send({}); + + // Generate another authentication token. + this.authToken = uuid(); + this.events.request('authenticator:displayUrl', false); + + // Register token for this connection, and send it through. + const emittedToken = uuid(); + const remoteAddress = this.getRemoteAddress(req); + this.emittedTokens[remoteAddress] = emittedToken; + res.send({token: emittedToken}); } ); @@ -62,20 +85,38 @@ class Authenticator { let self = this; this.events.once('outputDone', () => { + this.events.request('authenticator:displayUrl', true); + }); + + this.events.setCommandHandler('authenticator:displayUrl', (firstOutput) => { const {protocol, port, host, enabled} = this.embark.config.webServerConfig; if (enabled) { + if(!firstOutput) this.logger.info(__('Previous token has now been used.')); this.logger.info(__('Access the web backend with the following url: %s', (`${protocol}://${host}:${port}/embark?token=${this.authToken}`.underline))); } }); this.events.setCommandHandler('authenticator:authorize', (req, res, cb) => { + // HACK + if(res.send && req.url === '/embark-api/authenticate') return cb(); + let authenticated = false; + if(!res.send) { - authenticated = (this.authToken === req.protocol); + const [cnonce, hash] = req.protocol.split('|'); + const computedHash = this.generateRequestHash({ + headers: { + 'x-forwarded-for': this.getRemoteAddress(req), + 'x-embark-cnonce': cnonce + }, + url: '/embark-api/', + method: 'ws' + }); + authenticated = (hash === computedHash); } else { - let hash = self.generateRequestHash(req); + const hash = self.generateRequestHash(req); authenticated = (hash === req.headers['x-embark-request-hash']); }