Merge branch 'dev' into fix/lam-298/custom_info_requests_flow
This commit is contained in:
commit
c5bf50b932
46 changed files with 441 additions and 149 deletions
|
|
@ -14,13 +14,13 @@ function setup (dataDir) {
|
||||||
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} --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} --stagenet --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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -828,9 +828,12 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -861,7 +864,8 @@ function plugins (settings, deviceId) {
|
||||||
notifyOperator,
|
notifyOperator,
|
||||||
fetchCurrentConfigVersion,
|
fetchCurrentConfigVersion,
|
||||||
pruneMachinesHeartbeat,
|
pruneMachinesHeartbeat,
|
||||||
rateWallet
|
rateWallet,
|
||||||
|
isValidWalletScore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
47
lib/plugins/wallet-scoring/ciphertrace/ciphertrace.js
Normal file
47
lib/plugins/wallet-scoring/ciphertrace/ciphertrace.js
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
const axios = require('axios')
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
|
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 Promise.resolve(score < threshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
NAME,
|
||||||
|
rateWallet,
|
||||||
|
isValidWalletScore
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,27 @@
|
||||||
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(7)
|
||||||
|
.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)
|
}, 100)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
NAME,
|
NAME,
|
||||||
rateWallet
|
rateWallet,
|
||||||
|
isValidWalletScore
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,17 @@ function cryptoNetwork (account, cryptoCode) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function supportsBatching (cryptoCode) {
|
||||||
|
return checkCryptoCode(cryptoCode)
|
||||||
|
.then(() => SUPPORTS_BATCHING)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
balance,
|
balance,
|
||||||
sendCoins,
|
sendCoins,
|
||||||
newAddress,
|
newAddress,
|
||||||
getStatus,
|
getStatus,
|
||||||
newFunding,
|
newFunding,
|
||||||
cryptoNetwork
|
cryptoNetwork,
|
||||||
|
supportsBatching
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,32 @@ 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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
rateWallet
|
rateWallet,
|
||||||
|
isValidWalletScore
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
migrations/1639577650032-wallet-scoring.js
Normal file
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()
|
||||||
|
}
|
||||||
|
|
@ -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: {}
|
||||||
|
|
|
||||||
|
|
@ -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
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'
|
||||||
|
|
||||||
|
|
@ -163,6 +170,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.`
|
||||||
|
|
@ -282,7 +323,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ const GET_TRANSACTIONS = gql`
|
||||||
isAnonymous
|
isAnonymous
|
||||||
batched
|
batched
|
||||||
batchTime
|
batchTime
|
||||||
|
walletScore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.d91ec0e3.chunk.js",
|
"main.js": "/static/js/main.6ef30c04.chunk.js",
|
||||||
"main.js.map": "/static/js/main.d91ec0e3.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.7b88b6e4.chunk.js": "/static/js/2.7b88b6e4.chunk.js",
|
"static/js/2.c4e7abab.chunk.js": "/static/js/2.c4e7abab.chunk.js",
|
||||||
"static/js/2.7b88b6e4.chunk.js.map": "/static/js/2.7b88b6e4.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.7b88b6e4.chunk.js.LICENSE.txt": "/static/js/2.7b88b6e4.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",
|
||||||
|
|
@ -149,7 +150,7 @@
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/js/runtime-main.5b925903.js",
|
"static/js/runtime-main.5b925903.js",
|
||||||
"static/js/2.7b88b6e4.chunk.js",
|
"static/js/2.c4e7abab.chunk.js",
|
||||||
"static/js/main.d91ec0e3.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.7b88b6e4.chunk.js"></script><script src="/static/js/main.d91ec0e3.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>
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/static/js/main.6ef30c04.chunk.js
Normal file
2
public/static/js/main.6ef30c04.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
public/static/js/main.6ef30c04.chunk.js.map
Normal file
1
public/static/js/main.6ef30c04.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
public/static/media/icon-monero-colour.650b7bd1.svg
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 |
Loading…
Add table
Add a link
Reference in a new issue