diff --git a/lib/app.js b/lib/app.js index 1e9e2d11..af228d4b 100644 --- a/lib/app.js +++ b/lib/app.js @@ -3,7 +3,7 @@ const http = require('http') const https = require('https') const argv = require('minimist')(process.argv.slice(2)) -const routes = require('./routes') +const routes = require('./new-routes') const logger = require('./logger') const poller = require('./poller') const settingsLoader = require('./new-settings-loader') diff --git a/lib/middlewares/authorize.js b/lib/middlewares/authorize.js new file mode 100644 index 00000000..27cf260e --- /dev/null +++ b/lib/middlewares/authorize.js @@ -0,0 +1,19 @@ +const pairing = require('../pairing') + +const authorize = function (req, res, next) { + const deviceId = req.deviceId + + return pairing.isPaired(deviceId) + .then(deviceName => { + if (deviceName) { + req.deviceId = deviceId + req.deviceName = deviceName + return next() + } + + return res.status(403).json({ error: 'Forbidden' }) + }) + .catch(next) +} + +module.exports = authorize \ No newline at end of file diff --git a/lib/middlewares/ca.js b/lib/middlewares/ca.js new file mode 100644 index 00000000..f1283e2c --- /dev/null +++ b/lib/middlewares/ca.js @@ -0,0 +1,12 @@ +const pairing = require('../pairing') + +function ca (req, res) { + console.log("ca") + const token = req.query.token + + return pairing.authorizeCaDownload(token) + .then(ca => res.json({ ca })) + .catch(() => res.status(403).json({ error: 'forbidden' })) +} + +module.exports = ca \ No newline at end of file diff --git a/lib/middlewares/errorHandler.js b/lib/middlewares/errorHandler.js new file mode 100644 index 00000000..04b3074d --- /dev/null +++ b/lib/middlewares/errorHandler.js @@ -0,0 +1,15 @@ +const logger = require('../logger') + +function errorHandler (err, req, res, next) { + 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 \ No newline at end of file diff --git a/lib/middlewares/filterOldRequests.js b/lib/middlewares/filterOldRequests.js new file mode 100644 index 00000000..7645a254 --- /dev/null +++ b/lib/middlewares/filterOldRequests.js @@ -0,0 +1,27 @@ +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 \ No newline at end of file diff --git a/lib/middlewares/pair.js b/lib/middlewares/pair.js new file mode 100644 index 00000000..3b402f8a --- /dev/null +++ b/lib/middlewares/pair.js @@ -0,0 +1,20 @@ +const pairing = require('../pairing') + +function pair (req, res, next) { + console.log("pair") + const token = req.query.token + const deviceId = req.deviceId + const model = req.query.model + + return pairing.pair(token, deviceId, model) + .then(valid => { + if (valid) { + return res.json({ status: 'paired' }) + } + + throw httpError('Pairing failed') + }) + .catch(next) +} + +module.exports = pair \ No newline at end of file diff --git a/lib/middlewares/populateDeviceId.js b/lib/middlewares/populateDeviceId.js new file mode 100644 index 00000000..8d95c7b0 --- /dev/null +++ b/lib/middlewares/populateDeviceId.js @@ -0,0 +1,22 @@ +const _ = require('lodash/fp') + +function sha256 (buf) { + const crypto = require('crypto') + const hash = crypto.createHash('sha256') + + hash.update(buf) + return hash.digest('hex').toString('hex') +} + +const populateDeviceId = function (req, res, next) { + const deviceId = _.isFunction(req.connection.getPeerCertificate) + ? sha256(req.connection.getPeerCertificate().raw) + : null + + req.deviceId = deviceId + req.deviceTime = req.get('date') + + next() +} + +module.exports = populateDeviceId \ No newline at end of file diff --git a/lib/middlewares/populateSettings.js b/lib/middlewares/populateSettings.js new file mode 100644 index 00000000..b20d233e --- /dev/null +++ b/lib/middlewares/populateSettings.js @@ -0,0 +1,42 @@ +const state = require('./state') +const settingsCache = require('./settingsCache') +const newSettingsLoader = require('../new-settings-loader') +const helpers = require('../route-helpers') + +const SETTINGS_CACHE_REFRESH = 60 * 60 * 1000 + +const populateSettings = function (req, res, next) { + const versionId = req.headers['config-version'] + if (versionId !== state.oldVersionId) { + state.oldVersionId = versionId + } + + // Clear cache every hour + if (Date.now() - settingsCache.getTimestamp() > SETTINGS_CACHE_REFRESH) { + settingsCache.clearCache() + } + + if (!versionId && settingsCache.getCache()) { + req.settings = settingsCache.getCache() + return next() + } + + if (!versionId && !settingsCache.getCache()) { + return newSettingsLoader.loadLatest() + .then(settings => { + settingsCache.setCache(settings) + settingsCache.setTimestamp(Date.now()) + req.settings = settings + }) + .then(() => next()) + .catch(next) + } + + newSettingsLoader.load(versionId) + .then(settings => { req.settings = settings }) + .then(() => helpers.updateDeviceConfigVersion(versionId)) + .then(() => next()) + .catch(next) +} + +module.exports = populateSettings \ No newline at end of file diff --git a/lib/middlewares/settingsCache.js b/lib/middlewares/settingsCache.js new file mode 100644 index 00000000..dc4df80b --- /dev/null +++ b/lib/middlewares/settingsCache.js @@ -0,0 +1,19 @@ +const state = require('./state') + +const getTimestamp = () => state.settingsCache.timestamp + +const getCache = () => state.settingsCache.cache + +const setTimestamp = (newTimestamp) => state.settingsCache.timestamp = newTimestamp + +const setCache = (newCache) => state.settingsCache.cache = newCache + +const clearCache = () => state.settingsCache.cache = null + +module.exports = { + getTimestamp, + getCache, + setTimestamp, + setCache, + clearCache +} \ No newline at end of file diff --git a/lib/middlewares/state.js b/lib/middlewares/state.js new file mode 100644 index 00000000..cc0145b4 --- /dev/null +++ b/lib/middlewares/state.js @@ -0,0 +1,12 @@ +module.exports = function () { + return { + oldVersionId: "unset", + settingsCache: {}, + canLogClockSkewMap: {}, + canGetLastSeenMap: {}, + pids: {}, + reboots: {}, + shutdowns: {}, + restartServicesMap: {} + } +}() \ No newline at end of file diff --git a/lib/new-routes.js b/lib/new-routes.js new file mode 100644 index 00000000..57c9a342 --- /dev/null +++ b/lib/new-routes.js @@ -0,0 +1,80 @@ +const express = require('express') +const argv = require('minimist')(process.argv.slice(2)) +const bodyParser = require('body-parser') +const compression = require('compression') +const helmet = require('helmet') +const morgan = require('morgan') +const nocache = require('nocache') + +const authorize = require('./middlewares/authorize') +const errorHandler = require('./middlewares/errorHandler') +const filterOldRequests = require('./middlewares/filterOldRequests') +const logger = require('./logger') +const options = require('./options') +const populateDeviceId = require('./middlewares/populateDeviceId') +const populateSettings = require('./middlewares/populateSettings') + +const customerRoutes = require('./routes/customerRoutes') +const logsRoutes = require('./routes/logsRoutes') +const ownAuthorizationRoutes = require('./routes/ownAuthorizationRoutes') +const phoneCodeRoutes = require('./routes/phoneCodeRoutes') +const pollingRoutes = require('./routes/pollingRoutes') +const stateRoutes = require('./routes/stateRoutes') +const termsAndConditionsRoutes = require('./routes/termsAndConditionsRoutes') +const txRoutes = require('./routes/txRoutes') +const verifyUserRoutes = require('./routes/verifyUserRoutes') +const verifyTxRoutes = require('./routes/verifyTxRoutes') + +const localAppRoutes = require('./routes/localAppRoutes') + +const app = express() +const localApp = express() + +const configRequiredRoutes = [ + '/poll', + '/terms_conditions', + '/event', + '/phone_code', + '/customer', + '/tx' +] +const devMode = argv.dev || options.http +// middleware setup +app.use(compression({ threshold: 500 })) +app.use(helmet()) +app.use(nocache()) +app.use(bodyParser.json({ limit: '2mb' })) +app.use(morgan(':method :url :status :response-time ms - :res[content-length]', { stream: logger.stream })) + +// app /pair and /ca routes +app.use('/', ownAuthorizationRoutes) + +app.use(populateDeviceId) +if (!devMode) app.use(authorize) +app.use(configRequiredRoutes, populateSettings) +app.use(filterOldRequests) + +// other app routes +app.use('/poll', pollingRoutes) +app.use('/terms_conditions', termsAndConditionsRoutes) +app.use('/state', stateRoutes) + +app.use('/verify_user', verifyUserRoutes) +app.use('/verify_transaction', verifyTxRoutes) + +app.use('/phone_code', phoneCodeRoutes) +app.use('/customer', customerRoutes) + +app.use('/tx', txRoutes) + +app.use('/logs', logsRoutes) + +app.use(errorHandler) +app.use((req, res) => { + res.status(404).json({ error: 'No such route' }) +}) + +// localapp routes +localApp.use('/', localAppRoutes) + +module.exports = { app, localApp } diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js new file mode 100644 index 00000000..9114fa06 --- /dev/null +++ b/lib/routes/customerRoutes.js @@ -0,0 +1,95 @@ +const express = require('express') +const router = express.Router() +const semver = require('semver') +const _ = require('lodash/fp') + +const compliance = require('../compliance') +const complianceTriggers = require('../compliance-triggers') +const configManager = require('../new-config-manager') +const customers = require('../customers') + +function httpError (msg, code) { + const err = new Error(msg) + err.name = 'HTTPError' + err.code = code || 500 + + return err +} + +function updateCustomer (req, res, next) { + const id = req.params.id + const machineVersion = req.query.version + const txId = req.query.txId + const patch = req.body + const triggers = configManager.getTriggers(req.settings.config) + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) + + customers.getById(id) + .then(customer => { + if (!customer) { throw httpError('Not Found', 404) } + + const mergedCustomer = _.merge(customer, patch) + + // BACKWARDS_COMPATIBILITY 7.5 + // machines before 7.5 expect customer with sanctions result + const isOlderMachineVersion = !machineVersion || semver.lt(machineVersion, '7.5.0-beta.0') + + return Promise.resolve({}) + .then(emptyObj => { + if (!isOlderMachineVersion) return Promise.resolve(emptyObj) + return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, mergedCustomer) + }) + .then(_.merge(patch)) + .then(newPatch => customers.updatePhotoCard(id, newPatch)) + .then(newPatch => customers.updateFrontCamera(id, newPatch)) + .then(newPatch => customers.update(id, newPatch, null, txId)) + }) + .then(customer => res.status(200).json({ customer })) + .catch(next) +} + +function triggerSanctions (req, res, next) { + const id = req.params.id + + customers.getById(id) + .then(customer => { + if (!customer) { throw httpError('Not Found', 404) } + + return compliance.validationPatch(req.deviceId, true, customer) + .then(patch => customers.update(id, patch)) + + }) + .then(customer => res.status(200).json({ customer })) + .catch(next) +} + +function triggerBlock (req, res, next) { + const id = req.params.id + + customers.update(id, { authorizedOverride: 'blocked' }) + .then(customer => res.status(200).json({ customer })) + .catch(next) +} + +function triggerSuspend (req, res, next) { + const id = req.params.id + const triggerId = req.body.triggerId + + const triggers = configManager.getTriggers(req.settings.config) + const getSuspendDays = _.compose(_.get('suspensionDays'), _.find(_.matches({ id: triggerId }))) + + const days = triggerId === 'no-ff-camera' ? 1 : getSuspendDays(triggers) + + const date = new Date() + date.setDate(date.getDate() + days); + customers.update(id, { suspendedUntil: date }) + .then(customer => res.status(200).json({ customer })) + .catch(next) +} + +router.patch('/:id', updateCustomer) +router.patch('/:id/sanctions', triggerSanctions) +router.patch('/:id/block', triggerBlock) +router.patch('/:id/suspend', triggerSuspend) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/localAppRoutes.js b/lib/routes/localAppRoutes.js new file mode 100644 index 00000000..3b6d8cee --- /dev/null +++ b/lib/routes/localAppRoutes.js @@ -0,0 +1,63 @@ +const express = require('express') +const router = express.Router() + +const logger = require('../logger') +const newSettingsLoader = require('../new-settings-loader') +const poller = require('../poller') +const settingsCache = require('../middlewares/settingsCache') +const state = require('../middlewares/state') + +router.get('/pid', (req, res) => { + const deviceId = req.query.device_id + const pidRec = state.pids[deviceId] + res.json(pidRec) +}) + +router.post('/reboot', (req, res) => { + const deviceId = req.query.device_id + const pid = state.pids[deviceId] && state.pids[deviceId].pid + + if (!deviceId || !pid) { + return res.sendStatus(400) + } + + state.reboots[deviceId] = pid + res.sendStatus(200) +}) + +router.post('/shutdown', (req, res) => { + const deviceId = req.query.device_id + const pid = state.pids[deviceId] && state.pids[deviceId].pid + + if (!deviceId || !pid) { + return res.sendStatus(400) + } + + state.shutdowns[deviceId] = pid + res.sendStatus(200) +}) + +router.post('/restartServices', (req, res) => { + const deviceId = req.query.device_id + const pid = state.pids[deviceId] && state.pids[deviceId].pid + + if (!deviceId || !pid) { + return res.sendStatus(400) + } + + state.restartServicesMap[deviceId] = pid + res.sendStatus(200) +}) + +router.post('/dbChange', (req, res, next) => { + settingsCache.clearCache() + return newSettingsLoader.loadLatest() + .then(poller.reload) + .then(() => logger.info('Config reloaded')) + .catch(err => { + logger.error(err) + res.sendStatus(500) + }) +}) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/logsRoutes.js b/lib/routes/logsRoutes.js new file mode 100644 index 00000000..406879cf --- /dev/null +++ b/lib/routes/logsRoutes.js @@ -0,0 +1,35 @@ +const express = require('express') +const router = express.Router() +const _ = require('lodash/fp') + +const state = require('../middlewares/state') +const logs = require('../logs') + +const THROTTLE_LOGS_QUERY = 30 * 1000 + +function getLastSeen (req, res, next) { + const deviceId = req.deviceId + const timestamp = Date.now() + const shouldTrigger = !state.canGetLastSeenMap[deviceId] || + timestamp - state.canGetLastSeenMap[deviceId] >= THROTTLE_LOGS_QUERY + + if (shouldTrigger) { + state.canGetLastSeenMap[deviceId] = timestamp + return logs.getLastSeen(deviceId) + .then(r => res.json(r)) + .catch(next) + } + + return res.status(408).json({}) +} + +function updateLogs (req, res, next) { + return logs.update(req.deviceId, req.body.logs) + .then(status => res.json({ success: status })) + .catch(next) +} + +router.get('/', getLastSeen) +router.post('/', updateLogs) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/ownAuthorizationRoutes.js b/lib/routes/ownAuthorizationRoutes.js new file mode 100644 index 00000000..d35327b1 --- /dev/null +++ b/lib/routes/ownAuthorizationRoutes.js @@ -0,0 +1,11 @@ +const express = require('express') +const router = express.Router() + +const ca = require('../middlewares/ca') +const pair = require('../middlewares/pair') +const populateDeviceId = require('../middlewares/populateDeviceId') + +router.post('/pair', populateDeviceId, pair) +router.get('/ca', ca) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/phoneCodeRoutes.js b/lib/routes/phoneCodeRoutes.js new file mode 100644 index 00000000..3ec83bb9 --- /dev/null +++ b/lib/routes/phoneCodeRoutes.js @@ -0,0 +1,73 @@ +const express = require('express') +const router = express.Router() +const semver = require('semver') +const _ = require('lodash/fp') + +const compliance = require('../compliance') +const complianceTriggers = require('../compliance-triggers') +const configManager = require('../new-config-manager') +const customers = require('../customers') +const plugins = require('../plugins') +const Tx = require('../tx') + +function addOrUpdateCustomer (req) { + const customerData = req.body + const machineVersion = req.query.version + const triggers = configManager.getTriggers(req.settings.config) + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) + const maxDaysThreshold = complianceTriggers.maxDaysThreshold(triggers) + + return customers.get(customerData.phone) + .then(customer => { + if (customer) return customer + + return customers.add(req.body) + }) + .then(customer => { + // BACKWARDS_COMPATIBILITY 7.5 + // machines before 7.5 expect customer with sanctions result + const isOlderMachineVersion = !machineVersion || semver.lt(machineVersion, '7.5.0-beta.0') + const shouldRunOfacCompat = !compatTriggers.sanctions && isOlderMachineVersion + if (!shouldRunOfacCompat) return customer + + return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, customer) + .then(patch => { + if (_.isEmpty(patch)) return customer + return customers.update(customer.id, patch) + }) + }).then(customer => { + return Tx.customerHistory(customer.id, maxDaysThreshold) + .then(result => { + customer.txHistory = result + return customer + }) + }) +} + +function httpError (msg, code) { + const err = new Error(msg) + err.name = 'HTTPError' + err.code = code || 500 + + return err +} + +function getCustomerWithPhoneCode (req, res, next) { + const pi = plugins(req.settings, req.deviceId) + const phone = req.body.phone + + return pi.getPhoneCode(phone) + .then(code => { + return addOrUpdateCustomer(req) + .then(customer => res.status(200).json({ code, customer })) + }) + .catch(err => { + if (err.name === 'BadNumberError') throw httpError('Bad number', 401) + throw err + }) + .catch(next) +} + +router.post('/', getCustomerWithPhoneCode) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js new file mode 100644 index 00000000..3e3a4261 --- /dev/null +++ b/lib/routes/pollingRoutes.js @@ -0,0 +1,112 @@ +const express = require('express') +const router = express.Router() +const _ = require('lodash/fp') + +const complianceTriggers = require('../compliance-triggers') +const configManager = require('../new-config-manager') +const plugins = require('../plugins') +const semver = require('semver') +const state = require('../middlewares/state') +const version = require('../../package.json').version + +function checkHasLightning (settings) { + return configManager.getWalletSettings('BTC', settings.config).layer2 !== 'no-layer2' +} + +function createTerms (terms) { + if (!terms.active || !terms.text) return null + + return { + active: terms.active, + title: terms.title, + text: nmd(terms.text), + accept: terms.acceptButtonText, + cancel: terms.cancelButtonText + } +} + +function poll (req, res, next) { + const machineVersion = req.query.version + const machineModel = req.query.model + const deviceId = req.deviceId + const deviceTime = req.deviceTime + const serialNumber = req.query.sn + const pid = req.query.pid + const settings = req.settings + const localeConfig = configManager.getLocale(deviceId, settings.config) + const pi = plugins(settings, deviceId) + const hasLightning = checkHasLightning(settings) + + const triggers = configManager.getTriggers(settings.config) + + const operatorInfo = configManager.getOperatorInfo(settings.config) + const cashOutConfig = configManager.getCashOut(deviceId, settings.config) + const receipt = configManager.getReceipt(settings.config) + + state.pids[deviceId] = { pid, ts: Date.now() } + + return pi.pollQueries(serialNumber, deviceTime, req.query, machineVersion, machineModel) + .then(results => { + const cassettes = results.cassettes + + const reboot = pid && state.reboots[deviceId] && state.reboots[deviceId] === pid + const shutdown = pid && state.shutdowns[deviceId] && state.shutdowns[deviceId] === pid + const restartServices = pid && state.restartServicesMap[deviceId] && state.restartServicesMap[deviceId] === pid + const langs = localeConfig.languages + + const locale = { + fiatCode: localeConfig.fiatCurrency, + localeInfo: { + primaryLocale: langs[0], + primaryLocales: langs, + country: localeConfig.country + } + } + + const response = { + error: null, + locale, + version, + receiptPrintingActive: receipt.active, + cassettes, + twoWayMode: cashOutConfig.active, + zeroConfLimit: cashOutConfig.zeroConfLimit, + reboot, + shutdown, + restartServices, + hasLightning, + receipt, + operatorInfo, + triggers + } + + // BACKWARDS_COMPATIBILITY 7.5 + // machines before 7.5 expect old compliance + if (!machineVersion || semver.lt(machineVersion, '7.5.0-beta.0')) { + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) + response.smsVerificationActive = !!compatTriggers.sms + response.smsVerificationThreshold = compatTriggers.sms + response.idCardDataVerificationActive = !!compatTriggers.idCardData + response.idCardDataVerificationThreshold = compatTriggers.idCardData + response.idCardPhotoVerificationActive = !!compatTriggers.idCardPhoto + response.idCardPhotoVerificationThreshold = compatTriggers.idCardPhoto + response.sanctionsVerificationActive = !!compatTriggers.sancations + response.sanctionsVerificationThreshold = compatTriggers.sancations + response.frontCameraVerificationActive = !!compatTriggers.facephoto + response.frontCameraVerificationThreshold = compatTriggers.facephoto + } + + // BACKWARDS_COMPATIBILITY 7.4.9 + // machines before 7.4.9 expect t&c on poll + if (!machineVersion || semver.lt(machineVersion, '7.4.9')) { + response.terms = config.termsScreenActive && config.termsScreenText ? createTerms(config) : null + } + + return res.json(_.assign(response, results)) + }) + .catch(next) +} + +router.get('/', poll) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/stateRoutes.js b/lib/routes/stateRoutes.js new file mode 100644 index 00000000..f52a0a5f --- /dev/null +++ b/lib/routes/stateRoutes.js @@ -0,0 +1,14 @@ +const express = require('express') +const router = express.Router() + +const helpers = require('../route-helpers') + +function stateChange (req, res, next) { + helpers.stateChange(req.deviceId, req.deviceTime, req.body) + .then(() => res.status(200).json({})) + .catch(next) +} + +router.post('/', stateChange) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/termsAndConditionsRoutes.js b/lib/routes/termsAndConditionsRoutes.js new file mode 100644 index 00000000..b3680ad6 --- /dev/null +++ b/lib/routes/termsAndConditionsRoutes.js @@ -0,0 +1,35 @@ +const express = require('express') +const router = express.Router() + +const configManager = require('../new-config-manager') +const plugins = require('../plugins') + +function createTerms (terms) { + if (!terms.active || !terms.text) return null + + return { + active: terms.active, + title: terms.title, + text: nmd(terms.text), + accept: terms.acceptButtonText, + cancel: terms.cancelButtonText + } +} + +function getTermsConditions (req, res, next) { + const deviceId = req.deviceId + const settings = req.settings + + const terms = configManager.getTermsConditions(settings.config) + + const pi = plugins(settings, deviceId) + + return pi.fetchCurrentConfigVersion().then(version => { + return res.json({ terms: createTerms(terms), version }) + }) + .catch(next) +} + +router.get('/', getTermsConditions) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/txRoutes.js b/lib/routes/txRoutes.js new file mode 100644 index 00000000..4b5f0a31 --- /dev/null +++ b/lib/routes/txRoutes.js @@ -0,0 +1,70 @@ +const express = require('express') +const router = express.Router() +const _ = require('lodash/fp') + +const dbErrorCodes = require('../db-error-codes') +const helpers = require('../route-helpers') +const logger = require('../logger') +const plugins = require('../plugins') +const Tx = require('../tx') + +function httpError (msg, code) { + const err = new Error(msg) + err.name = 'HTTPError' + err.code = code || 500 + + return err +} + +function postTx (req, res, next) { + const pi = plugins(req.settings, req.deviceId) + + return Tx.post(_.set('deviceId', req.deviceId, req.body), pi) + .then(tx => { + if (tx.errorCode) { + logger.error(tx.error) + throw httpError(tx.error, 500) + } + + return res.json(tx) + }) + .catch(err => { + // 204 so that l-m can ignore the error + // this is fine because the request is polled and will be retried if needed. + if (err.code === dbErrorCodes.SERIALIZATION_FAILURE) { + logger.warn('Harmless DB conflict, the query will be retried.') + return res.status(204).json({}) + } + if (err instanceof E.StaleTxError) return res.status(409).json({ errorType: 'stale' }) + if (err instanceof E.RatchetError) return res.status(409).json({ errorType: 'ratchet' }) + + throw err + }) + .catch(next) +} + +function getTx (req, res, next) { + if (req.query.status) { + return helpers.fetchStatusTx(req.params.id, req.query.status) + .then(r => res.json(r)) + .catch(next) + } + + return next(httpError('Not Found', 404)) +} + +function getPhoneTx (req, res, next) { + if (req.query.phone) { + return helpers.fetchPhoneTx(req.query.phone) + .then(r => res.json(r)) + .catch(next) + } + + return next(httpError('Not Found', 404)) +} + +router.post('/', postTx) +router.get('/:id', getTx) +router.get('/', getPhoneTx) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/verifyTxRoutes.js b/lib/routes/verifyTxRoutes.js new file mode 100644 index 00000000..98a03934 --- /dev/null +++ b/lib/routes/verifyTxRoutes.js @@ -0,0 +1,15 @@ +const express = require('express') +const router = express.Router() + +const plugins = require('../plugins') + +function verifyTx (req, res, next) { + const pi = plugins(req.settings, req.deviceId) + pi.verifyTransaction(req.body) + .then(idResult => res.status(200).json(idResult)) + .catch(next) +} + +router.post('/', verifyTx) + +module.exports = router \ No newline at end of file diff --git a/lib/routes/verifyUserRoutes.js b/lib/routes/verifyUserRoutes.js new file mode 100644 index 00000000..2ee17f18 --- /dev/null +++ b/lib/routes/verifyUserRoutes.js @@ -0,0 +1,15 @@ +const express = require('express') +const router = express.Router() + +const plugins = require('../plugins') + +function verifyUser (req, res, next) { + const pi = plugins(req.settings, req.deviceId) + pi.verifyUser(req.body) + .then(idResult => res.status(200).json(idResult)) + .catch(next) +} + +router.post('/', verifyUser) + +module.exports = router \ No newline at end of file