Merge pull request #873 from chaotixkilla/feat-integrate-hedging-and-accounting-with-pazuz-admin
Integrate hedging and accounting with pazuz related screens
This commit is contained in:
commit
d138b26903
40 changed files with 761 additions and 386 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { asyncLocalStorage, defaultStore } = require('../lib/async-storage')
|
||||||
const authentication = require('../lib/new-admin/graphql/modules/authentication')
|
const authentication = require('../lib/new-admin/graphql/modules/authentication')
|
||||||
const options = require('../lib/options')
|
const options = require('../lib/options')
|
||||||
|
|
||||||
|
|
@ -29,20 +30,22 @@ if (role !== 'user' && role !== 'superuser') {
|
||||||
process.exit(2)
|
process.exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
authentication.createRegisterToken(name, role).then(token => {
|
asyncLocalStorage.run(defaultStore(), () => {
|
||||||
if (!token) {
|
authentication.createRegisterToken(name, role).then(token => {
|
||||||
console.log(`A user named ${name} already exists!`)
|
if (!token) {
|
||||||
process.exit(2)
|
console.log(`A user named ${name} already exists!`)
|
||||||
}
|
process.exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
if (domain === 'localhost') {
|
if (domain === 'localhost') {
|
||||||
console.log(`https://${domain}:3001/register?t=${token.token}`)
|
console.log(`https://${domain}:3001/register?t=${token.token}`)
|
||||||
} else {
|
} else {
|
||||||
console.log(`https://${domain}/register?t=${token.token}`)
|
console.log(`https://${domain}/register?t=${token.token}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.log('Error: %s', err)
|
console.log('Error: %s', err)
|
||||||
process.exit(3)
|
process.exit(3)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,7 @@ function loadSanctions (settings) {
|
||||||
function startServer (settings) {
|
function startServer (settings) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
poller.start(settings)
|
poller.setup(['public'])
|
||||||
|
|
||||||
const httpsServerOptions = {
|
const httpsServerOptions = {
|
||||||
key: fs.readFileSync(options.keyPath),
|
key: fs.readFileSync(options.keyPath),
|
||||||
cert: fs.readFileSync(options.certPath),
|
cert: fs.readFileSync(options.certPath),
|
||||||
|
|
|
||||||
8
lib/compute-schema.js
Normal file
8
lib/compute-schema.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
const { asyncLocalStorage, defaultStore } = require('./async-storage')
|
||||||
|
|
||||||
|
const computeSchema = (req, res, next) => {
|
||||||
|
const store = defaultStore()
|
||||||
|
return asyncLocalStorage.run(store, () => next())
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = computeSchema
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
const T = require('./time')
|
||||||
|
|
||||||
const anonymousCustomer = {
|
const anonymousCustomer = {
|
||||||
uuid: '47ac1184-8102-11e7-9079-8f13a7117867',
|
uuid: '47ac1184-8102-11e7-9079-8f13a7117867',
|
||||||
name: 'anonymous'
|
name: 'anonymous'
|
||||||
|
|
@ -8,6 +10,8 @@ const cassetteMaxCapacity = 500
|
||||||
const AUTHENTICATOR_ISSUER_ENTITY = 'Lamassu'
|
const AUTHENTICATOR_ISSUER_ENTITY = 'Lamassu'
|
||||||
const AUTH_TOKEN_EXPIRATION_TIME = '30 minutes'
|
const AUTH_TOKEN_EXPIRATION_TIME = '30 minutes'
|
||||||
const REGISTRATION_TOKEN_EXPIRATION_TIME = '30 minutes'
|
const REGISTRATION_TOKEN_EXPIRATION_TIME = '30 minutes'
|
||||||
|
const USER_SESSIONS_TABLE_NAME = 'user_sessions'
|
||||||
|
const USER_SESSIONS_CLEAR_INTERVAL = 1 * T.hour
|
||||||
|
|
||||||
const AUTOMATIC = 'automatic'
|
const AUTOMATIC = 'automatic'
|
||||||
const MANUAL = 'manual'
|
const MANUAL = 'manual'
|
||||||
|
|
@ -19,5 +23,7 @@ module.exports = {
|
||||||
AUTH_TOKEN_EXPIRATION_TIME,
|
AUTH_TOKEN_EXPIRATION_TIME,
|
||||||
REGISTRATION_TOKEN_EXPIRATION_TIME,
|
REGISTRATION_TOKEN_EXPIRATION_TIME,
|
||||||
AUTOMATIC,
|
AUTOMATIC,
|
||||||
MANUAL
|
MANUAL,
|
||||||
|
USER_SESSIONS_TABLE_NAME,
|
||||||
|
USER_SESSIONS_CLEAR_INTERVAL
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ const stripDefaultDbFuncs = dbCtx => {
|
||||||
tx: dbCtx.$tx,
|
tx: dbCtx.$tx,
|
||||||
task: dbCtx.$task,
|
task: dbCtx.$task,
|
||||||
batch: dbCtx.batch,
|
batch: dbCtx.batch,
|
||||||
multi: dbCtx.$multi
|
multi: dbCtx.$multi,
|
||||||
|
connect: dbCtx.connect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +38,10 @@ const _task = (obj, opts, cb) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSchema = () => 'public'
|
const getSchema = () => {
|
||||||
|
const store = asyncLocalStorage.getStore() ?? defaultStore()
|
||||||
|
return asyncLocalStorage.run(store, () => store.get('schema'))
|
||||||
|
}
|
||||||
const getDefaultSchema = () => 'ERROR_SCHEMA'
|
const getDefaultSchema = () => 'ERROR_SCHEMA'
|
||||||
|
|
||||||
const searchPathWrapper = (t, cb) => {
|
const searchPathWrapper = (t, cb) => {
|
||||||
|
|
|
||||||
|
|
@ -157,18 +157,37 @@ function unpair (rec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reboot (rec) {
|
function reboot (rec) {
|
||||||
return axios.post(`http://localhost:3030/reboot?device_id=${rec.deviceId}`)
|
return db.none('NOTIFY $1:name, $2', ['poller', JSON.stringify(
|
||||||
|
{
|
||||||
|
type: 'machineAction',
|
||||||
|
action: 'reboot',
|
||||||
|
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
||||||
|
}
|
||||||
|
)])
|
||||||
}
|
}
|
||||||
|
|
||||||
function shutdown (rec) {
|
function shutdown (rec) {
|
||||||
return axios.post(`http://localhost:3030/shutdown?device_id=${rec.deviceId}`)
|
return db.none('NOTIFY $1:name, $2', ['poller', JSON.stringify(
|
||||||
|
{
|
||||||
|
type: 'machineAction',
|
||||||
|
action: 'shutdown',
|
||||||
|
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
||||||
|
}
|
||||||
|
)])
|
||||||
}
|
}
|
||||||
|
|
||||||
function restartServices (rec) {
|
function restartServices (rec) {
|
||||||
return axios.post(`http://localhost:3030/restartServices?device_id=${rec.deviceId}`)
|
return db.none('NOTIFY $1:name, $2', ['poller', JSON.stringify(
|
||||||
|
{
|
||||||
|
type: 'machineAction',
|
||||||
|
action: 'restartServices',
|
||||||
|
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
||||||
|
}
|
||||||
|
)])
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMachine (rec) {
|
function setMachine (rec, operatorId) {
|
||||||
|
rec.operatorId = operatorId
|
||||||
switch (rec.action) {
|
switch (rec.action) {
|
||||||
case 'rename': return renameMachine(rec)
|
case 'rename': return renameMachine(rec)
|
||||||
case 'emptyCashInBills': return emptyCashInBills(rec)
|
case 'emptyCashInBills': return emptyCashInBills(rec)
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,12 @@ const options = require('../options')
|
||||||
const users = require('../users')
|
const users = require('../users')
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
|
|
||||||
const session = require('./middlewares/session')
|
|
||||||
const { AuthDirective } = require('./graphql/directives')
|
const { AuthDirective } = require('./graphql/directives')
|
||||||
const { typeDefs, resolvers } = require('./graphql/schema')
|
const { typeDefs, resolvers } = require('./graphql/schema')
|
||||||
|
const findOperatorId = require('../middlewares/operatorId')
|
||||||
|
const computeSchema = require('../compute-schema')
|
||||||
|
const { USER_SESSIONS_CLEAR_INTERVAL } = require('../constants')
|
||||||
|
const { session, cleanUserSessions, buildApolloContext } = require('./middlewares')
|
||||||
|
|
||||||
const devMode = require('minimist')(process.argv.slice(2)).dev
|
const devMode = require('minimist')(process.argv.slice(2)).dev
|
||||||
const idPhotoCardBasedir = _.get('idPhotoCardDir', options)
|
const idPhotoCardBasedir = _.get('idPhotoCardDir', options)
|
||||||
|
|
@ -32,6 +35,7 @@ if (!hostname) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
app.use(helmet())
|
app.use(helmet())
|
||||||
app.use(compression())
|
app.use(compression())
|
||||||
app.use(nocache())
|
app.use(nocache())
|
||||||
|
|
@ -39,6 +43,9 @@ app.use(cookieParser())
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
app.use(express.urlencoded({ extended: true })) // support encoded bodies
|
app.use(express.urlencoded({ extended: true })) // support encoded bodies
|
||||||
app.use(express.static(path.resolve(__dirname, '..', '..', 'public')))
|
app.use(express.static(path.resolve(__dirname, '..', '..', 'public')))
|
||||||
|
app.use(cleanUserSessions(USER_SESSIONS_CLEAR_INTERVAL))
|
||||||
|
app.use(computeSchema)
|
||||||
|
app.use(findOperatorId)
|
||||||
app.use(session)
|
app.use(session)
|
||||||
|
|
||||||
const apolloServer = new ApolloServer({
|
const apolloServer = new ApolloServer({
|
||||||
|
|
@ -53,27 +60,7 @@ const apolloServer = new ApolloServer({
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
return error
|
return error
|
||||||
},
|
},
|
||||||
context: async ({ req, res }) => {
|
context: async (obj) => buildApolloContext(obj)
|
||||||
if (!req.session.user) return { req }
|
|
||||||
|
|
||||||
const user = await users.verifyAndUpdateUser(
|
|
||||||
req.session.user.id,
|
|
||||||
req.headers['user-agent'] || 'Unknown',
|
|
||||||
req.ip
|
|
||||||
)
|
|
||||||
if (!user || !user.enabled) throw new AuthenticationError('Authentication failed')
|
|
||||||
|
|
||||||
req.session.ua = req.headers['user-agent'] || 'Unknown'
|
|
||||||
req.session.ipAddress = req.ip
|
|
||||||
req.session.lastUsed = new Date(Date.now()).toISOString()
|
|
||||||
req.session.user.id = user.id
|
|
||||||
req.session.user.role = user.role
|
|
||||||
|
|
||||||
res.set('role', user.role)
|
|
||||||
res.set('Access-Control-Expose-Headers', 'role')
|
|
||||||
|
|
||||||
return { req }
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
apolloServer.applyMiddleware({
|
apolloServer.applyMiddleware({
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ const authenticateUser = (username, password) => {
|
||||||
|
|
||||||
const destroySessionIfSameUser = (context, user) => {
|
const destroySessionIfSameUser = (context, user) => {
|
||||||
const sessionUser = getUserFromCookie(context)
|
const sessionUser = getUserFromCookie(context)
|
||||||
if (sessionUser && user.id === sessionUser.id)
|
if (sessionUser && user.id === sessionUser.id) { context.req.session.destroy() }
|
||||||
context.req.session.destroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const destroySessionIfBeingUsed = (sessID, context) => {
|
const destroySessionIfBeingUsed = (sessID, context) => {
|
||||||
|
|
@ -45,7 +44,7 @@ const getUserFromCookie = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLamassuCookie = context => {
|
const getLamassuCookie = context => {
|
||||||
return context.req.cookies && context.req.cookies.lid
|
return context.req.cookies && context.req.cookies.lamassu_sid
|
||||||
}
|
}
|
||||||
|
|
||||||
const initializeSession = (context, user, rememberMe) => {
|
const initializeSession = (context, user, rememberMe) => {
|
||||||
|
|
@ -60,7 +59,7 @@ const executeProtectedAction = (code, id, context, action) => {
|
||||||
if (user.role !== 'superuser') {
|
if (user.role !== 'superuser') {
|
||||||
return action()
|
return action()
|
||||||
}
|
}
|
||||||
|
|
||||||
return confirm2FA(code, context)
|
return confirm2FA(code, context)
|
||||||
.then(() => action())
|
.then(() => action())
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const resolvers = {
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
setCustomer: (root, { customerId, customerInput }, context, info) => {
|
setCustomer: (root, { customerId, customerInput }, context, info) => {
|
||||||
const token = !!context.req.cookies.lid && context.req.session.user.id
|
const token = !!context.req.cookies.lamassu_sid && context.req.session.user.id
|
||||||
if (customerId === anonymous.uuid) return customers.getCustomerById(customerId)
|
if (customerId === anonymous.uuid) return customers.getCustomerById(customerId)
|
||||||
return customers.updateCustomer(customerId, customerInput, token)
|
return customers.updateCustomer(customerId, customerInput, token)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ const resolvers = {
|
||||||
machine: (...[, { deviceId }]) => machineLoader.getMachine(deviceId)
|
machine: (...[, { deviceId }]) => machineLoader.getMachine(deviceId)
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, newName }]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, newName })
|
machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, newName }, context]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, newName }, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
const got = require('got')
|
|
||||||
|
|
||||||
const logger = require('../../../logger')
|
|
||||||
const settingsLoader = require('../../../new-settings-loader')
|
const settingsLoader = require('../../../new-settings-loader')
|
||||||
|
|
||||||
const notify = () => got.post('http://localhost:3030/dbChange')
|
|
||||||
.catch(e => logger.error('lamassu-server not responding'))
|
|
||||||
|
|
||||||
const resolvers = {
|
const resolvers = {
|
||||||
Query: {
|
Query: {
|
||||||
accounts: () => settingsLoader.showAccounts(),
|
accounts: () => settingsLoader.showAccounts(),
|
||||||
|
|
@ -14,10 +8,7 @@ const resolvers = {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
saveAccounts: (...[, { accounts }]) => settingsLoader.saveAccounts(accounts),
|
saveAccounts: (...[, { accounts }]) => settingsLoader.saveAccounts(accounts),
|
||||||
// resetAccounts: (...[, { schemaVersion }]) => settingsLoader.resetAccounts(schemaVersion),
|
// resetAccounts: (...[, { schemaVersion }]) => settingsLoader.resetAccounts(schemaVersion),
|
||||||
saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config).then(it => {
|
saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config),
|
||||||
notify()
|
|
||||||
return it
|
|
||||||
}),
|
|
||||||
// resetConfig: (...[, { schemaVersion }]) => settingsLoader.resetConfig(schemaVersion),
|
// resetConfig: (...[, { schemaVersion }]) => settingsLoader.resetConfig(schemaVersion),
|
||||||
// migrateConfigAndAccounts: () => settingsLoader.migrate()
|
// migrateConfigAndAccounts: () => settingsLoader.migrate()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
lib/new-admin/middlewares/cleanUserSessions.js
Normal file
24
lib/new-admin/middlewares/cleanUserSessions.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
const { asyncLocalStorage } = require('../../async-storage')
|
||||||
|
const db = require('../../db')
|
||||||
|
const { USER_SESSIONS_TABLE_NAME } = require('../../constants')
|
||||||
|
const logger = require('../../logger')
|
||||||
|
|
||||||
|
const schemaCache = {}
|
||||||
|
|
||||||
|
const cleanUserSessions = (cleanInterval) => (req, res, next) => {
|
||||||
|
const schema = asyncLocalStorage.getStore() ? asyncLocalStorage.getStore().get('schema') : null
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
if (!schema) return next()
|
||||||
|
if (schema && schemaCache.schema + cleanInterval > now) return next()
|
||||||
|
|
||||||
|
logger.debug(`Clearing expired sessions for schema ${schema}`)
|
||||||
|
return db.none('DELETE FROM $1^ WHERE expire < to_timestamp($2 / 1000.0)', [USER_SESSIONS_TABLE_NAME, now])
|
||||||
|
.then(() => {
|
||||||
|
schemaCache.schema = now
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = cleanUserSessions
|
||||||
29
lib/new-admin/middlewares/context.js
Normal file
29
lib/new-admin/middlewares/context.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
const { AuthenticationError } = require('apollo-server-express')
|
||||||
|
const base64 = require('base-64')
|
||||||
|
const users = require('../../users')
|
||||||
|
|
||||||
|
const buildApolloContext = async ({ req, res }) => {
|
||||||
|
if (!req.session.user) return { req, res }
|
||||||
|
|
||||||
|
const user = await users.verifyAndUpdateUser(
|
||||||
|
req.session.user.id,
|
||||||
|
req.headers['user-agent'] || 'Unknown',
|
||||||
|
req.ip
|
||||||
|
)
|
||||||
|
if (!user || !user.enabled) throw new AuthenticationError('Authentication failed')
|
||||||
|
|
||||||
|
req.session.ua = req.headers['user-agent'] || 'Unknown'
|
||||||
|
req.session.ipAddress = req.ip
|
||||||
|
req.session.lastUsed = new Date(Date.now()).toISOString()
|
||||||
|
req.session.user.id = user.id
|
||||||
|
req.session.user.username = user.username
|
||||||
|
req.session.user.role = user.role
|
||||||
|
|
||||||
|
res.set('lamassu_role', user.role)
|
||||||
|
res.cookie('pazuz_operatoridentifier', base64.encode(user.username))
|
||||||
|
res.set('Access-Control-Expose-Headers', 'lamassu_role')
|
||||||
|
|
||||||
|
return { req, res }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = buildApolloContext
|
||||||
9
lib/new-admin/middlewares/index.js
Normal file
9
lib/new-admin/middlewares/index.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
const cleanUserSessions = require('./cleanUserSessions')
|
||||||
|
const buildApolloContext = require('./context')
|
||||||
|
const session = require('./session')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
cleanUserSessions,
|
||||||
|
buildApolloContext,
|
||||||
|
session
|
||||||
|
}
|
||||||
|
|
@ -3,10 +3,11 @@ const express = require('express')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const hkdf = require('futoin-hkdf')
|
const hkdf = require('futoin-hkdf')
|
||||||
const session = require('express-session')
|
const session = require('express-session')
|
||||||
const pgSession = require('connect-pg-simple')(session)
|
const PgSession = require('connect-pg-simple')(session)
|
||||||
const mnemonicHelpers = require('../../mnemonic-helpers')
|
const mnemonicHelpers = require('../../mnemonic-helpers')
|
||||||
const db = require('../../db')
|
const db = require('../../db')
|
||||||
const options = require('../../options')
|
const options = require('../../options')
|
||||||
|
const { USER_SESSIONS_TABLE_NAME } = require('../../constants')
|
||||||
|
|
||||||
const getSecret = () => {
|
const getSecret = () => {
|
||||||
const mnemonic = fs.readFileSync(options.mnemonicPath, 'utf8')
|
const mnemonic = fs.readFileSync(options.mnemonicPath, 'utf8')
|
||||||
|
|
@ -20,11 +21,11 @@ const getSecret = () => {
|
||||||
const hostname = options.hostname
|
const hostname = options.hostname
|
||||||
|
|
||||||
router.use('*', session({
|
router.use('*', session({
|
||||||
store: new pgSession({
|
store: new PgSession({
|
||||||
pgPromise: db,
|
pgPromise: db,
|
||||||
tableName: 'user_sessions'
|
tableName: USER_SESSIONS_TABLE_NAME
|
||||||
}),
|
}),
|
||||||
name: 'lid',
|
name: 'lamassu_sid',
|
||||||
secret: getSecret(),
|
secret: getSecret(),
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@ function getMachine (machineId) {
|
||||||
.then(machines => machines.find(({ deviceId }) => deviceId === machineId))
|
.then(machines => machines.find(({ deviceId }) => deviceId === machineId))
|
||||||
}
|
}
|
||||||
|
|
||||||
function machineAction ({ deviceId, action, cashbox, cassette1, cassette2, newName }) {
|
function machineAction ({ deviceId, action, cashbox, cassette1, cassette2, newName }, context) {
|
||||||
|
const operatorId = context.res.locals.operatorId
|
||||||
return getMachine(deviceId)
|
return getMachine(deviceId)
|
||||||
.then(machine => {
|
.then(machine => {
|
||||||
if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
|
if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
|
||||||
return machine
|
return machine
|
||||||
})
|
})
|
||||||
.then(machineLoader.setMachine({ deviceId, action, cashbox, cassettes: [cassette1, cassette2], newName }))
|
.then(machineLoader.setMachine({ deviceId, action, cashbox, cassettes: [cassette1, cassette2], newName }, operatorId))
|
||||||
.then(getMachine(deviceId))
|
.then(getMachine(deviceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const pify = require('pify')
|
||||||
const readFile = pify(fs.readFile)
|
const readFile = pify(fs.readFile)
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const baseX = require('base-x')
|
const baseX = require('base-x')
|
||||||
|
const { NIL } = require('uuid')
|
||||||
|
|
||||||
const options = require('../../options')
|
const options = require('../../options')
|
||||||
const db = require('../../db')
|
const db = require('../../db')
|
||||||
|
|
@ -19,7 +20,7 @@ function totem (name) {
|
||||||
return readFile(caPath)
|
return readFile(caPath)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const caHash = crypto.createHash('sha256').update(data).digest()
|
const caHash = crypto.createHash('sha256').update(data).digest()
|
||||||
const token = crypto.randomBytes(32)
|
const token = Buffer.concat([crypto.randomBytes(32), NIL])
|
||||||
const hexToken = token.toString('hex')
|
const hexToken = token.toString('hex')
|
||||||
const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex')
|
const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex')
|
||||||
const buf = Buffer.concat([caHash, token, Buffer.from(options.hostname)])
|
const buf = Buffer.concat([caHash, token, Buffer.from(options.hostname)])
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const migration = require('./config-migration')
|
const migration = require('./config-migration')
|
||||||
|
const { asyncLocalStorage } = require('./async-storage')
|
||||||
|
|
||||||
const OLD_SETTINGS_LOADER_SCHEMA_VERSION = 1
|
const OLD_SETTINGS_LOADER_SCHEMA_VERSION = 1
|
||||||
const NEW_SETTINGS_LOADER_SCHEMA_VERSION = 2
|
const NEW_SETTINGS_LOADER_SCHEMA_VERSION = 2
|
||||||
|
|
@ -73,7 +74,10 @@ function saveConfig (config) {
|
||||||
return loadLatestConfigOrNone()
|
return loadLatestConfigOrNone()
|
||||||
.then(currentConfig => {
|
.then(currentConfig => {
|
||||||
const newConfig = _.assign(currentConfig, config)
|
const newConfig = _.assign(currentConfig, config)
|
||||||
return db.none(configSql, ['config', { config: newConfig }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
|
return db.tx(t => {
|
||||||
|
return t.none(configSql, ['config', { config: newConfig }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
|
||||||
|
.then(() => t.none('NOTIFY $1:name, $2', ['poller', JSON.stringify({ type: 'reload', schema: asyncLocalStorage.getStore().get('schema') })]))
|
||||||
|
}).catch(console.error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ const _ = require('lodash/fp')
|
||||||
|
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
|
|
||||||
|
const DATABASE = process.env.LAMASSU_DB ?? 'DEV'
|
||||||
|
|
||||||
const dbMapping = psqlConf => ({
|
const dbMapping = psqlConf => ({
|
||||||
STRESS_TEST: _.replace('lamassu', 'lamassu_stress', psqlConf),
|
STRESS_TEST: _.replace('lamassu', 'lamassu_stress', psqlConf),
|
||||||
RELEASE: _.replace('lamassu', 'lamassu_release', psqlConf),
|
RELEASE: _.replace('lamassu', 'lamassu_release', psqlConf),
|
||||||
|
|
@ -39,7 +41,7 @@ function load () {
|
||||||
opts: JSON.parse(fs.readFileSync(globalConfigPath))
|
opts: JSON.parse(fs.readFileSync(globalConfigPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
config.opts.postgresql = dbMapping(config.opts.postgresql)[process.env.LAMASSU_DB]
|
config.opts.postgresql = dbMapping(config.opts.postgresql)[DATABASE]
|
||||||
|
|
||||||
return config
|
return config
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
@ -50,7 +52,7 @@ function load () {
|
||||||
opts: JSON.parse(fs.readFileSync(homeConfigPath))
|
opts: JSON.parse(fs.readFileSync(homeConfigPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
config.opts.postgresql = dbMapping(config.opts.postgresql)[process.env.LAMASSU_DB]
|
config.opts.postgresql = dbMapping(config.opts.postgresql)[DATABASE]
|
||||||
|
|
||||||
return config
|
return config
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
|
||||||
192
lib/poller.js
192
lib/poller.js
|
|
@ -1,5 +1,5 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
const Queue = require('queue-promise')
|
||||||
const plugins = require('./plugins')
|
const plugins = require('./plugins')
|
||||||
const notifier = require('./notifier')
|
const notifier = require('./notifier')
|
||||||
const T = require('./time')
|
const T = require('./time')
|
||||||
|
|
@ -11,6 +11,12 @@ const sanctions = require('./ofac/index')
|
||||||
const coinAtmRadar = require('./coinatmradar/coinatmradar')
|
const coinAtmRadar = require('./coinatmradar/coinatmradar')
|
||||||
const configManager = require('./new-config-manager')
|
const configManager = require('./new-config-manager')
|
||||||
const complianceTriggers = require('./compliance-triggers')
|
const complianceTriggers = require('./compliance-triggers')
|
||||||
|
const { asyncLocalStorage, defaultStore } = require('./async-storage')
|
||||||
|
const settingsLoader = require('./new-settings-loader')
|
||||||
|
const NodeCache = require('node-cache')
|
||||||
|
const util = require('util')
|
||||||
|
const db = require('./db')
|
||||||
|
const state = require('./middlewares/state')
|
||||||
|
|
||||||
const INCOMING_TX_INTERVAL = 30 * T.seconds
|
const INCOMING_TX_INTERVAL = 30 * T.seconds
|
||||||
const LIVE_INCOMING_TX_INTERVAL = 5 * T.seconds
|
const LIVE_INCOMING_TX_INTERVAL = 5 * T.seconds
|
||||||
|
|
@ -24,25 +30,106 @@ const LOGS_CLEAR_INTERVAL = 1 * T.day
|
||||||
const SANCTIONS_INITIAL_DOWNLOAD_INTERVAL = 5 * T.minutes
|
const SANCTIONS_INITIAL_DOWNLOAD_INTERVAL = 5 * T.minutes
|
||||||
const SANCTIONS_UPDATE_INTERVAL = 1 * T.week
|
const SANCTIONS_UPDATE_INTERVAL = 1 * T.week
|
||||||
const RADAR_UPDATE_INTERVAL = 5 * T.minutes
|
const RADAR_UPDATE_INTERVAL = 5 * T.minutes
|
||||||
const PRUNE_MACHINES_HEARBEAT = 1 * T.day
|
const PRUNE_MACHINES_HEARTBEAT = 1 * T.day
|
||||||
|
|
||||||
const CHECK_NOTIFICATION_INTERVAL = 20 * T.seconds
|
const CHECK_NOTIFICATION_INTERVAL = 20 * T.seconds
|
||||||
|
|
||||||
const PENDING_INTERVAL = 10 * T.seconds
|
const PENDING_INTERVAL = 10 * T.seconds
|
||||||
|
const CACHE_ENTRY_TTL = 3600 // seconds
|
||||||
|
|
||||||
const coinFilter = ['ETH']
|
const FAST_QUEUE_WAIT = 1 * T.seconds
|
||||||
|
const SLOW_QUEUE_WAIT = 10 * T.seconds
|
||||||
|
|
||||||
let _pi, _settings
|
const FAST_QUEUE = new Queue({
|
||||||
|
concurrent: 600,
|
||||||
|
interval: FAST_QUEUE_WAIT
|
||||||
|
})
|
||||||
|
|
||||||
function reload (__settings) {
|
const SLOW_QUEUE = new Queue({
|
||||||
_settings = __settings
|
concurrent: 10,
|
||||||
_pi = plugins(_settings)
|
interval: SLOW_QUEUE_WAIT
|
||||||
logger.debug('settings reloaded in poller')
|
})
|
||||||
updateAndLoadSanctions()
|
|
||||||
|
// Fix for asyncLocalStorage store being lost due to callback-based queue
|
||||||
|
FAST_QUEUE.enqueue = util.promisify(FAST_QUEUE.enqueue)
|
||||||
|
SLOW_QUEUE.enqueue = util.promisify(SLOW_QUEUE.enqueue)
|
||||||
|
|
||||||
|
const QUEUE = {
|
||||||
|
FAST: FAST_QUEUE,
|
||||||
|
SLOW: SLOW_QUEUE
|
||||||
}
|
}
|
||||||
|
|
||||||
function pi () { return _pi }
|
const coinFilter = ['ETH']
|
||||||
function settings () { return _settings }
|
const schemaCallbacks = new Map()
|
||||||
|
|
||||||
|
const cachedVariables = new NodeCache({
|
||||||
|
stdTTL: CACHE_ENTRY_TTL,
|
||||||
|
checkperiod: CACHE_ENTRY_TTL,
|
||||||
|
deleteOnExpire: false,
|
||||||
|
useClones: false // pass values by reference instead of cloning
|
||||||
|
})
|
||||||
|
|
||||||
|
cachedVariables.on('expired', (key, val) => {
|
||||||
|
if (!val.isReloading) {
|
||||||
|
// since val is passed by reference we don't need to do cachedVariables.set()
|
||||||
|
val.isReloading = true
|
||||||
|
return reload(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
db.connect({ direct: true }).then(sco => {
|
||||||
|
sco.client.on('notification', data => {
|
||||||
|
const parsedData = JSON.parse(data.payload)
|
||||||
|
switch (parsedData.type) {
|
||||||
|
case 'reload':
|
||||||
|
return reload(parsedData.schema)
|
||||||
|
case 'machineAction':
|
||||||
|
return machineAction(parsedData.action, parsedData.value)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return sco.none('LISTEN $1:name', 'poller')
|
||||||
|
}).catch(console.error)
|
||||||
|
|
||||||
|
function reload (schema) {
|
||||||
|
const store = defaultStore()
|
||||||
|
store.set('schema', schema)
|
||||||
|
// set asyncLocalStorage so settingsLoader loads settings for the right schema
|
||||||
|
return asyncLocalStorage.run(store, () => {
|
||||||
|
return settingsLoader.loadLatest().then(settings => {
|
||||||
|
const pi = plugins(settings)
|
||||||
|
cachedVariables.set(schema, { settings, pi, isReloading: false })
|
||||||
|
logger.debug(`Settings for schema '${schema}' reloaded in poller`)
|
||||||
|
return updateAndLoadSanctions()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pi () { return cachedVariables.get(asyncLocalStorage.getStore().get('schema')).pi }
|
||||||
|
function settings () { return cachedVariables.get(asyncLocalStorage.getStore().get('schema')).settings }
|
||||||
|
|
||||||
function initialSanctionsDownload () {
|
function initialSanctionsDownload () {
|
||||||
const structs = sanctions.getStructs()
|
const structs = sanctions.getStructs()
|
||||||
|
|
@ -70,9 +157,40 @@ function updateCoinAtmRadar () {
|
||||||
.then(rates => coinAtmRadar.update(rates, settings()))
|
.then(rates => coinAtmRadar.update(rates, settings()))
|
||||||
}
|
}
|
||||||
|
|
||||||
function start (__settings) {
|
function initializeEachSchema (schemas = ['public']) {
|
||||||
reload(__settings)
|
// for each schema set "thread variables" and do polling
|
||||||
|
return _.forEach(schema => {
|
||||||
|
const store = defaultStore()
|
||||||
|
store.set('schema', schema)
|
||||||
|
return asyncLocalStorage.run(store, () => {
|
||||||
|
return settingsLoader.loadLatest().then(settings => {
|
||||||
|
// prevent inadvertedly clearing the array without clearing timeouts
|
||||||
|
if (schemaCallbacks.has(schema)) throw new Error(`The schema "${schema}" cannot be initialized twice on poller`)
|
||||||
|
const pi = plugins(settings)
|
||||||
|
cachedVariables.set(schema, { settings, pi, isReloading: false })
|
||||||
|
schemaCallbacks.set(schema, [])
|
||||||
|
return doPolling(schema)
|
||||||
|
})
|
||||||
|
}).catch(console.error)
|
||||||
|
}, schemas)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToQueue (func, interval, schema, queue, ...vars) {
|
||||||
|
return schemaCallbacks.get(schema).push(setInterval(() => {
|
||||||
|
return queue.enqueue().then(() => {
|
||||||
|
// get plugins or settings from the cache every time func is run
|
||||||
|
const loadVariables = vars.length > 0 && typeof vars[0] === 'function'
|
||||||
|
if (loadVariables) {
|
||||||
|
const funcVars = [...vars]
|
||||||
|
funcVars[0] = vars[0]()
|
||||||
|
return func(...funcVars)
|
||||||
|
}
|
||||||
|
return func(...vars)
|
||||||
|
}).catch(console.error)
|
||||||
|
}, interval))
|
||||||
|
}
|
||||||
|
|
||||||
|
function doPolling (schema) {
|
||||||
pi().executeTrades()
|
pi().executeTrades()
|
||||||
pi().pong()
|
pi().pong()
|
||||||
pi().clearOldLogs()
|
pi().clearOldLogs()
|
||||||
|
|
@ -87,23 +205,37 @@ function start (__settings) {
|
||||||
notifier.checkNotification(pi())
|
notifier.checkNotification(pi())
|
||||||
updateCoinAtmRadar()
|
updateCoinAtmRadar()
|
||||||
|
|
||||||
setInterval(() => pi().executeTrades(), TRADE_INTERVAL)
|
addToQueue(pi().executeTrades, TRADE_INTERVAL, schema, QUEUE.FAST)
|
||||||
setInterval(() => cashOutTx.monitorLiveIncoming(settings(), false, coinFilter), LIVE_INCOMING_TX_INTERVAL)
|
addToQueue(cashOutTx.monitorLiveIncoming, LIVE_INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings, false, coinFilter)
|
||||||
setInterval(() => cashOutTx.monitorStaleIncoming(settings(), false, coinFilter), INCOMING_TX_INTERVAL)
|
addToQueue(cashOutTx.monitorStaleIncoming, INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings, false, coinFilter)
|
||||||
if (!_.isEmpty(coinFilter)) {
|
if (!_.isEmpty(coinFilter)) {
|
||||||
setInterval(() => cashOutTx.monitorLiveIncoming(settings(), true, coinFilter), LIVE_INCOMING_TX_INTERVAL_FILTER)
|
addToQueue(cashOutTx.monitorLiveIncoming, LIVE_INCOMING_TX_INTERVAL_FILTER, schema, QUEUE.FAST, settings, true, coinFilter)
|
||||||
setInterval(() => cashOutTx.monitorStaleIncoming(settings(), true, coinFilter), INCOMING_TX_INTERVAL_FILTER)
|
addToQueue(cashOutTx.monitorStaleIncoming, INCOMING_TX_INTERVAL_FILTER, schema, QUEUE.FAST, settings, true, coinFilter)
|
||||||
}
|
}
|
||||||
setInterval(() => cashOutTx.monitorUnnotified(settings()), UNNOTIFIED_INTERVAL)
|
addToQueue(cashOutTx.monitorUnnotified, UNNOTIFIED_INTERVAL, schema, QUEUE.FAST, settings)
|
||||||
setInterval(() => cashInTx.monitorPending(settings()), PENDING_INTERVAL)
|
addToQueue(cashInTx.monitorPending, PENDING_INTERVAL, schema, QUEUE.FAST, settings)
|
||||||
setInterval(() => pi().sweepHd(), SWEEP_HD_INTERVAL)
|
addToQueue(pi().sweepHd, SWEEP_HD_INTERVAL, schema, QUEUE.FAST, settings)
|
||||||
setInterval(() => pi().pong(), PONG_INTERVAL)
|
addToQueue(pi().pong, PONG_INTERVAL, schema, QUEUE.FAST)
|
||||||
setInterval(() => pi().clearOldLogs(), LOGS_CLEAR_INTERVAL)
|
addToQueue(pi().clearOldLogs, LOGS_CLEAR_INTERVAL, schema, QUEUE.SLOW)
|
||||||
setInterval(() => notifier.checkNotification(pi()), CHECK_NOTIFICATION_INTERVAL)
|
addToQueue(notifier.checkNotification, CHECK_NOTIFICATION_INTERVAL, schema, QUEUE.FAST, pi)
|
||||||
setInterval(initialSanctionsDownload, SANCTIONS_INITIAL_DOWNLOAD_INTERVAL)
|
addToQueue(initialSanctionsDownload, SANCTIONS_INITIAL_DOWNLOAD_INTERVAL, schema, QUEUE.SLOW)
|
||||||
setInterval(updateAndLoadSanctions, SANCTIONS_UPDATE_INTERVAL)
|
addToQueue(updateAndLoadSanctions, SANCTIONS_UPDATE_INTERVAL, schema, QUEUE.SLOW)
|
||||||
setInterval(updateCoinAtmRadar, RADAR_UPDATE_INTERVAL)
|
addToQueue(updateCoinAtmRadar, RADAR_UPDATE_INTERVAL, schema, QUEUE.SLOW)
|
||||||
setInterval(() => pi().pruneMachinesHeartbeat(), PRUNE_MACHINES_HEARBEAT)
|
addToQueue(pi().pruneMachinesHeartbeat(), PRUNE_MACHINES_HEARTBEAT, schema, QUEUE.SLOW, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { start, reload }
|
function setup (schemasToAdd = [], schemasToRemove = []) {
|
||||||
|
// clear callback array for each schema in schemasToRemove and clear cached variables
|
||||||
|
_.forEach(schema => {
|
||||||
|
const callbacks = schemaCallbacks.get(schema)
|
||||||
|
_.forEach(clearInterval, callbacks)
|
||||||
|
schemaCallbacks.delete(schema)
|
||||||
|
cachedVariables.del(schema)
|
||||||
|
}, schemasToRemove)
|
||||||
|
|
||||||
|
return initializeEachSchema(schemasToAdd)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getActiveSchemas = () => Array.from(schemaCallbacks.keys())
|
||||||
|
|
||||||
|
module.exports = { setup, reload, getActiveSchemas }
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,12 @@ const phoneCodeRoutes = require('./routes/phoneCodeRoutes')
|
||||||
const pollingRoutes = require('./routes/pollingRoutes')
|
const pollingRoutes = require('./routes/pollingRoutes')
|
||||||
const stateRoutes = require('./routes/stateRoutes')
|
const stateRoutes = require('./routes/stateRoutes')
|
||||||
const termsAndConditionsRoutes = require('./routes/termsAndConditionsRoutes')
|
const termsAndConditionsRoutes = require('./routes/termsAndConditionsRoutes')
|
||||||
const txRoutes = require('./routes/txRoutes')
|
const { router: txRoutes } = require('./routes/txRoutes')
|
||||||
const verifyUserRoutes = require('./routes/verifyUserRoutes')
|
const verifyUserRoutes = require('./routes/verifyUserRoutes')
|
||||||
const verifyTxRoutes = require('./routes/verifyTxRoutes')
|
const verifyTxRoutes = require('./routes/verifyTxRoutes')
|
||||||
const verifyPromoCodeRoutes = require('./routes/verifyPromoCodeRoutes')
|
const verifyPromoCodeRoutes = require('./routes/verifyPromoCodeRoutes')
|
||||||
|
|
||||||
const localAppRoutes = require('./routes/localAppRoutes')
|
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const localApp = express()
|
|
||||||
|
|
||||||
const configRequiredRoutes = [
|
const configRequiredRoutes = [
|
||||||
'/poll',
|
'/poll',
|
||||||
|
|
@ -87,7 +84,4 @@ app.use((req, res) => {
|
||||||
res.status(404).json({ error: 'No such route' })
|
res.status(404).json({ error: 'No such route' })
|
||||||
})
|
})
|
||||||
|
|
||||||
// localapp routes
|
module.exports = { app }
|
||||||
localApp.use('/', localAppRoutes)
|
|
||||||
|
|
||||||
module.exports = { app, localApp }
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const { getCashInSettings } = require('../new-config-manager')
|
||||||
const { AUTOMATIC } = require('../constants.js')
|
const { AUTOMATIC } = require('../constants.js')
|
||||||
|
|
||||||
function notifyCashboxRemoval (req, res, next) {
|
function notifyCashboxRemoval (req, res, next) {
|
||||||
|
const operatorId = res.locals.operatorId
|
||||||
return Promise.all([getMachine(req.deviceId), loadLatestConfig()])
|
return Promise.all([getMachine(req.deviceId), loadLatestConfig()])
|
||||||
.then(([machine, config]) => {
|
.then(([machine, config]) => {
|
||||||
const cashInSettings = getCashInSettings(config)
|
const cashInSettings = getCashInSettings(config)
|
||||||
|
|
@ -15,7 +16,7 @@ function notifyCashboxRemoval (req, res, next) {
|
||||||
return res.status(200).send({ status: 'OK' })
|
return res.status(200).send({ status: 'OK' })
|
||||||
}
|
}
|
||||||
return cashbox.createCashboxBatch(req.deviceId, machine.cashbox)
|
return cashbox.createCashboxBatch(req.deviceId, machine.cashbox)
|
||||||
.then(() => setMachine({ deviceId: req.deviceId, action: 'emptyCashInBills' }))
|
.then(() => setMachine({ deviceId: req.deviceId, action: 'emptyCashInBills' }, operatorId))
|
||||||
.then(() => res.status(200).send({ status: 'OK' }))
|
.then(() => res.status(200).send({ status: 'OK' }))
|
||||||
})
|
})
|
||||||
.catch(next)
|
.catch(next)
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
const express = require('express')
|
|
||||||
const router = express.Router()
|
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = router
|
|
||||||
|
|
@ -31,6 +31,7 @@ function poll (req, res, next) {
|
||||||
const serialNumber = req.query.sn
|
const serialNumber = req.query.sn
|
||||||
const pid = req.query.pid
|
const pid = req.query.pid
|
||||||
const settings = req.settings
|
const settings = req.settings
|
||||||
|
const operatorId = res.locals.operatorId
|
||||||
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
||||||
const zeroConfLimits = _.reduce((acc, cryptoCode) => {
|
const zeroConfLimits = _.reduce((acc, cryptoCode) => {
|
||||||
acc[cryptoCode] = configManager.getWalletSettings(cryptoCode, settings.config).zeroConfLimit
|
acc[cryptoCode] = configManager.getWalletSettings(cryptoCode, settings.config).zeroConfLimit
|
||||||
|
|
@ -48,15 +49,15 @@ function poll (req, res, next) {
|
||||||
const receipt = configManager.getReceipt(settings.config)
|
const receipt = configManager.getReceipt(settings.config)
|
||||||
const terms = configManager.getTermsConditions(settings.config)
|
const terms = configManager.getTermsConditions(settings.config)
|
||||||
|
|
||||||
state.pids[deviceId] = { pid, ts: Date.now() }
|
state.pids[operatorId] = { [deviceId]: { pid, ts: Date.now() } }
|
||||||
|
|
||||||
return pi.pollQueries(serialNumber, deviceTime, req.query, machineVersion, machineModel)
|
return pi.pollQueries(serialNumber, deviceTime, req.query, machineVersion, machineModel)
|
||||||
.then(results => {
|
.then(results => {
|
||||||
const cassettes = results.cassettes
|
const cassettes = results.cassettes
|
||||||
|
|
||||||
const reboot = pid && state.reboots[deviceId] && state.reboots[deviceId] === pid
|
const reboot = pid && state.reboots?.[operatorId]?.[deviceId] === pid
|
||||||
const shutdown = pid && state.shutdowns[deviceId] && state.shutdowns[deviceId] === pid
|
const shutdown = pid && state.shutdowns?.[operatorId]?.[deviceId] === pid
|
||||||
const restartServices = pid && state.restartServicesMap[deviceId] && state.restartServicesMap[deviceId] === pid
|
const restartServices = pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid
|
||||||
const langs = localeConfig.languages
|
const langs = localeConfig.languages
|
||||||
|
|
||||||
const locale = {
|
const locale = {
|
||||||
|
|
|
||||||
|
|
@ -66,4 +66,4 @@ router.post('/', postTx)
|
||||||
router.get('/:id', getTx)
|
router.get('/:id', getTx)
|
||||||
router.get('/', getPhoneTx)
|
router.get('/', getPhoneTx)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = { postTx, getTx, getPhoneTx, router }
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ function verifyAndUpdateUser (id, ua, ip) {
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (!user) return null
|
if (!user) return null
|
||||||
|
|
||||||
const sql2 = `UPDATE users SET last_accessed=now(), last_accessed_from=$1, last_accessed_address=$2 WHERE id=$3 RETURNING id, role, enabled`
|
const sql2 = `UPDATE users SET last_accessed=now(), last_accessed_from=$1, last_accessed_address=$2 WHERE id=$3 RETURNING id, username, role, enabled`
|
||||||
return db.one(sql2, [ua, ip, id])
|
return db.one(sql2, [ua, ip, id])
|
||||||
})
|
})
|
||||||
.then(user => user)
|
.then(user => user)
|
||||||
|
|
|
||||||
5
new-lamassu-admin/package-lock.json
generated
5
new-lamassu-admin/package-lock.json
generated
|
|
@ -8192,6 +8192,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"base-64": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
|
||||||
|
},
|
||||||
"base-x": {
|
"base-x": {
|
||||||
"version": "3.0.8",
|
"version": "3.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"apollo-link-error": "^1.1.13",
|
"apollo-link-error": "^1.1.13",
|
||||||
"apollo-link-http": "^1.5.17",
|
"apollo-link-http": "^1.5.17",
|
||||||
"axios": "0.21.1",
|
"axios": "0.21.1",
|
||||||
|
"base-64": "^1.0.0",
|
||||||
"bignumber.js": "9.0.0",
|
"bignumber.js": "9.0.0",
|
||||||
"classnames": "2.2.6",
|
"classnames": "2.2.6",
|
||||||
"countries-and-timezones": "^2.4.0",
|
"countries-and-timezones": "^2.4.0",
|
||||||
|
|
@ -96,8 +97,8 @@
|
||||||
"storybook": "start-storybook -p 9009 -s public",
|
"storybook": "start-storybook -p 9009 -s public",
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
"build-storybook": "build-storybook -s public",
|
"build-storybook": "build-storybook -s public",
|
||||||
"start-lamassu": "REACT_APP_BUILD_TARGET=LAMASSU react-scripts start",
|
"lamassu": "REACT_APP_BUILD_TARGET=LAMASSU react-scripts start",
|
||||||
"start-pazuz": "REACT_APP_BUILD_TARGET=PAZUZ react-scripts start"
|
"pazuz": "REACT_APP_BUILD_TARGET=PAZUZ react-scripts start"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ const App = () => {
|
||||||
const [userData, setUserData] = useState(null)
|
const [userData, setUserData] = useState(null)
|
||||||
|
|
||||||
const setRole = role => {
|
const setRole = role => {
|
||||||
if (userData && userData.role !== role) {
|
if (userData && role && userData.role !== role) {
|
||||||
setUserData({ ...userData, role })
|
setUserData({ ...userData, role })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import { Paper } from '@material-ui/core'
|
import { Paper } from '@material-ui/core'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React, { useContext } from 'react'
|
||||||
|
|
||||||
|
import AppContext from 'src/AppContext'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import { H3, Info2, Label2, Label3, P } from 'src/components/typography'
|
import { H3, Info2, Label2, Label3, P } from 'src/components/typography'
|
||||||
import { ReactComponent as BitcoinLogo } from 'src/styling/logos/icon-bitcoin-colour.svg'
|
import { ReactComponent as BitcoinLogo } from 'src/styling/logos/icon-bitcoin-colour.svg'
|
||||||
|
|
@ -17,6 +20,40 @@ import styles from './ATMWallet.styles'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const GET_OPERATOR_BY_USERNAME = gql`
|
||||||
|
query operatorByUsername($username: String) {
|
||||||
|
operatorByUsername(username: $username) {
|
||||||
|
id
|
||||||
|
entityId
|
||||||
|
name
|
||||||
|
fiatBalances
|
||||||
|
cryptoBalances
|
||||||
|
machines
|
||||||
|
joined
|
||||||
|
assetValue
|
||||||
|
preferredFiatCurrency
|
||||||
|
contactInfo {
|
||||||
|
name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
fundings {
|
||||||
|
id
|
||||||
|
origin
|
||||||
|
destination
|
||||||
|
fiatAmount
|
||||||
|
fiatBalanceAfter
|
||||||
|
fiatCurrency
|
||||||
|
created
|
||||||
|
status
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const formatCurrency = amount =>
|
||||||
|
amount.toLocaleString('en-US', { maximumFractionDigits: 2 })
|
||||||
|
|
||||||
const CHIPS_PER_ROW = 6
|
const CHIPS_PER_ROW = 6
|
||||||
|
|
||||||
const Assets = ({ balance, wallets, currency }) => {
|
const Assets = ({ balance, wallets, currency }) => {
|
||||||
|
|
@ -32,10 +69,10 @@ const Assets = ({ balance, wallets, currency }) => {
|
||||||
<P className={classes.fieldHeader}>Available balance</P>
|
<P className={classes.fieldHeader}>Available balance</P>
|
||||||
<div className={classes.totalAssetWrapper}>
|
<div className={classes.totalAssetWrapper}>
|
||||||
<Info2 noMargin className={classes.fieldValue}>
|
<Info2 noMargin className={classes.fieldValue}>
|
||||||
{balance.toLocaleString('en-US', { maximumFractionDigits: 2 })}
|
{formatCurrency(balance)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{currency}
|
{R.toUpper(currency)}
|
||||||
</Info2>
|
</Info2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -44,12 +81,10 @@ const Assets = ({ balance, wallets, currency }) => {
|
||||||
<P className={classes.fieldHeader}>Total balance in wallets</P>
|
<P className={classes.fieldHeader}>Total balance in wallets</P>
|
||||||
<div className={classes.totalAssetWrapper}>
|
<div className={classes.totalAssetWrapper}>
|
||||||
<Info2 noMargin className={classes.fieldValue}>
|
<Info2 noMargin className={classes.fieldValue}>
|
||||||
{walletFiatSum().toLocaleString('en-US', {
|
{formatCurrency(walletFiatSum())}
|
||||||
maximumFractionDigits: 2
|
|
||||||
})}
|
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{currency}
|
{R.toUpper(currency)}
|
||||||
</Info2>
|
</Info2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -58,10 +93,10 @@ const Assets = ({ balance, wallets, currency }) => {
|
||||||
<P className={classes.fieldHeader}>Total assets</P>
|
<P className={classes.fieldHeader}>Total assets</P>
|
||||||
<div className={classes.totalAssetWrapper}>
|
<div className={classes.totalAssetWrapper}>
|
||||||
<Info2 noMargin className={classes.fieldValue}>
|
<Info2 noMargin className={classes.fieldValue}>
|
||||||
{balance.toLocaleString('en-US', { maximumFractionDigits: 2 })}
|
{formatCurrency(balance)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{currency}
|
{R.toUpper(currency)}
|
||||||
</Info2>
|
</Info2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -129,66 +164,82 @@ const WalletInfoChip = ({ wallet, currency }) => {
|
||||||
|
|
||||||
const ATMWallet = () => {
|
const ATMWallet = () => {
|
||||||
const classes = useStyles({ numberOfChips: CHIPS_PER_ROW })
|
const classes = useStyles({ numberOfChips: CHIPS_PER_ROW })
|
||||||
|
const { userData } = useContext(AppContext)
|
||||||
|
|
||||||
|
const { data, loading } = useQuery(GET_OPERATOR_BY_USERNAME, {
|
||||||
|
context: { clientName: 'pazuz' },
|
||||||
|
variables: { username: userData?.username }
|
||||||
|
})
|
||||||
|
|
||||||
|
const operatorData = R.path(['operatorByUsername'], data)
|
||||||
|
|
||||||
const wallets = [
|
const wallets = [
|
||||||
{
|
{
|
||||||
cryptoCode: 'BTC',
|
cryptoCode: 'BTC',
|
||||||
name: 'Bitcoin',
|
name: 'Bitcoin',
|
||||||
amount: 2.7,
|
amount: operatorData?.cryptoBalances.xbt ?? 0,
|
||||||
fiatValue: 81452,
|
fiatValue: 0,
|
||||||
isHedged: true
|
isHedged: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cryptoCode: 'ETH',
|
cryptoCode: 'ETH',
|
||||||
name: 'Ethereum',
|
name: 'Ethereum',
|
||||||
amount: 4.1,
|
amount: operatorData?.cryptoBalances.eth ?? 0,
|
||||||
fiatValue: 4924,
|
fiatValue: 0,
|
||||||
isHedged: true
|
isHedged: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cryptoCode: 'LTC',
|
cryptoCode: 'LTC',
|
||||||
name: 'Litecoin',
|
name: 'Litecoin',
|
||||||
amount: 15,
|
amount: operatorData?.cryptoBalances.ltc ?? 0,
|
||||||
fiatValue: 3016,
|
fiatValue: 0,
|
||||||
isHedged: true
|
isHedged: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cryptoCode: 'ZEC',
|
cryptoCode: 'ZEC',
|
||||||
name: 'Z-Cash',
|
name: 'Z-Cash',
|
||||||
amount: 20,
|
amount: operatorData?.cryptoBalances.zec ?? 0,
|
||||||
fiatValue: 2887,
|
fiatValue: 0,
|
||||||
isHedged: false
|
isHedged: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cryptoCode: 'BCH',
|
cryptoCode: 'BCH',
|
||||||
name: 'Bitcoin Cash',
|
name: 'Bitcoin Cash',
|
||||||
amount: 10.7,
|
amount: operatorData?.cryptoBalances.bch ?? 0,
|
||||||
fiatValue: 7074,
|
fiatValue: 0,
|
||||||
isHedged: true
|
isHedged: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cryptoCode: 'DASH',
|
cryptoCode: 'DASH',
|
||||||
name: 'Dash',
|
name: 'Dash',
|
||||||
amount: 10.7,
|
amount: operatorData?.cryptoBalances.dash ?? 0,
|
||||||
fiatValue: 1091,
|
fiatValue: 0,
|
||||||
isHedged: false
|
isHedged: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
!loading && (
|
||||||
<TitleSection title="ATM Wallets" />
|
<>
|
||||||
<Assets balance={8952} wallets={wallets} currency={'USD'} />
|
<TitleSection title="ATM Wallets" />
|
||||||
<H3 className={classes.walletChipTitle}>ATM Wallets</H3>
|
<Assets
|
||||||
<div className={classes.walletChipList}>
|
balance={
|
||||||
{R.map(
|
operatorData.fiatBalances[operatorData.preferredFiatCurrency]
|
||||||
it => (
|
}
|
||||||
<WalletInfoChip wallet={it} currency={'USD'} />
|
wallets={wallets}
|
||||||
),
|
currency={operatorData.preferredFiatCurrency}
|
||||||
wallets
|
/>
|
||||||
)}
|
<H3 className={classes.walletChipTitle}>ATM Wallets</H3>
|
||||||
</div>
|
<div className={classes.walletChipList}>
|
||||||
</>
|
{R.map(
|
||||||
|
it => (
|
||||||
|
<WalletInfoChip wallet={it} currency={'USD'} />
|
||||||
|
),
|
||||||
|
wallets
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import React from 'react'
|
import * as R from 'ramda'
|
||||||
|
import React, { useContext } from 'react'
|
||||||
|
|
||||||
|
import AppContext from 'src/AppContext'
|
||||||
import { Tooltip } from 'src/components/Tooltip'
|
import { Tooltip } from 'src/components/Tooltip'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import DataTable from 'src/components/tables/DataTable'
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
|
|
@ -9,47 +13,42 @@ import { H4, Info2, P } from 'src/components/typography'
|
||||||
|
|
||||||
import styles from './Accounting.styles'
|
import styles from './Accounting.styles'
|
||||||
|
|
||||||
const mockData = [
|
|
||||||
{
|
|
||||||
operation: 'Hedging summary',
|
|
||||||
direction: 'in',
|
|
||||||
extraInfo: 'This is mocked information',
|
|
||||||
amount: 486,
|
|
||||||
currency: 'USD',
|
|
||||||
balanceAfterTx: 10438,
|
|
||||||
date: '2021-02-22T20:16:12.020Z'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operation: 'Funding transaction',
|
|
||||||
direction: 'in',
|
|
||||||
amount: 2000,
|
|
||||||
currency: 'USD',
|
|
||||||
balanceAfterTx: 9952,
|
|
||||||
date: '2021-02-22T12:40:32.020Z'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operation: 'ZEC hot wallet top up',
|
|
||||||
direction: 'out',
|
|
||||||
amount: 1000,
|
|
||||||
currency: 'USD',
|
|
||||||
balanceAfterTx: 7952,
|
|
||||||
date: '2021-02-21T16:30:44.020Z'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operation: 'Funding transaction',
|
|
||||||
direction: 'in',
|
|
||||||
amount: 8000,
|
|
||||||
currency: 'USD',
|
|
||||||
balanceAfterTx: 8952,
|
|
||||||
date: '2021-02-21T08:16:20.020Z'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const formatCurrency = amount =>
|
const formatCurrency = amount =>
|
||||||
amount.toLocaleString('en-US', { maximumFractionDigits: 2 })
|
amount.toLocaleString('en-US', { maximumFractionDigits: 2 })
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const GET_OPERATOR_BY_USERNAME = gql`
|
||||||
|
query operatorByUsername($username: String) {
|
||||||
|
operatorByUsername(username: $username) {
|
||||||
|
id
|
||||||
|
entityId
|
||||||
|
name
|
||||||
|
fiatBalances
|
||||||
|
cryptoBalances
|
||||||
|
machines
|
||||||
|
joined
|
||||||
|
assetValue
|
||||||
|
preferredFiatCurrency
|
||||||
|
contactInfo {
|
||||||
|
name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
fundings {
|
||||||
|
id
|
||||||
|
origin
|
||||||
|
destination
|
||||||
|
fiatAmount
|
||||||
|
fiatBalanceAfter
|
||||||
|
fiatCurrency
|
||||||
|
created
|
||||||
|
status
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const Assets = ({ balance, hedgingReserve, currency }) => {
|
const Assets = ({ balance, hedgingReserve, currency }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
|
@ -62,7 +61,7 @@ const Assets = ({ balance, hedgingReserve, currency }) => {
|
||||||
{formatCurrency(balance)}
|
{formatCurrency(balance)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{currency}
|
{R.toUpper(currency)}
|
||||||
</Info2>
|
</Info2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -74,7 +73,7 @@ const Assets = ({ balance, hedgingReserve, currency }) => {
|
||||||
{formatCurrency(hedgingReserve)}
|
{formatCurrency(hedgingReserve)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{currency}
|
{R.toUpper(currency)}
|
||||||
</Info2>
|
</Info2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -86,7 +85,7 @@ const Assets = ({ balance, hedgingReserve, currency }) => {
|
||||||
{formatCurrency(balance - hedgingReserve)}
|
{formatCurrency(balance - hedgingReserve)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{currency}
|
{R.toUpper(currency)}
|
||||||
</Info2>
|
</Info2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -96,6 +95,14 @@ const Assets = ({ balance, hedgingReserve, currency }) => {
|
||||||
|
|
||||||
const Accounting = () => {
|
const Accounting = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const { userData } = useContext(AppContext)
|
||||||
|
|
||||||
|
const { data, loading } = useQuery(GET_OPERATOR_BY_USERNAME, {
|
||||||
|
context: { clientName: 'pazuz' },
|
||||||
|
variables: { username: userData?.username }
|
||||||
|
})
|
||||||
|
|
||||||
|
const operatorData = R.path(['operatorByUsername'], data)
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
|
|
@ -106,7 +113,7 @@ const Accounting = () => {
|
||||||
view: it => {
|
view: it => {
|
||||||
return (
|
return (
|
||||||
<span className={classes.operation}>
|
<span className={classes.operation}>
|
||||||
{it.operation}
|
{it.description}
|
||||||
{!!it.extraInfo && (
|
{!!it.extraInfo && (
|
||||||
<Tooltip width={175}>
|
<Tooltip width={175}>
|
||||||
<P>{it.extraInfo}</P>
|
<P>{it.extraInfo}</P>
|
||||||
|
|
@ -122,18 +129,15 @@ const Accounting = () => {
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it =>
|
view: it =>
|
||||||
`${
|
`${formatCurrency(it.fiatAmount)} ${R.toUpper(it.fiatCurrency)}`
|
||||||
it.direction === 'in'
|
|
||||||
? formatCurrency(it.amount)
|
|
||||||
: formatCurrency(-it.amount)
|
|
||||||
} ${it.currency}`
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Balance after operation',
|
header: 'Balance after operation',
|
||||||
width: 250,
|
width: 250,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it => `${formatCurrency(it.balanceAfterTx)} ${it.currency}`
|
view: it =>
|
||||||
|
`${formatCurrency(it.fiatBalanceAfter)} ${R.toUpper(it.fiatCurrency)}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Date',
|
header: 'Date',
|
||||||
|
|
@ -152,18 +156,26 @@ const Accounting = () => {
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
!loading && (
|
||||||
<TitleSection title="Accounting" />
|
<>
|
||||||
<Assets balance={10438} hedgingReserve={1486} currency={'USD'} />
|
<TitleSection title="Accounting" />
|
||||||
<H4 className={classes.tableTitle}>Fiat balance history</H4>
|
<Assets
|
||||||
<DataTable
|
balance={
|
||||||
loading={false}
|
operatorData.fiatBalances[operatorData.preferredFiatCurrency]
|
||||||
emptyText="No transactions so far"
|
}
|
||||||
elements={elements}
|
hedgingReserve={operatorData.hedgingReserve ?? 0}
|
||||||
data={mockData}
|
currency={operatorData.preferredFiatCurrency}
|
||||||
rowSize="sm"
|
/>
|
||||||
/>
|
<H4 className={classes.tableTitle}>Fiat balance history</H4>
|
||||||
</>
|
<DataTable
|
||||||
|
loading={false}
|
||||||
|
emptyText="No transactions so far"
|
||||||
|
elements={elements}
|
||||||
|
data={operatorData.fundings ?? []}
|
||||||
|
rowSize="sm"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import Grid from '@material-ui/core/Grid'
|
import Grid from '@material-ui/core/Grid'
|
||||||
import Table from '@material-ui/core/Table'
|
import Table from '@material-ui/core/Table'
|
||||||
import TableBody from '@material-ui/core/TableBody'
|
import TableBody from '@material-ui/core/TableBody'
|
||||||
|
|
@ -6,78 +7,47 @@ import TableContainer from '@material-ui/core/TableContainer'
|
||||||
import TableHead from '@material-ui/core/TableHead'
|
import TableHead from '@material-ui/core/TableHead'
|
||||||
import TableRow from '@material-ui/core/TableRow'
|
import TableRow from '@material-ui/core/TableRow'
|
||||||
import { makeStyles, withStyles } from '@material-ui/core/styles'
|
import { makeStyles, withStyles } from '@material-ui/core/styles'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React, { useContext } from 'react'
|
||||||
|
|
||||||
|
import AppContext from 'src/AppContext'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import { H4, Label2, P, Info2 } from 'src/components/typography'
|
import { H4, Label2, P, Info2 } from 'src/components/typography'
|
||||||
|
|
||||||
import styles from './Assets.styles'
|
import styles from './Assets.styles'
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const mockData = [
|
const GET_OPERATOR_BY_USERNAME = gql`
|
||||||
{
|
query operatorByUsername($username: String) {
|
||||||
id: 'fiatBalance',
|
operatorByUsername(username: $username) {
|
||||||
display: 'Fiat balance',
|
id
|
||||||
amount: 10438,
|
entityId
|
||||||
currency: 'USD',
|
name
|
||||||
class: 'Available balance'
|
fiatBalances
|
||||||
},
|
cryptoBalances
|
||||||
{
|
machines
|
||||||
id: 'hedgingReserve',
|
joined
|
||||||
display: 'Hedging reserve',
|
assetValue
|
||||||
amount: -1486,
|
preferredFiatCurrency
|
||||||
currency: 'USD',
|
contactInfo {
|
||||||
class: 'Available balance',
|
name
|
||||||
direction: 'out'
|
email
|
||||||
},
|
}
|
||||||
{
|
fundings {
|
||||||
id: 'hedgedWalletAssets',
|
id
|
||||||
display: 'Hedged wallet assets',
|
origin
|
||||||
amount: 96446,
|
destination
|
||||||
currency: 'USD',
|
fiatAmount
|
||||||
class: 'Wallet assets',
|
fiatBalanceAfter
|
||||||
direction: 'in'
|
fiatCurrency
|
||||||
},
|
created
|
||||||
{
|
status
|
||||||
id: 'unhedgedWalletAssets',
|
description
|
||||||
display: 'Unhedged wallet assets',
|
}
|
||||||
amount: 3978,
|
}
|
||||||
currency: 'USD',
|
|
||||||
class: 'Wallet assets',
|
|
||||||
direction: 'in'
|
|
||||||
}
|
}
|
||||||
]
|
`
|
||||||
|
|
||||||
const mockDataTotal = [
|
|
||||||
{
|
|
||||||
id: 'fiatBalance',
|
|
||||||
display: 'Fiat balance',
|
|
||||||
amount: 10438,
|
|
||||||
currency: 'USD'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'hedgingReserve',
|
|
||||||
display: 'Hedging reserve',
|
|
||||||
amount: -1486,
|
|
||||||
currency: 'USD',
|
|
||||||
direction: 'out'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'hedgedWalletAssets',
|
|
||||||
display: 'Market value of hedged wallet assets',
|
|
||||||
amount: 94980,
|
|
||||||
currency: 'USD',
|
|
||||||
direction: 'in'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'unhedgedWalletAssets',
|
|
||||||
display: 'Unhedged wallet assets',
|
|
||||||
amount: 3978,
|
|
||||||
currency: 'USD',
|
|
||||||
direction: 'in'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const cellStyling = {
|
const cellStyling = {
|
||||||
borderBottom: '4px solid white',
|
borderBottom: '4px solid white',
|
||||||
|
|
@ -163,47 +133,124 @@ const formatCurrency = amount =>
|
||||||
|
|
||||||
const Assets = () => {
|
const Assets = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const { userData } = useContext(AppContext)
|
||||||
|
|
||||||
const filterByClass = x =>
|
const { data, loading } = useQuery(GET_OPERATOR_BY_USERNAME, {
|
||||||
R.filter(it => R.path(['class'])(it) === x)(mockData)
|
context: { clientName: 'pazuz' },
|
||||||
|
variables: { username: userData?.username }
|
||||||
|
})
|
||||||
|
|
||||||
|
const operatorData = R.path(['operatorByUsername'], data)
|
||||||
|
|
||||||
|
const balanceData = [
|
||||||
|
{
|
||||||
|
id: 'fiatBalance',
|
||||||
|
display: 'Fiat balance',
|
||||||
|
amount:
|
||||||
|
operatorData?.fiatBalances[operatorData?.preferredFiatCurrency] ?? 0,
|
||||||
|
currency: R.toUpper(operatorData?.preferredFiatCurrency ?? ''),
|
||||||
|
class: 'Available balance'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hedgingReserve',
|
||||||
|
display: 'Hedging reserve',
|
||||||
|
amount:
|
||||||
|
operatorData?.fiatBalances[operatorData?.preferredFiatCurrency] ?? 0,
|
||||||
|
currency: R.toUpper(operatorData?.preferredFiatCurrency ?? ''),
|
||||||
|
class: 'Available balance',
|
||||||
|
direction: 'out'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const walletData = [
|
||||||
|
{
|
||||||
|
id: 'hedgedWalletAssets',
|
||||||
|
display: 'Hedged wallet assets',
|
||||||
|
amount: 0,
|
||||||
|
currency: R.toUpper(operatorData?.preferredFiatCurrency ?? ''),
|
||||||
|
class: 'Wallet assets',
|
||||||
|
direction: 'in'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'unhedgedWalletAssets',
|
||||||
|
display: 'Unhedged wallet assets',
|
||||||
|
amount: 0,
|
||||||
|
currency: R.toUpper(operatorData?.preferredFiatCurrency ?? ''),
|
||||||
|
class: 'Wallet assets',
|
||||||
|
direction: 'in'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const totalData = [
|
||||||
|
{
|
||||||
|
id: 'fiatBalance',
|
||||||
|
display: 'Fiat balance',
|
||||||
|
amount:
|
||||||
|
operatorData?.fiatBalances[operatorData?.preferredFiatCurrency] ?? 0,
|
||||||
|
currency: R.toUpper(operatorData?.preferredFiatCurrency ?? '')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hedgingReserve',
|
||||||
|
display: 'Hedging reserve',
|
||||||
|
amount: 0,
|
||||||
|
currency: R.toUpper(operatorData?.preferredFiatCurrency ?? ''),
|
||||||
|
direction: 'out'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hedgedWalletAssets',
|
||||||
|
display: 'Market value of hedged wallet assets',
|
||||||
|
amount: 0,
|
||||||
|
currency: R.toUpper(operatorData?.preferredFiatCurrency ?? ''),
|
||||||
|
direction: 'in'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'unhedgedWalletAssets',
|
||||||
|
display: 'Unhedged wallet assets',
|
||||||
|
amount: 0,
|
||||||
|
currency: R.toUpper(operatorData?.preferredFiatCurrency ?? ''),
|
||||||
|
direction: 'in'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
!loading && (
|
||||||
<TitleSection title="Balance sheet" />
|
<>
|
||||||
<div className={classes.root}>
|
<TitleSection title="Balance sheet" />
|
||||||
<Grid container>
|
<div className={classes.root}>
|
||||||
<Grid container direction="column" item xs={5}>
|
<Grid container>
|
||||||
<Grid item xs={12}>
|
<Grid container direction="column" item xs={5}>
|
||||||
<div className={classes.leftSide}>
|
<Grid item xs={12}>
|
||||||
<AssetsAmountTable
|
<div className={classes.leftSide}>
|
||||||
title="Available balance"
|
<AssetsAmountTable
|
||||||
data={filterByClass('Available balance')}
|
title="Available balance"
|
||||||
numToRender={mockData.length}
|
data={balanceData}
|
||||||
/>
|
numToRender={balanceData.length}
|
||||||
</div>
|
/>
|
||||||
<div className={classes.leftSide}>
|
</div>
|
||||||
<AssetsAmountTable
|
<div className={classes.leftSide}>
|
||||||
title="Wallet assets"
|
<AssetsAmountTable
|
||||||
data={filterByClass('Wallet assets')}
|
title="Wallet assets"
|
||||||
numToRender={mockData.length}
|
data={walletData}
|
||||||
/>
|
numToRender={walletData.length}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container direction="column" item xs={7}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div className={classes.rightSide}>
|
||||||
|
<AssetsAmountTable
|
||||||
|
title="Total assets"
|
||||||
|
data={totalData}
|
||||||
|
numToRender={totalData.length}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid container direction="column" item xs={7}>
|
</div>
|
||||||
<Grid item xs={12}>
|
</>
|
||||||
<div className={classes.rightSide}>
|
)
|
||||||
<AssetsAmountTable
|
|
||||||
title="Total assets"
|
|
||||||
data={mockDataTotal}
|
|
||||||
numToRender={mockDataTotal.length}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useMutation, useLazyQuery } from '@apollo/react-hooks'
|
import { useMutation, useLazyQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import base64 from 'base-64'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import React, { useContext, useState } from 'react'
|
import React, { useContext, useState } from 'react'
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
|
|
@ -56,7 +57,17 @@ const Input2FAState = ({ state, dispatch }) => {
|
||||||
|
|
||||||
const [input2FA, { error: mutationError }] = useMutation(INPUT_2FA, {
|
const [input2FA, { error: mutationError }] = useMutation(INPUT_2FA, {
|
||||||
onCompleted: ({ input2FA: success }) => {
|
onCompleted: ({ input2FA: success }) => {
|
||||||
success ? getUserData() : setInvalidToken(true)
|
if (success) {
|
||||||
|
const options = {
|
||||||
|
context: {
|
||||||
|
headers: {
|
||||||
|
'Pazuz-Operator-Identifier': base64.encode(state.clientField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getUserData(options)
|
||||||
|
}
|
||||||
|
return setInvalidToken(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -76,14 +87,21 @@ const Input2FAState = ({ state, dispatch }) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
input2FA({
|
const options = {
|
||||||
variables: {
|
variables: {
|
||||||
username: state.clientField,
|
username: state.clientField,
|
||||||
password: state.passwordField,
|
password: state.passwordField,
|
||||||
code: state.twoFAField,
|
code: state.twoFAField,
|
||||||
rememberMe: state.rememberMeField
|
rememberMe: state.rememberMeField
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
headers: {
|
||||||
|
'Pazuz-Operator-Identifier': base64.encode(state.clientField)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
input2FA(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getErrorMsg = () => {
|
const getErrorMsg = () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useMutation } from '@apollo/react-hooks'
|
import { useMutation } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import base64 from 'base-64'
|
||||||
import { Field, Form, Formik } from 'formik'
|
import { Field, Form, Formik } from 'formik'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
@ -49,12 +50,18 @@ const LoginState = ({ state, dispatch }) => {
|
||||||
const [login, { error: mutationError }] = useMutation(LOGIN)
|
const [login, { error: mutationError }] = useMutation(LOGIN)
|
||||||
|
|
||||||
const submitLogin = async (username, password, rememberMe) => {
|
const submitLogin = async (username, password, rememberMe) => {
|
||||||
const { data: loginResponse } = await login({
|
const options = {
|
||||||
variables: {
|
variables: {
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
headers: {
|
||||||
|
'Pazuz-Operator-Identifier': base64.encode(username)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
const { data: loginResponse } = await login(options)
|
||||||
|
|
||||||
if (!loginResponse.login) return
|
if (!loginResponse.login) return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,10 +91,16 @@ const Register = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const token = QueryParams().get('t')
|
const token = QueryParams().get('t')
|
||||||
|
const identifier = QueryParams().get('id') ?? null
|
||||||
|
|
||||||
const [state, dispatch] = useReducer(reducer, initialState)
|
const [state, dispatch] = useReducer(reducer, initialState)
|
||||||
|
|
||||||
const { error: queryError, loading } = useQuery(VALIDATE_REGISTER_LINK, {
|
const queryOptions = {
|
||||||
|
context: {
|
||||||
|
headers: {
|
||||||
|
'Pazuz-Operator-Identifier': identifier
|
||||||
|
}
|
||||||
|
},
|
||||||
variables: { token: token },
|
variables: { token: token },
|
||||||
onCompleted: ({ validateRegisterLink: info }) => {
|
onCompleted: ({ validateRegisterLink: info }) => {
|
||||||
if (!info) {
|
if (!info) {
|
||||||
|
|
@ -114,7 +120,12 @@ const Register = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'failure'
|
type: 'failure'
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const { error: queryError, loading } = useQuery(
|
||||||
|
VALIDATE_REGISTER_LINK,
|
||||||
|
queryOptions
|
||||||
|
)
|
||||||
|
|
||||||
const [register, { error: mutationError }] = useMutation(REGISTER, {
|
const [register, { error: mutationError }] = useMutation(REGISTER, {
|
||||||
onCompleted: ({ register: success }) => {
|
onCompleted: ({ register: success }) => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useMutation, useQuery, useLazyQuery } from '@apollo/react-hooks'
|
import { useMutation, useQuery, useLazyQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import base64 from 'base-64'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import QRCode from 'qrcode.react'
|
import QRCode from 'qrcode.react'
|
||||||
import React, { useContext, useState } from 'react'
|
import React, { useContext, useState } from 'react'
|
||||||
|
|
@ -67,13 +68,34 @@ const Setup2FAState = ({ state, dispatch }) => {
|
||||||
setInvalidToken(false)
|
setInvalidToken(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { error: queryError } = useQuery(GET_2FA_SECRET, {
|
const queryOptions = {
|
||||||
variables: { username: state.clientField, password: state.passwordField },
|
variables: { username: state.clientField, password: state.passwordField },
|
||||||
|
context: {
|
||||||
|
headers: {
|
||||||
|
'Pazuz-Operator-Identifier': base64.encode(state.clientField)
|
||||||
|
}
|
||||||
|
},
|
||||||
onCompleted: ({ get2FASecret }) => {
|
onCompleted: ({ get2FASecret }) => {
|
||||||
setSecret(get2FASecret.secret)
|
setSecret(get2FASecret.secret)
|
||||||
setOtpauth(get2FASecret.otpauth)
|
setOtpauth(get2FASecret.otpauth)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const mutationOptions = {
|
||||||
|
variables: {
|
||||||
|
username: state.clientField,
|
||||||
|
password: state.passwordField,
|
||||||
|
rememberMe: state.rememberMeField,
|
||||||
|
codeConfirmation: twoFAConfirmation
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
headers: {
|
||||||
|
'Pazuz-Operator-Identifier': base64.encode(state.clientField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error: queryError } = useQuery(GET_2FA_SECRET, queryOptions)
|
||||||
|
|
||||||
const [getUserData] = useLazyQuery(GET_USER_DATA, {
|
const [getUserData] = useLazyQuery(GET_USER_DATA, {
|
||||||
onCompleted: ({ userData }) => {
|
onCompleted: ({ userData }) => {
|
||||||
|
|
@ -84,7 +106,14 @@ const Setup2FAState = ({ state, dispatch }) => {
|
||||||
|
|
||||||
const [setup2FA, { error: mutationError }] = useMutation(SETUP_2FA, {
|
const [setup2FA, { error: mutationError }] = useMutation(SETUP_2FA, {
|
||||||
onCompleted: ({ setup2FA: success }) => {
|
onCompleted: ({ setup2FA: success }) => {
|
||||||
success ? getUserData() : setInvalidToken(true)
|
const options = {
|
||||||
|
context: {
|
||||||
|
headers: {
|
||||||
|
'Pazuz-Operator-Identifier': base64.encode(state.clientField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
success ? getUserData(options) : setInvalidToken(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -149,14 +178,7 @@ const Setup2FAState = ({ state, dispatch }) => {
|
||||||
setInvalidToken(true)
|
setInvalidToken(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setup2FA({
|
setup2FA(mutationOptions)
|
||||||
variables: {
|
|
||||||
username: state.clientField,
|
|
||||||
password: state.passwordField,
|
|
||||||
rememberMe: state.rememberMeField,
|
|
||||||
codeConfirmation: twoFAConfirmation
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
}}
|
||||||
buttonClassName={classes.loginButton}>
|
buttonClassName={classes.loginButton}>
|
||||||
Done
|
Done
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useMutation } from '@apollo/react-hooks'
|
import { useMutation } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import base64 from 'base-64'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { Field, Form, Formik } from 'formik'
|
import { Field, Form, Formik } from 'formik'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
|
@ -74,7 +75,12 @@ const CreateUserModal = ({ state, dispatch }) => {
|
||||||
|
|
||||||
const [createUser, { error }] = useMutation(CREATE_USER, {
|
const [createUser, { error }] = useMutation(CREATE_USER, {
|
||||||
onCompleted: ({ createRegisterToken: token }) => {
|
onCompleted: ({ createRegisterToken: token }) => {
|
||||||
setCreateUserURL(urlResolver(`/register?t=${token.token}`))
|
const queryParams =
|
||||||
|
// Pazuz-created register tokens add a field to identify the creator
|
||||||
|
process.env.REACT_APP_BUILD_TARGET === 'LAMASSU'
|
||||||
|
? `t=${token.token}`
|
||||||
|
: `t=${token.token}&id=${base64.encode(usernameField)}`
|
||||||
|
setCreateUserURL(urlResolver(`/register?${queryParams}`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ import AppContext from 'src/AppContext'
|
||||||
const URI =
|
const URI =
|
||||||
process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : ''
|
process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : ''
|
||||||
|
|
||||||
const getClient = (history, location, setUserData, setRole) =>
|
const ALT_URI =
|
||||||
|
process.env.NODE_ENV === 'development' ? 'http://localhost:4001' : ''
|
||||||
|
|
||||||
|
const getClient = (history, location, getUserData, setUserData, setRole) =>
|
||||||
new ApolloClient({
|
new ApolloClient({
|
||||||
link: ApolloLink.from([
|
link: ApolloLink.from([
|
||||||
onError(({ graphQLErrors, networkError }) => {
|
onError(({ graphQLErrors, networkError }) => {
|
||||||
|
|
@ -36,17 +39,24 @@ const getClient = (history, location, setUserData, setRole) =>
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
if (headers) {
|
if (headers) {
|
||||||
const role = headers.get('role')
|
const role = headers.get('lamassu_role')
|
||||||
setRole(role)
|
setRole(role)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
new HttpLink({
|
ApolloLink.split(
|
||||||
credentials: 'include',
|
operation => operation.getContext().clientName === 'pazuz',
|
||||||
uri: `${URI}/graphql`
|
new HttpLink({
|
||||||
})
|
credentials: 'include',
|
||||||
|
uri: `${ALT_URI}/graphql`
|
||||||
|
}),
|
||||||
|
new HttpLink({
|
||||||
|
credentials: 'include',
|
||||||
|
uri: `${URI}/graphql`
|
||||||
|
})
|
||||||
|
)
|
||||||
]),
|
]),
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache(),
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
|
|
@ -67,8 +77,14 @@ const getClient = (history, location, setUserData, setRole) =>
|
||||||
const Provider = ({ children }) => {
|
const Provider = ({ children }) => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { setUserData, setRole } = useContext(AppContext)
|
const { userData, setUserData, setRole } = useContext(AppContext)
|
||||||
const client = getClient(history, location, setUserData, setRole)
|
const client = getClient(
|
||||||
|
history,
|
||||||
|
location,
|
||||||
|
() => userData,
|
||||||
|
setUserData,
|
||||||
|
setRole
|
||||||
|
)
|
||||||
|
|
||||||
return <ApolloProvider client={client}>{children}</ApolloProvider>
|
return <ApolloProvider client={client}>{children}</ApolloProvider>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -5979,6 +5979,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"base-64": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
|
||||||
|
},
|
||||||
"base-x": {
|
"base-x": {
|
||||||
"version": "3.0.9",
|
"version": "3.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
|
||||||
|
|
@ -17669,6 +17674,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
|
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
|
||||||
},
|
},
|
||||||
|
"queue-promise": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/queue-promise/-/queue-promise-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-C3eyRwLF9m6dPV4MtqMVFX+Xmc7keZ9Ievm3jJ/wWM5t3uVbFnGsJXwpYzZ4LaIEcX9bss/mdaKzyrO6xheRuA=="
|
||||||
|
},
|
||||||
"random-bytes": {
|
"random-bytes": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"apollo-server-express": "2.25.1",
|
"apollo-server-express": "2.25.1",
|
||||||
"argon2": "0.28.2",
|
"argon2": "0.28.2",
|
||||||
"axios": "0.21.1",
|
"axios": "0.21.1",
|
||||||
|
"base-64": "^1.0.0",
|
||||||
"base-x": "3.0.9",
|
"base-x": "3.0.9",
|
||||||
"bchaddrjs": "^0.3.0",
|
"bchaddrjs": "^0.3.0",
|
||||||
"bignumber.js": "9.0.1",
|
"bignumber.js": "9.0.1",
|
||||||
|
|
@ -65,6 +66,7 @@
|
||||||
"pretty-ms": "^2.1.0",
|
"pretty-ms": "^2.1.0",
|
||||||
"promise-sequential": "^1.1.1",
|
"promise-sequential": "^1.1.1",
|
||||||
"request-promise": "^4.2.6",
|
"request-promise": "^4.2.6",
|
||||||
|
"queue-promise": "^2.2.1",
|
||||||
"semver": "^7.1.3",
|
"semver": "^7.1.3",
|
||||||
"serve-static": "^1.12.4",
|
"serve-static": "^1.12.4",
|
||||||
"socket.io": "^2.0.3",
|
"socket.io": "^2.0.3",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue