Merge branch 'dev' into fix/lam-266/profits-calculation
|
|
@ -18,7 +18,7 @@ function setup (dataDir) {
|
||||||
|
|
||||||
function updateCore (coinRec, isCurrentlyRunning) {
|
function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.logger.info('Updating Bitcoin Core. This may take a minute...')
|
common.logger.info('Updating Bitcoin Core. This may take a minute...')
|
||||||
if (isCurrentlyRunning) common.es(`sudo supervisorctl stop bitcoin`)
|
common.es(`sudo supervisorctl stop bitcoin`)
|
||||||
common.es(`curl -#o /tmp/bitcoin.tar.gz ${coinRec.url}`)
|
common.es(`curl -#o /tmp/bitcoin.tar.gz ${coinRec.url}`)
|
||||||
common.es(`tar -xzf /tmp/bitcoin.tar.gz -C /tmp/`)
|
common.es(`tar -xzf /tmp/bitcoin.tar.gz -C /tmp/`)
|
||||||
|
|
||||||
|
|
@ -48,5 +48,6 @@ addresstype=p2sh-segwit
|
||||||
changetype=bech32
|
changetype=bech32
|
||||||
walletrbf=1
|
walletrbf=1
|
||||||
bind=0.0.0.0:8332
|
bind=0.0.0.0:8332
|
||||||
rpcport=8333`
|
rpcport=8333
|
||||||
|
listenonion=0`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,6 @@ maxconnections=40
|
||||||
keypool=10000
|
keypool=10000
|
||||||
prune=4000
|
prune=4000
|
||||||
daemon=0
|
daemon=0
|
||||||
bind=0.0.0.0:8334
|
bind=0.0.0.0:8335
|
||||||
rpcport=8335`
|
rpcport=8336`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ const _ = require('lodash/fp')
|
||||||
|
|
||||||
const { utils: coinUtils } = require('lamassu-coins')
|
const { utils: coinUtils } = require('lamassu-coins')
|
||||||
const options = require('../options')
|
const options = require('../options')
|
||||||
|
const settingsLoader = require('../new-settings-loader')
|
||||||
|
const wallet = require('../wallet')
|
||||||
|
|
||||||
const common = require('./common')
|
const common = require('./common')
|
||||||
const doVolume = require('./do-volume')
|
const doVolume = require('./do-volume')
|
||||||
|
|
@ -112,6 +114,35 @@ function plugin (crypto) {
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBlockchainSyncStatus (cryptoList) {
|
||||||
|
const installedCryptos = _.reduce((acc, value) => ({ ...acc, [value.cryptoCode]: isInstalledSoftware(value) && isInstalledVolume(value) }), {}, cryptoList)
|
||||||
|
|
||||||
|
return settingsLoader.loadLatest()
|
||||||
|
.then(settings => {
|
||||||
|
const installedButNotConfigured = []
|
||||||
|
const blockchainStatuses = _.reduce((acc, value) => {
|
||||||
|
const processStatus = common.es(`sudo supervisorctl status ${value.code} | awk '{ print $2 }'`).trim()
|
||||||
|
return acc.then(a => {
|
||||||
|
return wallet.checkBlockchainStatus(settings, value.cryptoCode)
|
||||||
|
.then(res => _.includes(value.cryptoCode, _.keys(installedCryptos)) ? Promise.resolve({ ...a, [value.cryptoCode]: res }) : Promise.resolve({ ...a }))
|
||||||
|
.catch(() => {
|
||||||
|
if (processStatus === 'RUNNING') {
|
||||||
|
installedButNotConfigured.push(value.cryptoCode)
|
||||||
|
return Promise.resolve({ ...a, [value.cryptoCode]: 'syncing' })
|
||||||
|
}
|
||||||
|
return Promise.resolve({ ...a })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Promise.resolve({}),
|
||||||
|
cryptoList
|
||||||
|
)
|
||||||
|
|
||||||
|
return Promise.all([blockchainStatuses, installedButNotConfigured])
|
||||||
|
})
|
||||||
|
.then(([blockchainStatuses, installedButNotConfigured]) => ({ blockchainStatuses, installedButNotConfigured }))
|
||||||
|
}
|
||||||
|
|
||||||
function run () {
|
function run () {
|
||||||
const choices = _.flow([
|
const choices = _.flow([
|
||||||
_.filter(c => c.type !== 'erc-20'),
|
_.filter(c => c.type !== 'erc-20'),
|
||||||
|
|
@ -129,13 +160,40 @@ function run () {
|
||||||
|
|
||||||
const questions = []
|
const questions = []
|
||||||
|
|
||||||
|
const validateAnswers = async (answers) => {
|
||||||
|
if (_.size(answers) > 2) return { message: `Please insert a maximum of two coins to install.`, isValid: false }
|
||||||
|
return getBlockchainSyncStatus(cryptos)
|
||||||
|
.then(({ blockchainStatuses, installedButNotConfigured }) => {
|
||||||
|
if (!_.isEmpty(installedButNotConfigured)) {
|
||||||
|
logger.warn(`Detected ${_.join(' and ', installedButNotConfigured)} installed on this machine, but couldn't establish connection. ${_.size(installedButNotConfigured) === 1 ? `Is this plugin` : `Are these plugins`} configured via admin?`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = _.reduce((acc, value) => ({ ...acc, [value]: _.isNil(acc[value]) ? 1 : acc[value] + 1 }), {}, _.values(blockchainStatuses))
|
||||||
|
if (_.size(answers) + result.syncing > 2) {
|
||||||
|
return { message: `Installing these coins would pass the 2 parallel blockchain synchronization limit. Please try again with fewer coins or try again later.`, isValid: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.syncing > 2) {
|
||||||
|
return { message: `There are currently more than 2 blockchains in their initial synchronization. Please try again later.`, isValid: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { message: null, isValid: true }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
questions.push({
|
questions.push({
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
name: 'crypto',
|
name: 'crypto',
|
||||||
message: 'Which cryptocurrencies would you like to install?',
|
message: 'Which cryptocurrencies would you like to install?\nTo prevent server resource overloading, only TWO coins should be syncing simultaneously.\nMore coins can be installed after this process is over.',
|
||||||
choices
|
choices
|
||||||
})
|
})
|
||||||
|
|
||||||
inquirer.prompt(questions)
|
inquirer.prompt(questions)
|
||||||
.then(answers => processCryptos(answers.crypto))
|
.then(answers => Promise.all([validateAnswers(answers.crypto), answers]))
|
||||||
|
.then(([res, answers]) => {
|
||||||
|
if (res.isValid) {
|
||||||
|
return processCryptos(answers.crypto)
|
||||||
|
}
|
||||||
|
logger.error(res.message)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,14 @@ function setup (dataDir) {
|
||||||
const auth = `lamassuserver:${common.randomPass()}`
|
const auth = `lamassuserver:${common.randomPass()}`
|
||||||
const config = buildConfig(auth)
|
const config = buildConfig(auth)
|
||||||
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
|
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
|
||||||
const cmd = `/usr/local/bin/${coinRec.daemon} --data-dir ${dataDir} --config-file ${dataDir}/${coinRec.configFile}`
|
const cmd = `/usr/local/bin/${coinRec.daemon} --no-zmq --data-dir ${dataDir} --config-file ${dataDir}/${coinRec.configFile}`
|
||||||
const walletCmd = `/usr/local/bin/${coinRec.wallet} --stagenet --rpc-login ${auth} --daemon-host 127.0.0.1 --daemon-port 38081 --trusted-daemon --daemon-login ${auth} --rpc-bind-port 38083 --wallet-dir ${dataDir}/wallets`
|
const walletCmd = `/usr/local/bin/${coinRec.wallet} --rpc-login ${auth} --daemon-host 127.0.0.1 --daemon-port 18081 --trusted-daemon --daemon-login ${auth} --rpc-bind-port 18082 --wallet-dir ${dataDir}/wallets`
|
||||||
common.writeSupervisorConfig(coinRec, cmd, walletCmd)
|
common.writeSupervisorConfig(coinRec, cmd, walletCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildConfig (auth) {
|
function buildConfig (auth) {
|
||||||
return `rpc-login=${auth}
|
return `rpc-login=${auth}
|
||||||
stagenet=1
|
stagenet=0
|
||||||
restricted-rpc=1
|
restricted-rpc=1
|
||||||
db-sync-mode=safe
|
db-sync-mode=safe
|
||||||
out-peers=20
|
out-peers=20
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const E = require('../error')
|
||||||
|
|
||||||
const PENDING_INTERVAL_MS = 60 * T.minutes
|
const PENDING_INTERVAL_MS = 60 * T.minutes
|
||||||
|
|
||||||
const massageFields = ['direction', 'cryptoNetwork', 'bills', 'blacklisted', 'addressReuse', 'promoCodeApplied', 'failedWalletScore']
|
const massageFields = ['direction', 'cryptoNetwork', 'bills', 'blacklisted', 'addressReuse', 'promoCodeApplied', 'validWalletScore']
|
||||||
const massageUpdateFields = _.concat(massageFields, 'cryptoAtoms')
|
const massageUpdateFields = _.concat(massageFields, 'cryptoAtoms')
|
||||||
|
|
||||||
const massage = _.flow(_.omit(massageFields),
|
const massage = _.flow(_.omit(massageFields),
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ const cashInLow = require('./cash-in-low')
|
||||||
|
|
||||||
const PENDING_INTERVAL = '60 minutes'
|
const PENDING_INTERVAL = '60 minutes'
|
||||||
const MAX_PENDING = 10
|
const MAX_PENDING = 10
|
||||||
const WALLET_SCORE_THRESHOLD = 10
|
|
||||||
|
|
||||||
const TRANSACTION_STATES = `
|
const TRANSACTION_STATES = `
|
||||||
case
|
case
|
||||||
|
|
@ -34,13 +33,13 @@ function post (machineTx, pi) {
|
||||||
const updatedTx = r.tx
|
const updatedTx = r.tx
|
||||||
let blacklisted = false
|
let blacklisted = false
|
||||||
let addressReuse = false
|
let addressReuse = false
|
||||||
let failedWalletScore = false
|
let walletScore = {}
|
||||||
|
|
||||||
return Promise.all([settingsLoader.loadLatest(), checkForBlacklisted(updatedTx), doesTxReuseAddress(updatedTx), doesWalletScoreFail(updatedTx, pi)])
|
return Promise.all([settingsLoader.loadLatest(), checkForBlacklisted(updatedTx), doesTxReuseAddress(updatedTx), getWalletScore(updatedTx, pi)])
|
||||||
.then(([{ config }, blacklistItems, isReusedAddress, walletScoreFailed]) => {
|
.then(([{ config }, blacklistItems, isReusedAddress, fetchedWalletScore]) => {
|
||||||
const rejectAddressReuse = configManager.getCompliance(config).rejectAddressReuse
|
const rejectAddressReuse = configManager.getCompliance(config).rejectAddressReuse
|
||||||
|
|
||||||
failedWalletScore = walletScoreFailed
|
walletScore = fetchedWalletScore
|
||||||
|
|
||||||
if (_.some(it => it.address === updatedTx.toAddress)(blacklistItems)) {
|
if (_.some(it => it.address === updatedTx.toAddress)(blacklistItems)) {
|
||||||
blacklisted = true
|
blacklisted = true
|
||||||
|
|
@ -49,13 +48,14 @@ function post (machineTx, pi) {
|
||||||
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, true)
|
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, true)
|
||||||
addressReuse = true
|
addressReuse = true
|
||||||
}
|
}
|
||||||
return postProcess(r, pi, blacklisted, addressReuse, failedWalletScore)
|
return postProcess(r, pi, blacklisted, addressReuse, walletScore)
|
||||||
})
|
})
|
||||||
.then(changes => cashInLow.update(db, updatedTx, changes))
|
.then(changes => cashInLow.update(db, updatedTx, changes))
|
||||||
.then(tx => _.set('bills', machineTx.bills, tx))
|
.then(tx => _.set('bills', machineTx.bills, tx))
|
||||||
.then(tx => _.set('blacklisted', blacklisted, tx))
|
.then(tx => _.set('blacklisted', blacklisted, tx))
|
||||||
.then(tx => _.set('addressReuse', addressReuse, tx))
|
.then(tx => _.set('addressReuse', addressReuse, tx))
|
||||||
.then(tx => _.set('failedWalletScore', failedWalletScore, tx))
|
.then(tx => _.set('validWalletScore', _.isNil(walletScore) ? true : walletScore.isValid, tx))
|
||||||
|
.then(tx => _.set('walletScore', _.isNil(walletScore) ? null : walletScore.score, tx))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ function checkForBlacklisted (tx) {
|
||||||
return Promise.resolve(false)
|
return Promise.resolve(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
function postProcess (r, pi, isBlacklisted, addressReuse, failedWalletScore) {
|
function postProcess (r, pi, isBlacklisted, addressReuse, walletScore) {
|
||||||
if (addressReuse) {
|
if (addressReuse) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
operatorCompleted: true,
|
operatorCompleted: true,
|
||||||
|
|
@ -108,10 +108,11 @@ function postProcess (r, pi, isBlacklisted, addressReuse, failedWalletScore) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failedWalletScore) {
|
if (!_.isNil(walletScore) && !walletScore.isValid) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
walletScore: walletScore.score,
|
||||||
operatorCompleted: true,
|
operatorCompleted: true,
|
||||||
error: 'Failed wallet score'
|
error: 'Ciphertrace score is above defined threshold'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,12 +172,17 @@ function doesTxReuseAddress (tx) {
|
||||||
return Promise.resolve(false)
|
return Promise.resolve(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
function doesWalletScoreFail (tx, pi) {
|
function getWalletScore (tx, pi) {
|
||||||
if (!tx.fiat || tx.fiat.isZero()) {
|
if (!tx.fiat || tx.fiat.isZero()) {
|
||||||
return pi.rateWallet(tx.toAddress)
|
return pi.rateWallet(tx.cryptoCode, tx.toAddress)
|
||||||
.then(res => res >= WALLET_SCORE_THRESHOLD)
|
|
||||||
}
|
}
|
||||||
return Promise.resolve(false)
|
// Passthrough the previous result
|
||||||
|
return pi.isValidWalletScore(tx.walletScore)
|
||||||
|
.then(isValid => ({
|
||||||
|
address: tx.toAddress,
|
||||||
|
score: tx.walletScore,
|
||||||
|
isValid
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
function monitorPending (settings) {
|
function monitorPending (settings) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const toObj = helper.toObj
|
||||||
|
|
||||||
const UPDATEABLE_FIELDS = ['txHash', 'txVersion', 'status', 'dispense', 'dispenseConfirmed',
|
const UPDATEABLE_FIELDS = ['txHash', 'txVersion', 'status', 'dispense', 'dispenseConfirmed',
|
||||||
'notified', 'redeem', 'phone', 'error', 'swept', 'publishedAt', 'confirmedAt', 'errorCode',
|
'notified', 'redeem', 'phone', 'error', 'swept', 'publishedAt', 'confirmedAt', 'errorCode',
|
||||||
'receivedCryptoAtoms' ]
|
'receivedCryptoAtoms', 'walletScore' ]
|
||||||
|
|
||||||
module.exports = {upsert, update, insert}
|
module.exports = {upsert, update, insert}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,56 @@ function processTxStatus (tx, settings) {
|
||||||
|
|
||||||
return pi.getStatus(tx)
|
return pi.getStatus(tx)
|
||||||
.then(res => _.assign(tx, { receivedCryptoAtoms: res.receivedCryptoAtoms, status: res.status }))
|
.then(res => _.assign(tx, { receivedCryptoAtoms: res.receivedCryptoAtoms, status: res.status }))
|
||||||
|
.then(_tx => getWalletScore(_tx, pi))
|
||||||
.then(_tx => selfPost(_tx, pi))
|
.then(_tx => selfPost(_tx, pi))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWalletScore (tx, pi) {
|
||||||
|
const statuses = ['published', 'authorized', 'rejected', 'insufficientFunds']
|
||||||
|
|
||||||
|
if (_.includes(tx.status, statuses) && _.isNil(tx.walletScore)) {
|
||||||
|
// Transaction shows up on the blockchain, we can request the sender address
|
||||||
|
return pi.getTransactionHash(tx)
|
||||||
|
.then(txHashes => pi.getInputAddresses(tx, txHashes))
|
||||||
|
.then(addresses => {
|
||||||
|
const addressesPromise = []
|
||||||
|
_.forEach(it => addressesPromise.push(pi.rateWallet(tx.cryptoCode, it)), addresses)
|
||||||
|
return Promise.all(addressesPromise)
|
||||||
|
})
|
||||||
|
.then(scores => {
|
||||||
|
if (_.isNil(scores) || _.isEmpty(scores)) return tx
|
||||||
|
const highestScore = _.maxBy(it => it.score, scores)
|
||||||
|
|
||||||
|
// Conservatively assign the highest risk of all input addresses to the risk of this transaction
|
||||||
|
return highestScore.isValid
|
||||||
|
? _.assign(tx, { walletScore: highestScore.score })
|
||||||
|
: _.assign(tx, {
|
||||||
|
walletScore: highestScore.score,
|
||||||
|
error: 'Ciphertrace score is above defined threshold',
|
||||||
|
errorCode: 'operatorCancel',
|
||||||
|
dispense: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => _.assign(tx, {
|
||||||
|
walletScore: 10,
|
||||||
|
error: 'Ciphertrace services not available',
|
||||||
|
errorCode: 'operatorCancel',
|
||||||
|
dispense: true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.includes(tx.status, statuses) && !_.isNil(tx.walletScore)) {
|
||||||
|
return pi.isValidWalletScore(tx.walletScore)
|
||||||
|
.then(isValid => isValid ? tx : _.assign(tx, {
|
||||||
|
error: 'Ciphertrace score is above defined threshold',
|
||||||
|
errorCode: 'operatorCancel',
|
||||||
|
dispense: true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
function monitorLiveIncoming (settings, applyFilter, coinFilter) {
|
function monitorLiveIncoming (settings, applyFilter, coinFilter) {
|
||||||
const statuses = ['notSeen', 'published', 'insufficientFunds']
|
const statuses = ['notSeen', 'published', 'insufficientFunds']
|
||||||
const toAge = applyFilter ? STALE_LIVE_INCOMING_TX_AGE_FILTER : STALE_LIVE_INCOMING_TX_AGE
|
const toAge = applyFilter ? STALE_LIVE_INCOMING_TX_AGE_FILTER : STALE_LIVE_INCOMING_TX_AGE
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ const MANUAL = 'manual'
|
||||||
const CASH_OUT_DISPENSE_READY = 'cash_out_dispense_ready'
|
const CASH_OUT_DISPENSE_READY = 'cash_out_dispense_ready'
|
||||||
const CONFIRMATION_CODE = 'sms_code'
|
const CONFIRMATION_CODE = 'sms_code'
|
||||||
|
|
||||||
|
const WALLET_SCORE_THRESHOLD = 9
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
anonymousCustomer,
|
anonymousCustomer,
|
||||||
CASSETTE_MAX_CAPACITY,
|
CASSETTE_MAX_CAPACITY,
|
||||||
|
|
@ -34,5 +36,6 @@ module.exports = {
|
||||||
CASH_OUT_DISPENSE_READY,
|
CASH_OUT_DISPENSE_READY,
|
||||||
CONFIRMATION_CODE,
|
CONFIRMATION_CODE,
|
||||||
CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES,
|
CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES,
|
||||||
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES
|
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES,
|
||||||
|
WALLET_SCORE_THRESHOLD
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,10 @@ function update (id, data, userToken, txId) {
|
||||||
' where id=$1 returning *'
|
' where id=$1 returning *'
|
||||||
|
|
||||||
return db.one(sql, [id])
|
return db.one(sql, [id])
|
||||||
|
.then(customerData => {
|
||||||
|
return getEditedData(id)
|
||||||
|
.then(customerEditedData => selectLatestData(customerData, customerEditedData))
|
||||||
|
})
|
||||||
.then(addComplianceOverrides(id, updateData, userToken))
|
.then(addComplianceOverrides(id, updateData, userToken))
|
||||||
.then(populateOverrideUsernames)
|
.then(populateOverrideUsernames)
|
||||||
.then(computeStatus)
|
.then(computeStatus)
|
||||||
|
|
@ -117,7 +121,8 @@ async function updateCustomer (id, data, userToken) {
|
||||||
'us_ssn_override',
|
'us_ssn_override',
|
||||||
'sanctions_override',
|
'sanctions_override',
|
||||||
'front_camera_override',
|
'front_camera_override',
|
||||||
'suspended_until'
|
'suspended_until',
|
||||||
|
'phone_override'
|
||||||
],
|
],
|
||||||
_.mapKeys(_.snakeCase, data))
|
_.mapKeys(_.snakeCase, data))
|
||||||
|
|
||||||
|
|
@ -165,6 +170,7 @@ function edit (id, data, userToken) {
|
||||||
const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, _.omitBy(_.isNil, data)))
|
const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, _.omitBy(_.isNil, data)))
|
||||||
if (_.isEmpty(filteredData)) return getCustomerById(id)
|
if (_.isEmpty(filteredData)) return getCustomerById(id)
|
||||||
const formattedData = enhanceEditedPhotos(enhanceEditedFields(filteredData, userToken))
|
const formattedData = enhanceEditedPhotos(enhanceEditedFields(filteredData, userToken))
|
||||||
|
|
||||||
const defaultDbData = {
|
const defaultDbData = {
|
||||||
customer_id: id,
|
customer_id: id,
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
|
|
@ -684,18 +690,18 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
|
||||||
function getCustomerById (id) {
|
function getCustomerById (id) {
|
||||||
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
||||||
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_at, front_camera_override,
|
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_at, front_camera_override,
|
||||||
phone, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration,
|
phone, phone_at, phone_override, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration,
|
||||||
id_card_photo_path, id_card_photo_at, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
id_card_photo_path, id_card_photo_at, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
||||||
sanctions_override, total_txs, total_spent, LEAST(created, last_transaction) AS last_active, fiat AS last_tx_fiat,
|
sanctions_override, total_txs, total_spent, LEAST(created, last_transaction) AS last_active, fiat AS last_tx_fiat,
|
||||||
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes, is_test_customer
|
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, subscriber_info_at, custom_fields, notes, is_test_customer
|
||||||
FROM (
|
FROM (
|
||||||
SELECT c.id, c.authorized_override,
|
SELECT c.id, c.authorized_override,
|
||||||
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
|
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
|
||||||
c.suspended_until > now() AS is_suspended,
|
c.suspended_until > now() AS is_suspended,
|
||||||
c.front_camera_path, c.front_camera_override, c.front_camera_at,
|
c.front_camera_path, c.front_camera_override, c.front_camera_at,
|
||||||
c.phone, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
|
c.phone, c.phone_at, c.phone_override, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
|
||||||
c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions,
|
c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions,
|
||||||
c.sanctions_at, c.sanctions_override, c.subscriber_info, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
|
c.sanctions_at, c.sanctions_override, c.subscriber_info, c.subscriber_info_at, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
|
||||||
row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn,
|
row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn,
|
||||||
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY c.id) AS total_txs,
|
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY c.id) AS total_txs,
|
||||||
sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (PARTITION BY c.id) AS total_spent, ccf.custom_fields
|
sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (PARTITION BY c.id) AS total_spent, ccf.custom_fields
|
||||||
|
|
@ -1053,5 +1059,7 @@ module.exports = {
|
||||||
updateEditedPhoto,
|
updateEditedPhoto,
|
||||||
updateTxCustomerPhoto,
|
updateTxCustomerPhoto,
|
||||||
enableTestCustomer,
|
enableTestCustomer,
|
||||||
disableTestCustomer
|
disableTestCustomer,
|
||||||
|
selectLatestData,
|
||||||
|
getEditedData
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -151,9 +151,8 @@ function unpair (rec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reboot (rec) {
|
function reboot (rec) {
|
||||||
return db.none('NOTIFY $1:name, $2', ['poller', JSON.stringify(
|
return db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
|
||||||
{
|
{
|
||||||
type: 'machineAction',
|
|
||||||
action: 'reboot',
|
action: 'reboot',
|
||||||
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
||||||
}
|
}
|
||||||
|
|
@ -161,9 +160,8 @@ function reboot (rec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function shutdown (rec) {
|
function shutdown (rec) {
|
||||||
return db.none('NOTIFY $1:name, $2', ['poller', JSON.stringify(
|
return db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
|
||||||
{
|
{
|
||||||
type: 'machineAction',
|
|
||||||
action: 'shutdown',
|
action: 'shutdown',
|
||||||
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
||||||
}
|
}
|
||||||
|
|
@ -171,9 +169,8 @@ function shutdown (rec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function restartServices (rec) {
|
function restartServices (rec) {
|
||||||
return db.none('NOTIFY $1:name, $2', ['poller', JSON.stringify(
|
return db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
|
||||||
{
|
{
|
||||||
type: 'machineAction',
|
|
||||||
action: 'restartServices',
|
action: 'restartServices',
|
||||||
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,56 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
|
const db = require('../db')
|
||||||
const state = require('./state')
|
const state = require('./state')
|
||||||
const newSettingsLoader = require('../new-settings-loader')
|
const newSettingsLoader = require('../new-settings-loader')
|
||||||
const helpers = require('../route-helpers')
|
const helpers = require('../route-helpers')
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
|
|
||||||
const { settingsCache } = state
|
db.connect({ direct: true }).then(sco => {
|
||||||
|
sco.client.on('notification', data => {
|
||||||
|
const parsedData = JSON.parse(data.payload)
|
||||||
|
return reload(parsedData.operatorId)
|
||||||
|
})
|
||||||
|
return sco.none('LISTEN $1:name', 'reload')
|
||||||
|
}).catch(console.error)
|
||||||
|
|
||||||
|
db.connect({ direct: true }).then(sco => {
|
||||||
|
sco.client.on('notification', data => {
|
||||||
|
const parsedData = JSON.parse(data.payload)
|
||||||
|
return machineAction(parsedData.action, parsedData.value)
|
||||||
|
})
|
||||||
|
return sco.none('LISTEN $1:name', 'machineAction')
|
||||||
|
}).catch(console.error)
|
||||||
|
|
||||||
|
function machineAction (type, value) {
|
||||||
|
const deviceId = value.deviceId
|
||||||
|
const operatorId = value.operatorId
|
||||||
|
const pid = state.pids?.[operatorId]?.[deviceId]?.pid
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'reboot':
|
||||||
|
logger.debug(`Rebooting machine '${deviceId}' from operator ${operatorId}`)
|
||||||
|
state.reboots[operatorId] = { [deviceId]: pid }
|
||||||
|
break
|
||||||
|
case 'shutdown':
|
||||||
|
logger.debug(`Shutting down machine '${deviceId}' from operator ${operatorId}`)
|
||||||
|
state.shutdowns[operatorId] = { [deviceId]: pid }
|
||||||
|
break
|
||||||
|
case 'restartServices':
|
||||||
|
logger.debug(`Restarting services of machine '${deviceId}' from operator ${operatorId}`)
|
||||||
|
state.restartServicesMap[operatorId] = { [deviceId]: pid }
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload (operatorId) {
|
||||||
|
state.needsSettingsReload[operatorId.operatorId] = true
|
||||||
|
}
|
||||||
|
|
||||||
const populateSettings = function (req, res, next) {
|
const populateSettings = function (req, res, next) {
|
||||||
|
const { needsSettingsReload, settingsCache } = state
|
||||||
const operatorId = res.locals.operatorId
|
const operatorId = res.locals.operatorId
|
||||||
const versionId = req.headers['config-version']
|
const versionId = req.headers['config-version']
|
||||||
if (versionId !== state.oldVersionId) {
|
if (versionId !== state.oldVersionId) {
|
||||||
|
|
@ -14,20 +59,21 @@ const populateSettings = function (req, res, next) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const operatorSettings = settingsCache.get(operatorId)
|
const operatorSettings = settingsCache.get(operatorId)
|
||||||
if (!versionId && operatorSettings) {
|
if (!versionId && (!operatorSettings || !!needsSettingsReload[operatorId])) {
|
||||||
req.settings = operatorSettings
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!versionId && !operatorSettings) {
|
|
||||||
return newSettingsLoader.loadLatest()
|
return newSettingsLoader.loadLatest()
|
||||||
.then(settings => {
|
.then(settings => {
|
||||||
settingsCache.set(operatorId, settings)
|
settingsCache.set(operatorId, settings)
|
||||||
|
delete needsSettingsReload[operatorId]
|
||||||
req.settings = settings
|
req.settings = settings
|
||||||
})
|
})
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!versionId && operatorSettings) {
|
||||||
|
req.settings = operatorSettings
|
||||||
|
return next()
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ const SETTINGS_CACHE_REFRESH = 3600
|
||||||
module.exports = (function () {
|
module.exports = (function () {
|
||||||
return {
|
return {
|
||||||
oldVersionId: 'unset',
|
oldVersionId: 'unset',
|
||||||
|
needsSettingsReload: {},
|
||||||
settingsCache: new NodeCache({
|
settingsCache: new NodeCache({
|
||||||
stdTTL: SETTINGS_CACHE_REFRESH,
|
stdTTL: SETTINGS_CACHE_REFRESH,
|
||||||
checkperiod: SETTINGS_CACHE_REFRESH // Clear cache every hour
|
checkperiod: SETTINGS_CACHE_REFRESH // Clear cache every hour
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const SMS = 'sms'
|
||||||
const ID_VERIFIER = 'idVerifier'
|
const ID_VERIFIER = 'idVerifier'
|
||||||
const EMAIL = 'email'
|
const EMAIL = 'email'
|
||||||
const ZERO_CONF = 'zeroConf'
|
const ZERO_CONF = 'zeroConf'
|
||||||
|
const WALLET_SCORING = 'wallet_scoring'
|
||||||
|
|
||||||
const ALL_ACCOUNTS = [
|
const ALL_ACCOUNTS = [
|
||||||
{ code: 'binanceus', display: 'Binance.us', class: TICKER, cryptos: binanceus.CRYPTO },
|
{ code: 'binanceus', display: 'Binance.us', class: TICKER, cryptos: binanceus.CRYPTO },
|
||||||
|
|
@ -50,7 +51,9 @@ const ALL_ACCOUNTS = [
|
||||||
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
|
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
|
||||||
{ code: 'none', display: 'None', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH, ETH, XMR] },
|
{ code: 'none', display: 'None', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH, ETH, XMR] },
|
||||||
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
|
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
|
||||||
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true }
|
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true },
|
||||||
|
{ code: 'ciphertrace', display: 'CipherTrace', class: WALLET_SCORING, cryptos: [BTC, ETH, LTC, BCH] },
|
||||||
|
{ code: 'mock-scoring', display: 'Mock scoring', class: WALLET_SCORING, cryptos: ALL_CRYPTOS, dev: true }
|
||||||
]
|
]
|
||||||
|
|
||||||
const devMode = require('minimist')(process.argv.slice(2)).dev
|
const devMode = require('minimist')(process.argv.slice(2)).dev
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,20 @@ const _ = require('lodash/fp')
|
||||||
|
|
||||||
const userManagement = require('../userManagement')
|
const userManagement = require('../userManagement')
|
||||||
const credentials = require('../../../../hardware-credentials')
|
const credentials = require('../../../../hardware-credentials')
|
||||||
const options = require('../../../../options')
|
|
||||||
const T = require('../../../../time')
|
const T = require('../../../../time')
|
||||||
const users = require('../../../../users')
|
const users = require('../../../../users')
|
||||||
|
|
||||||
const domain = options.hostname
|
|
||||||
const devMode = require('minimist')(process.argv.slice(2)).dev
|
const devMode = require('minimist')(process.argv.slice(2)).dev
|
||||||
|
|
||||||
const REMEMBER_ME_AGE = 90 * T.day
|
const REMEMBER_ME_AGE = 90 * T.day
|
||||||
|
|
||||||
const rpID = devMode ? `localhost:3001` : domain
|
|
||||||
const expectedOrigin = `https://${rpID}`
|
|
||||||
|
|
||||||
const generateAttestationOptions = (session, options) => {
|
const generateAttestationOptions = (session, options) => {
|
||||||
return users.getUserById(options.userId).then(user => {
|
return users.getUserById(options.userId).then(user => {
|
||||||
return Promise.all([credentials.getHardwareCredentialsByUserId(user.id), user])
|
return Promise.all([credentials.getHardwareCredentialsByUserId(user.id), user])
|
||||||
}).then(([userDevices, user]) => {
|
}).then(([userDevices, user]) => {
|
||||||
const options = simpleWebauthn.generateAttestationOptions({
|
const opts = simpleWebauthn.generateAttestationOptions({
|
||||||
rpName: 'Lamassu',
|
rpName: 'Lamassu',
|
||||||
rpID,
|
rpID: options.domain,
|
||||||
userName: user.username,
|
userName: user.username,
|
||||||
userID: user.id,
|
userID: user.id,
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
|
|
@ -40,11 +35,11 @@ const generateAttestationOptions = (session, options) => {
|
||||||
|
|
||||||
session.webauthn = {
|
session.webauthn = {
|
||||||
attestation: {
|
attestation: {
|
||||||
challenge: options.challenge
|
challenge: opts.challenge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return options
|
return opts
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +54,7 @@ const generateAssertionOptions = (session, options) => {
|
||||||
transports: ['usb', 'ble', 'nfc', 'internal']
|
transports: ['usb', 'ble', 'nfc', 'internal']
|
||||||
})),
|
})),
|
||||||
userVerification: 'discouraged',
|
userVerification: 'discouraged',
|
||||||
rpID
|
rpID: options.domain
|
||||||
})
|
})
|
||||||
|
|
||||||
session.webauthn = {
|
session.webauthn = {
|
||||||
|
|
@ -82,8 +77,8 @@ const validateAttestation = (session, options) => {
|
||||||
simpleWebauthn.verifyAttestationResponse({
|
simpleWebauthn.verifyAttestationResponse({
|
||||||
credential: options.attestationResponse,
|
credential: options.attestationResponse,
|
||||||
expectedChallenge: `${expectedChallenge}`,
|
expectedChallenge: `${expectedChallenge}`,
|
||||||
expectedOrigin,
|
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
|
||||||
expectedRPID: rpID
|
expectedRPID: options.domain
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
.then(([user, verification]) => {
|
.then(([user, verification]) => {
|
||||||
|
|
@ -142,8 +137,8 @@ const validateAssertion = (session, options) => {
|
||||||
verification = simpleWebauthn.verifyAssertionResponse({
|
verification = simpleWebauthn.verifyAssertionResponse({
|
||||||
credential: options.assertionResponse,
|
credential: options.assertionResponse,
|
||||||
expectedChallenge: `${expectedChallenge}`,
|
expectedChallenge: `${expectedChallenge}`,
|
||||||
expectedOrigin,
|
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
|
||||||
expectedRPID: rpID,
|
expectedRPID: options.domain,
|
||||||
authenticator: convertedAuthenticator
|
authenticator: convertedAuthenticator
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,20 @@ const base64url = require('base64url')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
const credentials = require('../../../../hardware-credentials')
|
const credentials = require('../../../../hardware-credentials')
|
||||||
const options = require('../../../../options')
|
|
||||||
const T = require('../../../../time')
|
const T = require('../../../../time')
|
||||||
const users = require('../../../../users')
|
const users = require('../../../../users')
|
||||||
|
|
||||||
const domain = options.hostname
|
|
||||||
const devMode = require('minimist')(process.argv.slice(2)).dev
|
const devMode = require('minimist')(process.argv.slice(2)).dev
|
||||||
|
|
||||||
const REMEMBER_ME_AGE = 90 * T.day
|
const REMEMBER_ME_AGE = 90 * T.day
|
||||||
|
|
||||||
const rpID = devMode ? `localhost:3001` : domain
|
|
||||||
const expectedOrigin = `https://${rpID}`
|
|
||||||
|
|
||||||
const generateAttestationOptions = (session, options) => {
|
const generateAttestationOptions = (session, options) => {
|
||||||
return users.getUserById(options.userId).then(user => {
|
return users.getUserById(options.userId).then(user => {
|
||||||
return Promise.all([credentials.getHardwareCredentialsByUserId(user.id), user])
|
return Promise.all([credentials.getHardwareCredentialsByUserId(user.id), user])
|
||||||
}).then(([userDevices, user]) => {
|
}).then(([userDevices, user]) => {
|
||||||
const opts = simpleWebauthn.generateAttestationOptions({
|
const opts = simpleWebauthn.generateAttestationOptions({
|
||||||
rpName: 'Lamassu',
|
rpName: 'Lamassu',
|
||||||
rpID,
|
rpID: options.domain,
|
||||||
userName: user.username,
|
userName: user.username,
|
||||||
userID: user.id,
|
userID: user.id,
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
|
|
@ -58,7 +53,7 @@ const generateAssertionOptions = (session, options) => {
|
||||||
transports: ['usb', 'ble', 'nfc', 'internal']
|
transports: ['usb', 'ble', 'nfc', 'internal']
|
||||||
})),
|
})),
|
||||||
userVerification: 'discouraged',
|
userVerification: 'discouraged',
|
||||||
rpID
|
rpID: options.domain
|
||||||
})
|
})
|
||||||
|
|
||||||
session.webauthn = {
|
session.webauthn = {
|
||||||
|
|
@ -81,8 +76,8 @@ const validateAttestation = (session, options) => {
|
||||||
simpleWebauthn.verifyAttestationResponse({
|
simpleWebauthn.verifyAttestationResponse({
|
||||||
credential: options.attestationResponse,
|
credential: options.attestationResponse,
|
||||||
expectedChallenge: `${expectedChallenge}`,
|
expectedChallenge: `${expectedChallenge}`,
|
||||||
expectedOrigin,
|
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
|
||||||
expectedRPID: rpID
|
expectedRPID: options.domain
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
.then(([user, verification]) => {
|
.then(([user, verification]) => {
|
||||||
|
|
@ -141,8 +136,8 @@ const validateAssertion = (session, options) => {
|
||||||
verification = simpleWebauthn.verifyAssertionResponse({
|
verification = simpleWebauthn.verifyAssertionResponse({
|
||||||
credential: options.assertionResponse,
|
credential: options.assertionResponse,
|
||||||
expectedChallenge: `${expectedChallenge}`,
|
expectedChallenge: `${expectedChallenge}`,
|
||||||
expectedOrigin,
|
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
|
||||||
expectedRPID: rpID,
|
expectedRPID: options.domain,
|
||||||
authenticator: convertedAuthenticator
|
authenticator: convertedAuthenticator
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,18 @@ const base64url = require('base64url')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
const credentials = require('../../../../hardware-credentials')
|
const credentials = require('../../../../hardware-credentials')
|
||||||
const options = require('../../../../options')
|
|
||||||
const T = require('../../../../time')
|
const T = require('../../../../time')
|
||||||
const users = require('../../../../users')
|
const users = require('../../../../users')
|
||||||
|
|
||||||
const domain = options.hostname
|
|
||||||
const devMode = require('minimist')(process.argv.slice(2)).dev
|
const devMode = require('minimist')(process.argv.slice(2)).dev
|
||||||
|
|
||||||
const REMEMBER_ME_AGE = 90 * T.day
|
const REMEMBER_ME_AGE = 90 * T.day
|
||||||
|
|
||||||
const rpID = devMode ? `localhost:3001` : domain
|
|
||||||
const expectedOrigin = `https://${rpID}`
|
|
||||||
|
|
||||||
const generateAttestationOptions = (session, options) => {
|
const generateAttestationOptions = (session, options) => {
|
||||||
return credentials.getHardwareCredentials().then(devices => {
|
return credentials.getHardwareCredentials().then(devices => {
|
||||||
const opts = simpleWebauthn.generateAttestationOptions({
|
const opts = simpleWebauthn.generateAttestationOptions({
|
||||||
rpName: 'Lamassu',
|
rpName: 'Lamassu',
|
||||||
rpID,
|
rpID: options.domain,
|
||||||
userName: `Usernameless user created at ${new Date().toISOString()}`,
|
userName: `Usernameless user created at ${new Date().toISOString()}`,
|
||||||
userID: options.userId,
|
userID: options.userId,
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
|
|
@ -46,9 +41,9 @@ const generateAttestationOptions = (session, options) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateAssertionOptions = session => {
|
const generateAssertionOptions = (session, options) => {
|
||||||
return credentials.getHardwareCredentials().then(devices => {
|
return credentials.getHardwareCredentials().then(devices => {
|
||||||
const options = simpleWebauthn.generateAssertionOptions({
|
const opts = simpleWebauthn.generateAssertionOptions({
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
allowCredentials: devices.map(dev => ({
|
allowCredentials: devices.map(dev => ({
|
||||||
id: dev.data.credentialID,
|
id: dev.data.credentialID,
|
||||||
|
|
@ -56,15 +51,15 @@ const generateAssertionOptions = session => {
|
||||||
transports: ['usb', 'ble', 'nfc', 'internal']
|
transports: ['usb', 'ble', 'nfc', 'internal']
|
||||||
})),
|
})),
|
||||||
userVerification: 'discouraged',
|
userVerification: 'discouraged',
|
||||||
rpID
|
rpID: options.domain
|
||||||
})
|
})
|
||||||
|
|
||||||
session.webauthn = {
|
session.webauthn = {
|
||||||
assertion: {
|
assertion: {
|
||||||
challenge: options.challenge
|
challenge: opts.challenge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return options
|
return opts
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,8 +72,8 @@ const validateAttestation = (session, options) => {
|
||||||
simpleWebauthn.verifyAttestationResponse({
|
simpleWebauthn.verifyAttestationResponse({
|
||||||
credential: options.attestationResponse,
|
credential: options.attestationResponse,
|
||||||
expectedChallenge: `${expectedChallenge}`,
|
expectedChallenge: `${expectedChallenge}`,
|
||||||
expectedOrigin,
|
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
|
||||||
expectedRPID: rpID
|
expectedRPID: options.domain
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
.then(([user, verification]) => {
|
.then(([user, verification]) => {
|
||||||
|
|
@ -146,8 +141,8 @@ const validateAssertion = (session, options) => {
|
||||||
verification = simpleWebauthn.verifyAssertionResponse({
|
verification = simpleWebauthn.verifyAssertionResponse({
|
||||||
credential: options.assertionResponse,
|
credential: options.assertionResponse,
|
||||||
expectedChallenge: `${expectedChallenge}`,
|
expectedChallenge: `${expectedChallenge}`,
|
||||||
expectedOrigin,
|
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
|
||||||
expectedRPID: rpID,
|
expectedRPID: options.domain,
|
||||||
authenticator: convertedAuthenticator
|
authenticator: convertedAuthenticator
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const txLogFields = ['txClass', 'id', 'deviceId', 'toAddress', 'cryptoAtoms',
|
||||||
'commissionPercentage', 'rawTickerPrice', 'receivedCryptoAtoms',
|
'commissionPercentage', 'rawTickerPrice', 'receivedCryptoAtoms',
|
||||||
'discount', 'txHash', 'customerPhone', 'customerIdCardDataNumber',
|
'discount', 'txHash', 'customerPhone', 'customerIdCardDataNumber',
|
||||||
'customerIdCardDataExpiration', 'customerIdCardData', 'customerName',
|
'customerIdCardDataExpiration', 'customerIdCardData', 'customerName',
|
||||||
'customerFrontCameraPath', 'customerIdCardPhotoPath', 'expired', 'machineName']
|
'customerFrontCameraPath', 'customerIdCardPhotoPath', 'expired', 'machineName', 'walletScore']
|
||||||
|
|
||||||
const resolvers = {
|
const resolvers = {
|
||||||
Customer: {
|
Customer: {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ const sessionManager = require('../../../session-manager')
|
||||||
const getAttestationQueryOptions = variables => {
|
const getAttestationQueryOptions = variables => {
|
||||||
switch (authentication.CHOSEN_STRATEGY) {
|
switch (authentication.CHOSEN_STRATEGY) {
|
||||||
case 'FIDO2FA':
|
case 'FIDO2FA':
|
||||||
return { userId: variables.userID }
|
return { userId: variables.userID, domain: variables.domain }
|
||||||
case 'FIDOPasswordless':
|
case 'FIDOPasswordless':
|
||||||
return { userId: variables.userID }
|
return { userId: variables.userID, domain: variables.domain }
|
||||||
case 'FIDOUsernameless':
|
case 'FIDOUsernameless':
|
||||||
return { userId: variables.userID }
|
return { userId: variables.userID, domain: variables.domain }
|
||||||
default:
|
default:
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
@ -19,11 +19,11 @@ const getAttestationQueryOptions = variables => {
|
||||||
const getAssertionQueryOptions = variables => {
|
const getAssertionQueryOptions = variables => {
|
||||||
switch (authentication.CHOSEN_STRATEGY) {
|
switch (authentication.CHOSEN_STRATEGY) {
|
||||||
case 'FIDO2FA':
|
case 'FIDO2FA':
|
||||||
return { username: variables.username, password: variables.password }
|
return { username: variables.username, password: variables.password, domain: variables.domain }
|
||||||
case 'FIDOPasswordless':
|
case 'FIDOPasswordless':
|
||||||
return { username: variables.username }
|
return { username: variables.username, domain: variables.domain }
|
||||||
case 'FIDOUsernameless':
|
case 'FIDOUsernameless':
|
||||||
return {}
|
return { domain: variables.domain }
|
||||||
default:
|
default:
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
@ -32,11 +32,11 @@ const getAssertionQueryOptions = variables => {
|
||||||
const getAttestationMutationOptions = variables => {
|
const getAttestationMutationOptions = variables => {
|
||||||
switch (authentication.CHOSEN_STRATEGY) {
|
switch (authentication.CHOSEN_STRATEGY) {
|
||||||
case 'FIDO2FA':
|
case 'FIDO2FA':
|
||||||
return { userId: variables.userID, attestationResponse: variables.attestationResponse }
|
return { userId: variables.userID, attestationResponse: variables.attestationResponse, domain: variables.domain }
|
||||||
case 'FIDOPasswordless':
|
case 'FIDOPasswordless':
|
||||||
return { userId: variables.userID, attestationResponse: variables.attestationResponse }
|
return { userId: variables.userID, attestationResponse: variables.attestationResponse, domain: variables.domain }
|
||||||
case 'FIDOUsernameless':
|
case 'FIDOUsernameless':
|
||||||
return { userId: variables.userID, attestationResponse: variables.attestationResponse }
|
return { userId: variables.userID, attestationResponse: variables.attestationResponse, domain: variables.domain }
|
||||||
default:
|
default:
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
@ -45,11 +45,11 @@ const getAttestationMutationOptions = variables => {
|
||||||
const getAssertionMutationOptions = variables => {
|
const getAssertionMutationOptions = variables => {
|
||||||
switch (authentication.CHOSEN_STRATEGY) {
|
switch (authentication.CHOSEN_STRATEGY) {
|
||||||
case 'FIDO2FA':
|
case 'FIDO2FA':
|
||||||
return { username: variables.username, password: variables.password, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse }
|
return { username: variables.username, password: variables.password, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse, domain: variables.domain }
|
||||||
case 'FIDOPasswordless':
|
case 'FIDOPasswordless':
|
||||||
return { username: variables.username, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse }
|
return { username: variables.username, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse, domain: variables.domain }
|
||||||
case 'FIDOUsernameless':
|
case 'FIDOUsernameless':
|
||||||
return { assertionResponse: variables.assertionResponse }
|
return { assertionResponse: variables.assertionResponse, domain: variables.domain }
|
||||||
default:
|
default:
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ const typeDef = gql`
|
||||||
lastTxClass: String
|
lastTxClass: String
|
||||||
transactions: [Transaction]
|
transactions: [Transaction]
|
||||||
subscriberInfo: JSONObject
|
subscriberInfo: JSONObject
|
||||||
|
phoneOverride: String
|
||||||
customFields: [CustomerCustomField]
|
customFields: [CustomerCustomField]
|
||||||
customInfoRequests: [CustomRequestData]
|
customInfoRequests: [CustomRequestData]
|
||||||
notes: [CustomerNote]
|
notes: [CustomerNote]
|
||||||
|
|
@ -63,12 +64,14 @@ const typeDef = gql`
|
||||||
lastTxClass: String
|
lastTxClass: String
|
||||||
suspendedUntil: Date
|
suspendedUntil: Date
|
||||||
subscriberInfo: Boolean
|
subscriberInfo: Boolean
|
||||||
|
phoneOverride: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input CustomerEdit {
|
input CustomerEdit {
|
||||||
idCardData: JSONObject
|
idCardData: JSONObject
|
||||||
idCardPhoto: UploadGQL
|
idCardPhoto: UploadGQL
|
||||||
usSsn: String
|
usSsn: String
|
||||||
|
subscriberInfo: JSONObject
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerNote {
|
type CustomerNote {
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ const typeDef = gql`
|
||||||
txCustomerPhotoAt: Date
|
txCustomerPhotoAt: Date
|
||||||
batched: Boolean
|
batched: Boolean
|
||||||
batchTime: Date
|
batchTime: Date
|
||||||
|
walletScore: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Filter {
|
type Filter {
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@ const authentication = require('../modules/authentication')
|
||||||
const getFIDOStrategyQueryTypes = () => {
|
const getFIDOStrategyQueryTypes = () => {
|
||||||
switch (authentication.CHOSEN_STRATEGY) {
|
switch (authentication.CHOSEN_STRATEGY) {
|
||||||
case 'FIDO2FA':
|
case 'FIDO2FA':
|
||||||
return `generateAttestationOptions(userID: ID!): JSONObject
|
return `generateAttestationOptions(userID: ID!, domain: String!): JSONObject
|
||||||
generateAssertionOptions(username: String!, password: String!): JSONObject`
|
generateAssertionOptions(username: String!, password: String!, domain: String!): JSONObject`
|
||||||
case 'FIDOPasswordless':
|
case 'FIDOPasswordless':
|
||||||
return `generateAttestationOptions(userID: ID!): JSONObject
|
return `generateAttestationOptions(userID: ID!, domain: String!): JSONObject
|
||||||
generateAssertionOptions(username: String!): JSONObject`
|
generateAssertionOptions(username: String!, domain: String!): JSONObject`
|
||||||
case 'FIDOUsernameless':
|
case 'FIDOUsernameless':
|
||||||
return `generateAttestationOptions(userID: ID!): JSONObject
|
return `generateAttestationOptions(userID: ID!, domain: String!): JSONObject
|
||||||
generateAssertionOptions: JSONObject`
|
generateAssertionOptions(domain: String!): JSONObject`
|
||||||
default:
|
default:
|
||||||
return ``
|
return ``
|
||||||
}
|
}
|
||||||
|
|
@ -19,14 +19,14 @@ const getFIDOStrategyQueryTypes = () => {
|
||||||
const getFIDOStrategyMutationsTypes = () => {
|
const getFIDOStrategyMutationsTypes = () => {
|
||||||
switch (authentication.CHOSEN_STRATEGY) {
|
switch (authentication.CHOSEN_STRATEGY) {
|
||||||
case 'FIDO2FA':
|
case 'FIDO2FA':
|
||||||
return `validateAttestation(userID: ID!, attestationResponse: JSONObject!): Boolean
|
return `validateAttestation(userID: ID!, attestationResponse: JSONObject!, domain: String!): Boolean
|
||||||
validateAssertion(username: String!, password: String!, rememberMe: Boolean!, assertionResponse: JSONObject!): Boolean`
|
validateAssertion(username: String!, password: String!, rememberMe: Boolean!, assertionResponse: JSONObject!, domain: String!): Boolean`
|
||||||
case 'FIDOPasswordless':
|
case 'FIDOPasswordless':
|
||||||
return `validateAttestation(userID: ID!, attestationResponse: JSONObject!): Boolean
|
return `validateAttestation(userID: ID!, attestationResponse: JSONObject!, domain: String!): Boolean
|
||||||
validateAssertion(username: String!, rememberMe: Boolean!, assertionResponse: JSONObject!): Boolean`
|
validateAssertion(username: String!, rememberMe: Boolean!, assertionResponse: JSONObject!, domain: String!): Boolean`
|
||||||
case 'FIDOUsernameless':
|
case 'FIDOUsernameless':
|
||||||
return `validateAttestation(userID: ID!, attestationResponse: JSONObject!): Boolean
|
return `validateAttestation(userID: ID!, attestationResponse: JSONObject!, domain: String!): Boolean
|
||||||
validateAssertion(assertionResponse: JSONObject!): Boolean`
|
validateAssertion(assertionResponse: JSONObject!, domain: String!): Boolean`
|
||||||
default:
|
default:
|
||||||
return ``
|
return ``
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ router.use('*', async (req, res, next) => getOperatorId('authentication').then((
|
||||||
cookie: {
|
cookie: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: true,
|
secure: true,
|
||||||
domain: hostname,
|
|
||||||
sameSite: true,
|
sameSite: true,
|
||||||
maxAge: 60 * 10 * 1000 // 10 minutes
|
maxAge: 60 * 10 * 1000 // 10 minutes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const db = require('../../db')
|
||||||
const uuid = require('uuid')
|
const uuid = require('uuid')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const pgp = require('pg-promise')()
|
const pgp = require('pg-promise')()
|
||||||
|
const { loadLatestConfigOrNone, saveConfig } = require('../../../lib/new-settings-loader')
|
||||||
|
|
||||||
const getCustomInfoRequests = (onlyEnabled = false) => {
|
const getCustomInfoRequests = (onlyEnabled = false) => {
|
||||||
const sql = onlyEnabled
|
const sql = onlyEnabled
|
||||||
|
|
@ -23,7 +24,10 @@ const addCustomInfoRequest = (customRequest) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeCustomInfoRequest = (id) => {
|
const removeCustomInfoRequest = (id) => {
|
||||||
return db.none('UPDATE custom_info_requests SET enabled = false WHERE id = $1', [id]).then(() => ({ id }))
|
return loadLatestConfigOrNone()
|
||||||
|
.then(cfg => saveConfig({triggers: _.remove(x => x.customInfoRequestId === id, cfg.triggers ?? [])}))
|
||||||
|
.then(() => db.none('UPDATE custom_info_requests SET enabled = false WHERE id = $1', [id]))
|
||||||
|
.then(() => ({ id }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const editCustomInfoRequest = (id, customRequest) => {
|
const editCustomInfoRequest = (id, customRequest) => {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ function batch (
|
||||||
AND ($12 is null or txs.to_address = $12)
|
AND ($12 is null or txs.to_address = $12)
|
||||||
AND ($13 is null or txs.txStatus = $13)
|
AND ($13 is null or txs.txStatus = $13)
|
||||||
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
||||||
AND (fiat > 0)
|
AND (error IS NOT null OR fiat > 0)
|
||||||
ORDER BY created DESC limit $4 offset $5`
|
ORDER BY created DESC limit $4 offset $5`
|
||||||
|
|
||||||
const cashOutSql = `SELECT 'cashOut' AS tx_class,
|
const cashOutSql = `SELECT 'cashOut' AS tx_class,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ 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 { asyncLocalStorage } = require('./async-storage')
|
||||||
|
const { getOperatorId } = require('./operator')
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -71,16 +72,25 @@ function showAccounts (schemaVersion) {
|
||||||
|
|
||||||
const configSql = 'insert into user_config (type, data, valid, schema_version) values ($1, $2, $3, $4)'
|
const configSql = 'insert into user_config (type, data, valid, schema_version) values ($1, $2, $3, $4)'
|
||||||
function saveConfig (config) {
|
function saveConfig (config) {
|
||||||
return loadLatestConfigOrNone()
|
return Promise.all([loadLatestConfigOrNone(), getOperatorId('middleware')])
|
||||||
.then(currentConfig => {
|
.then(([currentConfig, operatorId]) => {
|
||||||
const newConfig = _.assign(currentConfig, config)
|
const newConfig = _.assign(currentConfig, config)
|
||||||
return db.tx(t => {
|
return db.tx(t => {
|
||||||
return t.none(configSql, ['config', { config: newConfig }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
|
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') })]))
|
.then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })]))
|
||||||
}).catch(console.error)
|
}).catch(console.error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function migrationSaveConfig (config) {
|
||||||
|
return loadLatestConfigOrNone()
|
||||||
|
.then(currentConfig => {
|
||||||
|
const newConfig = _.assign(currentConfig, config)
|
||||||
|
return db.none(configSql, ['config', { config: newConfig }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
|
||||||
|
.catch(console.error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function resetConfig (schemaVersion) {
|
function resetConfig (schemaVersion) {
|
||||||
return db.none(
|
return db.none(
|
||||||
configSql,
|
configSql,
|
||||||
|
|
@ -171,6 +181,7 @@ function migrate () {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
saveConfig,
|
saveConfig,
|
||||||
|
migrationSaveConfig,
|
||||||
resetConfig,
|
resetConfig,
|
||||||
saveAccounts,
|
saveAccounts,
|
||||||
resetAccounts,
|
resetAccounts,
|
||||||
|
|
|
||||||
|
|
@ -828,9 +828,20 @@ function plugins (settings, deviceId) {
|
||||||
.then(buildRates)
|
.then(buildRates)
|
||||||
}
|
}
|
||||||
|
|
||||||
function rateWallet (address) {
|
function rateWallet (cryptoCode, address) {
|
||||||
return walletScoring.rateWallet(settings, address)
|
return walletScoring.rateWallet(settings, cryptoCode, address)
|
||||||
.then(res => res.rating)
|
}
|
||||||
|
|
||||||
|
function isValidWalletScore (score) {
|
||||||
|
return walletScoring.isValidWalletScore(settings, score)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTransactionHash (tx) {
|
||||||
|
return walletScoring.getTransactionHash(settings, tx.cryptoCode, tx.toAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputAddresses (tx, txHashes) {
|
||||||
|
return walletScoring.getInputAddresses(settings, tx.cryptoCode, txHashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -861,7 +872,10 @@ function plugins (settings, deviceId) {
|
||||||
notifyOperator,
|
notifyOperator,
|
||||||
fetchCurrentConfigVersion,
|
fetchCurrentConfigVersion,
|
||||||
pruneMachinesHeartbeat,
|
pruneMachinesHeartbeat,
|
||||||
rateWallet
|
rateWallet,
|
||||||
|
isValidWalletScore,
|
||||||
|
getTransactionHash,
|
||||||
|
getInputAddresses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const _ = require('lodash/fp')
|
||||||
const request = require('request-promise')
|
const request = require('request-promise')
|
||||||
const { utils: coinUtils } = require('lamassu-coins')
|
const { utils: coinUtils } = require('lamassu-coins')
|
||||||
|
|
||||||
const options = require('../../../options')
|
const options = require('../../options')
|
||||||
|
|
||||||
const blockchainDir = options.blockchainDir
|
const blockchainDir = options.blockchainDir
|
||||||
|
|
||||||
|
|
|
||||||
91
lib/plugins/wallet-scoring/ciphertrace/ciphertrace.js
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
const axios = require('axios')
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
|
const logger = require('../../../logger')
|
||||||
|
|
||||||
|
const NAME = 'CipherTrace'
|
||||||
|
const SUPPORTED_COINS = ['BTC', 'ETH', 'BCH', 'LTC', 'BNB', 'RSK']
|
||||||
|
|
||||||
|
function getClient(account) {
|
||||||
|
if (_.isNil(account) || !account.enabled) return null
|
||||||
|
|
||||||
|
const [ctv1, username, secretKey] = account.authorizationValue.split(':')
|
||||||
|
if (_.isNil(ctv1) || _.isNil(username) || _.isNil(secretKey)) {
|
||||||
|
throw new Error('Invalid CipherTrace configuration')
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiVersion = ctv1.slice(-2)
|
||||||
|
const authHeader = {
|
||||||
|
"Authorization": account.authorizationValue
|
||||||
|
}
|
||||||
|
return { apiVersion, authHeader }
|
||||||
|
}
|
||||||
|
|
||||||
|
function rateWallet(account, cryptoCode, address) {
|
||||||
|
const client = getClient(account)
|
||||||
|
if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null)
|
||||||
|
|
||||||
|
const { apiVersion, authHeader } = client
|
||||||
|
const threshold = account.scoreThreshold
|
||||||
|
|
||||||
|
return axios.get(`https://rest.ciphertrace.com/aml/${apiVersion}/${_.toLower(cryptoCode)}/risk?address=${address}`, {
|
||||||
|
headers: authHeader
|
||||||
|
})
|
||||||
|
.then(res => ({ address, score: res.data.risk, isValid: res.data.risk < threshold }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidWalletScore(account, score) {
|
||||||
|
const client = getClient(account)
|
||||||
|
if (_.isNil(client)) return Promise.resolve(true)
|
||||||
|
|
||||||
|
const threshold = account.scoreThreshold
|
||||||
|
return _.isNil(account) ? Promise.resolve(true) : Promise.resolve(score < threshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTransactionHash(account, cryptoCode, receivingAddress) {
|
||||||
|
const client = getClient(account)
|
||||||
|
if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null)
|
||||||
|
|
||||||
|
const { apiVersion, authHeader } = client
|
||||||
|
|
||||||
|
return axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}`, {
|
||||||
|
headers: authHeader
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
const data = res.data
|
||||||
|
if (_.size(data.txHistory) > 1) {
|
||||||
|
logger.warn('An address generated by this wallet was used in more than one transaction')
|
||||||
|
}
|
||||||
|
return _.join(', ', _.map(it => it.txHash, data.txHistory))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputAddresses(account, cryptoCode, txHashes) {
|
||||||
|
const client = getClient(account)
|
||||||
|
if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null)
|
||||||
|
|
||||||
|
const { apiVersion, authHeader } = client
|
||||||
|
|
||||||
|
return axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}tx?txhashes=${txHashes}`, {
|
||||||
|
headers: authHeader
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
const data = res.data
|
||||||
|
if (_.size(data.transactions) > 1) {
|
||||||
|
logger.warn('An address generated by this wallet was used in more than one transaction')
|
||||||
|
}
|
||||||
|
|
||||||
|
const transactionInputs = _.flatMap(it => it.inputs, data.transactions)
|
||||||
|
const inputAddresses = _.map(it => it.address, transactionInputs)
|
||||||
|
|
||||||
|
return inputAddresses
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
NAME,
|
||||||
|
rateWallet,
|
||||||
|
isValidWalletScore,
|
||||||
|
getTransactionHash,
|
||||||
|
getInputAddresses
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,45 @@
|
||||||
const NAME = 'FakeScoring'
|
const NAME = 'FakeScoring'
|
||||||
|
|
||||||
function rateWallet (account, address) {
|
const { WALLET_SCORE_THRESHOLD } = require('../../../constants')
|
||||||
|
|
||||||
|
function rateWallet (account, cryptoCode, address) {
|
||||||
return new Promise((resolve, _) => {
|
return new Promise((resolve, _) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('[WALLET-SCORING] DEBUG: Mock scoring rating wallet address %s', address)
|
console.log('[WALLET-SCORING] DEBUG: Mock scoring rating wallet address %s', address)
|
||||||
return resolve({ address, rating: 5 })
|
return Promise.resolve(2)
|
||||||
|
.then(score => resolve({ address, score, isValid: score < WALLET_SCORE_THRESHOLD }))
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidWalletScore (account, score) {
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
return resolve(score < WALLET_SCORE_THRESHOLD)
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTransactionHash (account, cryptoCode, receivingAddress) {
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
return resolve('<Fake transaction hash>')
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputAddresses (account, cryptoCode, txHashes) {
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
return resolve(['<Fake input address hash>'])
|
||||||
}, 100)
|
}, 100)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
NAME,
|
NAME,
|
||||||
rateWallet
|
rateWallet,
|
||||||
|
isValidWalletScore,
|
||||||
|
getTransactionHash,
|
||||||
|
getInputAddresses
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,12 @@ function supportsBatching (cryptoCode) {
|
||||||
.then(() => SUPPORTS_BATCHING)
|
.then(() => SUPPORTS_BATCHING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => fetch('getblockchaininfo'))
|
||||||
|
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
balance,
|
balance,
|
||||||
sendCoins,
|
sendCoins,
|
||||||
|
|
@ -130,5 +136,6 @@ module.exports = {
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
cryptoNetwork,
|
cryptoNetwork,
|
||||||
supportsBatching
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,12 @@ function supportsBatching (cryptoCode) {
|
||||||
.then(() => SUPPORTS_BATCHING)
|
.then(() => SUPPORTS_BATCHING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => fetch('getblockchaininfo'))
|
||||||
|
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
balance,
|
balance,
|
||||||
sendCoins,
|
sendCoins,
|
||||||
|
|
@ -186,5 +192,6 @@ module.exports = {
|
||||||
fetchRBF,
|
fetchRBF,
|
||||||
estimateFee,
|
estimateFee,
|
||||||
sendCoinsBatch,
|
sendCoinsBatch,
|
||||||
supportsBatching
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,11 @@ function supportsBatching (cryptoCode) {
|
||||||
.then(() => SUPPORTS_BATCHING)
|
.then(() => SUPPORTS_BATCHING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => Promise.resolve('ready'))
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
NAME,
|
NAME,
|
||||||
balance,
|
balance,
|
||||||
|
|
@ -172,5 +177,6 @@ module.exports = {
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
cryptoNetwork,
|
cryptoNetwork,
|
||||||
supportsBatching
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,11 +118,18 @@ function supportsBatching (cryptoCode) {
|
||||||
.then(() => SUPPORTS_BATCHING)
|
.then(() => SUPPORTS_BATCHING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => fetch('getblockchaininfo'))
|
||||||
|
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
balance,
|
balance,
|
||||||
sendCoins,
|
sendCoins,
|
||||||
newAddress,
|
newAddress,
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
supportsBatching
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ module.exports = {
|
||||||
privateKey,
|
privateKey,
|
||||||
isStrictAddress,
|
isStrictAddress,
|
||||||
connect,
|
connect,
|
||||||
supportsBatching
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect (url) {
|
function connect (url) {
|
||||||
|
|
@ -230,3 +231,9 @@ function supportsBatching (cryptoCode) {
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
.then(() => SUPPORTS_BATCHING)
|
.then(() => SUPPORTS_BATCHING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(pify(web3.eth.isSyncing))
|
||||||
|
.then(res => _.isObject(res) ? 'syncing' : 'ready')
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,11 +118,18 @@ function supportsBatching (cryptoCode) {
|
||||||
.then(() => SUPPORTS_BATCHING)
|
.then(() => SUPPORTS_BATCHING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => fetch('getblockchaininfo'))
|
||||||
|
.then(res => !!res['initialblockdownload'] ? 'syncing' : 'ready')
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
balance,
|
balance,
|
||||||
sendCoins,
|
sendCoins,
|
||||||
newAddress,
|
newAddress,
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
supportsBatching
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ const _ = require('lodash/fp')
|
||||||
const BN = require('../../../bn')
|
const BN = require('../../../bn')
|
||||||
const E = require('../../../error')
|
const E = require('../../../error')
|
||||||
const { utils: coinUtils } = require('lamassu-coins')
|
const { utils: coinUtils } = require('lamassu-coins')
|
||||||
const consoleLogLevel = require('console-log-level')
|
|
||||||
|
|
||||||
const NAME = 'FakeWallet'
|
const NAME = 'FakeWallet'
|
||||||
const BATCHABLE_COINS = ['BTC']
|
const BATCHABLE_COINS = ['BTC']
|
||||||
|
|
@ -116,6 +115,11 @@ function supportsBatching (cryptoCode) {
|
||||||
return Promise.resolve(_.includes(cryptoCode, BATCHABLE_COINS))
|
return Promise.resolve(_.includes(cryptoCode, BATCHABLE_COINS))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => Promise.resolve('ready'))
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
NAME,
|
NAME,
|
||||||
balance,
|
balance,
|
||||||
|
|
@ -124,5 +128,6 @@ module.exports = {
|
||||||
newAddress,
|
newAddress,
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
supportsBatching
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ const configPath = utils.configPath(cryptoRec, blockchainDir)
|
||||||
const walletDir = path.resolve(utils.cryptoDir(cryptoRec, blockchainDir), 'wallets')
|
const walletDir = path.resolve(utils.cryptoDir(cryptoRec, blockchainDir), 'wallets')
|
||||||
const unitScale = cryptoRec.unitScale
|
const unitScale = cryptoRec.unitScale
|
||||||
|
|
||||||
|
const SUPPORTS_BATCHING = false
|
||||||
|
|
||||||
function rpcConfig () {
|
function rpcConfig () {
|
||||||
try {
|
try {
|
||||||
const config = jsonRpc.parseConf(configPath)
|
const config = jsonRpc.parseConf(configPath)
|
||||||
|
|
@ -186,9 +188,9 @@ function cryptoNetwork (account, cryptoCode) {
|
||||||
return checkCryptoCode(cryptoCode)
|
return checkCryptoCode(cryptoCode)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
switch(parseInt(rpcConfig().port, 10)) {
|
switch(parseInt(rpcConfig().port, 10)) {
|
||||||
case 18083:
|
case 18082:
|
||||||
return 'main'
|
return 'main'
|
||||||
case 28083:
|
case 28082:
|
||||||
return 'test'
|
return 'test'
|
||||||
case 38083:
|
case 38083:
|
||||||
return 'stage'
|
return 'stage'
|
||||||
|
|
@ -198,11 +200,42 @@ function cryptoNetwork (account, cryptoCode) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function supportsBatching (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => SUPPORTS_BATCHING)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => {
|
||||||
|
try {
|
||||||
|
const config = jsonRpc.parseConf(configPath)
|
||||||
|
|
||||||
|
// Daemon uses a different connection of the wallet
|
||||||
|
const rpcConfig = {
|
||||||
|
username: config['rpc-login'].split(':')[0],
|
||||||
|
password: config['rpc-login'].split(':')[1],
|
||||||
|
port: cryptoRec.defaultPort
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonRpc.fetchDigest(rpcConfig, 'get_info')
|
||||||
|
.then(res => {
|
||||||
|
console.log('res XMR', res)
|
||||||
|
return !!res.synchronized ? 'ready' : 'syncing'
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error('XMR daemon is currently not installed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
balance,
|
balance,
|
||||||
sendCoins,
|
sendCoins,
|
||||||
newAddress,
|
newAddress,
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
cryptoNetwork
|
cryptoNetwork,
|
||||||
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,11 +144,18 @@ function supportsBatching (cryptoCode) {
|
||||||
.then(() => SUPPORTS_BATCHING)
|
.then(() => SUPPORTS_BATCHING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => fetch('getblockchaininfo'))
|
||||||
|
.then(res => !!res['initial_block_download_complete'] ? 'ready' : 'syncing')
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
balance,
|
balance,
|
||||||
sendCoins,
|
sendCoins,
|
||||||
newAddress,
|
newAddress,
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
supportsBatching
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,16 +81,9 @@ cachedVariables.on('expired', (key, val) => {
|
||||||
db.connect({ direct: true }).then(sco => {
|
db.connect({ direct: true }).then(sco => {
|
||||||
sco.client.on('notification', data => {
|
sco.client.on('notification', data => {
|
||||||
const parsedData = JSON.parse(data.payload)
|
const parsedData = JSON.parse(data.payload)
|
||||||
switch (parsedData.type) {
|
return reload(parsedData.schema)
|
||||||
case 'reload':
|
|
||||||
return reload(parsedData.schema)
|
|
||||||
case 'machineAction':
|
|
||||||
return machineAction(parsedData.action, parsedData.value)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return sco.none('LISTEN $1:name', 'poller')
|
return sco.none('LISTEN $1:name', 'reload')
|
||||||
}).catch(console.error)
|
}).catch(console.error)
|
||||||
|
|
||||||
function reload (schema) {
|
function reload (schema) {
|
||||||
|
|
@ -98,38 +91,16 @@ function reload (schema) {
|
||||||
store.set('schema', schema)
|
store.set('schema', schema)
|
||||||
// set asyncLocalStorage so settingsLoader loads settings for the right schema
|
// set asyncLocalStorage so settingsLoader loads settings for the right schema
|
||||||
return asyncLocalStorage.run(store, () => {
|
return asyncLocalStorage.run(store, () => {
|
||||||
return settingsLoader.loadLatest().then(settings => {
|
return settingsLoader.loadLatest()
|
||||||
const pi = plugins(settings)
|
.then(settings => {
|
||||||
cachedVariables.set(schema, { settings, pi, isReloading: false })
|
const pi = plugins(settings)
|
||||||
logger.debug(`Settings for schema '${schema}' reloaded in poller`)
|
cachedVariables.set(schema, { settings, pi, isReloading: false })
|
||||||
return updateAndLoadSanctions()
|
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 pi () { return cachedVariables.get(asyncLocalStorage.getStore().get('schema')).pi }
|
||||||
function settings () { return cachedVariables.get(asyncLocalStorage.getStore().get('schema')).settings }
|
function settings () { return cachedVariables.get(asyncLocalStorage.getStore().get('schema')).settings }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const _ = require('lodash/fp')
|
||||||
const compliance = require('../compliance')
|
const compliance = require('../compliance')
|
||||||
const complianceTriggers = require('../compliance-triggers')
|
const complianceTriggers = require('../compliance-triggers')
|
||||||
const configManager = require('../new-config-manager')
|
const configManager = require('../new-config-manager')
|
||||||
const customers = require('../customers')
|
const { get, add, getEditedData, selectLatestData, getCustomerById, update } = require('../customers')
|
||||||
const httpError = require('../route-helpers').httpError
|
const httpError = require('../route-helpers').httpError
|
||||||
const plugins = require('../plugins')
|
const plugins = require('../plugins')
|
||||||
const Tx = require('../tx')
|
const Tx = require('../tx')
|
||||||
|
|
@ -20,12 +20,14 @@ function addOrUpdateCustomer (req) {
|
||||||
const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers)
|
const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers)
|
||||||
const maxDaysThreshold = complianceTriggers.maxDaysThreshold(triggers)
|
const maxDaysThreshold = complianceTriggers.maxDaysThreshold(triggers)
|
||||||
|
|
||||||
return customers.get(customerData.phone)
|
return get(customerData.phone)
|
||||||
.then(customer => {
|
.then(customer => {
|
||||||
if (customer) return customer
|
if (customer) return customer
|
||||||
|
|
||||||
return customers.add(req.body)
|
return add(req.body)
|
||||||
})
|
})
|
||||||
|
.then(customer => Promise.all([getEditedData(customer.id), getCustomerById(customer.id)]))
|
||||||
|
.then(([customerEditedData, customerOriginalData]) => selectLatestData(customerOriginalData, customerEditedData))
|
||||||
.then(customer => {
|
.then(customer => {
|
||||||
// BACKWARDS_COMPATIBILITY 7.5
|
// BACKWARDS_COMPATIBILITY 7.5
|
||||||
// machines before 7.5 expect customer with sanctions result
|
// machines before 7.5 expect customer with sanctions result
|
||||||
|
|
@ -36,7 +38,7 @@ function addOrUpdateCustomer (req) {
|
||||||
return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, customer)
|
return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, customer)
|
||||||
.then(patch => {
|
.then(patch => {
|
||||||
if (_.isEmpty(patch)) return customer
|
if (_.isEmpty(patch)) return customer
|
||||||
return customers.update(customer.id, patch)
|
return update(customer.id, patch)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(customer => {
|
.then(customer => {
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,52 @@ const _ = require('lodash/fp')
|
||||||
const argv = require('minimist')(process.argv.slice(2))
|
const argv = require('minimist')(process.argv.slice(2))
|
||||||
|
|
||||||
function loadWalletScoring (settings) {
|
function loadWalletScoring (settings) {
|
||||||
if (_.isNil(argv.mockScoring)) {
|
const pluginCode = argv.mockScoring ? 'mock-scoring' : 'ciphertrace'
|
||||||
throw new Error('No wallet scoring API set!')
|
|
||||||
}
|
|
||||||
const pluginCode = argv.mockScoring ? 'mock-scoring' : ''
|
|
||||||
const plugin = ph.load(ph.WALLET_SCORING, pluginCode)
|
const plugin = ph.load(ph.WALLET_SCORING, pluginCode)
|
||||||
const account = settings.accounts[pluginCode]
|
const account = settings.accounts[pluginCode]
|
||||||
|
|
||||||
return { plugin, account }
|
return { plugin, account }
|
||||||
}
|
}
|
||||||
|
|
||||||
function rateWallet (settings, address) {
|
function rateWallet (settings, cryptoCode, address) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const { plugin, account } = loadWalletScoring(settings)
|
const { plugin, account } = loadWalletScoring(settings)
|
||||||
|
|
||||||
return plugin.rateWallet(account, address)
|
return plugin.rateWallet(account, cryptoCode, address)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidWalletScore (settings, score) {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const { plugin, account } = loadWalletScoring(settings)
|
||||||
|
|
||||||
|
return plugin.isValidWalletScore(account, score)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTransactionHash (settings, cryptoCode, receivingAddress) {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const { plugin, account } = loadWalletScoring(settings)
|
||||||
|
|
||||||
|
return plugin.getTransactionHash(account, cryptoCode, receivingAddress)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputAddresses (settings, cryptoCode, txHashes) {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const { plugin, account } = loadWalletScoring(settings)
|
||||||
|
|
||||||
|
return plugin.getInputAddresses(account, cryptoCode, txHashes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
rateWallet
|
rateWallet,
|
||||||
|
isValidWalletScore,
|
||||||
|
getTransactionHash,
|
||||||
|
getInputAddresses
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,11 @@ function supportsBatching (settings, cryptoCode) {
|
||||||
.then(r => r.wallet.supportsBatching(cryptoCode))
|
.then(r => r.wallet.supportsBatching(cryptoCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBlockchainStatus (settings, cryptoCode) {
|
||||||
|
return fetchWallet(settings, cryptoCode)
|
||||||
|
.then(r => r.wallet.checkBlockchainStatus(cryptoCode))
|
||||||
|
}
|
||||||
|
|
||||||
const coinFilter = ['ETH']
|
const coinFilter = ['ETH']
|
||||||
|
|
||||||
const balance = (settings, cryptoCode) => {
|
const balance = (settings, cryptoCode) => {
|
||||||
|
|
@ -265,5 +270,6 @@ module.exports = {
|
||||||
isHd,
|
isHd,
|
||||||
newFunding,
|
newFunding,
|
||||||
cryptoNetwork,
|
cryptoNetwork,
|
||||||
supportsBatching
|
supportsBatching,
|
||||||
|
checkBlockchainStatus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const machineLoader = require('../lib/machine-loader')
|
const machineLoader = require('../lib/machine-loader')
|
||||||
const { saveConfig, saveAccounts, loadLatest } = require('../lib/new-settings-loader')
|
const { migrationSaveConfig, saveAccounts, loadLatest } = require('../lib/new-settings-loader')
|
||||||
const { migrate } = require('../lib/config-migration')
|
const { migrate } = require('../lib/config-migration')
|
||||||
|
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
|
@ -11,7 +11,7 @@ module.exports.up = function (next) {
|
||||||
function migrateConfig (settings) {
|
function migrateConfig (settings) {
|
||||||
const newSettings = migrate(settings.config, settings.accounts)
|
const newSettings = migrate(settings.config, settings.accounts)
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
saveConfig(newSettings.config),
|
migrationSaveConfig(newSettings.config),
|
||||||
saveAccounts(newSettings.accounts)
|
saveAccounts(newSettings.accounts)
|
||||||
])
|
])
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ exports.up = function (next) {
|
||||||
}
|
}
|
||||||
})(deviceIds)
|
})(deviceIds)
|
||||||
|
|
||||||
return settingsLoader.saveConfig(config)
|
return settingsLoader.migrationSaveConfig(config)
|
||||||
})
|
})
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch(err => next(err))
|
.catch(err => next(err))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const { saveConfig } = require('../lib/new-settings-loader')
|
const { migrationSaveConfig } = require('../lib/new-settings-loader')
|
||||||
|
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
const triggersDefault = {
|
const triggersDefault = {
|
||||||
|
|
@ -6,7 +6,7 @@ exports.up = function (next) {
|
||||||
triggersConfig_automation: 'Automatic'
|
triggersConfig_automation: 'Automatic'
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveConfig(triggersDefault)
|
return migrationSaveConfig(triggersDefault)
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.log(err.message)
|
console.log(err.message)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ exports.up = async function (next) {
|
||||||
config[key] = 'none'
|
config[key] = 'none'
|
||||||
}
|
}
|
||||||
}, cryptoCodes)
|
}, cryptoCodes)
|
||||||
return settingsLoader.saveConfig(config)
|
return settingsLoader.migrationSaveConfig(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.down = function (next) {
|
exports.down = function (next) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
|
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
|
||||||
const { CASSETTE_MAX_CAPACITY } = require('../lib/constants')
|
const { CASSETTE_MAX_CAPACITY } = require('../lib/constants')
|
||||||
|
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
|
|
@ -49,7 +49,7 @@ exports.up = function (next) {
|
||||||
return newOverride
|
return newOverride
|
||||||
}, config.notifications_fiatBalanceOverrides)
|
}, config.notifications_fiatBalanceOverrides)
|
||||||
}
|
}
|
||||||
return saveConfig(newConfig)
|
return migrationSaveConfig(newConfig)
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ exports.up = function (next) {
|
||||||
.then(({ config }) => {
|
.then(({ config }) => {
|
||||||
if (!_.isEmpty(config))
|
if (!_.isEmpty(config))
|
||||||
config.locale_timezone = '0:0'
|
config.locale_timezone = '0:0'
|
||||||
return settingsLoader.saveConfig(config)
|
return settingsLoader.migrationSaveConfig(config)
|
||||||
})
|
})
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch(err => next(err))
|
.catch(err => next(err))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
|
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
|
||||||
|
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
const newConfig = {
|
const newConfig = {
|
||||||
|
|
@ -6,7 +6,7 @@ exports.up = function (next) {
|
||||||
}
|
}
|
||||||
return loadLatest()
|
return loadLatest()
|
||||||
.then(config => {
|
.then(config => {
|
||||||
return saveConfig(newConfig)
|
return migrationSaveConfig(newConfig)
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.message === 'lamassu-server is not configured') {
|
if (err.message === 'lamassu-server is not configured') {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
|
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
|
||||||
const { getCryptosFromWalletNamespace } = require('../lib/new-config-manager')
|
const { getCryptosFromWalletNamespace } = require('../lib/new-config-manager')
|
||||||
|
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
|
|
@ -8,7 +8,7 @@ exports.up = function (next) {
|
||||||
.then(config => {
|
.then(config => {
|
||||||
const coins = getCryptosFromWalletNamespace(config)
|
const coins = getCryptosFromWalletNamespace(config)
|
||||||
_.map(coin => { newConfig[`wallets_${coin}_feeMultiplier`] = '1' }, coins)
|
_.map(coin => { newConfig[`wallets_${coin}_feeMultiplier`] = '1' }, coins)
|
||||||
return saveConfig(newConfig)
|
return migrationSaveConfig(newConfig)
|
||||||
})
|
})
|
||||||
.then(next)
|
.then(next)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
|
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
|
||||||
|
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
const sql = [
|
const sql = [
|
||||||
|
|
@ -19,7 +19,7 @@ exports.up = function (next) {
|
||||||
newConfig.notifications_notificationCenter_security = true
|
newConfig.notifications_notificationCenter_security = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveConfig(newConfig)
|
return migrationSaveConfig(newConfig)
|
||||||
.then(() => db.multi(sql, next))
|
.then(() => db.multi(sql, next))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
return next(err)
|
return next(err)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
|
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
|
||||||
const { getCryptosFromWalletNamespace } = require('../lib/new-config-manager.js')
|
const { getCryptosFromWalletNamespace } = require('../lib/new-config-manager.js')
|
||||||
const { utils: coinUtils } = require('lamassu-coins')
|
const { utils: coinUtils } = require('lamassu-coins')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
|
@ -14,7 +14,7 @@ exports.up = function (next) {
|
||||||
newSettings[`wallets_${crypto}_cryptoUnits`] = defaultUnit
|
newSettings[`wallets_${crypto}_cryptoUnits`] = defaultUnit
|
||||||
return newSettings
|
return newSettings
|
||||||
}, activeCryptos)
|
}, activeCryptos)
|
||||||
return saveConfig(newSettings)
|
return migrationSaveConfig(newSettings)
|
||||||
})
|
})
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
var db = require('./db')
|
var db = require('./db')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
|
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
|
||||||
const { getMachines } = require('../lib/machine-loader')
|
const { getMachines } = require('../lib/machine-loader')
|
||||||
|
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
|
|
@ -40,7 +40,7 @@ exports.up = function (next) {
|
||||||
return acc
|
return acc
|
||||||
}, {}, formattedMachines)
|
}, {}, formattedMachines)
|
||||||
|
|
||||||
return saveConfig(newConfig)
|
return migrationSaveConfig(newConfig)
|
||||||
.then(() => db.multi(sql, next))
|
.then(() => db.multi(sql, next))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
migrations/1639577650032-wallet-scoring.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
var sql = [
|
||||||
|
`ALTER TABLE cash_in_txs ADD COLUMN wallet_score SMALLINT`
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
17
migrations/1641482376890-add-overrides-to-subscriber-info.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
var sql = [
|
||||||
|
`ALTER TABLE customers
|
||||||
|
ADD COLUMN phone_override VERIFICATION_TYPE NOT NULL DEFAULT 'automatic',
|
||||||
|
ADD COLUMN phone_override_by UUID,
|
||||||
|
ADD COLUMN phone_override_at TIMESTAMPTZ
|
||||||
|
`
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
13
migrations/1641486859782-wallet-scoring-cash-out.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
var sql = [
|
||||||
|
`ALTER TABLE cash_out_txs ADD COLUMN wallet_score SMALLINT`
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -10,13 +10,12 @@ const useStyles = makeStyles({
|
||||||
imgWrapper: {
|
imgWrapper: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
display: 'flex',
|
display: 'flex'
|
||||||
width: 550
|
|
||||||
},
|
},
|
||||||
imgInner: {
|
imgInner: {
|
||||||
objectFit: 'cover',
|
objectFit: 'cover',
|
||||||
objectPosition: 'center',
|
objectPosition: 'center',
|
||||||
width: 550,
|
width: 500,
|
||||||
marginBottom: 40
|
marginBottom: 40
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -33,17 +32,16 @@ export const Carousel = memo(({ photosData, slidePhoto }) => {
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
width: 50,
|
|
||||||
color: 'transparent',
|
color: 'transparent',
|
||||||
opacity: 1
|
opacity: 1
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
// navButtonsWrapperProps={{
|
navButtonsWrapperProps={{
|
||||||
// style: {
|
style: {
|
||||||
// background: 'linear-gradient(to right, black 10%, transparent 80%)',
|
marginLeft: -22,
|
||||||
// opacity: '0.4'
|
marginRight: -22
|
||||||
// }
|
}
|
||||||
// }}
|
}}
|
||||||
autoPlay={false}
|
autoPlay={false}
|
||||||
indicators={false}
|
indicators={false}
|
||||||
navButtonsAlwaysVisible={true}
|
navButtonsAlwaysVisible={true}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,15 @@ import styles from './Button.styles'
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const ActionButton = memo(
|
const ActionButton = memo(
|
||||||
({ size = 'lg', children, className, buttonClassName, ...props }) => {
|
({
|
||||||
const classes = useStyles({ size })
|
size = 'lg',
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
buttonClassName,
|
||||||
|
backgroundColor,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles({ size, backgroundColor })
|
||||||
return (
|
return (
|
||||||
<div className={classnames(className, classes.wrapper)}>
|
<div className={classnames(className, classes.wrapper)}>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import {
|
||||||
secondaryColor,
|
secondaryColor,
|
||||||
secondaryColorDark,
|
secondaryColorDark,
|
||||||
secondaryColorDarker,
|
secondaryColorDarker,
|
||||||
|
offColor,
|
||||||
|
offDarkColor,
|
||||||
|
offDarkerColor,
|
||||||
spacer
|
spacer
|
||||||
} from 'src/styling/variables'
|
} from 'src/styling/variables'
|
||||||
|
|
||||||
|
|
@ -28,11 +31,11 @@ export default {
|
||||||
const shadowSize = height / 12
|
const shadowSize = height / 12
|
||||||
return { height: height + shadowSize / 2 }
|
return { height: height + shadowSize / 2 }
|
||||||
},
|
},
|
||||||
button: ({ size }) => {
|
button: ({ size, backgroundColor }) => {
|
||||||
const height = pickSize(size)
|
const height = pickSize(size)
|
||||||
const shadowSize = size === 'xl' ? 3 : height / 12
|
const shadowSize = size === 'xl' ? 3 : height / 12
|
||||||
const padding = size === 'xl' ? 20 : height / 2
|
const padding = size === 'xl' ? 20 : height / 2
|
||||||
|
const isGrey = backgroundColor === 'grey'
|
||||||
return {
|
return {
|
||||||
extend: size === 'xl' ? h1 : h3,
|
extend: size === 'xl' ? h1 : h3,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
|
|
@ -40,7 +43,7 @@ export default {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontWeight: 900,
|
fontWeight: 900,
|
||||||
outline: 0,
|
outline: 0,
|
||||||
backgroundColor: secondaryColor,
|
backgroundColor: isGrey ? offDarkColor : secondaryColor,
|
||||||
'&:disabled': {
|
'&:disabled': {
|
||||||
backgroundColor: disabledColor,
|
backgroundColor: disabledColor,
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
|
|
@ -56,15 +59,19 @@ export default {
|
||||||
height,
|
height,
|
||||||
padding: `0 ${padding}px`,
|
padding: `0 ${padding}px`,
|
||||||
borderRadius: height / 4,
|
borderRadius: height / 4,
|
||||||
boxShadow: `0 ${shadowSize}px ${secondaryColorDark}`,
|
boxShadow: `0 ${shadowSize}px ${isGrey ? offColor : secondaryColorDark}`,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: secondaryColorDark,
|
backgroundColor: isGrey ? offColor : secondaryColorDark,
|
||||||
boxShadow: `0 ${shadowSize}px ${secondaryColorDarker}`
|
boxShadow: `0 ${shadowSize}px ${
|
||||||
|
isGrey ? offDarkerColor : secondaryColorDarker
|
||||||
|
}`
|
||||||
},
|
},
|
||||||
'&:active': {
|
'&:active': {
|
||||||
marginTop: shadowSize / 2,
|
marginTop: shadowSize / 2,
|
||||||
backgroundColor: secondaryColorDark,
|
backgroundColor: isGrey ? offDarkColor : secondaryColorDark,
|
||||||
boxShadow: `0 ${shadowSize / 2}px ${secondaryColorDarker}`
|
boxShadow: `0 ${shadowSize / 2}px ${
|
||||||
|
isGrey ? offDarkerColor : secondaryColorDarker
|
||||||
|
}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,9 @@ const Subheader = ({ item, classes, user }) => {
|
||||||
setPrev(it.route)
|
setPrev(it.route)
|
||||||
return true
|
return true
|
||||||
}}>
|
}}>
|
||||||
{it.label}
|
<span className={classes.forceSize} forcesize={it.label}>
|
||||||
|
{it.label}
|
||||||
|
</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -185,12 +185,12 @@ const DataTable = ({
|
||||||
<TBody className={classes.body}>
|
<TBody className={classes.body}>
|
||||||
{loading && <H4>Loading...</H4>}
|
{loading && <H4>Loading...</H4>}
|
||||||
{!loading && R.isEmpty(data) && <EmptyTable message={emptyText} />}
|
{!loading && R.isEmpty(data) && <EmptyTable message={emptyText} />}
|
||||||
{!R.isEmpty(data) && (
|
{!loading && !R.isEmpty(data) && (
|
||||||
<AutoSizer disableWidth>
|
<AutoSizer disableWidth>
|
||||||
{({ height }) => (
|
{({ height }) => (
|
||||||
<List
|
<List
|
||||||
// this has to be in a style because of how the component works
|
// this has to be in a style because of how the component works
|
||||||
style={{ overflow: 'inherit', outline: 'none' }}
|
style={{ outline: 'none' }}
|
||||||
{...props}
|
{...props}
|
||||||
height={loading ? 0 : height}
|
height={loading ? 0 : height}
|
||||||
width={width}
|
width={width}
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,10 @@ const InputFIDOState = ({ state, strategy }) => {
|
||||||
const GENERATE_ASSERTION = gql`
|
const GENERATE_ASSERTION = gql`
|
||||||
query generateAssertionOptions($username: String!${
|
query generateAssertionOptions($username: String!${
|
||||||
strategy === 'FIDO2FA' ? `, $password: String!` : ``
|
strategy === 'FIDO2FA' ? `, $password: String!` : ``
|
||||||
}) {
|
}, $domain: String!) {
|
||||||
generateAssertionOptions(username: $username${
|
generateAssertionOptions(username: $username${
|
||||||
strategy === 'FIDO2FA' ? `, password: $password` : ``
|
strategy === 'FIDO2FA' ? `, password: $password` : ``
|
||||||
})
|
}, domain: $domain)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -55,12 +55,14 @@ const InputFIDOState = ({ state, strategy }) => {
|
||||||
${strategy === 'FIDO2FA' ? `, $password: String!` : ``}
|
${strategy === 'FIDO2FA' ? `, $password: String!` : ``}
|
||||||
$rememberMe: Boolean!
|
$rememberMe: Boolean!
|
||||||
$assertionResponse: JSONObject!
|
$assertionResponse: JSONObject!
|
||||||
|
$domain: String!
|
||||||
) {
|
) {
|
||||||
validateAssertion(
|
validateAssertion(
|
||||||
username: $username
|
username: $username
|
||||||
${strategy === 'FIDO2FA' ? `password: $password` : ``}
|
${strategy === 'FIDO2FA' ? `password: $password` : ``}
|
||||||
rememberMe: $rememberMe
|
rememberMe: $rememberMe
|
||||||
assertionResponse: $assertionResponse
|
assertionResponse: $assertionResponse
|
||||||
|
domain: $domain
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -90,10 +92,12 @@ const InputFIDOState = ({ state, strategy }) => {
|
||||||
strategy === 'FIDO2FA'
|
strategy === 'FIDO2FA'
|
||||||
? {
|
? {
|
||||||
username: state.clientField,
|
username: state.clientField,
|
||||||
password: state.passwordField
|
password: state.passwordField,
|
||||||
|
domain: window.location.hostname
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
username: localClientField
|
username: localClientField,
|
||||||
|
domain: window.location.hostname
|
||||||
},
|
},
|
||||||
onCompleted: ({ generateAssertionOptions: options }) => {
|
onCompleted: ({ generateAssertionOptions: options }) => {
|
||||||
startAssertion(options)
|
startAssertion(options)
|
||||||
|
|
@ -104,12 +108,14 @@ const InputFIDOState = ({ state, strategy }) => {
|
||||||
username: state.clientField,
|
username: state.clientField,
|
||||||
password: state.passwordField,
|
password: state.passwordField,
|
||||||
rememberMe: state.rememberMeField,
|
rememberMe: state.rememberMeField,
|
||||||
assertionResponse: res
|
assertionResponse: res,
|
||||||
|
domain: window.location.hostname
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
username: localClientField,
|
username: localClientField,
|
||||||
rememberMe: localRememberMeField,
|
rememberMe: localRememberMeField,
|
||||||
assertionResponse: res
|
assertionResponse: res,
|
||||||
|
domain: window.location.hostname
|
||||||
}
|
}
|
||||||
validateAssertion({
|
validateAssertion({
|
||||||
variables
|
variables
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,17 @@ const LOGIN = gql`
|
||||||
`
|
`
|
||||||
|
|
||||||
const GENERATE_ASSERTION = gql`
|
const GENERATE_ASSERTION = gql`
|
||||||
query generateAssertionOptions {
|
query generateAssertionOptions($domain: String!) {
|
||||||
generateAssertionOptions
|
generateAssertionOptions(domain: $domain)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const VALIDATE_ASSERTION = gql`
|
const VALIDATE_ASSERTION = gql`
|
||||||
mutation validateAssertion($assertionResponse: JSONObject!) {
|
mutation validateAssertion(
|
||||||
validateAssertion(assertionResponse: $assertionResponse)
|
$assertionResponse: JSONObject!
|
||||||
|
$domain: String!
|
||||||
|
) {
|
||||||
|
validateAssertion(assertionResponse: $assertionResponse, domain: $domain)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -117,7 +120,8 @@ const LoginState = ({ state, dispatch, strategy }) => {
|
||||||
.then(res => {
|
.then(res => {
|
||||||
validateAssertion({
|
validateAssertion({
|
||||||
variables: {
|
variables: {
|
||||||
assertionResponse: res
|
assertionResponse: res,
|
||||||
|
domain: window.location.hostname
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -212,7 +216,9 @@ const LoginState = ({ state, dispatch, strategy }) => {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
return strategy === 'FIDOUsernameless'
|
return strategy === 'FIDOUsernameless'
|
||||||
? assertionOptions()
|
? assertionOptions({
|
||||||
|
variables: { domain: window.location.hostname }
|
||||||
|
})
|
||||||
: dispatch({
|
: dispatch({
|
||||||
type: 'FIDO',
|
type: 'FIDO',
|
||||||
payload: {}
|
payload: {}
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,7 @@ const DenominationsSchema = Yup.object().shape({
|
||||||
.min(1)
|
.min(1)
|
||||||
.max(CURRENCY_MAX)
|
.max(CURRENCY_MAX)
|
||||||
.nullable()
|
.nullable()
|
||||||
.transform(transformNumber),
|
.transform(transformNumber)
|
||||||
zeroConfLimit: Yup.number()
|
|
||||||
.label('0-conf Limit')
|
|
||||||
.required()
|
|
||||||
.min(0)
|
|
||||||
.max(CURRENCY_MAX)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const getElements = (machines, locale = {}, classes) => {
|
const getElements = (machines, locale = {}, classes) => {
|
||||||
|
|
@ -99,20 +94,6 @@ const getElements = (machines, locale = {}, classes) => {
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
|
|
||||||
elements.push({
|
|
||||||
name: 'zeroConfLimit',
|
|
||||||
header: '0-conf Limit',
|
|
||||||
size: 'sm',
|
|
||||||
stripe: true,
|
|
||||||
textAlign: 'right',
|
|
||||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes].zeroConf,
|
|
||||||
input: NumberInput,
|
|
||||||
inputProps: {
|
|
||||||
decimalPlaces: 0
|
|
||||||
},
|
|
||||||
suffix: fiatCurrency
|
|
||||||
})
|
|
||||||
|
|
||||||
return elements
|
return elements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
|
import { DialogActions, DialogContent, Dialog } from '@material-ui/core'
|
||||||
import Grid from '@material-ui/core/Grid'
|
import Grid from '@material-ui/core/Grid'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { parse, format } from 'date-fns/fp'
|
import { parse, format } from 'date-fns/fp'
|
||||||
import _ from 'lodash/fp'
|
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import { useState, React } from 'react'
|
import { useState, React } from 'react'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import ImagePopper from 'src/components/ImagePopper'
|
import ImagePopper from 'src/components/ImagePopper'
|
||||||
import { FeatureButton } from 'src/components/buttons'
|
import { FeatureButton, Button, IconButton } from 'src/components/buttons'
|
||||||
import { TextInput } from 'src/components/inputs/formik'
|
import { TextInput } from 'src/components/inputs/formik'
|
||||||
import { H3, Info3 } from 'src/components/typography'
|
import { H3, Info3, H2 } from 'src/components/typography'
|
||||||
import {
|
import {
|
||||||
OVERRIDE_AUTHORIZED,
|
OVERRIDE_AUTHORIZED,
|
||||||
OVERRIDE_REJECTED
|
OVERRIDE_REJECTED
|
||||||
|
|
@ -17,19 +17,22 @@ import {
|
||||||
import { ReactComponent as CardIcon } from 'src/styling/icons/ID/card/comet.svg'
|
import { ReactComponent as CardIcon } from 'src/styling/icons/ID/card/comet.svg'
|
||||||
import { ReactComponent as PhoneIcon } from 'src/styling/icons/ID/phone/comet.svg'
|
import { ReactComponent as PhoneIcon } from 'src/styling/icons/ID/phone/comet.svg'
|
||||||
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
|
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
|
||||||
|
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
||||||
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/comet.svg'
|
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/comet.svg'
|
||||||
import { ReactComponent as CustomerListViewReversedIcon } from 'src/styling/icons/circle buttons/customer-list-view/white.svg'
|
import { ReactComponent as CustomerListViewReversedIcon } from 'src/styling/icons/circle buttons/customer-list-view/white.svg'
|
||||||
import { ReactComponent as CustomerListViewIcon } from 'src/styling/icons/circle buttons/customer-list-view/zodiac.svg'
|
import { ReactComponent as CustomerListViewIcon } from 'src/styling/icons/circle buttons/customer-list-view/zodiac.svg'
|
||||||
import { ReactComponent as OverviewReversedIcon } from 'src/styling/icons/circle buttons/overview/white.svg'
|
import { ReactComponent as OverviewReversedIcon } from 'src/styling/icons/circle buttons/overview/white.svg'
|
||||||
import { ReactComponent as OverviewIcon } from 'src/styling/icons/circle buttons/overview/zodiac.svg'
|
import { ReactComponent as OverviewIcon } from 'src/styling/icons/circle buttons/overview/zodiac.svg'
|
||||||
import { URI } from 'src/utils/apollo'
|
import { URI } from 'src/utils/apollo'
|
||||||
|
import { onlyFirstToUpper } from 'src/utils/string'
|
||||||
|
|
||||||
import styles from './CustomerData.styles.js'
|
import styles from './CustomerData.styles.js'
|
||||||
import { EditableCard } from './components'
|
import { EditableCard } from './components'
|
||||||
import {
|
import {
|
||||||
customerDataElements,
|
customerDataElements,
|
||||||
customerDataSchemas,
|
customerDataSchemas,
|
||||||
formatDates
|
formatDates,
|
||||||
|
getFormattedPhone
|
||||||
} from './helper.js'
|
} from './helper.js'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
@ -62,6 +65,7 @@ const Photo = ({ show, src }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomerData = ({
|
const CustomerData = ({
|
||||||
|
locale,
|
||||||
customer,
|
customer,
|
||||||
updateCustomer,
|
updateCustomer,
|
||||||
replacePhoto,
|
replacePhoto,
|
||||||
|
|
@ -69,10 +73,12 @@ const CustomerData = ({
|
||||||
deleteEditedData,
|
deleteEditedData,
|
||||||
updateCustomRequest,
|
updateCustomRequest,
|
||||||
authorizeCustomRequest,
|
authorizeCustomRequest,
|
||||||
updateCustomEntry
|
updateCustomEntry,
|
||||||
|
retrieveAdditionalData
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [listView, setListView] = useState(false)
|
const [listView, setListView] = useState(false)
|
||||||
|
const [retrieve, setRetrieve] = useState(false)
|
||||||
|
|
||||||
const idData = R.path(['idCardData'])(customer)
|
const idData = R.path(['idCardData'])(customer)
|
||||||
const rawExpirationDate = R.path(['expirationDate'])(idData)
|
const rawExpirationDate = R.path(['expirationDate'])(idData)
|
||||||
|
|
@ -96,9 +102,12 @@ const CustomerData = ({
|
||||||
R.path(['customInfoRequests'])(customer) ?? []
|
R.path(['customInfoRequests'])(customer) ?? []
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const phone = R.path(['phone'])(customer)
|
||||||
|
const smsData = R.path(['subscriberInfo', 'result'])(customer)
|
||||||
|
|
||||||
const isEven = elem => elem % 2 === 0
|
const isEven = elem => elem % 2 === 0
|
||||||
|
|
||||||
const getVisibleCards = _.filter(elem => elem.isAvailable)
|
const getVisibleCards = R.filter(elem => elem.isAvailable)
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
idCardData: {
|
idCardData: {
|
||||||
|
|
@ -126,9 +135,34 @@ const CustomerData = ({
|
||||||
},
|
},
|
||||||
idCardPhoto: {
|
idCardPhoto: {
|
||||||
idCardPhoto: null
|
idCardPhoto: null
|
||||||
|
},
|
||||||
|
smsData: {
|
||||||
|
phoneNumber: getFormattedPhone(phone, locale.country)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const smsDataElements = [
|
||||||
|
{
|
||||||
|
name: 'phoneNumber',
|
||||||
|
label: 'Phone number',
|
||||||
|
component: TextInput,
|
||||||
|
editable: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const smsDataSchema = {
|
||||||
|
smsData: Yup.lazy(values => {
|
||||||
|
const additionalData = R.omit(['phoneNumber'])(values)
|
||||||
|
const fields = R.keys(additionalData)
|
||||||
|
if (R.length(fields) === 2) {
|
||||||
|
return Yup.object().shape({
|
||||||
|
[R.head(fields)]: Yup.string().required(),
|
||||||
|
[R.last(fields)]: Yup.string().required()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const cards = [
|
const cards = [
|
||||||
{
|
{
|
||||||
fields: customerDataElements.idCardData,
|
fields: customerDataElements.idCardData,
|
||||||
|
|
@ -141,19 +175,31 @@ const CustomerData = ({
|
||||||
deleteEditedData: () => deleteEditedData({ idCardData: null }),
|
deleteEditedData: () => deleteEditedData({ idCardData: null }),
|
||||||
save: values =>
|
save: values =>
|
||||||
editCustomer({
|
editCustomer({
|
||||||
idCardData: _.merge(idData, formatDates(values))
|
idCardData: R.merge(idData, formatDates(values))
|
||||||
}),
|
}),
|
||||||
validationSchema: customerDataSchemas.idCardData,
|
validationSchema: customerDataSchemas.idCardData,
|
||||||
initialValues: initialValues.idCardData,
|
initialValues: initialValues.idCardData,
|
||||||
isAvailable: !_.isNil(idData)
|
isAvailable: !R.isNil(idData)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'SMS Confirmation',
|
fields: smsDataElements,
|
||||||
|
title: 'SMS data',
|
||||||
titleIcon: <PhoneIcon className={classes.cardIcon} />,
|
titleIcon: <PhoneIcon className={classes.cardIcon} />,
|
||||||
authorize: () => {},
|
state: R.path(['phoneOverride'])(customer),
|
||||||
reject: () => {},
|
authorize: () => updateCustomer({ phoneOverride: OVERRIDE_AUTHORIZED }),
|
||||||
save: () => {},
|
reject: () => updateCustomer({ phoneOverride: OVERRIDE_REJECTED }),
|
||||||
isAvailable: false
|
save: values => {
|
||||||
|
editCustomer({
|
||||||
|
subscriberInfo: {
|
||||||
|
result: R.merge(smsData, R.omit(['phoneNumber'])(values))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
validationSchema: smsDataSchema.smsData,
|
||||||
|
retrieveAdditionalData: () => setRetrieve(true),
|
||||||
|
initialValues: initialValues.smsData,
|
||||||
|
isAvailable: !R.isNil(phone),
|
||||||
|
hasAdditionalData: !R.isNil(smsData) && !R.isEmpty(smsData)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
|
|
@ -171,7 +217,7 @@ const CustomerData = ({
|
||||||
updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED }),
|
updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED }),
|
||||||
reject: () => updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED }),
|
reject: () => updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED }),
|
||||||
children: <Info3>{sanctionsDisplay}</Info3>,
|
children: <Info3>{sanctionsDisplay}</Info3>,
|
||||||
isAvailable: !_.isNil(sanctions)
|
isAvailable: !R.isNil(sanctions)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: customerDataElements.frontCamera,
|
fields: customerDataElements.frontCamera,
|
||||||
|
|
@ -198,7 +244,7 @@ const CustomerData = ({
|
||||||
hasImage: true,
|
hasImage: true,
|
||||||
validationSchema: customerDataSchemas.frontCamera,
|
validationSchema: customerDataSchemas.frontCamera,
|
||||||
initialValues: initialValues.frontCamera,
|
initialValues: initialValues.frontCamera,
|
||||||
isAvailable: !_.isNil(customer.frontCameraPath)
|
isAvailable: !R.isNil(customer.frontCameraPath)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: customerDataElements.idCardPhoto,
|
fields: customerDataElements.idCardPhoto,
|
||||||
|
|
@ -223,7 +269,7 @@ const CustomerData = ({
|
||||||
hasImage: true,
|
hasImage: true,
|
||||||
validationSchema: customerDataSchemas.idCardPhoto,
|
validationSchema: customerDataSchemas.idCardPhoto,
|
||||||
initialValues: initialValues.idCardPhoto,
|
initialValues: initialValues.idCardPhoto,
|
||||||
isAvailable: !_.isNil(customer.idCardPhotoPath)
|
isAvailable: !R.isNil(customer.idCardPhotoPath)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: customerDataElements.usSsn,
|
fields: customerDataElements.usSsn,
|
||||||
|
|
@ -236,7 +282,7 @@ const CustomerData = ({
|
||||||
deleteEditedData: () => deleteEditedData({ usSsn: null }),
|
deleteEditedData: () => deleteEditedData({ usSsn: null }),
|
||||||
validationSchema: customerDataSchemas.usSsn,
|
validationSchema: customerDataSchemas.usSsn,
|
||||||
initialValues: initialValues.usSsn,
|
initialValues: initialValues.usSsn,
|
||||||
isAvailable: !_.isNil(customer.usSsn)
|
isAvailable: !R.isNil(customer.usSsn)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -247,7 +293,8 @@ const CustomerData = ({
|
||||||
name: it.customInfoRequest.id,
|
name: it.customInfoRequest.id,
|
||||||
label: it.customInfoRequest.customRequest.name,
|
label: it.customInfoRequest.customRequest.name,
|
||||||
value: it.customerData.data ?? '',
|
value: it.customerData.data ?? '',
|
||||||
component: TextInput
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
title: it.customInfoRequest.customRequest.name,
|
title: it.customInfoRequest.customRequest.name,
|
||||||
|
|
@ -298,7 +345,8 @@ const CustomerData = ({
|
||||||
name: it.label,
|
name: it.label,
|
||||||
label: it.label,
|
label: it.label,
|
||||||
value: it.value ?? '',
|
value: it.value ?? '',
|
||||||
component: TextInput
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
title: it.label,
|
title: it.label,
|
||||||
|
|
@ -319,6 +367,16 @@ const CustomerData = ({
|
||||||
})
|
})
|
||||||
}, R.path(['customFields'])(customer) ?? [])
|
}, R.path(['customFields'])(customer) ?? [])
|
||||||
|
|
||||||
|
R.forEach(it => {
|
||||||
|
initialValues.smsData[it] = smsData[it]
|
||||||
|
smsDataElements.push({
|
||||||
|
name: it,
|
||||||
|
label: onlyFirstToUpper(it),
|
||||||
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
|
})
|
||||||
|
}, R.keys(smsData) ?? [])
|
||||||
|
|
||||||
const editableCard = (
|
const editableCard = (
|
||||||
{
|
{
|
||||||
title,
|
title,
|
||||||
|
|
@ -329,10 +387,12 @@ const CustomerData = ({
|
||||||
fields,
|
fields,
|
||||||
save,
|
save,
|
||||||
deleteEditedData,
|
deleteEditedData,
|
||||||
|
retrieveAdditionalData,
|
||||||
children,
|
children,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
initialValues,
|
initialValues,
|
||||||
hasImage
|
hasImage,
|
||||||
|
hasAdditionalData
|
||||||
},
|
},
|
||||||
idx
|
idx
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -345,12 +405,14 @@ const CustomerData = ({
|
||||||
state={state}
|
state={state}
|
||||||
titleIcon={titleIcon}
|
titleIcon={titleIcon}
|
||||||
hasImage={hasImage}
|
hasImage={hasImage}
|
||||||
|
hasAdditionalData={hasAdditionalData}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
children={children}
|
children={children}
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
save={save}
|
save={save}
|
||||||
deleteEditedData={deleteEditedData}></EditableCard>
|
deleteEditedData={deleteEditedData}
|
||||||
|
retrieveAdditionalData={retrieveAdditionalData}></EditableCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -394,7 +456,7 @@ const CustomerData = ({
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
{!_.isEmpty(customFields) && (
|
{!R.isEmpty(customFields) && (
|
||||||
<div className={classes.wrapper}>
|
<div className={classes.wrapper}>
|
||||||
<span className={classes.separator}>Custom data entry</span>
|
<span className={classes.separator}>Custom data entry</span>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
|
|
@ -429,8 +491,67 @@ const CustomerData = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<RetrieveDataDialog
|
||||||
|
setRetrieve={setRetrieve}
|
||||||
|
retrieveAdditionalData={retrieveAdditionalData}
|
||||||
|
open={retrieve}></RetrieveDataDialog>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RetrieveDataDialog = ({
|
||||||
|
setRetrieve,
|
||||||
|
retrieveAdditionalData,
|
||||||
|
open,
|
||||||
|
props
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
aria-labelledby="form-dialog-title"
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
borderRadius: 8,
|
||||||
|
minWidth: 656,
|
||||||
|
bottom: 125,
|
||||||
|
right: 7
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...props}>
|
||||||
|
<div className={classes.closeButton}>
|
||||||
|
<IconButton
|
||||||
|
size={16}
|
||||||
|
aria-label="close"
|
||||||
|
onClick={() => setRetrieve(false)}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
<H2 className={classes.dialogTitle}>{'Retrieve API data from Twilio'}</H2>
|
||||||
|
<DialogContent className={classes.dialogContent}>
|
||||||
|
<Info3>{`With this action you'll be using Twilio's API to retrieve additional
|
||||||
|
data from this user. This includes name and address, if available.\n`}</Info3>
|
||||||
|
<Info3>{` There is a small cost from Twilio for each retrieval. Would you like
|
||||||
|
to proceed?`}</Info3>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions className={classes.dialogActions}>
|
||||||
|
<Button
|
||||||
|
backgroundColor="grey"
|
||||||
|
className={classes.cancelButton}
|
||||||
|
onClick={() => setRetrieve(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
retrieveAdditionalData()
|
||||||
|
setRetrieve(false)
|
||||||
|
}}>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default CustomerData
|
export default CustomerData
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { offColor } from 'src/styling/variables'
|
import { offColor, spacer } from 'src/styling/variables'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
header: {
|
header: {
|
||||||
|
|
@ -45,5 +45,26 @@ export default {
|
||||||
left: '100%',
|
left: '100%',
|
||||||
marginLeft: 15
|
marginLeft: 15
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
display: 'flex',
|
||||||
|
padding: [[spacer * 2, spacer * 2, 0, spacer * 2]],
|
||||||
|
paddingRight: spacer * 1.5,
|
||||||
|
justifyContent: 'end'
|
||||||
|
},
|
||||||
|
dialogTitle: {
|
||||||
|
margin: [[0, spacer * 2, spacer, spacer * 4 + spacer]]
|
||||||
|
},
|
||||||
|
dialogContent: {
|
||||||
|
width: 615,
|
||||||
|
marginLeft: 16
|
||||||
|
},
|
||||||
|
dialogActions: {
|
||||||
|
padding: spacer * 4,
|
||||||
|
paddingTop: spacer * 2
|
||||||
|
},
|
||||||
|
cancelButton: {
|
||||||
|
marginRight: 8,
|
||||||
|
padding: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@ const GET_CUSTOMER = gql`
|
||||||
daysSuspended
|
daysSuspended
|
||||||
isSuspended
|
isSuspended
|
||||||
isTestCustomer
|
isTestCustomer
|
||||||
|
subscriberInfo
|
||||||
|
phoneOverride
|
||||||
customFields {
|
customFields {
|
||||||
id
|
id
|
||||||
label
|
label
|
||||||
|
|
@ -138,6 +140,7 @@ const SET_CUSTOMER = gql`
|
||||||
lastTxFiatCode
|
lastTxFiatCode
|
||||||
lastTxClass
|
lastTxClass
|
||||||
subscriberInfo
|
subscriberInfo
|
||||||
|
phoneOverride
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -438,6 +441,16 @@ const CustomerProfile = memo(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const retrieveAdditionalData = () =>
|
||||||
|
setCustomer({
|
||||||
|
variables: {
|
||||||
|
customerId,
|
||||||
|
customerInput: {
|
||||||
|
subscriberInfo: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const onClickSidebarItem = code => setClickedItem(code)
|
const onClickSidebarItem = code => setClickedItem(code)
|
||||||
|
|
||||||
const configData = R.path(['config'])(customerResponse) ?? []
|
const configData = R.path(['config'])(customerResponse) ?? []
|
||||||
|
|
@ -558,25 +571,6 @@ const CustomerProfile = memo(() => {
|
||||||
}>
|
}>
|
||||||
{`${blocked ? 'Authorize' : 'Block'} customer`}
|
{`${blocked ? 'Authorize' : 'Block'} customer`}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
|
||||||
color="primary"
|
|
||||||
className={classes.actionButton}
|
|
||||||
Icon={blocked ? AuthorizeIcon : BlockIcon}
|
|
||||||
InverseIcon={
|
|
||||||
blocked ? AuthorizeReversedIcon : BlockReversedIcon
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
setCustomer({
|
|
||||||
variables: {
|
|
||||||
customerId,
|
|
||||||
customerInput: {
|
|
||||||
subscriberInfo: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
{`Retrieve information`}
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -628,6 +622,7 @@ const CustomerProfile = memo(() => {
|
||||||
{isCustomerData && (
|
{isCustomerData && (
|
||||||
<div>
|
<div>
|
||||||
<CustomerData
|
<CustomerData
|
||||||
|
locale={locale}
|
||||||
customer={customerData}
|
customer={customerData}
|
||||||
updateCustomer={updateCustomer}
|
updateCustomer={updateCustomer}
|
||||||
replacePhoto={replacePhoto}
|
replacePhoto={replacePhoto}
|
||||||
|
|
@ -635,7 +630,8 @@ const CustomerProfile = memo(() => {
|
||||||
deleteEditedData={deleteEditedData}
|
deleteEditedData={deleteEditedData}
|
||||||
updateCustomRequest={setCustomerCustomInfoRequest}
|
updateCustomRequest={setCustomerCustomInfoRequest}
|
||||||
authorizeCustomRequest={authorizeCustomRequest}
|
authorizeCustomRequest={authorizeCustomRequest}
|
||||||
updateCustomEntry={updateCustomEntry}></CustomerData>
|
updateCustomEntry={updateCustomEntry}
|
||||||
|
retrieveAdditionalData={retrieveAdditionalData}></CustomerData>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isNotes && (
|
{isNotes && (
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { useState, React } from 'react'
|
||||||
import ErrorMessage from 'src/components/ErrorMessage'
|
import ErrorMessage from 'src/components/ErrorMessage'
|
||||||
import PromptWhenDirty from 'src/components/PromptWhenDirty'
|
import PromptWhenDirty from 'src/components/PromptWhenDirty'
|
||||||
import { MainStatus } from 'src/components/Status'
|
import { MainStatus } from 'src/components/Status'
|
||||||
import { HoverableTooltip } from 'src/components/Tooltip'
|
// import { HoverableTooltip } from 'src/components/Tooltip'
|
||||||
import { ActionButton } from 'src/components/buttons'
|
import { ActionButton } from 'src/components/buttons'
|
||||||
import { Label1, P, H3 } from 'src/components/typography'
|
import { Label1, P, H3 } from 'src/components/typography'
|
||||||
import {
|
import {
|
||||||
|
|
@ -23,6 +23,8 @@ import { ReactComponent as EditReversedIcon } from 'src/styling/icons/action/edi
|
||||||
import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/white.svg'
|
import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/white.svg'
|
||||||
import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/white.svg'
|
import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/white.svg'
|
||||||
import { ReactComponent as CancelReversedIcon } from 'src/styling/icons/button/cancel/white.svg'
|
import { ReactComponent as CancelReversedIcon } from 'src/styling/icons/button/cancel/white.svg'
|
||||||
|
import { ReactComponent as DataReversedIcon } from 'src/styling/icons/button/data/white.svg'
|
||||||
|
import { ReactComponent as DataIcon } from 'src/styling/icons/button/data/zodiac.svg'
|
||||||
import { ReactComponent as ReplaceReversedIcon } from 'src/styling/icons/button/replace/white.svg'
|
import { ReactComponent as ReplaceReversedIcon } from 'src/styling/icons/button/replace/white.svg'
|
||||||
import { ReactComponent as SaveReversedIcon } from 'src/styling/icons/circle buttons/save/white.svg'
|
import { ReactComponent as SaveReversedIcon } from 'src/styling/icons/circle buttons/save/white.svg'
|
||||||
import { comet } from 'src/styling/variables'
|
import { comet } from 'src/styling/variables'
|
||||||
|
|
@ -67,6 +69,13 @@ const fieldStyles = {
|
||||||
fontSize: 14
|
fontSize: 14
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
readOnlyLabel: {
|
||||||
|
color: comet,
|
||||||
|
margin: [[3, 0, 3, 0]]
|
||||||
|
},
|
||||||
|
readOnlyValue: {
|
||||||
|
margin: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,6 +114,23 @@ const EditableField = ({ editing, field, value, size, ...props }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ReadOnlyField = ({ field, value, ...props }) => {
|
||||||
|
const classes = fieldUseStyles()
|
||||||
|
const classNames = {
|
||||||
|
[classes.field]: true,
|
||||||
|
[classes.notEditing]: true
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classnames(classNames)}>
|
||||||
|
<Label1 className={classes.readOnlyLabel}>{field.label}</Label1>
|
||||||
|
<P className={classes.readOnlyValue}>{value}</P>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const EditableCard = ({
|
const EditableCard = ({
|
||||||
fields,
|
fields,
|
||||||
save,
|
save,
|
||||||
|
|
@ -117,7 +143,9 @@ const EditableCard = ({
|
||||||
children,
|
children,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
initialValues,
|
initialValues,
|
||||||
deleteEditedData
|
deleteEditedData,
|
||||||
|
retrieveAdditionalData,
|
||||||
|
hasAdditionalData = true
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
|
@ -148,7 +176,10 @@ const EditableCard = ({
|
||||||
<div className={classes.cardHeader}>
|
<div className={classes.cardHeader}>
|
||||||
{titleIcon}
|
{titleIcon}
|
||||||
<H3 className={classes.cardTitle}>{title}</H3>
|
<H3 className={classes.cardTitle}>{title}</H3>
|
||||||
<HoverableTooltip width={304}></HoverableTooltip>
|
{
|
||||||
|
// TODO: Enable for next release
|
||||||
|
/* <HoverableTooltip width={304}></HoverableTooltip> */
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
{state && authorize && (
|
{state && authorize && (
|
||||||
<div className={classnames(label1ClassNames)}>
|
<div className={classnames(label1ClassNames)}>
|
||||||
|
|
@ -171,7 +202,7 @@ const EditableCard = ({
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
setError(false)
|
setError(false)
|
||||||
}}>
|
}}>
|
||||||
{({ values, touched, errors, setFieldValue }) => (
|
{({ setFieldValue }) => (
|
||||||
<Form>
|
<Form>
|
||||||
<PromptWhenDirty />
|
<PromptWhenDirty />
|
||||||
<div className={classes.row}>
|
<div className={classes.row}>
|
||||||
|
|
@ -180,12 +211,19 @@ const EditableCard = ({
|
||||||
{!hasImage &&
|
{!hasImage &&
|
||||||
fields?.map((field, idx) => {
|
fields?.map((field, idx) => {
|
||||||
return idx >= 0 && idx < 4 ? (
|
return idx >= 0 && idx < 4 ? (
|
||||||
<EditableField
|
!field.editable ? (
|
||||||
field={field}
|
<ReadOnlyField
|
||||||
value={initialValues[field.name]}
|
field={field}
|
||||||
editing={editing}
|
value={initialValues[field.name]}
|
||||||
size={180}
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<EditableField
|
||||||
|
field={field}
|
||||||
|
value={initialValues[field.name]}
|
||||||
|
editing={editing}
|
||||||
|
size={180}
|
||||||
|
/>
|
||||||
|
)
|
||||||
) : null
|
) : null
|
||||||
})}
|
})}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -193,12 +231,19 @@ const EditableCard = ({
|
||||||
{!hasImage &&
|
{!hasImage &&
|
||||||
fields?.map((field, idx) => {
|
fields?.map((field, idx) => {
|
||||||
return idx >= 4 ? (
|
return idx >= 4 ? (
|
||||||
<EditableField
|
!field.editable ? (
|
||||||
field={field}
|
<ReadOnlyField
|
||||||
value={initialValues[field.name]}
|
field={field}
|
||||||
editing={editing}
|
value={initialValues[field.name]}
|
||||||
size={180}
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<EditableField
|
||||||
|
field={field}
|
||||||
|
value={initialValues[field.name]}
|
||||||
|
editing={editing}
|
||||||
|
size={180}
|
||||||
|
/>
|
||||||
|
)
|
||||||
) : null
|
) : null
|
||||||
})}
|
})}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -207,25 +252,34 @@ const EditableCard = ({
|
||||||
<div className={classes.edit}>
|
<div className={classes.edit}>
|
||||||
{!editing && (
|
{!editing && (
|
||||||
<div className={classes.editButton}>
|
<div className={classes.editButton}>
|
||||||
{// TODO: Remove false condition for next release
|
<div className={classes.deleteButton}>
|
||||||
false && (
|
{false && (
|
||||||
<div className={classes.deleteButton}>
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
color="primary"
|
color="primary"
|
||||||
type="button"
|
type="button"
|
||||||
Icon={DeleteIcon}
|
Icon={DeleteIcon}
|
||||||
InverseIcon={DeleteReversedIcon}
|
InverseIcon={DeleteReversedIcon}
|
||||||
onClick={() => deleteEditedData()}>
|
onClick={() => deleteEditedData()}>
|
||||||
{`Delete`}
|
Delete
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
)}
|
||||||
)}
|
{!hasAdditionalData && (
|
||||||
|
<ActionButton
|
||||||
|
color="primary"
|
||||||
|
type="button"
|
||||||
|
Icon={DataIcon}
|
||||||
|
InverseIcon={DataReversedIcon}
|
||||||
|
onClick={() => retrieveAdditionalData()}>
|
||||||
|
Retrieve API data
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
color="primary"
|
color="primary"
|
||||||
Icon={EditIcon}
|
Icon={EditIcon}
|
||||||
InverseIcon={EditReversedIcon}
|
InverseIcon={EditReversedIcon}
|
||||||
onClick={() => setEditing(true)}>
|
onClick={() => setEditing(true)}>
|
||||||
{`Edit`}
|
Edit
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -58,28 +58,36 @@ const ID_CARD_DATA = 'idCardData'
|
||||||
|
|
||||||
const getAuthorizedStatus = (it, triggers) => {
|
const getAuthorizedStatus = (it, triggers) => {
|
||||||
const fields = [
|
const fields = [
|
||||||
'frontCameraPath',
|
'frontCamera',
|
||||||
'idCardData',
|
'idCardData',
|
||||||
'idCardPhotoPath',
|
'idCardPhoto',
|
||||||
'usSsn',
|
'usSsn',
|
||||||
'sanctions'
|
'sanctions'
|
||||||
]
|
]
|
||||||
|
const fieldsWithPathSuffix = ['frontCamera', 'idCardPhoto']
|
||||||
|
|
||||||
const isManualField = fieldName => {
|
const isManualField = fieldName => {
|
||||||
|
const triggerName = R.equals(fieldName, 'frontCamera')
|
||||||
|
? 'facephoto'
|
||||||
|
: fieldName
|
||||||
const manualOverrides = R.filter(
|
const manualOverrides = R.filter(
|
||||||
ite => R.equals(R.toLower(ite.automation), MANUAL),
|
ite => R.equals(R.toLower(ite.automation), MANUAL),
|
||||||
triggers?.overrides ?? []
|
triggers?.overrides ?? []
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!!R.find(ite => R.equals(ite.requirement, fieldName), manualOverrides) ||
|
!!R.find(
|
||||||
R.equals(triggers.automation, MANUAL)
|
ite => R.equals(ite.requirement, triggerName),
|
||||||
|
manualOverrides
|
||||||
|
) || R.equals(R.toLower(triggers.automation), MANUAL)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const pendingFieldStatus = R.map(
|
const pendingFieldStatus = R.map(
|
||||||
ite =>
|
ite =>
|
||||||
!R.isNil(it[`${ite}`])
|
!R.isNil(
|
||||||
|
R.includes(ite, fieldsWithPathSuffix) ? it[`${ite}Path`] : it[`${ite}`]
|
||||||
|
)
|
||||||
? isManualField(ite)
|
? isManualField(ite)
|
||||||
? R.equals(it[`${ite}Override`], 'automatic')
|
? R.equals(it[`${ite}Override`], 'automatic')
|
||||||
: false
|
: false
|
||||||
|
|
@ -347,37 +355,44 @@ const customerDataElements = {
|
||||||
{
|
{
|
||||||
name: 'firstName',
|
name: 'firstName',
|
||||||
label: 'First name',
|
label: 'First name',
|
||||||
component: TextInput
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'documentNumber',
|
name: 'documentNumber',
|
||||||
label: 'ID number',
|
label: 'ID number',
|
||||||
component: TextInput
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'dateOfBirth',
|
name: 'dateOfBirth',
|
||||||
label: 'Birthdate',
|
label: 'Birthdate',
|
||||||
component: TextInput
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'gender',
|
name: 'gender',
|
||||||
label: 'Gender',
|
label: 'Gender',
|
||||||
component: TextInput
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lastName',
|
name: 'lastName',
|
||||||
label: 'Last name',
|
label: 'Last name',
|
||||||
component: TextInput
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'expirationDate',
|
name: 'expirationDate',
|
||||||
label: 'Expiration Date',
|
label: 'Expiration Date',
|
||||||
component: TextInput
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'country',
|
name: 'country',
|
||||||
label: 'Country',
|
label: 'Country',
|
||||||
component: TextInput
|
component: TextInput,
|
||||||
|
editable: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
usSsn: [
|
usSsn: [
|
||||||
|
|
@ -385,7 +400,8 @@ const customerDataElements = {
|
||||||
name: 'usSsn',
|
name: 'usSsn',
|
||||||
label: 'US SSN',
|
label: 'US SSN',
|
||||||
component: TextInput,
|
component: TextInput,
|
||||||
size: 190
|
size: 190,
|
||||||
|
editable: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
idCardPhoto: [{ name: 'idCardPhoto' }],
|
idCardPhoto: [{ name: 'idCardPhoto' }],
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,10 @@ const Services = () => {
|
||||||
|
|
||||||
const updateSettings = element => {
|
const updateSettings = element => {
|
||||||
const settings = element.settings
|
const settings = element.settings
|
||||||
const wallet = R.lensPath(['config', 'wallets_BTC_wallet'])
|
const field = R.lensPath(['config', settings.field])
|
||||||
const isEnabled = R.equals(R.view(wallet, data), settings.requirement)
|
const isEnabled = R.isNil(settings.requirement)
|
||||||
|
? true
|
||||||
|
: R.equals(R.view(field, data), settings.requirement)
|
||||||
settings.enabled = isEnabled
|
settings.enabled = isEnabled
|
||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ export default {
|
||||||
code: 'rbf',
|
code: 'rbf',
|
||||||
component: CheckboxInput,
|
component: CheckboxInput,
|
||||||
settings: {
|
settings: {
|
||||||
|
field: 'wallets_BTC_wallet',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
disabledMessage: 'RBF verification not available',
|
disabledMessage: 'RBF verification not available',
|
||||||
label: 'Lower the confidence of RBF transactions',
|
label: 'Lower the confidence of RBF transactions',
|
||||||
|
|
|
||||||
49
new-lamassu-admin/src/pages/Services/schemas/ciphertrace.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
import CheckboxFormik from 'src/components/inputs/formik/Checkbox'
|
||||||
|
import NumberInputFormik from 'src/components/inputs/formik/NumberInput'
|
||||||
|
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
||||||
|
|
||||||
|
import secretTest from './helper'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
code: 'ciphertrace',
|
||||||
|
name: 'CipherTrace',
|
||||||
|
title: 'CipherTrace (Scoring)',
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
code: 'authorizationValue',
|
||||||
|
display: 'Authorization value',
|
||||||
|
component: SecretInputFormik
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'scoreThreshold',
|
||||||
|
display: 'Score threshold',
|
||||||
|
component: NumberInputFormik,
|
||||||
|
face: true,
|
||||||
|
long: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'enabled',
|
||||||
|
component: CheckboxFormik,
|
||||||
|
settings: {
|
||||||
|
enabled: true,
|
||||||
|
disabledMessage: 'This plugin is disabled',
|
||||||
|
label: 'Enabled',
|
||||||
|
requirement: null
|
||||||
|
},
|
||||||
|
face: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
getValidationSchema: account => {
|
||||||
|
return Yup.object().shape({
|
||||||
|
authorizationValue: Yup.string()
|
||||||
|
.max(100, 'Too long')
|
||||||
|
.test(secretTest(account?.authorizationValue)),
|
||||||
|
scoreThreshold: Yup.number()
|
||||||
|
.min(1, 'The number should be between 1 and 10')
|
||||||
|
.max(10, 'The number should be between 1 and 10')
|
||||||
|
.test(secretTest(account?.scoreThreshold))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ import bitgo from './bitgo'
|
||||||
import bitstamp from './bitstamp'
|
import bitstamp from './bitstamp'
|
||||||
import blockcypher from './blockcypher'
|
import blockcypher from './blockcypher'
|
||||||
import cex from './cex'
|
import cex from './cex'
|
||||||
|
import ciphertrace from './ciphertrace'
|
||||||
import ftx from './ftx'
|
import ftx from './ftx'
|
||||||
import infura from './infura'
|
import infura from './infura'
|
||||||
import itbit from './itbit'
|
import itbit from './itbit'
|
||||||
|
|
@ -21,5 +22,6 @@ export default {
|
||||||
[twilio.code]: twilio,
|
[twilio.code]: twilio,
|
||||||
[binanceus.code]: binanceus,
|
[binanceus.code]: binanceus,
|
||||||
[cex.code]: cex,
|
[cex.code]: cex,
|
||||||
[ftx.code]: ftx
|
[ftx.code]: ftx,
|
||||||
|
[ciphertrace.code]: ciphertrace
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useLazyQuery, useMutation } from '@apollo/react-hooks'
|
import { useLazyQuery, useMutation } from '@apollo/react-hooks'
|
||||||
import { makeStyles, Box } from '@material-ui/core'
|
import { makeStyles, Box } from '@material-ui/core'
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
|
import classNames from 'classnames'
|
||||||
import { add, differenceInYears, format, sub, parse } from 'date-fns/fp'
|
import { add, differenceInYears, format, sub, parse } from 'date-fns/fp'
|
||||||
import FileSaver from 'file-saver'
|
import FileSaver from 'file-saver'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
|
@ -25,6 +26,12 @@ import { ReactComponent as DownloadInverseIcon } from 'src/styling/icons/button/
|
||||||
import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.svg'
|
import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.svg'
|
||||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||||
|
import {
|
||||||
|
primaryColor,
|
||||||
|
subheaderColor,
|
||||||
|
errorColor,
|
||||||
|
offErrorColor
|
||||||
|
} from 'src/styling/variables'
|
||||||
import { URI } from 'src/utils/apollo'
|
import { URI } from 'src/utils/apollo'
|
||||||
import { onlyFirstToUpper } from 'src/utils/string'
|
import { onlyFirstToUpper } from 'src/utils/string'
|
||||||
|
|
||||||
|
|
@ -186,6 +193,40 @@ const DetailsRow = ({ it: tx, timezone }) => {
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const walletScoreEl = (
|
||||||
|
<div className={classes.walletScore}>
|
||||||
|
<svg width={103} height={10}>
|
||||||
|
{R.map(
|
||||||
|
it => (
|
||||||
|
<circle
|
||||||
|
cx={it * 10 + 6}
|
||||||
|
cy={4}
|
||||||
|
r={3.5}
|
||||||
|
fill={
|
||||||
|
it < tx.walletScore
|
||||||
|
? !R.includes('score is above', tx.hasError ?? '')
|
||||||
|
? primaryColor
|
||||||
|
: errorColor
|
||||||
|
: !R.includes('score is above', tx.hasError ?? '')
|
||||||
|
? subheaderColor
|
||||||
|
: offErrorColor
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
R.range(0, 10)
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
<P
|
||||||
|
noMargin
|
||||||
|
className={classNames({
|
||||||
|
[classes.bold]: true,
|
||||||
|
[classes.error]: R.includes('score is above', tx.hasError ?? '')
|
||||||
|
})}>
|
||||||
|
{tx.walletScore}
|
||||||
|
</P>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const getCancelMessage = () => {
|
const getCancelMessage = () => {
|
||||||
const cashInMessage = `The user will not be able to redeem the inserted bills, even if they subsequently confirm the transaction. If they've already deposited bills, you'll need to reconcile this transaction with them manually.`
|
const cashInMessage = `The user will not be able to redeem the inserted bills, even if they subsequently confirm the transaction. If they've already deposited bills, you'll need to reconcile this transaction with them manually.`
|
||||||
const cashOutMessage = `The user will not be able to redeem the cash, even if they subsequently send the required coins. If they've already sent you coins, you'll need to reconcile this transaction with them manually.`
|
const cashOutMessage = `The user will not be able to redeem the cash, even if they subsequently send the required coins. If they've already sent you coins, you'll need to reconcile this transaction with them manually.`
|
||||||
|
|
@ -301,7 +342,14 @@ const DetailsRow = ({ it: tx, timezone }) => {
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.secondRow}>
|
<div className={classes.secondRow}>
|
||||||
<div className={classes.address}>
|
<div className={classes.address}>
|
||||||
<Label>Address</Label>
|
<div className={classes.addressHeader}>
|
||||||
|
<Label>Address</Label>
|
||||||
|
{!R.isNil(tx.walletScore) && (
|
||||||
|
<HoverableTooltip parentElements={walletScoreEl}>
|
||||||
|
{`CipherTrace score: ${tx.walletScore}/10`}
|
||||||
|
</HoverableTooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CopyToClipboard>
|
<CopyToClipboard>
|
||||||
{formatAddress(tx.cryptoCode, tx.toAddress)}
|
{formatAddress(tx.cryptoCode, tx.toAddress)}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import typographyStyles from 'src/components/typography/styles'
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
import { offColor, comet, white } from 'src/styling/variables'
|
import { offColor, comet, white, tomato } from 'src/styling/variables'
|
||||||
|
|
||||||
const { p } = typographyStyles
|
const { p } = typographyStyles
|
||||||
|
|
||||||
|
|
@ -113,5 +113,22 @@ export default {
|
||||||
otherActionsGroup: {
|
otherActionsGroup: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
addressHeader: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
walletScore: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
'& > p': {
|
||||||
|
marginLeft: 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
color: tomato
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ const GET_TRANSACTIONS = gql`
|
||||||
batched
|
batched
|
||||||
batchTime
|
batchTime
|
||||||
rawTickerPrice
|
rawTickerPrice
|
||||||
|
walletScore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ const CustomInfoRequests = ({
|
||||||
setToBeEdited(null)
|
setToBeEdited(null)
|
||||||
toggleWizard()
|
toggleWizard()
|
||||||
},
|
},
|
||||||
refetchQueries: () => ['customInfoRequests']
|
refetchQueries: () => ['getData', 'customInfoRequests']
|
||||||
})
|
})
|
||||||
|
|
||||||
const [removeEntry] = useMutation(REMOVE_ROW, {
|
const [removeEntry] = useMutation(REMOVE_ROW, {
|
||||||
|
|
@ -105,7 +105,7 @@ const CustomInfoRequests = ({
|
||||||
setDeleteDialog(false)
|
setDeleteDialog(false)
|
||||||
setHasError(false)
|
setHasError(false)
|
||||||
},
|
},
|
||||||
refetchQueries: () => ['customInfoRequests']
|
refetchQueries: () => ['getData', 'customInfoRequests']
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleDelete = id => {
|
const handleDelete = id => {
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ import { ReactComponent as WhiteLockIcon } from 'src/styling/icons/button/lock/w
|
||||||
import { ReactComponent as LockIcon } from 'src/styling/icons/button/lock/zodiac.svg'
|
import { ReactComponent as LockIcon } from 'src/styling/icons/button/lock/zodiac.svg'
|
||||||
import { ReactComponent as WhiteUserRoleIcon } from 'src/styling/icons/button/user-role/white.svg'
|
import { ReactComponent as WhiteUserRoleIcon } from 'src/styling/icons/button/user-role/white.svg'
|
||||||
import { ReactComponent as UserRoleIcon } from 'src/styling/icons/button/user-role/zodiac.svg'
|
import { ReactComponent as UserRoleIcon } from 'src/styling/icons/button/user-role/zodiac.svg'
|
||||||
|
import { IP_CHECK_REGEX } from 'src/utils/constants'
|
||||||
|
|
||||||
import styles from './UserManagement.styles'
|
import styles from './UserManagement.styles'
|
||||||
import ChangeRoleModal from './modals/ChangeRoleModal'
|
import ChangeRoleModal from './modals/ChangeRoleModal'
|
||||||
import CreateUserModal from './modals/CreateUserModal'
|
import CreateUserModal from './modals/CreateUserModal'
|
||||||
import EnableUserModal from './modals/EnableUserModal'
|
import EnableUserModal from './modals/EnableUserModal'
|
||||||
|
import FIDOModal from './modals/FIDOModal'
|
||||||
import Reset2FAModal from './modals/Reset2FAModal'
|
import Reset2FAModal from './modals/Reset2FAModal'
|
||||||
import ResetPasswordModal from './modals/ResetPasswordModal'
|
import ResetPasswordModal from './modals/ResetPasswordModal'
|
||||||
|
|
||||||
|
|
@ -41,8 +43,8 @@ const GET_USERS = gql`
|
||||||
`
|
`
|
||||||
|
|
||||||
const GENERATE_ATTESTATION = gql`
|
const GENERATE_ATTESTATION = gql`
|
||||||
query generateAttestationOptions($userID: ID!) {
|
query generateAttestationOptions($userID: ID!, $domain: String!) {
|
||||||
generateAttestationOptions(userID: $userID)
|
generateAttestationOptions(userID: $userID, domain: $domain)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -50,10 +52,12 @@ const VALIDATE_ATTESTATION = gql`
|
||||||
mutation validateAttestation(
|
mutation validateAttestation(
|
||||||
$userID: ID!
|
$userID: ID!
|
||||||
$attestationResponse: JSONObject!
|
$attestationResponse: JSONObject!
|
||||||
|
$domain: String!
|
||||||
) {
|
) {
|
||||||
validateAttestation(
|
validateAttestation(
|
||||||
userID: $userID
|
userID: $userID
|
||||||
attestationResponse: $attestationResponse
|
attestationResponse: $attestationResponse
|
||||||
|
domain: $domain
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -100,11 +104,12 @@ const Users = () => {
|
||||||
|
|
||||||
const [generateAttestationOptions] = useLazyQuery(GENERATE_ATTESTATION, {
|
const [generateAttestationOptions] = useLazyQuery(GENERATE_ATTESTATION, {
|
||||||
onCompleted: ({ generateAttestationOptions: options }) => {
|
onCompleted: ({ generateAttestationOptions: options }) => {
|
||||||
startAttestation(options).then(res => {
|
return startAttestation(options).then(res => {
|
||||||
validateAttestation({
|
validateAttestation({
|
||||||
variables: {
|
variables: {
|
||||||
userID: userInfo.id,
|
userID: userInfo.id,
|
||||||
attestationResponse: res
|
attestationResponse: res,
|
||||||
|
domain: window.location.hostname
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -191,12 +196,20 @@ const Users = () => {
|
||||||
InverseIcon={WhiteUserRoleIcon}
|
InverseIcon={WhiteUserRoleIcon}
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserInfo(u)
|
if (IP_CHECK_REGEX.test(window.location.hostname)) {
|
||||||
generateAttestationOptions({
|
dispatch({
|
||||||
variables: {
|
type: 'open',
|
||||||
userID: u.id
|
payload: 'showFIDOModal'
|
||||||
}
|
})
|
||||||
})
|
} else {
|
||||||
|
setUserInfo(u)
|
||||||
|
generateAttestationOptions({
|
||||||
|
variables: {
|
||||||
|
userID: u.id,
|
||||||
|
domain: window.location.hostname
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}}>
|
}}>
|
||||||
Add FIDO
|
Add FIDO
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|
@ -272,6 +285,7 @@ const Users = () => {
|
||||||
user={userInfo}
|
user={userInfo}
|
||||||
requiresConfirmation={userInfo?.role === 'superuser'}
|
requiresConfirmation={userInfo?.role === 'superuser'}
|
||||||
/>
|
/>
|
||||||
|
<FIDOModal state={state} dispatch={dispatch} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import Modal from 'src/components/Modal'
|
||||||
|
import { Button } from 'src/components/buttons'
|
||||||
|
import { Info2, P } from 'src/components/typography'
|
||||||
|
|
||||||
|
import styles from '../UserManagement.styles'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const ChangeRoleModal = ({ state, dispatch }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch({
|
||||||
|
type: 'close',
|
||||||
|
payload: 'showFIDOModal'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
closeOnBackdropClick={true}
|
||||||
|
width={450}
|
||||||
|
height={275}
|
||||||
|
handleClose={handleClose}
|
||||||
|
open={state.showFIDOModal}>
|
||||||
|
<Info2 className={classes.modalTitle}>About FIDO authentication</Info2>
|
||||||
|
<P className={classes.info}>
|
||||||
|
This feature is only available for websites with configured domains, and
|
||||||
|
we detected that a domain is not configured at the moment.
|
||||||
|
</P>
|
||||||
|
<P>
|
||||||
|
Make sure that a domain is configured for this website and try again
|
||||||
|
later.
|
||||||
|
</P>
|
||||||
|
<div className={classes.footer}>
|
||||||
|
<Button className={classes.submit} onClick={() => handleClose()}>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChangeRoleModal
|
||||||
|
|
@ -8,6 +8,7 @@ import { ReactComponent as BitcoinCashLogo } from 'src/styling/logos/icon-bitcoi
|
||||||
import { ReactComponent as DashLogo } from 'src/styling/logos/icon-dash-colour.svg'
|
import { ReactComponent as DashLogo } from 'src/styling/logos/icon-dash-colour.svg'
|
||||||
import { ReactComponent as EthereumLogo } from 'src/styling/logos/icon-ethereum-colour.svg'
|
import { ReactComponent as EthereumLogo } from 'src/styling/logos/icon-ethereum-colour.svg'
|
||||||
import { ReactComponent as LitecoinLogo } from 'src/styling/logos/icon-litecoin-colour.svg'
|
import { ReactComponent as LitecoinLogo } from 'src/styling/logos/icon-litecoin-colour.svg'
|
||||||
|
import { ReactComponent as MoneroLogo } from 'src/styling/logos/icon-monero-colour.svg'
|
||||||
import { ReactComponent as TetherLogo } from 'src/styling/logos/icon-tether-colour.svg'
|
import { ReactComponent as TetherLogo } from 'src/styling/logos/icon-tether-colour.svg'
|
||||||
import { ReactComponent as ZCashLogo } from 'src/styling/logos/icon-zcash-colour.svg'
|
import { ReactComponent as ZCashLogo } from 'src/styling/logos/icon-zcash-colour.svg'
|
||||||
|
|
||||||
|
|
@ -53,6 +54,8 @@ const getLogo = code => {
|
||||||
return ZCashLogo
|
return ZCashLogo
|
||||||
case 'USDT':
|
case 'USDT':
|
||||||
return TetherLogo
|
return TetherLogo
|
||||||
|
case 'XMR':
|
||||||
|
return MoneroLogo
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="13px" height="33px" viewBox="0 0 13 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="13px" height="33px" viewBox="0 0 13 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
<polygon id="Simple-Arrow-White" fill="#FFFFFF" fill-rule="nonzero" points="12.1912718 1.56064837 10.8306233 0.395663059 0.196798664 16.2200463 10.8250965 32.3956631 12.1967987 31.2473125 2.33241023 16.233075"></polygon>
|
<polygon id="Simple-Arrow-White" fill="#1b2559" fill-rule="nonzero" points="12.1912718 1.56064837 10.8306233 0.395663059 0.196798664 16.2200463 10.8250965 32.3956631 12.1967987 31.2473125 2.33241023 16.233075"></polygon>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 512 B |
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="15px" height="34px" viewBox="0 0 15 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="15px" height="34px" viewBox="0 0 15 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
<g id="Group-2-Copy" transform="translate(1.000000, 1.000000)" stroke="#FFFFFF" stroke-width="2">
|
<g id="Group-2-Copy" transform="translate(1.000000, 1.000000)" stroke="#1b2559" stroke-width="2">
|
||||||
<polyline id="Path-4-Copy" points="0 0 12 15.8202247 0 32"></polyline>
|
<polyline id="Path-4-Copy" points="0 0 12 15.8202247 0 32"></polyline>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 485 B |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M127.998 0C57.318 0 0 57.317 0 127.999c0 14.127 2.29 27.716 6.518 40.43H44.8V60.733l83.2 83.2 83.198-83.2v107.695h38.282c4.231-12.714 6.521-26.303 6.521-40.43C256 57.314 198.681 0 127.998 0" fill="#F60"/><path d="M108.867 163.062l-36.31-36.311v67.765H18.623c22.47 36.863 63.051 61.48 109.373 61.48s86.907-24.617 109.374-61.48h-53.933V126.75l-36.31 36.31-19.13 19.129-19.128-19.128h-.002z" fill="#4C4C4C"/></svg>
|
||||||
|
After Width: | Height: | Size: 540 B |
|
|
@ -1,14 +1,19 @@
|
||||||
const CURRENCY_MAX = 9999999
|
const CURRENCY_MAX = 9999999
|
||||||
const MIN_NUMBER_OF_CASSETTES = 2
|
const MIN_NUMBER_OF_CASSETTES = 2
|
||||||
const MAX_NUMBER_OF_CASSETTES = 4
|
const MAX_NUMBER_OF_CASSETTES = 4
|
||||||
|
const WALLET_SCORING_DEFAULT_THRESHOLD = 9
|
||||||
|
|
||||||
const AUTOMATIC = 'automatic'
|
const AUTOMATIC = 'automatic'
|
||||||
const MANUAL = 'manual'
|
const MANUAL = 'manual'
|
||||||
|
|
||||||
|
const IP_CHECK_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CURRENCY_MAX,
|
CURRENCY_MAX,
|
||||||
MIN_NUMBER_OF_CASSETTES,
|
MIN_NUMBER_OF_CASSETTES,
|
||||||
MAX_NUMBER_OF_CASSETTES,
|
MAX_NUMBER_OF_CASSETTES,
|
||||||
AUTOMATIC,
|
AUTOMATIC,
|
||||||
MANUAL
|
MANUAL,
|
||||||
|
WALLET_SCORING_DEFAULT_THRESHOLD,
|
||||||
|
IP_CHECK_REGEX
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@
|
||||||
"test": "mocha --recursive tests",
|
"test": "mocha --recursive tests",
|
||||||
"jtest": "jest --detectOpenHandles",
|
"jtest": "jest --detectOpenHandles",
|
||||||
"build-admin": "npm run build-admin:css && npm run build-admin:main && npm run build-admin:lamassu",
|
"build-admin": "npm run build-admin:css && npm run build-admin:main && npm run build-admin:lamassu",
|
||||||
"server": "nodemon bin/lamassu-server --mockSms --mockScoring",
|
"server": "nodemon bin/lamassu-server --mockSms",
|
||||||
"admin-server": "nodemon bin/lamassu-admin-server --dev",
|
"admin-server": "nodemon bin/lamassu-admin-server --dev",
|
||||||
"graphql-server": "nodemon bin/new-graphql-dev-insecure",
|
"graphql-server": "nodemon bin/new-graphql-dev-insecure",
|
||||||
"watch": "concurrently \"npm:server\" \"npm:admin-server\" \"npm:graphql-server\"",
|
"watch": "concurrently \"npm:server\" \"npm:admin-server\" \"npm:graphql-server\"",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.js": "/static/js/main.af6b9292.chunk.js",
|
"main.js": "/static/js/main.6ef30c04.chunk.js",
|
||||||
"main.js.map": "/static/js/main.af6b9292.chunk.js.map",
|
"main.js.map": "/static/js/main.6ef30c04.chunk.js.map",
|
||||||
"runtime-main.js": "/static/js/runtime-main.5b925903.js",
|
"runtime-main.js": "/static/js/runtime-main.5b925903.js",
|
||||||
"runtime-main.js.map": "/static/js/runtime-main.5b925903.js.map",
|
"runtime-main.js.map": "/static/js/runtime-main.5b925903.js.map",
|
||||||
"static/js/2.cbbac6fe.chunk.js": "/static/js/2.cbbac6fe.chunk.js",
|
"static/js/2.c4e7abab.chunk.js": "/static/js/2.c4e7abab.chunk.js",
|
||||||
"static/js/2.cbbac6fe.chunk.js.map": "/static/js/2.cbbac6fe.chunk.js.map",
|
"static/js/2.c4e7abab.chunk.js.map": "/static/js/2.c4e7abab.chunk.js.map",
|
||||||
"index.html": "/index.html",
|
"index.html": "/index.html",
|
||||||
"static/js/2.cbbac6fe.chunk.js.LICENSE.txt": "/static/js/2.cbbac6fe.chunk.js.LICENSE.txt",
|
"static/js/2.c4e7abab.chunk.js.LICENSE.txt": "/static/js/2.c4e7abab.chunk.js.LICENSE.txt",
|
||||||
"static/media/3-cassettes-open-1-left.d6d9aa73.svg": "/static/media/3-cassettes-open-1-left.d6d9aa73.svg",
|
"static/media/3-cassettes-open-1-left.d6d9aa73.svg": "/static/media/3-cassettes-open-1-left.d6d9aa73.svg",
|
||||||
"static/media/3-cassettes-open-2-left.a9ee8d4c.svg": "/static/media/3-cassettes-open-2-left.a9ee8d4c.svg",
|
"static/media/3-cassettes-open-2-left.a9ee8d4c.svg": "/static/media/3-cassettes-open-2-left.a9ee8d4c.svg",
|
||||||
"static/media/3-cassettes-open-3-left.08fed660.svg": "/static/media/3-cassettes-open-3-left.08fed660.svg",
|
"static/media/3-cassettes-open-3-left.08fed660.svg": "/static/media/3-cassettes-open-3-left.08fed660.svg",
|
||||||
|
|
@ -60,6 +60,7 @@
|
||||||
"static/media/icon-dash-colour.e01c021b.svg": "/static/media/icon-dash-colour.e01c021b.svg",
|
"static/media/icon-dash-colour.e01c021b.svg": "/static/media/icon-dash-colour.e01c021b.svg",
|
||||||
"static/media/icon-ethereum-colour.761723a2.svg": "/static/media/icon-ethereum-colour.761723a2.svg",
|
"static/media/icon-ethereum-colour.761723a2.svg": "/static/media/icon-ethereum-colour.761723a2.svg",
|
||||||
"static/media/icon-litecoin-colour.bd861b5e.svg": "/static/media/icon-litecoin-colour.bd861b5e.svg",
|
"static/media/icon-litecoin-colour.bd861b5e.svg": "/static/media/icon-litecoin-colour.bd861b5e.svg",
|
||||||
|
"static/media/icon-monero-colour.650b7bd1.svg": "/static/media/icon-monero-colour.650b7bd1.svg",
|
||||||
"static/media/icon-tether-colour.92d7fda4.svg": "/static/media/icon-tether-colour.92d7fda4.svg",
|
"static/media/icon-tether-colour.92d7fda4.svg": "/static/media/icon-tether-colour.92d7fda4.svg",
|
||||||
"static/media/icon-zcash-colour.68b1c20b.svg": "/static/media/icon-zcash-colour.68b1c20b.svg",
|
"static/media/icon-zcash-colour.68b1c20b.svg": "/static/media/icon-zcash-colour.68b1c20b.svg",
|
||||||
"static/media/keyboard.cc22b859.svg": "/static/media/keyboard.cc22b859.svg",
|
"static/media/keyboard.cc22b859.svg": "/static/media/keyboard.cc22b859.svg",
|
||||||
|
|
@ -91,6 +92,7 @@
|
||||||
"static/media/white.41439910.svg": "/static/media/white.41439910.svg",
|
"static/media/white.41439910.svg": "/static/media/white.41439910.svg",
|
||||||
"static/media/white.460daa02.svg": "/static/media/white.460daa02.svg",
|
"static/media/white.460daa02.svg": "/static/media/white.460daa02.svg",
|
||||||
"static/media/white.4676bf59.svg": "/static/media/white.4676bf59.svg",
|
"static/media/white.4676bf59.svg": "/static/media/white.4676bf59.svg",
|
||||||
|
"static/media/white.47196e40.svg": "/static/media/white.47196e40.svg",
|
||||||
"static/media/white.51296906.svg": "/static/media/white.51296906.svg",
|
"static/media/white.51296906.svg": "/static/media/white.51296906.svg",
|
||||||
"static/media/white.5750bfd1.svg": "/static/media/white.5750bfd1.svg",
|
"static/media/white.5750bfd1.svg": "/static/media/white.5750bfd1.svg",
|
||||||
"static/media/white.5a37327b.svg": "/static/media/white.5a37327b.svg",
|
"static/media/white.5a37327b.svg": "/static/media/white.5a37327b.svg",
|
||||||
|
|
@ -100,16 +102,17 @@
|
||||||
"static/media/white.81edd31f.svg": "/static/media/white.81edd31f.svg",
|
"static/media/white.81edd31f.svg": "/static/media/white.81edd31f.svg",
|
||||||
"static/media/white.8406a3ba.svg": "/static/media/white.8406a3ba.svg",
|
"static/media/white.8406a3ba.svg": "/static/media/white.8406a3ba.svg",
|
||||||
"static/media/white.87f75e06.svg": "/static/media/white.87f75e06.svg",
|
"static/media/white.87f75e06.svg": "/static/media/white.87f75e06.svg",
|
||||||
|
"static/media/white.8c4085b7.svg": "/static/media/white.8c4085b7.svg",
|
||||||
"static/media/white.8ccc4767.svg": "/static/media/white.8ccc4767.svg",
|
"static/media/white.8ccc4767.svg": "/static/media/white.8ccc4767.svg",
|
||||||
"static/media/white.958fe55d.svg": "/static/media/white.958fe55d.svg",
|
"static/media/white.958fe55d.svg": "/static/media/white.958fe55d.svg",
|
||||||
"static/media/white.9814829c.svg": "/static/media/white.9814829c.svg",
|
"static/media/white.9814829c.svg": "/static/media/white.9814829c.svg",
|
||||||
|
"static/media/white.9f2c5216.svg": "/static/media/white.9f2c5216.svg",
|
||||||
"static/media/white.b7754662.svg": "/static/media/white.b7754662.svg",
|
"static/media/white.b7754662.svg": "/static/media/white.b7754662.svg",
|
||||||
"static/media/white.bd0d7dca.svg": "/static/media/white.bd0d7dca.svg",
|
"static/media/white.bd0d7dca.svg": "/static/media/white.bd0d7dca.svg",
|
||||||
"static/media/white.cc7667ff.svg": "/static/media/white.cc7667ff.svg",
|
"static/media/white.cc7667ff.svg": "/static/media/white.cc7667ff.svg",
|
||||||
"static/media/white.e53d9d4a.svg": "/static/media/white.e53d9d4a.svg",
|
"static/media/white.e53d9d4a.svg": "/static/media/white.e53d9d4a.svg",
|
||||||
"static/media/white.e72682b5.svg": "/static/media/white.e72682b5.svg",
|
"static/media/white.e72682b5.svg": "/static/media/white.e72682b5.svg",
|
||||||
"static/media/white.e8851a0a.svg": "/static/media/white.e8851a0a.svg",
|
"static/media/white.e8851a0a.svg": "/static/media/white.e8851a0a.svg",
|
||||||
"static/media/white.efb6eb57.svg": "/static/media/white.efb6eb57.svg",
|
|
||||||
"static/media/white.f97c75d2.svg": "/static/media/white.f97c75d2.svg",
|
"static/media/white.f97c75d2.svg": "/static/media/white.f97c75d2.svg",
|
||||||
"static/media/white.fa4681e8.svg": "/static/media/white.fa4681e8.svg",
|
"static/media/white.fa4681e8.svg": "/static/media/white.fa4681e8.svg",
|
||||||
"static/media/white.fe6ed797.svg": "/static/media/white.fe6ed797.svg",
|
"static/media/white.fe6ed797.svg": "/static/media/white.fe6ed797.svg",
|
||||||
|
|
@ -117,6 +120,7 @@
|
||||||
"static/media/zodiac-resized.c4907e4b.svg": "/static/media/zodiac-resized.c4907e4b.svg",
|
"static/media/zodiac-resized.c4907e4b.svg": "/static/media/zodiac-resized.c4907e4b.svg",
|
||||||
"static/media/zodiac.088002a2.svg": "/static/media/zodiac.088002a2.svg",
|
"static/media/zodiac.088002a2.svg": "/static/media/zodiac.088002a2.svg",
|
||||||
"static/media/zodiac.13543418.svg": "/static/media/zodiac.13543418.svg",
|
"static/media/zodiac.13543418.svg": "/static/media/zodiac.13543418.svg",
|
||||||
|
"static/media/zodiac.1806a875.svg": "/static/media/zodiac.1806a875.svg",
|
||||||
"static/media/zodiac.1bc04c23.svg": "/static/media/zodiac.1bc04c23.svg",
|
"static/media/zodiac.1bc04c23.svg": "/static/media/zodiac.1bc04c23.svg",
|
||||||
"static/media/zodiac.1bd00dea.svg": "/static/media/zodiac.1bd00dea.svg",
|
"static/media/zodiac.1bd00dea.svg": "/static/media/zodiac.1bd00dea.svg",
|
||||||
"static/media/zodiac.2fe856d5.svg": "/static/media/zodiac.2fe856d5.svg",
|
"static/media/zodiac.2fe856d5.svg": "/static/media/zodiac.2fe856d5.svg",
|
||||||
|
|
@ -124,6 +128,7 @@
|
||||||
"static/media/zodiac.5547e32c.svg": "/static/media/zodiac.5547e32c.svg",
|
"static/media/zodiac.5547e32c.svg": "/static/media/zodiac.5547e32c.svg",
|
||||||
"static/media/zodiac.594ae9e7.svg": "/static/media/zodiac.594ae9e7.svg",
|
"static/media/zodiac.594ae9e7.svg": "/static/media/zodiac.594ae9e7.svg",
|
||||||
"static/media/zodiac.6cff3051.svg": "/static/media/zodiac.6cff3051.svg",
|
"static/media/zodiac.6cff3051.svg": "/static/media/zodiac.6cff3051.svg",
|
||||||
|
"static/media/zodiac.71910a69.svg": "/static/media/zodiac.71910a69.svg",
|
||||||
"static/media/zodiac.74570495.svg": "/static/media/zodiac.74570495.svg",
|
"static/media/zodiac.74570495.svg": "/static/media/zodiac.74570495.svg",
|
||||||
"static/media/zodiac.779a5bbc.svg": "/static/media/zodiac.779a5bbc.svg",
|
"static/media/zodiac.779a5bbc.svg": "/static/media/zodiac.779a5bbc.svg",
|
||||||
"static/media/zodiac.84e03611.svg": "/static/media/zodiac.84e03611.svg",
|
"static/media/zodiac.84e03611.svg": "/static/media/zodiac.84e03611.svg",
|
||||||
|
|
@ -136,17 +141,16 @@
|
||||||
"static/media/zodiac.aa028a2c.svg": "/static/media/zodiac.aa028a2c.svg",
|
"static/media/zodiac.aa028a2c.svg": "/static/media/zodiac.aa028a2c.svg",
|
||||||
"static/media/zodiac.b27733af.svg": "/static/media/zodiac.b27733af.svg",
|
"static/media/zodiac.b27733af.svg": "/static/media/zodiac.b27733af.svg",
|
||||||
"static/media/zodiac.bb7722c5.svg": "/static/media/zodiac.bb7722c5.svg",
|
"static/media/zodiac.bb7722c5.svg": "/static/media/zodiac.bb7722c5.svg",
|
||||||
"static/media/zodiac.c424c928.svg": "/static/media/zodiac.c424c928.svg",
|
|
||||||
"static/media/zodiac.cdf82496.svg": "/static/media/zodiac.cdf82496.svg",
|
|
||||||
"static/media/zodiac.ce4a1545.svg": "/static/media/zodiac.ce4a1545.svg",
|
"static/media/zodiac.ce4a1545.svg": "/static/media/zodiac.ce4a1545.svg",
|
||||||
"static/media/zodiac.cfe5467c.svg": "/static/media/zodiac.cfe5467c.svg",
|
"static/media/zodiac.cfe5467c.svg": "/static/media/zodiac.cfe5467c.svg",
|
||||||
"static/media/zodiac.e161cf6b.svg": "/static/media/zodiac.e161cf6b.svg",
|
"static/media/zodiac.e161cf6b.svg": "/static/media/zodiac.e161cf6b.svg",
|
||||||
|
"static/media/zodiac.e181d06a.svg": "/static/media/zodiac.e181d06a.svg",
|
||||||
"static/media/zodiac.eea12e4f.svg": "/static/media/zodiac.eea12e4f.svg",
|
"static/media/zodiac.eea12e4f.svg": "/static/media/zodiac.eea12e4f.svg",
|
||||||
"static/media/zodiac.f9cb5ba2.svg": "/static/media/zodiac.f9cb5ba2.svg"
|
"static/media/zodiac.f3536991.svg": "/static/media/zodiac.f3536991.svg"
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/js/runtime-main.5b925903.js",
|
"static/js/runtime-main.5b925903.js",
|
||||||
"static/js/2.cbbac6fe.chunk.js",
|
"static/js/2.c4e7abab.chunk.js",
|
||||||
"static/js/main.af6b9292.chunk.js"
|
"static/js/main.6ef30c04.chunk.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1 +1 @@
|
||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="robots" content="noindex"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Lamassu Admin</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="root"></div><script>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.cbbac6fe.chunk.js"></script><script src="/static/js/main.af6b9292.chunk.js"></script></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="robots" content="noindex"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Lamassu Admin</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="root"></div><script>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.c4e7abab.chunk.js"></script><script src="/static/js/main.6ef30c04.chunk.js"></script></body></html>
|
||||||
3
public/static/js/2.c4e7abab.chunk.js
Normal file
|
|
@ -17,15 +17,6 @@ object-assign
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*!
|
|
||||||
* UAParser.js v0.7.22
|
|
||||||
* Lightweight JavaScript-based User-Agent string parser
|
|
||||||
* https://github.com/faisalman/ua-parser-js
|
|
||||||
*
|
|
||||||
* Copyright © 2012-2019 Faisal Salman <f@faisalman.com>
|
|
||||||
* Licensed under MIT License
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*! *****************************************************************************
|
/*! *****************************************************************************
|
||||||
Copyright (c) Microsoft Corporation.
|
Copyright (c) Microsoft Corporation.
|
||||||
|
|
||||||
2
public/static/js/main.6ef30c04.chunk.js
Normal file
1
public/static/js/main.6ef30c04.chunk.js.map
Normal file
1
public/static/media/icon-monero-colour.650b7bd1.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M127.998 0C57.318 0 0 57.317 0 127.999c0 14.127 2.29 27.716 6.518 40.43H44.8V60.733l83.2 83.2 83.198-83.2v107.695h38.282c4.231-12.714 6.521-26.303 6.521-40.43C256 57.314 198.681 0 127.998 0" fill="#F60"/><path d="M108.867 163.062l-36.31-36.311v67.765H18.623c22.47 36.863 63.051 61.48 109.373 61.48s86.907-24.617 109.374-61.48h-53.933V126.75l-36.31 36.31-19.13 19.129-19.128-19.128h-.002z" fill="#4C4C4C"/></svg>
|
||||||
|
After Width: | Height: | Size: 540 B |
10
public/static/media/white.47196e40.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon/button/user-role/white</title>
|
||||||
|
<g id="icon/button/user-role/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
|
||||||
|
<g id="User-Role-Icon-White" transform="translate(2.500000, 0.500000)" stroke="#FFFFFF">
|
||||||
|
<path d="M5.50008791,6.84274776 L5.5,11 L3.66666667,9.35927189 L1.83333333,11 L1.83223109,6.84216075 C2.37179795,7.15453375 2.99835187,7.33333333 3.66666667,7.33333333 C4.33456272,7.33333333 4.96075021,7.15475774 5.50008791,6.84274776 Z" id="Bottom"></path>
|
||||||
|
<circle id="Top" cx="3.66666667" cy="3.66666667" r="3.66666667"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 840 B |
11
public/static/media/white.8c4085b7.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon/button/key/white</title>
|
||||||
|
<g id="icon/button/key/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Group" transform="translate(0.500000, 0.500000)" stroke="#FFFFFF">
|
||||||
|
<circle id="Oval" cx="2.75" cy="8.25" r="2.75"></circle>
|
||||||
|
<line x1="5.04166667" y1="5.95833333" x2="11" y2="0" id="Path-13" stroke-linecap="round" stroke-linejoin="round"></line>
|
||||||
|
<line x1="8.25" y1="3.66666667" x2="10.5416667" y2="1.375" id="Path-13-Copy" stroke-width="2" stroke-linejoin="round"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 773 B |
11
public/static/media/white.9f2c5216.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon/button/lock/white</title>
|
||||||
|
<g id="icon/button/lock/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Lock-Icon-White" transform="translate(0.500000, 0.500000)">
|
||||||
|
<path d="M7.98058644,2.48058644 C7.98058644,1.11059638 6.86999006,0 5.5,0 C4.13000994,0 3.01941356,1.11059638 3.01941356,2.48058644 C3.01941356,3.39391315 3.01941356,4.09482878 3.01941356,4.58333333 L7.98058644,4.58333333 C7.98058644,4.09482878 7.98058644,3.39391315 7.98058644,2.48058644 Z" id="Lock" stroke="#FFFFFF" stroke-linejoin="round"></path>
|
||||||
|
<rect id="Body" stroke="#FFFFFF" stroke-linejoin="round" x="0" y="4.58333333" width="11" height="6.41666667"></rect>
|
||||||
|
<circle id="Key-Hole" fill="#FFFFFF" cx="5.5" cy="7.33333333" r="1"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1,010 B |