Merge branch 'dev' into feat/lam-1291/stress-testing
* dev: (85 commits) chore: console.log debug leftovers fix: third level navigation links fix: show subheader on refresh fix: machines/:id routing fix: customer route chore: update wallet nodes feat: shorten long addresses in funding page feat: shorten long addresses refactor: support copied text different from presented text chore: udpate react, downshift and routing refactor: use Wizard component on first route fix: autocomplete component rendering feat: skip2fa option on .env fix: drop contraint before dropping index chore: stop using alias imports fix: re-instate urlResolver chore: server code formatting chore: reformat code chore: adding eslint and prettier config chore: typo ...
This commit is contained in:
commit
e10493abc6
1398 changed files with 60329 additions and 157527 deletions
15
packages/server/lib/middlewares/addRWBytes.js
Normal file
15
packages/server/lib/middlewares/addRWBytes.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const addRWBytes = () => (req, res, next) => {
|
||||
const handle = () => {
|
||||
res.removeListener('finish', handle)
|
||||
res.removeListener('close', handle)
|
||||
res.bytesRead = req.connection.bytesRead
|
||||
res.bytesWritten = req.connection.bytesWritten
|
||||
}
|
||||
|
||||
res.on('finish', handle)
|
||||
res.on('close', handle)
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports = addRWBytes
|
||||
22
packages/server/lib/middlewares/authorize.js
Normal file
22
packages/server/lib/middlewares/authorize.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
const pairing = require('../pairing')
|
||||
const logger = require('../logger')
|
||||
|
||||
const authorize = function (req, res, next) {
|
||||
return pairing
|
||||
.isPaired(req.deviceId)
|
||||
.then(deviceName => {
|
||||
if (deviceName) {
|
||||
req.deviceName = deviceName
|
||||
return next()
|
||||
}
|
||||
|
||||
logger.error(`Device ${req.deviceId} not found`)
|
||||
return res.status(403).json({ error: 'Forbidden' })
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(error)
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = authorize
|
||||
16
packages/server/lib/middlewares/ca.js
Normal file
16
packages/server/lib/middlewares/ca.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const pairing = require('../pairing')
|
||||
const logger = require('../logger')
|
||||
|
||||
function ca(req, res) {
|
||||
const token = req.query.token
|
||||
|
||||
return pairing
|
||||
.authorizeCaDownload(token)
|
||||
.then(ca => res.json({ ca }))
|
||||
.catch(error => {
|
||||
logger.error(error.message)
|
||||
return res.status(403).json({ error: 'forbidden' })
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = ca
|
||||
13
packages/server/lib/middlewares/errorHandler.js
Normal file
13
packages/server/lib/middlewares/errorHandler.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const logger = require('../logger')
|
||||
|
||||
function errorHandler(err, req, res) {
|
||||
const statusCode = err.name === 'HTTPError' ? err.code || 500 : 500
|
||||
|
||||
const json = { error: err.message }
|
||||
|
||||
if (statusCode >= 400) logger.error(err)
|
||||
|
||||
return res.status(statusCode).json(json)
|
||||
}
|
||||
|
||||
module.exports = errorHandler
|
||||
31
packages/server/lib/middlewares/filterOldRequests.js
Normal file
31
packages/server/lib/middlewares/filterOldRequests.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
const state = require('./state')
|
||||
const logger = require('../logger')
|
||||
|
||||
const CLOCK_SKEW = 60 * 1000
|
||||
const REQUEST_TTL = 3 * 60 * 1000
|
||||
const THROTTLE_CLOCK_SKEW = 60 * 1000
|
||||
|
||||
function filterOldRequests(req, res, next) {
|
||||
const deviceTime = req.deviceTime
|
||||
const deviceId = req.deviceId
|
||||
const timestamp = Date.now()
|
||||
const delta = timestamp - Date.parse(deviceTime)
|
||||
|
||||
const shouldTrigger =
|
||||
!state.canLogClockSkewMap[deviceId] ||
|
||||
timestamp - state.canLogClockSkewMap[deviceId] >= THROTTLE_CLOCK_SKEW
|
||||
|
||||
if (delta > CLOCK_SKEW && shouldTrigger) {
|
||||
state.canLogClockSkewMap[deviceId] = timestamp
|
||||
logger.error(
|
||||
'Clock skew with lamassu-machine[%s] too high [%ss], adjust lamassu-machine clock',
|
||||
req.deviceName,
|
||||
(delta / 1000).toFixed(2),
|
||||
)
|
||||
}
|
||||
|
||||
if (delta > REQUEST_TTL) return res.status(408).json({ error: 'stale' })
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports = filterOldRequests
|
||||
15
packages/server/lib/middlewares/operatorId.js
Normal file
15
packages/server/lib/middlewares/operatorId.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const { getOperatorId } = require('../operator')
|
||||
|
||||
function findOperatorId(req, res, next) {
|
||||
return getOperatorId('middleware')
|
||||
.then(operatorId => {
|
||||
res.locals.operatorId = operatorId
|
||||
return next()
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('Error while computing operator id\n' + e)
|
||||
next(e)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = findOperatorId
|
||||
29
packages/server/lib/middlewares/populateDeviceId.js
Normal file
29
packages/server/lib/middlewares/populateDeviceId.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
const crypto = require('crypto')
|
||||
|
||||
const IS_STRESS_TESTING = process.env.LAMASSU_STRESS_TESTING === 'YES'
|
||||
|
||||
function sha256(buf) {
|
||||
if (!buf) return null
|
||||
const hash = crypto.createHash('sha256')
|
||||
|
||||
hash.update(buf)
|
||||
return hash.digest('hex').toString('hex')
|
||||
}
|
||||
|
||||
const populateDeviceId = function (req, res, next) {
|
||||
const peerCert = req.socket.getPeerCertificate
|
||||
? req.socket.getPeerCertificate()
|
||||
: null
|
||||
let deviceId = peerCert?.raw ? sha256(peerCert.raw) : null
|
||||
|
||||
if (!deviceId && IS_STRESS_TESTING) deviceId = req.headers.device_id
|
||||
|
||||
if (!deviceId)
|
||||
return res.status(500).json({ error: 'Unable to find certificate' })
|
||||
req.deviceId = deviceId
|
||||
req.deviceTime = req.get('date')
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports = populateDeviceId
|
||||
147
packages/server/lib/middlewares/populateSettings.js
Normal file
147
packages/server/lib/middlewares/populateSettings.js
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
const db = require('../db')
|
||||
const state = require('./state')
|
||||
const newSettingsLoader = require('../new-settings-loader')
|
||||
const logger = require('../logger')
|
||||
|
||||
db.connect({ direct: true })
|
||||
.then(sco => {
|
||||
sco.client.on('notification', data => {
|
||||
const parsedData = JSON.parse(data.payload)
|
||||
return reload(parsedData.operatorId)
|
||||
})
|
||||
return sco.none('LISTEN $1:name', 'reload')
|
||||
})
|
||||
.catch(console.error)
|
||||
|
||||
db.connect({ direct: true })
|
||||
.then(sco => {
|
||||
sco.client.on('notification', data => {
|
||||
const parsedData = JSON.parse(data.payload)
|
||||
return machineAction(parsedData.action, parsedData.value)
|
||||
})
|
||||
return sco.none('LISTEN $1:name', 'machineAction')
|
||||
})
|
||||
.catch(console.error)
|
||||
|
||||
function machineAction(type, value) {
|
||||
const deviceId = value.deviceId
|
||||
const operatorId = value.operatorId
|
||||
const pid = state.pids?.[operatorId]?.[deviceId]?.pid
|
||||
|
||||
switch (type) {
|
||||
case 'reboot':
|
||||
logger.debug(
|
||||
`Rebooting machine '${deviceId}' from operator ${operatorId}`,
|
||||
)
|
||||
state.reboots[operatorId] = { [deviceId]: pid }
|
||||
break
|
||||
case 'shutdown':
|
||||
logger.debug(
|
||||
`Shutting down machine '${deviceId}' from operator ${operatorId}`,
|
||||
)
|
||||
state.shutdowns[operatorId] = { [deviceId]: pid }
|
||||
break
|
||||
case 'restartServices':
|
||||
logger.debug(
|
||||
`Restarting services of machine '${deviceId}' from operator ${operatorId}`,
|
||||
)
|
||||
state.restartServicesMap[operatorId] = { [deviceId]: pid }
|
||||
break
|
||||
case 'emptyUnit':
|
||||
logger.debug(
|
||||
`Emptying units from machine '${deviceId}' from operator ${operatorId}`,
|
||||
)
|
||||
state.emptyUnit[operatorId] = { [deviceId]: pid }
|
||||
break
|
||||
case 'refillUnit':
|
||||
logger.debug(
|
||||
`Refilling recyclers from machine '${deviceId}' from operator ${operatorId}`,
|
||||
)
|
||||
state.refillUnit[operatorId] = { [deviceId]: pid }
|
||||
break
|
||||
case 'diagnostics':
|
||||
logger.debug(
|
||||
`Running diagnostics on machine '${deviceId}' from operator ${operatorId}`,
|
||||
)
|
||||
state.diagnostics[operatorId] = { [deviceId]: pid }
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function reload(operatorId) {
|
||||
state.needsSettingsReload[operatorId] = true
|
||||
}
|
||||
|
||||
const populateSettings = function (req, res, next) {
|
||||
const { needsSettingsReload, settingsCache } = state
|
||||
const operatorId = res.locals.operatorId
|
||||
const versionId = req.headers['config-version']
|
||||
if (versionId !== state.oldVersionId) {
|
||||
state.oldVersionId = versionId
|
||||
}
|
||||
|
||||
try {
|
||||
// Priority of configs to retrieve
|
||||
// 1. Machine is in the middle of a transaction and has the config-version header set, fetch that config from cache or database, depending on whether it exists in cache
|
||||
// 2. The operator settings changed, so we must update the cache
|
||||
// 3. There's a cached config, send the cached value
|
||||
// 4. There's no cached config, cache and send the latest config
|
||||
|
||||
if (versionId) {
|
||||
const cachedVersionedSettings = settingsCache.get(
|
||||
`${operatorId}-v${versionId}`,
|
||||
)
|
||||
|
||||
if (!cachedVersionedSettings) {
|
||||
logger.debug('Fetching a specific config version cached value')
|
||||
return newSettingsLoader
|
||||
.load(versionId)
|
||||
.then(settings => {
|
||||
settingsCache.set(`${operatorId}-v${versionId}`, settings)
|
||||
req.settings = settings
|
||||
})
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
logger.debug('Fetching a cached specific config version')
|
||||
req.settings = cachedVersionedSettings
|
||||
return next()
|
||||
}
|
||||
|
||||
const operatorSettings = settingsCache.get(`${operatorId}-latest`)
|
||||
|
||||
if (!!needsSettingsReload[operatorId] || !operatorSettings) {
|
||||
needsSettingsReload[operatorId]
|
||||
? logger.debug(
|
||||
'Fetching and caching a new latest config value, as a reload was requested',
|
||||
)
|
||||
: logger.debug(
|
||||
"Fetching the latest config version because there's no cached value",
|
||||
)
|
||||
|
||||
return newSettingsLoader
|
||||
.loadLatest()
|
||||
.then(settings => {
|
||||
const versionId = settings.version
|
||||
settingsCache.set(`${operatorId}-latest`, settings)
|
||||
settingsCache.set(`${operatorId}-v${versionId}`, settings)
|
||||
if (needsSettingsReload[operatorId])
|
||||
delete needsSettingsReload[operatorId]
|
||||
req.settings = settings
|
||||
})
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
logger.debug('Fetching the latest config value from cache')
|
||||
req.settings = operatorSettings
|
||||
return next()
|
||||
} catch (e) {
|
||||
logger.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = populateSettings
|
||||
7
packages/server/lib/middlewares/recordPing.js
Normal file
7
packages/server/lib/middlewares/recordPing.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const plugins = require('../plugins')
|
||||
|
||||
module.exports = (req, res, next) =>
|
||||
plugins(req.settings, req.deviceId)
|
||||
.recordPing(req.deviceTime, req.query.version, req.query.model)
|
||||
.then(() => next())
|
||||
.catch(() => next())
|
||||
35
packages/server/lib/middlewares/rejectIncompatbleMachines.js
Normal file
35
packages/server/lib/middlewares/rejectIncompatbleMachines.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const semver = require('semver')
|
||||
const version = require('../../package.json').version
|
||||
const logger = require('../logger')
|
||||
|
||||
const rejectIncompatibleMachines = function (req, res, next) {
|
||||
const machineVersion = req.query.version
|
||||
const deviceId = req.deviceId
|
||||
|
||||
if (!machineVersion) return next()
|
||||
|
||||
const serverMajor = semver.major(version)
|
||||
const machineMajor = semver.major(machineVersion)
|
||||
|
||||
if (serverMajor - machineMajor > 1) {
|
||||
logger.error(
|
||||
`Machine version too old: ${machineVersion} deviceId: ${deviceId}`,
|
||||
)
|
||||
return res.status(400).json({
|
||||
error: 'Machine version too old',
|
||||
})
|
||||
}
|
||||
|
||||
if (serverMajor < machineMajor) {
|
||||
logger.error(
|
||||
`Machine version too new: ${machineVersion} deviceId: ${deviceId}`,
|
||||
)
|
||||
return res.status(400).json({
|
||||
error: 'Machine version too new',
|
||||
})
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports = rejectIncompatibleMachines
|
||||
23
packages/server/lib/middlewares/state.js
Normal file
23
packages/server/lib/middlewares/state.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
const NodeCache = require('node-cache')
|
||||
const SETTINGS_CACHE_REFRESH = 3600
|
||||
|
||||
module.exports = (function () {
|
||||
return {
|
||||
oldVersionId: 'unset',
|
||||
needsSettingsReload: {},
|
||||
settingsCache: new NodeCache({
|
||||
stdTTL: SETTINGS_CACHE_REFRESH,
|
||||
checkperiod: SETTINGS_CACHE_REFRESH, // Clear cache every hour
|
||||
}),
|
||||
canLogClockSkewMap: {},
|
||||
canGetLastSeenMap: {},
|
||||
pids: {},
|
||||
reboots: {},
|
||||
shutdowns: {},
|
||||
restartServicesMap: {},
|
||||
emptyUnit: {},
|
||||
refillUnit: {},
|
||||
diagnostics: {},
|
||||
mnemonic: null,
|
||||
}
|
||||
})()
|
||||
Loading…
Add table
Add a link
Reference in a new issue