mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-30 08:09:15 +01:00
99b8f2d544
* Omit MetaMask `extensionId` from site metadata The site metadata was updated in #7218 to include the extension id of the extension connecting to MetaMask. This was done to allow external extensions to connect with MetaMask, so that we could show the id on the provider approval screen. Unbeknownst to me at the time, the extension id was being set for all connections to MetaMask from dapps. The id was set to MetaMask's id, because the connections are made through MetaMask's contentscript. This has been updated to only set the id when accepting a connection from a different extension. * Fix `siteMetadata` property names In #7218 a few things were added to the site metadata, so the provider approval controller was middleware was updated to accept the site metadata as an object rather than accepting each property as a separate parameter. Unfortunately we failed to notice that the site name and icon were named differently in the site metadata than they were in the provider approval controller, so the names of those properties were unintentionally changed in the controller state. The provider approval controller has been updated to restore the original property names of `siteTitle` and `siteIcon`. An unused prop that was added to the provider approval page in #7218 has also been removed.
176 lines
6.1 KiB
JavaScript
176 lines
6.1 KiB
JavaScript
const ObservableStore = require('obs-store')
|
|
const SafeEventEmitter = require('safe-event-emitter')
|
|
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
|
|
const { errors: rpcErrors } = require('eth-json-rpc-errors')
|
|
|
|
/**
|
|
* A controller that services user-approved requests for a full Ethereum provider API
|
|
*/
|
|
class ProviderApprovalController extends SafeEventEmitter {
|
|
/**
|
|
* Creates a ProviderApprovalController
|
|
*
|
|
* @param {Object} [config] - Options to configure controller
|
|
*/
|
|
constructor ({ closePopup, initState, keyringController, openPopup, preferencesController } = {}) {
|
|
super()
|
|
this.closePopup = closePopup
|
|
this.keyringController = keyringController
|
|
this.openPopup = openPopup
|
|
this.preferencesController = preferencesController
|
|
this.memStore = new ObservableStore({
|
|
providerRequests: [],
|
|
})
|
|
|
|
const defaultState = { approvedOrigins: {} }
|
|
this.store = new ObservableStore(Object.assign(defaultState, initState))
|
|
}
|
|
|
|
/**
|
|
* Called when a user approves access to a full Ethereum provider API
|
|
*
|
|
* @param {object} opts - opts for the middleware contains the origin for the middleware
|
|
*/
|
|
createMiddleware ({ senderUrl, extensionId, getSiteMetadata }) {
|
|
return createAsyncMiddleware(async (req, res, next) => {
|
|
// only handle requestAccounts
|
|
if (req.method !== 'eth_requestAccounts') return next()
|
|
// if already approved or privacy mode disabled, return early
|
|
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
|
const origin = senderUrl.hostname
|
|
if (this.shouldExposeAccounts(origin) && isUnlocked) {
|
|
res.result = [this.preferencesController.getSelectedAddress()]
|
|
return
|
|
}
|
|
// register the provider request
|
|
const metadata = { hostname: senderUrl.hostname, origin }
|
|
if (extensionId) {
|
|
metadata.extensionId = extensionId
|
|
} else {
|
|
const siteMetadata = await getSiteMetadata(origin)
|
|
Object.assign(metadata, { siteTitle: siteMetadata.name, siteImage: siteMetadata.icon})
|
|
}
|
|
this._handleProviderRequest(metadata)
|
|
// wait for resolution of request
|
|
const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved)))
|
|
if (approved) {
|
|
res.result = [this.preferencesController.getSelectedAddress()]
|
|
} else {
|
|
throw rpcErrors.eth.userRejectedRequest('User denied account authorization')
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @typedef {Object} SiteMetadata
|
|
* @param {string} hostname - The hostname of the site
|
|
* @param {string} origin - The origin of the site
|
|
* @param {string} [siteTitle] - The title of the site
|
|
* @param {string} [siteImage] - The icon for the site
|
|
* @param {string} [extensionId] - The extension ID of the extension
|
|
*/
|
|
/**
|
|
* Called when a tab requests access to a full Ethereum provider API
|
|
*
|
|
* @param {SiteMetadata} siteMetadata - The metadata for the site requesting full provider access
|
|
*/
|
|
_handleProviderRequest (siteMetadata) {
|
|
const { providerRequests } = this.memStore.getState()
|
|
const origin = siteMetadata.origin
|
|
this.memStore.updateState({
|
|
providerRequests: [
|
|
...providerRequests,
|
|
siteMetadata,
|
|
],
|
|
})
|
|
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
|
const { approvedOrigins } = this.store.getState()
|
|
const originAlreadyHandled = approvedOrigins[origin]
|
|
if (originAlreadyHandled && isUnlocked) {
|
|
return
|
|
}
|
|
this.openPopup && this.openPopup()
|
|
}
|
|
|
|
/**
|
|
* Called when a user approves access to a full Ethereum provider API
|
|
*
|
|
* @param {string} origin - origin of the domain that had provider access approved
|
|
*/
|
|
approveProviderRequestByOrigin (origin) {
|
|
if (this.closePopup) {
|
|
this.closePopup()
|
|
}
|
|
|
|
const { approvedOrigins } = this.store.getState()
|
|
const { providerRequests } = this.memStore.getState()
|
|
const providerRequest = providerRequests.find((request) => request.origin === origin)
|
|
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
|
|
this.store.updateState({
|
|
approvedOrigins: {
|
|
...approvedOrigins,
|
|
[origin]: {
|
|
siteTitle: providerRequest ? providerRequest.siteTitle : null,
|
|
siteImage: providerRequest ? providerRequest.siteImage : null,
|
|
hostname: providerRequest ? providerRequest.hostname : null,
|
|
},
|
|
},
|
|
})
|
|
this.memStore.updateState({ providerRequests: remainingProviderRequests })
|
|
this.emit(`resolvedRequest:${origin}`, { approved: true })
|
|
}
|
|
|
|
/**
|
|
* Called when a tab rejects access to a full Ethereum provider API
|
|
*
|
|
* @param {string} origin - origin of the domain that had provider access approved
|
|
*/
|
|
rejectProviderRequestByOrigin (origin) {
|
|
if (this.closePopup) {
|
|
this.closePopup()
|
|
}
|
|
|
|
const { approvedOrigins } = this.store.getState()
|
|
const { providerRequests } = this.memStore.getState()
|
|
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
|
|
|
|
// We're cloning and deleting keys here because we don't want to keep unneeded keys
|
|
const _approvedOrigins = Object.assign({}, approvedOrigins)
|
|
delete _approvedOrigins[origin]
|
|
|
|
this.store.putState({ approvedOrigins: _approvedOrigins })
|
|
this.memStore.putState({ providerRequests: remainingProviderRequests })
|
|
this.emit(`resolvedRequest:${origin}`, { approved: false })
|
|
}
|
|
|
|
/**
|
|
* Clears any approvals for user-approved origins
|
|
*/
|
|
clearApprovedOrigins () {
|
|
this.store.updateState({
|
|
approvedOrigins: {},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Determines if a given origin should have accounts exposed
|
|
*
|
|
* @param {string} origin - Domain origin to check for approval status
|
|
* @returns {boolean} - True if the origin has been approved
|
|
*/
|
|
shouldExposeAccounts (origin) {
|
|
return Boolean(this.store.getState().approvedOrigins[origin])
|
|
}
|
|
|
|
/**
|
|
* Returns a merged state representation
|
|
* @return {object}
|
|
* @private
|
|
*/
|
|
_getMergedState () {
|
|
return Object.assign({}, this.memStore.getState(), this.store.getState())
|
|
}
|
|
}
|
|
|
|
module.exports = ProviderApprovalController
|