This commit is contained in:
Josh Harvey 2016-11-20 17:49:36 +02:00
parent becd62a1fb
commit 938eb9ac97
9 changed files with 153 additions and 68 deletions

76
bin/cert-gen.sh Executable file
View file

@ -0,0 +1,76 @@
#!/usr/bin/env bash
# This is for setting up cryptographic certificates for a development environment
set -e
DOMAIN=localhost
LOG_FILE=/tmp/cert-gen.log
CERT_DIR=$PWD/certs
KEY_DIR=$PWD/certs
CONFIG_DIR=$HOME/.lamassu
mkdir -p $CERT_DIR
mkdir -p $CONFIG_DIR >> $LOG_FILE 2>&1
echo "Generating seed..."
SEEDS_DIR=seeds
SEED_FILE=$SEEDS_DIR/seed.txt
mkdir -p $SEEDS_DIR >> $LOG_FILE 2>&1
SEED=$(openssl rand -hex 32)
echo $SEED > $SEED_FILE
echo "Generating SSL certificates (takes a few seconds)..."
CA_KEY_PATH=$KEY_DIR/Lamassu_OP_Root_CA.key
CA_PATH=$CERT_DIR/Lamassu_OP_Root_CA.pem
SERVER_KEY_PATH=$KEY_DIR/Lamassu_OP.key
SERVER_CERT_PATH=$CERT_DIR/Lamassu_OP.pem
openssl genrsa \
-out $CA_KEY_PATH \
4096 >> $LOG_FILE 2>&1
openssl req \
-x509 \
-new \
-nodes \
-key $CA_KEY_PATH \
-days 3560 \
-out $CA_PATH \
-subj "/C=IS/ST=/L=Reykjavik/O=Lamassu Operator CA/CN=lamassu-operator.is" \
>> $LOG_FILE 2>&1
openssl genrsa \
-out $SERVER_KEY_PATH \
4096 >> $LOG_FILE 2>&1
openssl req -new \
-key $SERVER_KEY_PATH \
-out /tmp/Lamassu_OP.csr.pem \
-subj "/C=IS/ST=/L=Reykjavik/O=Lamassu Operator/CN=$DOMAIN" \
>> $LOG_FILE 2>&1
openssl x509 \
-req -in /tmp/Lamassu_OP.csr.pem \
-CA $CA_PATH \
-CAkey $CA_KEY_PATH \
-CAcreateserial \
-out $SERVER_CERT_PATH \
-days 3650 >> $LOG_FILE 2>&1
rm /tmp/Lamassu_OP.csr.pem
cat <<EOF > $CONFIG_DIR/lamassu.json
{
"postgresql": "psql://lamassu:lamassu@localhost/lamassu",
"seedPath": "$SEED_FILE",
"caPath": "$CA_PATH",
"certPath": "$SERVER_CERT_PATH",
"keyPath": "$SERVER_KEY_PATH",
"hostname": "$DOMAIN",
"logLevel": "debug"
}
EOF
echo "Done."

View file

@ -4,6 +4,7 @@ const app = require('../lib/app')
process.on('unhandledRejection', err => {
console.log('Unhandled rejection')
console.dir(err)
console.log(err.stack)
})

View file

@ -1,10 +1,9 @@
const path = require('path')
const fs = require('fs')
const pify = require('pify')
const readFile = pify(fs.readFile)
const db = require('./db')
const CA_PATH = path.resolve(__dirname, '..', 'certs', 'root-ca.crt.pem')
const options = require('./options')
const logger = require('./logger')
function pullToken (token) {
const sql = `delete from pairing_tokens
@ -14,26 +13,31 @@ function pullToken (token) {
}
function pair (token, deviceId) {
pullToken(token)
return pullToken(token)
.then(r => {
if (r.expired) return false
const insertSql = `insert into devices (device_id, name) values ($1, $2)
on conflict (device_id)
do update set name=$1, paired=TRUE, display=TRUE`
do update set name=$2, paired=TRUE, display=TRUE`
return db.none(insertSql, [deviceId, r.name])
.then(() => true)
})
.catch(() => false)
.catch(err => {
logger.debug(err)
return false
})
}
function authorizeCaDownload (caToken) {
return pullToken(caToken)
}
.then(r => {
if (r.expired) throw new Error('Expired')
function ca () {
return readFile(CA_PATH)
const caPath = options.caPath
return readFile(caPath, {encoding: 'utf8'})
})
}
function isPaired (deviceId) {
@ -43,4 +47,4 @@ function isPaired (deviceId) {
.then(() => true)
}
module.exports = {pair, authorizeCaDownload, ca, isPaired}
module.exports = {pair, authorizeCaDownload, isPaired}

View file

@ -171,16 +171,11 @@ exports.configure = function configure (config) {
const cryptoCodes = getCryptoCodes()
console.log('DEBUG30')
return configManager.loadAccounts()
.then(accounts => {
cryptoCodes.forEach(cryptoCode => {
console.log('DEBUG31')
const cryptoScopedConfig = configManager.cryptoScoped(cryptoCode, cachedConfig)
console.log('DEBUG31.1')
// TICKER [required] configure (or load)
loadOrConfigPlugin(
tickerPlugins[cryptoCode],
@ -195,8 +190,6 @@ exports.configure = function configure (config) {
}
)
console.log('DEBUG31.2')
// Give each crypto a different derived seed so as not to allow any
// plugin to spend another plugin's funds
const cryptoSeed = hkdf.derive(cryptoCode, 32)
@ -298,7 +291,6 @@ exports.pollQueries = function pollQueries (deviceId) {
function _sendCoins (toAddress, cryptoAtoms, cryptoCode) {
return new Promise((resolve, reject) => {
_sendCoinsCb(toAddress, cryptoAtoms, cryptoCode, (err, txHash) => {
console.log('DEBUG12: %j, %j', err, txHash)
if (err) return reject(err)
return resolve(txHash)
})
@ -308,7 +300,6 @@ function _sendCoins (toAddress, cryptoAtoms, cryptoCode) {
function _sendCoinsCb (toAddress, cryptoAtoms, cryptoCode, cb) {
const walletPlugin = walletPlugins[cryptoCode]
const transactionFee = null
logger.debug('Sending coins [%s] to: %s', cryptoCode, toAddress)
if (cryptoCode === 'BTC') {
walletPlugin.sendBitcoins(toAddress, cryptoAtoms.truncated().toNumber(), transactionFee, cb)
@ -320,12 +311,9 @@ function _sendCoinsCb (toAddress, cryptoAtoms, cryptoCode, cb) {
// NOTE: This will fail if we have already sent coins because there will be
// a db unique db record in the table already.
function executeTx (deviceId, tx) {
console.log('DEBUG16: %j', tx)
return db.addOutgoingTx(deviceId, tx)
.then(() => _sendCoins(tx.toAddress, tx.cryptoAtoms, tx.cryptoCode))
.then(txHash => {
console.log('DEBUG13: %j', txHash)
const fee = null // Need to fill this out in plugins
const toSend = {cryptoAtoms: tx.cryptoAtoms, fiat: tx.fiat}
@ -412,7 +400,6 @@ exports.cashOut = function cashOut (deviceId, tx) {
}
exports.dispenseAck = function (deviceId, tx) {
console.log('DEBUG23: %j', tx)
const config = getConfig(deviceId)
const cartridges = [ config.currencies.topCashOutDenomination,
config.currencies.bottomCashOutDenomination ]
@ -767,7 +754,6 @@ function checkNotification () {
return sendMessage(rec)
})
.then(results => {
console.log('DEBUG25')
if (results && results.length > 0) logger.debug('Successfully sent alerts')
})
.catch(err => {

View file

@ -29,7 +29,6 @@ function getInsertQuery (tableName, fields) {
// logs inputted bill and overall tx status (if available)
exports.recordBill = function recordBill (deviceId, rec) {
console.log('DEBUG10: %j', rec)
const fields = [
'id',
'device_id',
@ -54,8 +53,6 @@ exports.recordBill = function recordBill (deviceId, rec) {
rec.fiat
]
console.log('DEBUG11: %j', values)
return db.none(getInsertQuery('bills', fields), values)
.catch(err => {
if (isUniqueViolation(err)) return logger.warn('Attempt to report bill twice')
@ -127,24 +124,19 @@ exports.addInitialIncoming = function addInitialIncoming (deviceId, tx) {
function insertDispense (deviceId, tx, cartridges) {
const fields = [
'device_id', 'cash_out_txs_id',
'dispense1', 'reject1', 'count1',
'dispense2', 'reject2', 'count2',
'refill', 'error'
'dispense1', 'reject1',
'dispense2', 'reject2', 'error'
]
const sql = getInsertQuery('dispenses', fields)
console.log('DEBUG24: %j', tx)
const dispense1 = tx.bills[0].actualDispense
const dispense2 = tx.bills[1].actualDispense
const reject1 = tx.bills[0].rejected
const reject2 = tx.bills[1].rejected
const count1 = cartridges[0].count
const count2 = cartridges[1].count
const values = [
deviceId, tx.id,
dispense1, reject1, count1, dispense2, reject2, count2,
dispense1, reject1, dispense2, reject2,
false, tx.error
]

View file

@ -1,5 +1,6 @@
'use strict'
const morgan = require('morgan')
const helmet = require('helmet')
const bodyParser = require('body-parser')
const BigNumber = require('bignumber.js')
@ -76,20 +77,13 @@ function poll (req, res) {
const config = plugins.getConfig(deviceId)
console.log('DEBUG30')
plugins.pollQueries(deviceId)
.then(results => {
console.log('DEBUG31')
const cartridges = results.cartridges
const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid
console.log('DEBUG2: %j, %s, %s', reboots, reboot, pid)
const langs = config.languages.machineLanguages
console.log('DEBUG33.1')
const locale = {
currency: config.currencies.fiatCurrency,
localeInfo: {
@ -126,7 +120,6 @@ function poll (req, res) {
}
function trade (req, res, next) {
console.log('DEBUG24')
const tx = req.body
tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms)
@ -183,22 +176,20 @@ function ca (req, res) {
const token = req.query.token
return pairing.authorizeCaDownload(token)
.then(valid => {
if (valid) return res.json({ca: pairing.ca()})
return res.status(408).end()
})
.then(ca => res.json({ca}))
.catch(() => res.status(408).end())
}
function pair (req, res) {
function pair (req, res, next) {
const token = req.query.token
const deviceId = getDeviceId(req)
return pairing.pair(token, deviceId)
.then(valid => {
if (valid) return cacheAndRespond(req, res)
if (valid) return res.end()
throw httpError('Pairing failed')
})
.catch(next)
}
function phoneCode (req, res) {
@ -233,7 +224,7 @@ function registerRedeem (req, res) {
.then(() => cacheAndRespond(req, res))
}
function waitForDispense (req, res) {
function waitForDispense (req, res, next) {
logger.debug('waitForDispense')
return plugins.fetchTx(req.params.txId)
.then(tx => {
@ -243,10 +234,7 @@ function waitForDispense (req, res) {
if (tx.status === req.query.status) return res.sendStatus(304)
res.json({tx})
})
.catch(err => {
logger.error(err)
res.sendStatus(500)
})
.catch(next)
}
function dispense (req, res) {
@ -261,12 +249,12 @@ function isUniqueViolation (err) {
}
function cacheAction (req, res, next) {
console.log('DEBUG22: %s', req.path)
const requestId = req.headers['request-id']
if (!requestId) return next()
const sql = `insert into idempotents (request_id, device_id, body, status, pending)
values ($1, $2, $3, $4, $5)`
const requestId = req.headers['request-id']
const deviceId = getDeviceId(req)
db.none(sql, [requestId, deviceId, {}, 204, true])
@ -284,21 +272,25 @@ function cacheAction (req, res, next) {
}
function updateCachedAction (req, body, status) {
const requestId = req.headers['request-id']
if (!requestId) return Promise.resolve()
const sql = `update idempotents set body=$1, status=$2, pending=$3
where request_id=$4 and device_id=$5 and pending=$6`
const requestId = req.headers['request-id']
const deviceId = getDeviceId(req)
return db.none(sql, [body, status, false, requestId, deviceId, true])
}
function postErrorHandler (err, req, res, next) {
function errorHandler (err, req, res, next) {
const statusCode = err.code || 500
const json = {error: err.message}
logger.debug(err)
return updateCachedAction(req, json, statusCode)
.then(() => res.status(statusCode).json({}))
.then(() => res.status(statusCode).json(json))
}
function cacheAndRespond (req, res, _body, _status) {
@ -361,6 +353,7 @@ function init (opts) {
? (req, res, next) => next()
: authorize
app.use(morgan('dev'))
app.use(helmet())
app.use(bodyParser.json())
app.use(filterOldRequests)
@ -388,7 +381,7 @@ function init (opts) {
app.get('/await_dispense/:txId', authMiddleware, waitForDispense)
app.post('/dispense', authMiddleware, dispense)
app.post('*', postErrorHandler)
app.use('*', errorHandler)
localApp.get('/pid', (req, res) => {
const deviceId = req.query.device_id

View file

@ -0,0 +1,14 @@
var db = require('./db')
exports.up = function (next) {
var sql = [
'alter table dispenses drop column count1',
'alter table dispenses drop column count2',
'alter table dispenses drop column refill'
]
db.multi(sql, next)
}
exports.down = function (next) {
next()
}

View file

@ -27,6 +27,7 @@
"lamassu-twilio": "^1.1.1",
"migrate": "^0.2.2",
"minimist": "^1.2.0",
"morgan": "^1.7.0",
"node-hkdf-sync": "^1.0.0",
"numeral": "^1.5.3",
"pg": "^6.1.0",

View file

@ -86,6 +86,10 @@ base64url@^2.0.0, base64url@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb"
basic-auth@~1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.0.4.tgz#030935b01de7c9b94a824b29f3fccb750d3a5290"
bcrypt-pbkdf@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz#3ca76b85241c7170bf7d9703e7b9aa74630040d4"
@ -969,6 +973,16 @@ mkdirp@^0.5.0:
dependencies:
minimist "0.0.8"
morgan:
version "1.7.0"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.7.0.tgz#eb10ca8e50d1abe0f8d3dad5c0201d052d981c62"
dependencies:
basic-auth "~1.0.3"
debug "~2.2.0"
depd "~1.1.0"
on-finished "~2.3.0"
on-headers "~1.0.1"
ms@^0.7.1:
version "0.7.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
@ -1040,6 +1054,10 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
on-headers@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"