Secure API access via single-use tokens (#5892)
* Single use token for dapp permissions * Add accountsInfo & allAccountsInfo * Covert token -> dappName in requests
This commit is contained in:
parent
97c67bc259
commit
a6d3d4ea4c
2
js/package-lock.json
generated
2
js/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "1.7.91",
|
"version": "1.7.92",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@parity/abi": {
|
"@parity/abi": {
|
||||||
|
@ -76,10 +76,10 @@ export default class DappsMethods extends Component {
|
|||||||
key={ `${dappIndex}_${requestIndex}` }
|
key={ `${dappIndex}_${requestIndex}` }
|
||||||
>
|
>
|
||||||
<MethodCheck
|
<MethodCheck
|
||||||
checked={ methodsStore.permissions[`${method}:${id}`] || false }
|
checked={ methodsStore.hasAppPermission(method, id) }
|
||||||
dappId={ id }
|
dappId={ id }
|
||||||
method={ method }
|
method={ method }
|
||||||
onToggle={ methodsStore.toggleMethodPermission }
|
onToggle={ methodsStore.toggleAppPermission }
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
))
|
))
|
||||||
|
@ -16,12 +16,15 @@
|
|||||||
|
|
||||||
import { action, observable } from 'mobx';
|
import { action, observable } from 'mobx';
|
||||||
|
|
||||||
|
import { sha3 } from '@parity/api/util/sha3';
|
||||||
|
|
||||||
import filteredRequests from '../DappRequests/filteredRequests';
|
import filteredRequests from '../DappRequests/filteredRequests';
|
||||||
|
|
||||||
export default class Store {
|
export default class Store {
|
||||||
@observable filteredRequests = Object.keys(filteredRequests);
|
@observable filteredRequests = Object.keys(filteredRequests);
|
||||||
@observable modalOpen = false;
|
@observable modalOpen = false;
|
||||||
@observable permissions = {};
|
@observable permissions = {};
|
||||||
|
@observable tokens = {};
|
||||||
|
|
||||||
@action closeModal = () => {
|
@action closeModal = () => {
|
||||||
this.modalOpen = false;
|
this.modalOpen = false;
|
||||||
@ -31,22 +34,40 @@ export default class Store {
|
|||||||
this.modalOpen = true;
|
this.modalOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action addMethodPermission = (method, token) => {
|
@action createToken = (appId) => {
|
||||||
const id = `${method}:${token}`;
|
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, {
|
this.permissions = Object.assign({}, this.permissions, {
|
||||||
[id]: true
|
[id]: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action toggleMethodPermission = (method, token) => {
|
@action toggleAppPermission = (method, appId) => {
|
||||||
const id = `${method}:${token}`;
|
const id = `${method}:${appId}`;
|
||||||
|
|
||||||
this.permissions = Object.assign({}, this.permissions, {
|
this.permissions = Object.assign({}, this.permissions, {
|
||||||
[id]: !this.permissions[id]
|
[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 instance = null;
|
||||||
|
|
||||||
static get () {
|
static get () {
|
||||||
|
@ -19,18 +19,22 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import { Button } from '@parity/ui';
|
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 _onApprove = () => approveRequest(queueId, false);
|
||||||
const _onApproveAll = () => approveRequest(queueId, true);
|
const _onApproveAll = () => approveRequest(queueId, true);
|
||||||
const _onReject = () => denyRequest(queueId);
|
const _onReject = () => denyRequest(queueId);
|
||||||
|
|
||||||
|
const app = DappsStore.get().getAppById(appId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ className }>
|
<div className={ className }>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='dappRequests.request.info'
|
id='dappRequests.request.info'
|
||||||
defaultMessage='Received request for {method} from {from}'
|
defaultMessage='Received request for {method} from {appName}'
|
||||||
values={ {
|
values={ {
|
||||||
from,
|
appName: app.name,
|
||||||
method
|
method
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
@ -66,6 +70,7 @@ export default function Request ({ className, approveRequest, denyRequest, queue
|
|||||||
}
|
}
|
||||||
|
|
||||||
Request.propTypes = {
|
Request.propTypes = {
|
||||||
|
appId: PropTypes.string.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
approveRequest: PropTypes.func.isRequired,
|
approveRequest: PropTypes.func.isRequired,
|
||||||
denyRequest: PropTypes.func.isRequired,
|
denyRequest: PropTypes.func.isRequired,
|
||||||
|
@ -31,8 +31,9 @@ function DappRequests () {
|
|||||||
return (
|
return (
|
||||||
<div className={ styles.requests }>
|
<div className={ styles.requests }>
|
||||||
{
|
{
|
||||||
store.squashedRequests.map(({ queueId, request: { data } }) => (
|
store.squashedRequests.map(({ appId, queueId, request: { data } }) => (
|
||||||
<Request
|
<Request
|
||||||
|
appId={ appId }
|
||||||
className={ styles.request }
|
className={ styles.request }
|
||||||
approveRequest={ store.approveRequest }
|
approveRequest={ store.approveRequest }
|
||||||
denyRequest={ store.rejectRequest }
|
denyRequest={ store.rejectRequest }
|
||||||
|
@ -15,5 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
'parity_accountsInfo': {},
|
||||||
|
'parity_allAccountsInfo': {},
|
||||||
'parity_hashContent': {}
|
'parity_hashContent': {}
|
||||||
};
|
};
|
||||||
|
@ -55,9 +55,10 @@ export default class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action queueRequest = (request) => {
|
@action queueRequest = (request) => {
|
||||||
|
const appId = this.methodsStore.tokens[request.data.from];
|
||||||
let queueId = ++nextQueueId;
|
let queueId = ++nextQueueId;
|
||||||
|
|
||||||
this.requests = this.requests.concat([{ queueId, request }]);
|
this.requests = this.requests.concat([{ appId, queueId, request }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action approveSingleRequest = ({ queueId, request: { data, source } }) => {
|
@action approveSingleRequest = ({ queueId, request: { data, source } }) => {
|
||||||
@ -72,8 +73,7 @@ export default class Store {
|
|||||||
const { request: { data: { method, token } } } = queued;
|
const { request: { data: { method, token } } } = queued;
|
||||||
const requests = this.findMatchingRequests(method, token);
|
const requests = this.findMatchingRequests(method, token);
|
||||||
|
|
||||||
// TODO: Use single-use token, map back to app name
|
this.methodsStore.addTokenPermission(method, token);
|
||||||
this.methodsStore.addMethodPermission(method, token);
|
|
||||||
requests.forEach(this.approveSingleRequest);
|
requests.forEach(this.approveSingleRequest);
|
||||||
} else {
|
} else {
|
||||||
this.approveSingleRequest(queued);
|
this.approveSingleRequest(queued);
|
||||||
@ -122,10 +122,7 @@ export default class Store {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterId = `${method}:${token}`;
|
if (filteredRequests[method] && !this.methodsStore.hasTokenPermission(method, token)) {
|
||||||
|
|
||||||
// TODO: Use single-use token, map back to app name
|
|
||||||
if (filteredRequests[method] && !this.methodsStore.permissions[filterId]) {
|
|
||||||
this.queueRequest({ data, origin, source });
|
this.queueRequest({ data, origin, source });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -291,6 +291,10 @@ export default class DappsStore extends EventEmitter {
|
|||||||
this.setDisplayApps(visibility);
|
this.setDisplayApps(visibility);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAppById = (id) => {
|
||||||
|
return this.apps.find((app) => app.id === id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -42,6 +42,7 @@ import SecureApi from '~/secureApi';
|
|||||||
import Application from './Application';
|
import Application from './Application';
|
||||||
import Dapp from './Dapp';
|
import Dapp from './Dapp';
|
||||||
import { setupProviderFilters } from './DappRequests';
|
import { setupProviderFilters } from './DappRequests';
|
||||||
|
import DappMethodsStore from './DappMethods/store';
|
||||||
import Dapps from './Dapps';
|
import Dapps from './Dapps';
|
||||||
|
|
||||||
injectTapEventPlugin();
|
injectTapEventPlugin();
|
||||||
@ -76,7 +77,9 @@ const dapps = [].concat(viewsDapps, builtinDapps);
|
|||||||
const dappsHistory = HistoryStore.get('dapps');
|
const dappsHistory = HistoryStore.get('dapps');
|
||||||
|
|
||||||
function onEnterDapp ({ params: { id } }) {
|
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) {
|
if (!dapps[id] || !dapps[id].skipHistory) {
|
||||||
dappsHistory.add(id);
|
dappsHistory.add(id);
|
||||||
|
@ -25,8 +25,8 @@ export const AttachFileIcon = (props) => <Icon name='attach' { ...props } />;
|
|||||||
export const BackgroundIcon = (props) => <Icon name='image' { ...props } />;
|
export const BackgroundIcon = (props) => <Icon name='image' { ...props } />;
|
||||||
export const CancelIcon = (props) => <Icon name='cancel' { ...props } />;
|
export const CancelIcon = (props) => <Icon name='cancel' { ...props } />;
|
||||||
export const CheckIcon = (props) => <Icon name='check' { ...props } />;
|
export const CheckIcon = (props) => <Icon name='check' { ...props } />;
|
||||||
export const CheckboxTickedIcon = (props) => <Icon name='check circle outline' { ...props } />;
|
export const CheckboxTickedIcon = (props) => <Icon name='checkmark box' { ...props } />;
|
||||||
export const CheckboxUntickedIcon = (props) => <Icon name='radio' { ...props } />;
|
export const CheckboxUntickedIcon = (props) => <Icon name='square outline' { ...props } />;
|
||||||
export const CloseIcon = (props) => <Icon name='close' { ...props } />;
|
export const CloseIcon = (props) => <Icon name='close' { ...props } />;
|
||||||
export const CompareIcon = (props) => <Icon name='exchange' { ...props } />;
|
export const CompareIcon = (props) => <Icon name='exchange' { ...props } />;
|
||||||
export const ComputerIcon = (props) => <Icon name='desktop' { ...props } />;
|
export const ComputerIcon = (props) => <Icon name='desktop' { ...props } />;
|
||||||
|
Loading…
Reference in New Issue
Block a user