You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
4.8 KiB
169 lines
4.8 KiB
|
|
const clone = require('clone')
|
|
const { isValidAddress } = require('ethereumjs-util')
|
|
|
|
const LOG_LIMIT = 100
|
|
|
|
/**
|
|
* Create middleware for logging requests and responses to restricted and
|
|
* permissions-related methods.
|
|
*/
|
|
module.exports = function createLoggerMiddleware ({
|
|
walletPrefix, restrictedMethods, store, logStoreKey, historyStoreKey, ignoreMethods,
|
|
}) {
|
|
return (req, res, next, _end) => {
|
|
let activityEntry, requestedMethods
|
|
const { origin, method } = req
|
|
const isInternal = method.startsWith(walletPrefix)
|
|
if ((isInternal || restrictedMethods.includes(method)) && !ignoreMethods.includes(method)) {
|
|
activityEntry = logActivity(req, isInternal)
|
|
if (method === `${walletPrefix}requestPermissions`) {
|
|
requestedMethods = getRequestedMethods(req)
|
|
}
|
|
} else if (method === 'eth_requestAccounts') {
|
|
activityEntry = logActivity(req, isInternal)
|
|
requestedMethods = [ 'eth_accounts' ]
|
|
} else {
|
|
return next()
|
|
}
|
|
|
|
next(cb => {
|
|
const time = Date.now()
|
|
addResponse(activityEntry, res, time)
|
|
if (!res.error && requestedMethods) {
|
|
logHistory(requestedMethods, origin, res.result, time, method === 'eth_requestAccounts')
|
|
}
|
|
cb()
|
|
})
|
|
}
|
|
|
|
function logActivity (request, isInternal) {
|
|
const activityEntry = {
|
|
id: request.id,
|
|
method: request.method,
|
|
methodType: isInternal ? 'internal' : 'restricted',
|
|
origin: request.origin,
|
|
request: cloneObj(request),
|
|
requestTime: Date.now(),
|
|
response: null,
|
|
responseTime: null,
|
|
success: null,
|
|
}
|
|
commitActivity(activityEntry)
|
|
return activityEntry
|
|
}
|
|
|
|
function addResponse (activityEntry, response, time) {
|
|
if (!response) {
|
|
return
|
|
}
|
|
activityEntry.response = cloneObj(response)
|
|
activityEntry.responseTime = time
|
|
activityEntry.success = !response.error
|
|
}
|
|
|
|
function commitActivity (entry) {
|
|
const logs = store.getState()[logStoreKey]
|
|
if (logs.length > LOG_LIMIT - 2) {
|
|
logs.pop()
|
|
}
|
|
logs.push(entry)
|
|
store.updateState({ [logStoreKey]: logs })
|
|
}
|
|
|
|
function getRequestedMethods (request) {
|
|
if (
|
|
!request.params ||
|
|
typeof request.params[0] !== 'object' ||
|
|
Array.isArray(request.params[0])
|
|
) {
|
|
return null
|
|
}
|
|
return Object.keys(request.params[0])
|
|
}
|
|
|
|
function logHistory (requestedMethods, origin, result, time, isEthRequestAccounts) {
|
|
let accounts, entries
|
|
if (isEthRequestAccounts) {
|
|
accounts = result
|
|
const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {})
|
|
entries = { 'eth_accounts': { accounts: accountToTimeMap, lastApproved: time } }
|
|
} else {
|
|
entries = result
|
|
? result
|
|
.map(perm => {
|
|
if (perm.parentCapability === 'eth_accounts') {
|
|
accounts = getAccountsFromPermission(perm)
|
|
}
|
|
return perm.parentCapability
|
|
})
|
|
.reduce((acc, m) => {
|
|
if (requestedMethods.includes(m)) {
|
|
if (m === 'eth_accounts') {
|
|
const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {})
|
|
acc[m] = { lastApproved: time, accounts: accountToTimeMap }
|
|
} else {
|
|
acc[m] = { lastApproved: time }
|
|
}
|
|
}
|
|
return acc
|
|
}, {})
|
|
: {}
|
|
}
|
|
|
|
if (Object.keys(entries).length > 0) {
|
|
commitHistory(origin, entries)
|
|
}
|
|
}
|
|
|
|
function commitHistory (origin, entries) {
|
|
const history = store.getState()[historyStoreKey] || {}
|
|
const newOriginHistory = {
|
|
...history[origin],
|
|
...entries,
|
|
}
|
|
|
|
if (history[origin] && history[origin]['eth_accounts'] && entries['eth_accounts']) {
|
|
newOriginHistory['eth_accounts'] = {
|
|
lastApproved: entries['eth_accounts'].lastApproved,
|
|
accounts: {
|
|
...history[origin]['eth_accounts'].accounts,
|
|
...entries['eth_accounts'].accounts,
|
|
},
|
|
}
|
|
}
|
|
|
|
history[origin] = newOriginHistory
|
|
|
|
store.updateState({ [historyStoreKey]: history })
|
|
}
|
|
}
|
|
|
|
// the call to clone is set to disallow circular references
|
|
// we attempt cloning at a depth of 3 and 2, then return a
|
|
// shallow copy of the object
|
|
function cloneObj (obj) {
|
|
for (let i = 3; i > 1; i--) {
|
|
try {
|
|
return clone(obj, false, i)
|
|
} catch (_) {}
|
|
}
|
|
return { ...obj }
|
|
}
|
|
|
|
function getAccountsFromPermission (perm) {
|
|
if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) {
|
|
return []
|
|
}
|
|
const accounts = {}
|
|
for (const c of perm.caveats) {
|
|
if (c.type === 'filterResponse' && Array.isArray(c.value)) {
|
|
for (const v of c.value) {
|
|
if (isValidAddress(v)) {
|
|
accounts[v] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Object.keys(accounts)
|
|
}
|
|
|