WIP
This commit is contained in:
parent
f7561acf3c
commit
80e851fb59
9 changed files with 131 additions and 76 deletions
9
bin/lamassu-ofac-update
Executable file
9
bin/lamassu-ofac-update
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const ofac = require('../lib/ofac/update')
|
||||||
|
|
||||||
|
console.log('Updating OFAC databases.')
|
||||||
|
|
||||||
|
ofac.update()
|
||||||
|
.then(() => console.log('Success.'))
|
||||||
|
.catch(console.log)
|
||||||
|
|
@ -8,6 +8,7 @@ const logger = require('./logger')
|
||||||
const poller = require('./poller')
|
const poller = require('./poller')
|
||||||
const settingsLoader = require('./settings-loader')
|
const settingsLoader = require('./settings-loader')
|
||||||
const options = require('./options')
|
const options = require('./options')
|
||||||
|
const ofac = require('./ofac/index')
|
||||||
|
|
||||||
const devMode = argv.dev || options.http
|
const devMode = argv.dev || options.http
|
||||||
|
|
||||||
|
|
@ -32,7 +33,8 @@ function run () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function runOnce () {
|
function runOnce () {
|
||||||
return settingsLoader.loadLatest()
|
return ofac.load()
|
||||||
|
.then(settingsLoader.loadLatest())
|
||||||
.then(settings => {
|
.then(settings => {
|
||||||
poller.start(settings)
|
poller.start(settings)
|
||||||
|
|
||||||
|
|
|
||||||
23
lib/compliance.js
Normal file
23
lib/compliance.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
|
const ofac = require('./ofac/index')
|
||||||
|
|
||||||
|
function matchOfac (customer) {
|
||||||
|
const nameParts = _.flatMap(_.split(/\s+/), [customer.firstName, customer.lastName])
|
||||||
|
const birthDate = customer.dateOfBirth
|
||||||
|
|
||||||
|
const result = ofac.match(nameParts, birthDate)
|
||||||
|
console.log('DEBUG200: %s', result)
|
||||||
|
|
||||||
|
if (result > 0.8) throw new Error('Compliance error')
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCustomer (config, customer) {
|
||||||
|
if (config.sanctionsVerificationActive) {
|
||||||
|
matchOfac(customer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customer
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {validateCustomer}
|
||||||
|
|
@ -328,4 +328,4 @@ function batch () {
|
||||||
}, customers)))
|
}, customers)))
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { add, get, batch, getById, update }
|
module.exports = {add, get, batch, getById, update}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@ const nameUtils = require('./name-utils')
|
||||||
const options = require('../options')
|
const options = require('../options')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
const debug_log = require('../pp')(__filename) // KOSTIS TODO: remove
|
const debugLog = require('../pp')(__filename) // KOSTIS TODO: remove
|
||||||
|
|
||||||
const OFAC_SOURCES_DIR = path.join(options.ofacDataDir, 'sources')
|
|
||||||
|
|
||||||
let structs = null
|
let structs = null
|
||||||
|
|
||||||
|
|
@ -18,19 +16,21 @@ const readdir = util.promisify(fs.readdir)
|
||||||
function load () {
|
function load () {
|
||||||
// NOTE: Not sure how you push code updates to existing clients. This problem
|
// NOTE: Not sure how you push code updates to existing clients. This problem
|
||||||
// might pop up if new code is pushed, without re-doing setup.
|
// might pop up if new code is pushed, without re-doing setup.
|
||||||
if (!OFAC_SOURCES_DIR) {
|
if (!options.ofacDataDir) {
|
||||||
const message = 'The ofacDataDir option has not been set in lamassu.json'
|
const message = 'The ofacDataDir option has not been set in lamassu.json'
|
||||||
return Promise.reject(new Error(message))
|
return Promise.reject(new Error(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
return readdir(OFAC_SOURCES_DIR)
|
const ofacSourcesDir = path.join(options.ofacDataDir, 'sources')
|
||||||
.then(_.flow(
|
|
||||||
_.map(file => path.join(OFAC_SOURCES_DIR, file)),
|
return readdir(ofacSourcesDir)
|
||||||
loader.load
|
.then(_.flow(
|
||||||
))
|
_.map(file => path.join(ofacSourcesDir, file)),
|
||||||
.then(result => {
|
loader.load
|
||||||
return (structs = result)
|
))
|
||||||
})
|
.then(result => {
|
||||||
|
return (structs = result)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// nameParts should be an object like {firstName: "John", lastName: "Doe", ...}
|
// nameParts should be an object like {firstName: "John", lastName: "Doe", ...}
|
||||||
|
|
@ -69,10 +69,10 @@ function match (nameParts, birthDateString, options) {
|
||||||
])(birthDateString)
|
])(birthDateString)
|
||||||
|
|
||||||
const candidate = {parts, fullName, words, birthDate}
|
const candidate = {parts, fullName, words, birthDate}
|
||||||
debug && debug_log(candidate)
|
debug && debugLog(candidate)
|
||||||
|
|
||||||
const result = matcher.match(structs, candidate, options)
|
const result = matcher.match(structs, candidate, options)
|
||||||
debug && debug_log(result)
|
debug && debugLog(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const jaro = require('talisman/metrics/distance/jaro')
|
const jaro = require('talisman/metrics/distance/jaro')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
const debug_log = require('../pp')(__filename) // KOSTIS TODO: remove
|
const debugLog = require('../pp')(__filename) // KOSTIS TODO: remove
|
||||||
|
|
||||||
const stringSimilarity = _.curry(jaro)
|
const stringSimilarity = _.curry(jaro)
|
||||||
|
|
||||||
|
|
@ -44,7 +44,6 @@ function match (structs, candidate, options) {
|
||||||
_.map(_.get('id'))
|
_.map(_.get('id'))
|
||||||
)(aliases)
|
)(aliases)
|
||||||
|
|
||||||
|
|
||||||
const phoneticWeight = ratio
|
const phoneticWeight = ratio
|
||||||
const stringWeight = 1 - phoneticWeight
|
const stringWeight = 1 - phoneticWeight
|
||||||
|
|
||||||
|
|
@ -90,8 +89,8 @@ function match (structs, candidate, options) {
|
||||||
_.map(_.first)
|
_.map(_.first)
|
||||||
)(matches)
|
)(matches)
|
||||||
|
|
||||||
debug && debug_log(aliasIdsFromFullName)
|
debug && debugLog(aliasIdsFromFullName)
|
||||||
debug && debug_log(aliasIdsFromNamePart)
|
debug && debugLog(aliasIdsFromNamePart)
|
||||||
|
|
||||||
// Get the full record for each matched id
|
// Get the full record for each matched id
|
||||||
const getIndividual = aliasId => {
|
const getIndividual = aliasId => {
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,28 @@ const util = require('util')
|
||||||
const options = require('../options')
|
const options = require('../options')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
const OFAC_DATA_DIR = options.ofacDataDir
|
|
||||||
const OFAC_SOURCES_DIR = path.join(OFAC_DATA_DIR, 'sources')
|
|
||||||
const OFAC_ETAGS_FILE = path.join(OFAC_DATA_DIR, 'etags.json')
|
|
||||||
const DOWNLOAD_DIR = path.resolve('/tmp')
|
const DOWNLOAD_DIR = path.resolve('/tmp')
|
||||||
|
|
||||||
|
function mkdir (path) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.mkdir(path, err => {
|
||||||
|
if (!err) return resolve()
|
||||||
|
if (err.code === 'EEXIST') return resolve()
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const mkdir = util.promisify(fs.mkdir)
|
|
||||||
const readFile = util.promisify(fs.readFile)
|
const readFile = util.promisify(fs.readFile)
|
||||||
const writeFile = util.promisify(fs.writeFile)
|
const writeFile = util.promisify(fs.writeFile)
|
||||||
const rename = util.promisify(fs.rename)
|
const rename = util.promisify(fs.rename)
|
||||||
const unlink = util.promisify(fs.unlink)
|
const unlink = util.promisify(fs.unlink)
|
||||||
|
|
||||||
const remove = file => {
|
const remove = file => {
|
||||||
console.log("remove", file)
|
console.log('remove', file)
|
||||||
return unlink(file)
|
return unlink(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const promiseGetEtag = (source) => {
|
const promiseGetEtag = (source) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const {url: sourceUrl} = source
|
const {url: sourceUrl} = source
|
||||||
|
|
@ -46,14 +51,14 @@ const promiseGetEtag = (source) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const download = _.curry((dstDir, source) => {
|
const download = _.curry((dstDir, source) => {
|
||||||
console.log("download", source)
|
console.log('download', source)
|
||||||
const {name, url: sourceUrl} = source
|
const {name, url: sourceUrl} = source
|
||||||
const dstFile = path.join(dstDir, name + '.xml')
|
const dstFile = path.join(dstDir, name + '.xml')
|
||||||
const file = fs.createWriteStream(dstFile)
|
const file = fs.createWriteStream(dstFile)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const request = https.get(sourceUrl, response => {
|
const request = https.get(sourceUrl, response => {
|
||||||
response.pipe(file);
|
response.pipe(file)
|
||||||
file.on('finish', () => file.close(() => resolve(dstFile)))
|
file.on('finish', () => file.close(() => resolve(dstFile)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -62,14 +67,14 @@ const download = _.curry((dstDir, source) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const parseToJson = srcFile => {
|
const parseToJson = srcFile => {
|
||||||
console.log("parseToJson", srcFile)
|
console.log('parseToJson', srcFile)
|
||||||
|
|
||||||
const dstFile = srcFile.replace(/\.xml$/, '.json')
|
const dstFile = srcFile.replace(/\.xml$/, '.json')
|
||||||
const writeStream = fs.createWriteStream(dstFile)
|
const writeStream = fs.createWriteStream(dstFile)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
parser.parse(srcFile, (err, profile) => {
|
parser.parse(srcFile, (err, profile) => {
|
||||||
console.log("callback", err, profile)
|
console.log('callback', err, profile)
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
|
|
@ -90,65 +95,78 @@ const parseToJson = srcFile => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveToSourcesDir = srcFile => {
|
const moveToSourcesDir = (srcFile, ofacSourcesDir) => {
|
||||||
console.log("moveToSourcesDir", srcFile)
|
console.log('moveToSourcesDir', srcFile)
|
||||||
const name = path.basename(srcFile)
|
const name = path.basename(srcFile)
|
||||||
const dstFile = path.join(OFAC_SOURCES_DIR, name)
|
const dstFile = path.join(ofacSourcesDir, name)
|
||||||
return rename(srcFile, dstFile)
|
return rename(srcFile, dstFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function update () {
|
||||||
|
const OFAC_DATA_DIR = options.ofacDataDir
|
||||||
|
|
||||||
const update = () => mkdir(OFAC_DATA_DIR).catch(err => null)
|
if (!OFAC_DATA_DIR) {
|
||||||
.then(() => mkdir(OFAC_SOURCES_DIR)).catch(err => null)
|
throw new Error('ofacDataDir must be defined in lamassu.json')
|
||||||
.then(() => writeFile(OFAC_ETAGS_FILE, '{}', {encoding: 'utf-8', flag: 'wx'}))
|
}
|
||||||
.catch(err => null)
|
|
||||||
.then(() => {
|
|
||||||
const promiseOldEtags = readFile(OFAC_ETAGS_FILE, {encoding: 'utf-8'})
|
|
||||||
.then(json => JSON.parse(json) || {})
|
|
||||||
|
|
||||||
const promiseNewEtags = Promise.resolve(options.ofacSources || [])
|
const OFAC_SOURCES_DIR = path.join(OFAC_DATA_DIR, 'sources')
|
||||||
.then(sources => Promise.all(_.map(promiseGetEtag, sources))
|
const OFAC_ETAGS_FILE = path.join(OFAC_DATA_DIR, 'etags.json')
|
||||||
.then(etags => _.map(
|
|
||||||
([source, etag]) => ({...source, etag}),
|
|
||||||
_.zip(sources, etags)
|
|
||||||
))
|
|
||||||
)
|
|
||||||
|
|
||||||
return Promise.all([promiseOldEtags, promiseNewEtags])
|
return mkdir(OFAC_DATA_DIR)
|
||||||
.then(([oldEtags, newEtags]) => {
|
.then(() => mkdir(OFAC_SOURCES_DIR))
|
||||||
console.log("OLD", JSON.stringify(oldEtags, null, 4))
|
.then(() => writeFile(OFAC_ETAGS_FILE, '{}', {encoding: 'utf-8', flag: 'wx'}))
|
||||||
console.log("NEW", JSON.stringify(newEtags, null, 4))
|
.catch(err => {
|
||||||
|
if (err.code === 'EEXIST') return
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
const promiseOldEtags = readFile(OFAC_ETAGS_FILE, {encoding: 'utf-8'})
|
||||||
|
.then(json => JSON.parse(json) || {})
|
||||||
|
|
||||||
const hasNotChanged = ({name, etag}) => oldEtags[name] === etag
|
const promiseNewEtags = Promise.resolve(options.ofacSources || [])
|
||||||
|
.then(sources => Promise.all(_.map(promiseGetEtag, sources))
|
||||||
|
.then(etags => _.map(
|
||||||
|
([source, etag]) => ({...source, etag}),
|
||||||
|
_.zip(sources, etags)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
const downloads = _.flow(
|
return Promise.all([promiseOldEtags, promiseNewEtags])
|
||||||
_.reject(hasNotChanged),
|
.then(([oldEtags, newEtags]) => {
|
||||||
_.map(file => download(DOWNLOAD_DIR, file).then(parseToJson))
|
console.log('OLD', JSON.stringify(oldEtags, null, 4))
|
||||||
)(newEtags)
|
console.log('NEW', JSON.stringify(newEtags, null, 4))
|
||||||
|
|
||||||
const oldFileNames = _.keys(oldEtags)
|
const hasNotChanged = ({name, etag}) => oldEtags[name] === etag
|
||||||
const newFileNames = _.map(_.get('name'), newEtags)
|
|
||||||
const missingFileNames = _.difference(oldFileNames, newFileNames)
|
|
||||||
const resolve = name => path.join(OFAC_SOURCES_DIR, name + '.json')
|
|
||||||
const missing = _.map(resolve, missingFileNames)
|
|
||||||
|
|
||||||
const etagsJson = _.flow(
|
const downloads = _.flow(
|
||||||
_.map(source => [source.name, source.etag]),
|
_.reject(hasNotChanged),
|
||||||
_.fromPairs,
|
_.map(file => download(DOWNLOAD_DIR, file).then(parseToJson))
|
||||||
obj => JSON.stringify(obj, null, 4)
|
)(newEtags)
|
||||||
)(newEtags)
|
|
||||||
|
|
||||||
return Promise.all(downloads)
|
const oldFileNames = _.keys(oldEtags)
|
||||||
.then(parsed => {
|
const newFileNames = _.map(_.get('name'), newEtags)
|
||||||
console.log("finished", parsed)
|
const missingFileNames = _.difference(oldFileNames, newFileNames)
|
||||||
|
const resolve = name => path.join(OFAC_SOURCES_DIR, name + '.json')
|
||||||
|
const missing = _.map(resolve, missingFileNames)
|
||||||
|
|
||||||
const moves = _.map(moveToSourcesDir, parsed)
|
const etagsJson = _.flow(
|
||||||
const deletions = _.map(remove, missing)
|
_.map(source => [source.name, source.etag]),
|
||||||
const updateEtags = writeFile(OFAC_ETAGS_FILE, etagsJson)
|
_.fromPairs,
|
||||||
|
obj => JSON.stringify(obj, null, 4)
|
||||||
|
)(newEtags)
|
||||||
|
|
||||||
return Promise.all([updateEtags, ...moves, ...deletions])
|
return Promise.all(downloads)
|
||||||
|
.then(parsed => {
|
||||||
|
console.log('finished', parsed)
|
||||||
|
|
||||||
|
const moves = _.map(src => moveToSourcesDir(src, OFAC_SOURCES_DIR), parsed)
|
||||||
|
const deletions = _.map(remove, missing)
|
||||||
|
const updateEtags = writeFile(OFAC_ETAGS_FILE, etagsJson)
|
||||||
|
|
||||||
|
return Promise.all([updateEtags, ...moves, ...deletions])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
module.exports = {update}
|
module.exports = {update}
|
||||||
|
|
|
||||||
|
|
@ -186,11 +186,14 @@ function getCustomerWithPhoneCode (req, res, next) {
|
||||||
function updateCustomer (req, res, next) {
|
function updateCustomer (req, res, next) {
|
||||||
const id = req.params.id
|
const id = req.params.id
|
||||||
const patch = req.body
|
const patch = req.body
|
||||||
|
const config = configManager.unscoped(req.settings.config)
|
||||||
|
|
||||||
customers.getById(id)
|
customers.getById(id)
|
||||||
.then(customer => {
|
.then(customer => {
|
||||||
if (!customer) { throw httpError('Not Found', 404) }
|
if (!customer) { throw httpError('Not Found', 404) }
|
||||||
return customers.update(id, patch)
|
return customers.update(id, patch)
|
||||||
})
|
})
|
||||||
|
.then(customer => customers.validate(config, customer))
|
||||||
.then(customer => respond(req, res, {customer}))
|
.then(customer => respond(req, res, {customer}))
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,8 @@
|
||||||
"lamassu-cancel": "./bin/lamassu-cancel",
|
"lamassu-cancel": "./bin/lamassu-cancel",
|
||||||
"lamassu-nuke-db": "./bin/lamassu-nuke-db",
|
"lamassu-nuke-db": "./bin/lamassu-nuke-db",
|
||||||
"lamassu-coins": "./bin/lamassu-coins",
|
"lamassu-coins": "./bin/lamassu-coins",
|
||||||
"lamassu-update": "./bin/lamassu-update"
|
"lamassu-update": "./bin/lamassu-update",
|
||||||
|
"lamassu-ofac-update": "./bin/lamassu-ofac-update"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node bin/lamassu-server",
|
"start": "node bin/lamassu-server",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue