From a6d3d4ea4c76cba9d2d9280464f2cb2153bda8ba Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 21 Jun 2017 15:15:23 +0200 Subject: [PATCH] Secure API access via single-use tokens (#5892) * Single use token for dapp permissions * Add accountsInfo & allAccountsInfo * Covert token -> dappName in requests --- js/package-lock.json | 2 +- js/src/shell/DappMethods/dappMethods.js | 4 +-- js/src/shell/DappMethods/store.js | 29 ++++++++++++++++--- js/src/shell/DappRequests/Request/request.js | 11 +++++-- js/src/shell/DappRequests/dappRequests.js | 3 +- js/src/shell/DappRequests/filteredRequests.js | 2 ++ js/src/shell/DappRequests/store.js | 11 +++---- js/src/shell/Dapps/dappsStore.js | 4 +++ js/src/shell/index.js | 5 +++- js/src/ui/Icons/index.js | 4 +-- 10 files changed, 54 insertions(+), 21 deletions(-) diff --git a/js/package-lock.json b/js/package-lock.json index 0ed02d4f3..8c64e2b91 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "1.7.91", + "version": "1.7.92", "lockfileVersion": 1, "dependencies": { "@parity/abi": { diff --git a/js/src/shell/DappMethods/dappMethods.js b/js/src/shell/DappMethods/dappMethods.js index af65a0bd0..178dc850d 100644 --- a/js/src/shell/DappMethods/dappMethods.js +++ b/js/src/shell/DappMethods/dappMethods.js @@ -76,10 +76,10 @@ export default class DappsMethods extends Component { key={ `${dappIndex}_${requestIndex}` } > )) diff --git a/js/src/shell/DappMethods/store.js b/js/src/shell/DappMethods/store.js index 43ae2167c..20347955b 100644 --- a/js/src/shell/DappMethods/store.js +++ b/js/src/shell/DappMethods/store.js @@ -16,12 +16,15 @@ import { action, observable } from 'mobx'; +import { sha3 } from '@parity/api/util/sha3'; + import filteredRequests from '../DappRequests/filteredRequests'; export default class Store { @observable filteredRequests = Object.keys(filteredRequests); @observable modalOpen = false; @observable permissions = {}; + @observable tokens = {}; @action closeModal = () => { this.modalOpen = false; @@ -31,22 +34,40 @@ export default class Store { this.modalOpen = true; } - @action addMethodPermission = (method, token) => { - const id = `${method}:${token}`; + @action createToken = (appId) => { + const token = sha3(`${appId}:${Date.now()}`); + + this.tokens = Object.assign({}, this.tokens, { + [token]: appId + }); + + return token; + } + + @action addTokenPermission = (method, token) => { + const id = `${method}:${this.tokens[token]}`; this.permissions = Object.assign({}, this.permissions, { [id]: true }); } - @action toggleMethodPermission = (method, token) => { - const id = `${method}:${token}`; + @action toggleAppPermission = (method, appId) => { + const id = `${method}:${appId}`; this.permissions = Object.assign({}, this.permissions, { [id]: !this.permissions[id] }); } + hasTokenPermission = (method, token) => { + return this.hasAppPermission(method, this.tokens[token]); + } + + hasAppPermission = (method, appId) => { + return this.permissions[`${method}:${appId}`] || false; + } + static instance = null; static get () { diff --git a/js/src/shell/DappRequests/Request/request.js b/js/src/shell/DappRequests/Request/request.js index 59180fa50..3b95bfc5a 100644 --- a/js/src/shell/DappRequests/Request/request.js +++ b/js/src/shell/DappRequests/Request/request.js @@ -19,18 +19,22 @@ import { FormattedMessage } from 'react-intl'; import { Button } from '@parity/ui'; -export default function Request ({ className, approveRequest, denyRequest, queueId, request: { from, method } }) { +import DappsStore from '../../Dapps/dappsStore'; + +export default function Request ({ appId, className, approveRequest, denyRequest, queueId, request: { from, method } }) { const _onApprove = () => approveRequest(queueId, false); const _onApproveAll = () => approveRequest(queueId, true); const _onReject = () => denyRequest(queueId); + const app = DappsStore.get().getAppById(appId); + return (
@@ -66,6 +70,7 @@ export default function Request ({ className, approveRequest, denyRequest, queue } Request.propTypes = { + appId: PropTypes.string.isRequired, className: PropTypes.string, approveRequest: PropTypes.func.isRequired, denyRequest: PropTypes.func.isRequired, diff --git a/js/src/shell/DappRequests/dappRequests.js b/js/src/shell/DappRequests/dappRequests.js index 933875e17..b6acb774e 100644 --- a/js/src/shell/DappRequests/dappRequests.js +++ b/js/src/shell/DappRequests/dappRequests.js @@ -31,8 +31,9 @@ function DappRequests () { return (
{ - store.squashedRequests.map(({ queueId, request: { data } }) => ( + store.squashedRequests.map(({ appId, queueId, request: { data } }) => ( . export default { + 'parity_accountsInfo': {}, + 'parity_allAccountsInfo': {}, 'parity_hashContent': {} }; diff --git a/js/src/shell/DappRequests/store.js b/js/src/shell/DappRequests/store.js index 6806e8bbd..689fcd1f6 100644 --- a/js/src/shell/DappRequests/store.js +++ b/js/src/shell/DappRequests/store.js @@ -55,9 +55,10 @@ export default class Store { } @action queueRequest = (request) => { + const appId = this.methodsStore.tokens[request.data.from]; let queueId = ++nextQueueId; - this.requests = this.requests.concat([{ queueId, request }]); + this.requests = this.requests.concat([{ appId, queueId, request }]); } @action approveSingleRequest = ({ queueId, request: { data, source } }) => { @@ -72,8 +73,7 @@ export default class Store { const { request: { data: { method, token } } } = queued; const requests = this.findMatchingRequests(method, token); - // TODO: Use single-use token, map back to app name - this.methodsStore.addMethodPermission(method, token); + this.methodsStore.addTokenPermission(method, token); requests.forEach(this.approveSingleRequest); } else { this.approveSingleRequest(queued); @@ -122,10 +122,7 @@ export default class Store { return; } - const filterId = `${method}:${token}`; - - // TODO: Use single-use token, map back to app name - if (filteredRequests[method] && !this.methodsStore.permissions[filterId]) { + if (filteredRequests[method] && !this.methodsStore.hasTokenPermission(method, token)) { this.queueRequest({ data, origin, source }); return; } diff --git a/js/src/shell/Dapps/dappsStore.js b/js/src/shell/Dapps/dappsStore.js index 74d0a86c8..0a672efde 100644 --- a/js/src/shell/Dapps/dappsStore.js +++ b/js/src/shell/Dapps/dappsStore.js @@ -291,6 +291,10 @@ export default class DappsStore extends EventEmitter { this.setDisplayApps(visibility); }); } + + getAppById = (id) => { + return this.apps.find((app) => app.id === id); + } } export { diff --git a/js/src/shell/index.js b/js/src/shell/index.js index 23626c0d8..f8f1b51f6 100644 --- a/js/src/shell/index.js +++ b/js/src/shell/index.js @@ -42,6 +42,7 @@ import SecureApi from '~/secureApi'; import Application from './Application'; import Dapp from './Dapp'; import { setupProviderFilters } from './DappRequests'; +import DappMethodsStore from './DappMethods/store'; import Dapps from './Dapps'; injectTapEventPlugin(); @@ -76,7 +77,9 @@ const dapps = [].concat(viewsDapps, builtinDapps); const dappsHistory = HistoryStore.get('dapps'); function onEnterDapp ({ params: { id } }) { - window.web3Provider = new Api.Provider.PostMessage(id, window); + const token = DappMethodsStore.get().createToken(id); + + window.web3Provider = new Api.Provider.PostMessage(token, window); if (!dapps[id] || !dapps[id].skipHistory) { dappsHistory.add(id); diff --git a/js/src/ui/Icons/index.js b/js/src/ui/Icons/index.js index ee03a5890..e3a59ca7e 100644 --- a/js/src/ui/Icons/index.js +++ b/js/src/ui/Icons/index.js @@ -25,8 +25,8 @@ export const AttachFileIcon = (props) => ; export const BackgroundIcon = (props) => ; export const CancelIcon = (props) => ; export const CheckIcon = (props) => ; -export const CheckboxTickedIcon = (props) => ; -export const CheckboxUntickedIcon = (props) => ; +export const CheckboxTickedIcon = (props) => ; +export const CheckboxUntickedIcon = (props) => ; export const CloseIcon = (props) => ; export const CompareIcon = (props) => ; export const ComputerIcon = (props) => ;