feat: add graphql support (#349)
* fix: eslint warnings * refactor: use ramda + sanctuary instead of lodash * refactor: use prettier-standard for formatting * feat: enable security * feat: add graphql * chore: remove trailing commas from linter * docs: new scripts on react and new-admin-server * feat: handle authentication on graphql * fix: perf improvement to date picker * chore: add insecure-dev script to run servers
This commit is contained in:
parent
49f434f1d1
commit
b8e0c2175b
182 changed files with 8827 additions and 4623 deletions
13
lib/logs.js
13
lib/logs.js
|
|
@ -95,4 +95,15 @@ function getMachineLogs (deviceId, until = new Date().toISOString()) {
|
|||
}))
|
||||
}
|
||||
|
||||
module.exports = { getUnlimitedMachineLogs, getMachineLogs, update, getLastSeen, clearOldLogs }
|
||||
function simpleGetMachineLogs (deviceId, until = new Date().toISOString()) {
|
||||
const sql = `select id, log_level, timestamp, message from logs
|
||||
where device_id=$1
|
||||
and timestamp <= $3
|
||||
order by timestamp desc, serial desc
|
||||
limit $2`
|
||||
|
||||
return db.any(sql, [ deviceId, NUM_RESULTS, until ])
|
||||
.then(_.map(_.mapKeys(_.camelCase)))
|
||||
}
|
||||
|
||||
module.exports = { getUnlimitedMachineLogs, getMachineLogs, simpleGetMachineLogs, update, getLastSeen, clearOldLogs }
|
||||
|
|
|
|||
6
lib/new-admin/README.md
Normal file
6
lib/new-admin/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
## Running
|
||||
|
||||
Differences from main lamassu-admin:
|
||||
|
||||
- `bin/new-lamassu-register <username>` to add a user
|
||||
- `bin/insecure-dev` to run the server
|
||||
|
|
@ -1,103 +1,92 @@
|
|||
const bodyParser = require('body-parser')
|
||||
const cors = require('cors')
|
||||
const fs = require('fs')
|
||||
const express = require('express')
|
||||
const http = require('http')
|
||||
const got = require('got')
|
||||
const https = require('https')
|
||||
const helmet = require('helmet')
|
||||
const cookieParser = require('cookie-parser')
|
||||
const { ApolloServer, AuthenticationError } = require('apollo-server-express')
|
||||
|
||||
const supportLogs = require('../support_logs')
|
||||
const machineLoader = require('../machine-loader')
|
||||
const logs = require('../logs')
|
||||
const transactions = require('./transactions')
|
||||
const T = require('../time')
|
||||
const options = require('../options')
|
||||
|
||||
const serverLogs = require('./server-logs')
|
||||
const supervisor = require('./supervisor')
|
||||
const funding = require('./funding')
|
||||
const config = require('./config')
|
||||
const login = require('./login')
|
||||
const { typeDefs, resolvers } = require('./graphql/schema')
|
||||
|
||||
const devMode = require('minimist')(process.argv.slice(2)).dev
|
||||
const NEVER = new Date(Date.now() + 100 * T.years)
|
||||
|
||||
const app = express()
|
||||
app.use(bodyParser.json())
|
||||
|
||||
if (devMode) {
|
||||
app.use(cors())
|
||||
const hostname = options.hostname
|
||||
if (!hostname) {
|
||||
console.error('Error: no hostname specified.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
app.get('/api/config', async (req, res, next) => {
|
||||
const state = config.getConfig(req.params.config)
|
||||
const data = await config.fetchData()
|
||||
res.json({ state, data })
|
||||
next()
|
||||
const app = express()
|
||||
app.use(helmet({ noCache: true }))
|
||||
app.use(cookieParser())
|
||||
|
||||
const apolloServer = new ApolloServer({
|
||||
typeDefs,
|
||||
resolvers,
|
||||
playground: false,
|
||||
introspection: false,
|
||||
formatError: error => {
|
||||
console.log(error)
|
||||
return error
|
||||
},
|
||||
context: async ({ req }) => {
|
||||
const token = req.cookies && req.cookies.token
|
||||
|
||||
const success = await login.authenticate(token)
|
||||
if (!success) throw new AuthenticationError('Authentication failed')
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/config', (req, res, next) => {
|
||||
config.saveConfig(req.body)
|
||||
.then(it => res.json(it))
|
||||
.then(() => dbNotify())
|
||||
.catch(next)
|
||||
apolloServer.applyMiddleware({
|
||||
app,
|
||||
cors: {
|
||||
credentials: true,
|
||||
origin: devMode && 'https://localhost:3000'
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/api/funding', (req, res) => {
|
||||
return funding.getFunding()
|
||||
.then(r => res.json(r))
|
||||
app.get('/api/register', (req, res, next) => {
|
||||
const otp = req.query.otp
|
||||
|
||||
if (!otp) return next()
|
||||
|
||||
return login.register(otp)
|
||||
.then(r => {
|
||||
if (r.expired) return res.status(401).send('OTP expired, generate new registration link')
|
||||
|
||||
// Maybe user is using old registration key, attempt to authenticate
|
||||
if (!r.success) return next()
|
||||
|
||||
const cookieOpts = {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
domain: hostname,
|
||||
sameSite: true,
|
||||
expires: NEVER
|
||||
}
|
||||
|
||||
const token = r.token
|
||||
req.token = token
|
||||
res.cookie('token', token, cookieOpts)
|
||||
res.sendStatus(200)
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/api/machines', (req, res) => {
|
||||
machineLoader.getMachineNames()
|
||||
.then(r => res.send({ machines: r }))
|
||||
})
|
||||
|
||||
app.get('/api/logs/:deviceId', (req, res, next) => {
|
||||
return logs.getMachineLogs(req.params.deviceId)
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.post('/api/support_logs', (req, res, next) => {
|
||||
return supportLogs.insert(req.query.deviceId)
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/version', (req, res, next) => {
|
||||
res.send(require('../../package.json').version)
|
||||
})
|
||||
|
||||
app.get('/api/uptimes', (req, res, next) => {
|
||||
return supervisor.getAllProcessInfo()
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.post('/api/server_support_logs', (req, res, next) => {
|
||||
return serverLogs.insert()
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/server_logs', (req, res, next) => {
|
||||
return serverLogs.getServerLogs()
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/txs', (req, res, next) => {
|
||||
return transactions.batch()
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
function dbNotify () {
|
||||
return got.post('http://localhost:3030/dbChange')
|
||||
.catch(e => console.error('Error: lamassu-server not responding'))
|
||||
const certOptions = {
|
||||
key: fs.readFileSync(options.keyPath),
|
||||
cert: fs.readFileSync(options.certPath)
|
||||
}
|
||||
|
||||
function run () {
|
||||
const serverPort = 8070
|
||||
const serverPort = devMode ? 8070 : 443
|
||||
|
||||
const serverLog = `lamassu-admin-server listening on port ${serverPort}`
|
||||
|
||||
const webServer = http.createServer(app)
|
||||
const webServer = https.createServer(certOptions, app)
|
||||
webServer.listen(serverPort, () => console.log(serverLog))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,109 +0,0 @@
|
|||
const _ = require('lodash/fp')
|
||||
const devMode = require('minimist')(process.argv.slice(2)).dev
|
||||
|
||||
const settingsLoader = require('../new-settings-loader')
|
||||
const machineLoader = require('../machine-loader')
|
||||
|
||||
const currencies = require('../../currencies.json')
|
||||
const languageRec = require('../../languages.json')
|
||||
const countries = require('../../countries.json')
|
||||
|
||||
function saveConfig (config) {
|
||||
return settingsLoader.saveConfig(config)
|
||||
}
|
||||
|
||||
function getConfig () {
|
||||
return settingsLoader.getConfig()
|
||||
}
|
||||
|
||||
function massageCurrencies (currencies) {
|
||||
const convert = r => ({
|
||||
code: r['Alphabetic Code'],
|
||||
display: r['Currency']
|
||||
})
|
||||
const top5Codes = ['USD', 'EUR', 'GBP', 'CAD', 'AUD']
|
||||
const mapped = _.map(convert, currencies)
|
||||
const codeToRec = code => _.find(_.matchesProperty('code', code), mapped)
|
||||
const top5 = _.map(codeToRec, top5Codes)
|
||||
const raw = _.uniqBy(_.get('code'), _.concat(top5, mapped))
|
||||
return raw.filter(r => r.code[0] !== 'X' && r.display.indexOf('(') === -1)
|
||||
}
|
||||
|
||||
const mapLanguage = lang => {
|
||||
const arr = lang.split('-')
|
||||
const code = arr[0]
|
||||
const country = arr[1]
|
||||
const langNameArr = languageRec.lang[code]
|
||||
if (!langNameArr) return null
|
||||
const langName = langNameArr[0]
|
||||
if (!country) return {code: lang, display: langName}
|
||||
return {code: lang, display: `${langName} [${country}]`}
|
||||
}
|
||||
|
||||
const supportedLanguages = languageRec.supported
|
||||
const languages = supportedLanguages.map(mapLanguage).filter(r => r)
|
||||
const ALL_CRYPTOS = ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']
|
||||
|
||||
const filterAccounts = (data, isDevMode) => {
|
||||
const notAllowed = ['mock-ticker', 'mock-wallet', 'mock-exchange', 'mock-sms', 'mock-id-verify', 'mock-zero-conf']
|
||||
const filterOut = o => _.includes(o.code, notAllowed)
|
||||
return isDevMode ? data : {...data, accounts: _.filter(a => !filterOut(a), data.accounts)}
|
||||
}
|
||||
|
||||
function fetchData () {
|
||||
return machineLoader.getMachineNames()
|
||||
.then(machineList => ({
|
||||
currencies: massageCurrencies(currencies),
|
||||
cryptoCurrencies: [
|
||||
{code: 'BTC', display: 'Bitcoin'},
|
||||
{code: 'ETH', display: 'Ethereum'},
|
||||
{code: 'LTC', display: 'Litecoin'},
|
||||
{code: 'DASH', display: 'Dash'},
|
||||
{code: 'ZEC', display: 'Zcash'},
|
||||
{code: 'BCH', display: 'Bitcoin Cash'}
|
||||
],
|
||||
languages: languages,
|
||||
countries,
|
||||
accounts: [
|
||||
{code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC', 'BCH']},
|
||||
{code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||
{code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'itbit', display: 'itBit', class: 'ticker', cryptos: ['BTC']},
|
||||
{code: 'mock-ticker', display: 'Mock (Caution!)', class: 'ticker', cryptos: ALL_CRYPTOS},
|
||||
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
|
||||
{code: 'no-layer2', display: 'No Layer 2', class: 'layer2', cryptos: ALL_CRYPTOS},
|
||||
{code: 'infura', display: 'Infura', class: 'wallet', cryptos: ['ETH']},
|
||||
{code: 'geth', display: 'geth', class: 'wallet', cryptos: ['ETH']},
|
||||
{code: 'zcashd', display: 'zcashd', class: 'wallet', cryptos: ['ZEC']},
|
||||
{code: 'litecoind', display: 'litecoind', class: 'wallet', cryptos: ['LTC']},
|
||||
{code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']},
|
||||
{code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']},
|
||||
{code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC', 'ZEC', 'LTC', 'BCH', 'DASH']},
|
||||
{code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'itbit', display: 'itBit', class: 'exchange', cryptos: ['BTC']},
|
||||
{code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||
{code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS},
|
||||
{code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS},
|
||||
{code: 'mock-exchange', display: 'Mock exchange', class: 'exchange', cryptos: ALL_CRYPTOS},
|
||||
{code: 'mock-sms', display: 'Mock SMS', class: 'sms'},
|
||||
{code: 'mock-id-verify', display: 'Mock ID verifier', class: 'idVerifier'},
|
||||
{code: 'twilio', display: 'Twilio', class: 'sms'},
|
||||
{code: 'mailgun', display: 'Mailgun', class: 'email'},
|
||||
{code: 'all-zero-conf', display: 'Always 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']},
|
||||
{code: 'no-zero-conf', display: 'Always 1-conf', class: 'zeroConf', cryptos: ALL_CRYPTOS},
|
||||
{code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf', cryptos: ['BTC']},
|
||||
{code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH', 'ETH']}
|
||||
],
|
||||
machines: machineList.map(machine => ({machine: machine.deviceId, display: machine.name}))
|
||||
}))
|
||||
.then((data) => {
|
||||
return filterAccounts(data, devMode)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
saveConfig,
|
||||
getConfig,
|
||||
fetchData
|
||||
}
|
||||
46
lib/new-admin/config/accounts.js
Normal file
46
lib/new-admin/config/accounts.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
const { COINS, ALL_CRYPTOS } = require('./coins')
|
||||
|
||||
const { BTC, BCH, DASH, ETH, LTC, ZEC } = COINS
|
||||
|
||||
const TICKER = 'ticker'
|
||||
const WALLET = 'wallet'
|
||||
const LAYER_2 = 'layer2'
|
||||
const EXCHANGE = 'exchange'
|
||||
const SMS = 'sms'
|
||||
const ID_VERIFIER = 'idVerifier'
|
||||
const EMAIL = 'email'
|
||||
const ZERO_CONF = 'zeroConf'
|
||||
|
||||
const ACCOUNT_LIST = [
|
||||
{ code: 'bitpay', display: 'Bitpay', class: TICKER, cryptos: [BTC, BCH] },
|
||||
{ code: 'kraken', display: 'Kraken', class: TICKER, cryptos: [BTC, ETH, LTC, DASH, ZEC, BCH] },
|
||||
{ code: 'bitstamp', display: 'Bitstamp', class: TICKER, cryptos: [BTC, ETH, LTC, BCH] },
|
||||
{ code: 'coinbase', display: 'Coinbase', class: TICKER, cryptos: [BTC, ETH, LTC, BCH] },
|
||||
{ code: 'itbit', display: 'itBit', class: TICKER, cryptos: [BTC] },
|
||||
{ code: 'mock-ticker', display: 'Mock (Caution!)', class: TICKER, cryptos: ALL_CRYPTOS },
|
||||
{ code: 'bitcoind', display: 'bitcoind', class: WALLET, cryptos: [BTC] },
|
||||
{ code: 'no-layer2', display: 'No Layer 2', class: LAYER_2, cryptos: ALL_CRYPTOS },
|
||||
{ code: 'infura', display: 'Infura', class: WALLET, cryptos: [ETH] },
|
||||
{ code: 'geth', display: 'geth', class: WALLET, cryptos: [ETH] },
|
||||
{ code: 'zcashd', display: 'zcashd', class: WALLET, cryptos: [ZEC] },
|
||||
{ code: 'litecoind', display: 'litecoind', class: WALLET, cryptos: [LTC] },
|
||||
{ code: 'dashd', display: 'dashd', class: WALLET, cryptos: [DASH] },
|
||||
{ code: 'bitcoincashd', display: 'bitcoincashd', class: WALLET, cryptos: [BCH] },
|
||||
{ code: 'bitgo', display: 'BitGo', class: WALLET, cryptos: [BTC, ZEC, LTC, BCH, DASH] },
|
||||
{ code: 'bitstamp', display: 'Bitstamp', class: EXCHANGE, cryptos: [BTC, ETH, LTC, BCH] },
|
||||
{ code: 'itbit', display: 'itBit', class: EXCHANGE, cryptos: [BTC] },
|
||||
{ code: 'kraken', display: 'Kraken', class: EXCHANGE, cryptos: [BTC, ETH, LTC, DASH, ZEC, BCH] },
|
||||
{ code: 'mock-wallet', display: 'Mock (Caution!)', class: WALLET, cryptos: ALL_CRYPTOS },
|
||||
{ code: 'no-exchange', display: 'No exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS },
|
||||
{ code: 'mock-exchange', display: 'Mock exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS },
|
||||
{ code: 'mock-sms', display: 'Mock SMS', class: SMS },
|
||||
{ code: 'mock-id-verify', display: 'Mock ID verifier', class: ID_VERIFIER },
|
||||
{ code: 'twilio', display: 'Twilio', class: SMS },
|
||||
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
|
||||
{ code: 'all-zero-conf', display: 'Always 0-conf', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH] },
|
||||
{ code: 'no-zero-conf', display: 'Always 1-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS },
|
||||
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
|
||||
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH, ETH] }
|
||||
]
|
||||
|
||||
module.exports = { ACCOUNT_LIST }
|
||||
23
lib/new-admin/config/coins.js
Normal file
23
lib/new-admin/config/coins.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
const _ = require('lodash/fp')
|
||||
|
||||
const COINS = {
|
||||
BTC: 'BTC',
|
||||
ETH: 'ETH',
|
||||
LTC: 'LTC',
|
||||
DASH: 'DASH',
|
||||
ZEC: 'ZEC',
|
||||
BCH: 'BCH'
|
||||
}
|
||||
|
||||
const COIN_LIST = [
|
||||
{ code: COINS.BTC, display: 'Bitcoin' },
|
||||
{ code: COINS.ETH, display: 'Ethereum' },
|
||||
{ code: COINS.LTC, display: 'Litecoin' },
|
||||
{ code: COINS.DASH, display: 'Dash' },
|
||||
{ code: COINS.ZEC, display: 'Zcash' },
|
||||
{ code: COINS.BCH, display: 'Bitcoin Cash' }
|
||||
]
|
||||
|
||||
const ALL_CRYPTOS = _.keys(COINS)
|
||||
|
||||
module.exports = { COINS, ALL_CRYPTOS, COIN_LIST }
|
||||
39
lib/new-admin/config/index.js
Normal file
39
lib/new-admin/config/index.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
const _ = require('lodash/fp')
|
||||
|
||||
const { COIN_LIST: coins } = require('./coins')
|
||||
const { ACCOUNT_LIST: accounts } = require('./accounts')
|
||||
|
||||
const countries = require('../../../countries.json')
|
||||
const currenciesRec = require('../../../currencies.json')
|
||||
const languageRec = require('../../../languages.json')
|
||||
|
||||
function massageCurrencies (currencies) {
|
||||
const convert = r => ({
|
||||
code: r['Alphabetic Code'],
|
||||
display: r['Currency']
|
||||
})
|
||||
const top5Codes = ['USD', 'EUR', 'GBP', 'CAD', 'AUD']
|
||||
const mapped = _.map(convert, currencies)
|
||||
const codeToRec = code => _.find(_.matchesProperty('code', code), mapped)
|
||||
const top5 = _.map(codeToRec, top5Codes)
|
||||
const raw = _.uniqBy(_.get('code'), _.concat(top5, mapped))
|
||||
return raw.filter(r => r.code[0] !== 'X' && r.display.indexOf('(') === -1)
|
||||
}
|
||||
|
||||
const mapLanguage = lang => {
|
||||
const arr = lang.split('-')
|
||||
const code = arr[0]
|
||||
const country = arr[1]
|
||||
const langNameArr = languageRec.lang[code]
|
||||
if (!langNameArr) return null
|
||||
const langName = langNameArr[0]
|
||||
if (!country) return { code: lang, display: langName }
|
||||
return { code: lang, display: `${langName} [${country}]` }
|
||||
}
|
||||
|
||||
const supportedLanguages = languageRec.supported
|
||||
|
||||
const languages = supportedLanguages.map(mapLanguage).filter(r => r)
|
||||
const currencies = massageCurrencies(currenciesRec)
|
||||
|
||||
module.exports = { coins, accounts, countries, currencies, languages }
|
||||
23
lib/new-admin/graphql-dev-insecure.js
Normal file
23
lib/new-admin/graphql-dev-insecure.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
const bodyParser = require('body-parser')
|
||||
const express = require('express')
|
||||
const { ApolloServer } = require('apollo-server-express')
|
||||
|
||||
const { typeDefs, resolvers } = require('./graphql/schema')
|
||||
|
||||
const app = express()
|
||||
const server = new ApolloServer({
|
||||
typeDefs,
|
||||
resolvers
|
||||
})
|
||||
|
||||
server.applyMiddleware({ app })
|
||||
|
||||
app.use(bodyParser.json())
|
||||
|
||||
function run () {
|
||||
const serverLog = `lamassu-admin-server listening on port ${8080}${server.graphqlPath}`
|
||||
|
||||
app.listen(8080, () => console.log(serverLog))
|
||||
}
|
||||
|
||||
module.exports = { run }
|
||||
197
lib/new-admin/graphql/schema.js
Normal file
197
lib/new-admin/graphql/schema.js
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
const { gql } = require('apollo-server-express')
|
||||
const { GraphQLDateTime } = require('graphql-iso-date')
|
||||
const { GraphQLJSON, GraphQLJSONObject } = require('graphql-type-json')
|
||||
const got = require('got')
|
||||
|
||||
const machineLoader = require('../../machine-loader')
|
||||
const logs = require('../../logs')
|
||||
const supportLogs = require('../../support_logs')
|
||||
const settingsLoader = require('../../new-settings-loader')
|
||||
|
||||
const serverVersion = require('../../../package.json').version
|
||||
|
||||
const transactions = require('../transactions')
|
||||
const funding = require('../funding')
|
||||
const supervisor = require('../supervisor')
|
||||
const serverLogs = require('../server-logs')
|
||||
const { accounts, coins, countries, currencies, languages } = require('../config')
|
||||
|
||||
// TODO why does server logs messages can be null?
|
||||
const typeDefs = gql`
|
||||
scalar JSON
|
||||
scalar JSONObject
|
||||
scalar Date
|
||||
|
||||
type Currency {
|
||||
code: String!
|
||||
display: String!
|
||||
}
|
||||
|
||||
type CryptoCurrency {
|
||||
code: String!
|
||||
display: String!
|
||||
}
|
||||
|
||||
type Country {
|
||||
code: String!
|
||||
display: String!
|
||||
}
|
||||
|
||||
type Language {
|
||||
code: String!
|
||||
display: String!
|
||||
}
|
||||
|
||||
type Machine {
|
||||
name: String!
|
||||
deviceId: ID!
|
||||
paired: Boolean!
|
||||
cashbox: Int
|
||||
cassette1: Int
|
||||
cassette2: Int
|
||||
}
|
||||
|
||||
type Account {
|
||||
code: String!
|
||||
display: String!
|
||||
class: String!
|
||||
cryptos: [String]
|
||||
}
|
||||
|
||||
type MachineLog {
|
||||
id: ID!
|
||||
logLevel: String!
|
||||
timestamp: Date!
|
||||
message: String!
|
||||
}
|
||||
|
||||
type ServerLog {
|
||||
id: ID!
|
||||
logLevel: String!
|
||||
timestamp: Date!
|
||||
message: String
|
||||
}
|
||||
|
||||
type CoinFunds {
|
||||
cryptoCode: String!
|
||||
fundingAddress: String!
|
||||
fundingAddressUrl: String!
|
||||
confirmedBalance: String!
|
||||
pending: String!
|
||||
fiatConfirmedBalance: String!
|
||||
fiatPending: String!
|
||||
fiatCode: String!
|
||||
display: String!
|
||||
unitScale: String!
|
||||
}
|
||||
|
||||
type ProcessStatus {
|
||||
name: String!
|
||||
state: String!
|
||||
uptime: Date!
|
||||
}
|
||||
|
||||
type Transaction {
|
||||
id: ID!
|
||||
txClass: String!
|
||||
deviceId: ID!
|
||||
toAddress: String
|
||||
cryptoAtoms: String!
|
||||
cryptoCode: String!
|
||||
fiat: String!
|
||||
fiatCode: String!
|
||||
fee: String
|
||||
txHash: String
|
||||
phone: String
|
||||
error: String
|
||||
created: Date
|
||||
send: Boolean
|
||||
sendConfirmed: Boolean
|
||||
timedout: Boolean
|
||||
sendTime: Date
|
||||
errorCode: String
|
||||
operatorCompleted: Boolean
|
||||
sendPending: Boolean
|
||||
cashInFee: String
|
||||
cashInFeeCrypto: String
|
||||
minimumTx: Float
|
||||
customerId: ID
|
||||
txVersion: Int!
|
||||
termsAccepted: Boolean
|
||||
commissionPercentage: String
|
||||
rawTickerPrice: String
|
||||
isPaperWallet: Boolean
|
||||
customerPhone: String
|
||||
customerIdCardDataNumber: String
|
||||
customerIdCardDataExpiration: Date
|
||||
customerIdCardData: JSONObject
|
||||
customerName: String
|
||||
customerFrontCameraPath: String
|
||||
customerIdCardPhotoPath: String
|
||||
expired: Boolean
|
||||
machineName: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
countries: [Country]
|
||||
currencies: [Currency]
|
||||
languages: [Language]
|
||||
accounts: [Account]
|
||||
cryptoCurrencies: [CryptoCurrency]
|
||||
machines: [Machine]
|
||||
machineLogs(deviceId: ID!): [MachineLog]
|
||||
funding: [CoinFunds]
|
||||
serverVersion: String!
|
||||
uptime: [ProcessStatus]
|
||||
serverLogs: [ServerLog]
|
||||
transactions: [Transaction]
|
||||
config: JSONObject
|
||||
}
|
||||
|
||||
type SupportLogsResponse {
|
||||
id: ID!
|
||||
timestamp: Date!
|
||||
deviceId: ID
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
machineSupportLogs(deviceId: ID!): SupportLogsResponse
|
||||
serverSupportLogs: SupportLogsResponse
|
||||
saveConfig(config: JSONObject): JSONObject
|
||||
}
|
||||
`
|
||||
|
||||
const notify = () => got.post('http://localhost:3030/dbChange')
|
||||
.catch(e => console.error('Error: lamassu-server not responding'))
|
||||
|
||||
const resolvers = {
|
||||
JSON: GraphQLJSON,
|
||||
JSONObject: GraphQLJSONObject,
|
||||
Date: GraphQLDateTime,
|
||||
Query: {
|
||||
countries: () => countries,
|
||||
currencies: () => currencies,
|
||||
languages: () => languages,
|
||||
accounts: () => accounts,
|
||||
cryptoCurrencies: () => coins,
|
||||
machines: () => machineLoader.getMachineNames(),
|
||||
funding: () => funding.getFunding(),
|
||||
machineLogs: (...[, { deviceId }]) => logs.simpleGetMachineLogs(deviceId),
|
||||
serverVersion: () => serverVersion,
|
||||
uptime: () => supervisor.getAllProcessInfo(),
|
||||
serverLogs: () => serverLogs.getServerLogs(),
|
||||
transactions: () => transactions.batch(),
|
||||
config: () => settingsLoader.getConfig()
|
||||
},
|
||||
Mutation: {
|
||||
machineSupportLogs: (...[, { deviceId }]) => supportLogs.insert(deviceId),
|
||||
serverSupportLogs: () => serverLogs.insert(),
|
||||
saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config)
|
||||
.then(it => {
|
||||
notify()
|
||||
return it
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { resolvers, typeDefs }
|
||||
48
lib/new-admin/login.js
Normal file
48
lib/new-admin/login.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
const crypto = require('crypto')
|
||||
|
||||
const db = require('../db')
|
||||
|
||||
function generateOTP (name) {
|
||||
const otp = crypto.randomBytes(32).toString('hex')
|
||||
|
||||
const sql = 'insert into one_time_passes (token, name) values ($1, $2)'
|
||||
|
||||
return db.none(sql, [otp, name])
|
||||
.then(() => otp)
|
||||
}
|
||||
|
||||
function validateOTP (otp) {
|
||||
const sql = `delete from one_time_passes
|
||||
where token=$1
|
||||
returning name, created < now() - interval '1 hour' as expired`
|
||||
|
||||
return db.one(sql, [otp])
|
||||
.then(r => ({ success: !r.expired, expired: r.expired, name: r.name }))
|
||||
.catch(() => ({ success: false, expired: false }))
|
||||
}
|
||||
|
||||
function register (otp) {
|
||||
return validateOTP(otp)
|
||||
.then(r => {
|
||||
if (!r.success) return r
|
||||
|
||||
const token = crypto.randomBytes(32).toString('hex')
|
||||
const sql = 'insert into user_tokens (token, name) values ($1, $2)'
|
||||
|
||||
return db.none(sql, [token, r.name])
|
||||
.then(() => ({ success: true, token: token }))
|
||||
})
|
||||
.catch(() => ({ success: false, expired: false }))
|
||||
}
|
||||
|
||||
function authenticate (token) {
|
||||
const sql = 'select token from user_tokens where token=$1'
|
||||
|
||||
return db.one(sql, [token]).then(() => true).catch(() => false)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateOTP,
|
||||
register,
|
||||
authenticate
|
||||
}
|
||||
|
|
@ -10,10 +10,8 @@ function getServerLogs (until = new Date().toISOString()) {
|
|||
order by timestamp desc
|
||||
limit $1`
|
||||
|
||||
return Promise.all([db.any(sql, [ NUM_RESULTS ])])
|
||||
.then(([logs]) => ({
|
||||
logs: _.map(_.mapKeys(_.camelCase), logs)
|
||||
}))
|
||||
return db.any(sql, [ NUM_RESULTS ])
|
||||
.then(_.map(_.mapKeys(_.camelCase)))
|
||||
}
|
||||
|
||||
function insert () {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function saveConfig (config) {
|
|||
.then(() => newState)
|
||||
}
|
||||
|
||||
function getConfig (config) {
|
||||
function getConfig () {
|
||||
return db.getState()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue