chore: server code formatting

This commit is contained in:
Rafael Taranto 2025-05-12 15:35:00 +01:00
parent aedabcbdee
commit 68517170e2
234 changed files with 9824 additions and 6195 deletions

View file

@ -9,8 +9,12 @@ const nocache = require('nocache')
const cookieParser = require('cookie-parser')
const { ApolloServer } = require('@apollo/server')
const { expressMiddleware } = require('@apollo/server/express4')
const { ApolloServerPluginLandingPageDisabled } = require('@apollo/server/plugin/disabled')
const { ApolloServerPluginLandingPageLocalDefault } = require('@apollo/server/plugin/landingPage/default')
const {
ApolloServerPluginLandingPageDisabled,
} = require('@apollo/server/plugin/disabled')
const {
ApolloServerPluginLandingPageLocalDefault,
} = require('@apollo/server/plugin/landingPage/default')
const { mergeResolvers } = require('@graphql-tools/merge')
const { makeExecutableSchema } = require('@graphql-tools/schema')
@ -23,7 +27,11 @@ const { authDirectiveTransformer } = require('./graphql/directives')
const { typeDefs, resolvers } = require('./graphql/schema')
const findOperatorId = require('../middlewares/operatorId')
const { USER_SESSIONS_CLEAR_INTERVAL } = require('../constants')
const { session, cleanUserSessions, buildApolloContext } = require('./middlewares')
const {
session,
cleanUserSessions,
buildApolloContext,
} = require('./middlewares')
const devMode = require('minimist')(process.argv.slice(2)).dev
@ -55,8 +63,12 @@ const loadRoutes = async () => {
app.use(session)
// Dynamic import for graphql-upload since it's not a CommonJS module
const { default: graphqlUploadExpress } = await import('graphql-upload/graphqlUploadExpress.mjs')
const { default: GraphQLUpload } = await import('graphql-upload/GraphQLUpload.mjs')
const { default: graphqlUploadExpress } = await import(
'graphql-upload/graphqlUploadExpress.mjs'
)
const { default: GraphQLUpload } = await import(
'graphql-upload/GraphQLUpload.mjs'
)
app.use(graphqlUploadExpress())
@ -75,29 +87,33 @@ const loadRoutes = async () => {
return formattedError
},
plugins: [
devMode
? ApolloServerPluginLandingPageLocalDefault()
: ApolloServerPluginLandingPageDisabled()
]
devMode
? ApolloServerPluginLandingPageLocalDefault()
: ApolloServerPluginLandingPageDisabled(),
],
})
await apolloServer.start();
await apolloServer.start()
app.use(
'/graphql',
express.json(),
expressMiddleware(apolloServer, {
context: async ({ req, res }) => buildApolloContext({ req, res })
})
);
context: async ({ req, res }) => buildApolloContext({ req, res }),
}),
)
app.use('/id-card-photo', serveStatic(ID_PHOTO_CARD_DIR, { index: false }))
app.use('/front-camera-photo', serveStatic(FRONT_CAMERA_DIR, { index: false }))
app.use(
'/front-camera-photo',
serveStatic(FRONT_CAMERA_DIR, { index: false }),
)
app.use('/operator-data', serveStatic(OPERATOR_DATA_DIR, { index: false }))
// Everything not on graphql or api/register is redirected to the front-end
app.get('*', (req, res) => res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html')))
app.get('*', (req, res) =>
res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html')),
)
return app
}
@ -105,10 +121,10 @@ const loadRoutes = async () => {
const certOptions = {
key: fs.readFileSync(KEY_PATH),
cert: fs.readFileSync(CERT_PATH),
ca: fs.readFileSync(CA_PATH)
ca: fs.readFileSync(CA_PATH),
}
async function run () {
async function run() {
const app = await loadRoutes()
const serverPort = devMode ? 8070 : 443

View file

@ -3,8 +3,10 @@ const _ = require('lodash/fp')
const { ALL } = require('../../plugins/common/ccxt')
const { BTC, BCH, DASH, ETH, LTC, USDT, ZEC, XMR, LN, TRX, USDT_TRON, USDC } = COINS
const { bitpay, itbit, bitstamp, kraken, binanceus, cex, binance, bitfinex } = ALL
const { BTC, BCH, DASH, ETH, LTC, USDT, ZEC, XMR, LN, TRX, USDT_TRON, USDC } =
COINS
const { bitpay, itbit, bitstamp, kraken, binanceus, cex, binance, bitfinex } =
ALL
const TICKER = 'ticker'
const WALLET = 'wallet'
@ -18,39 +20,142 @@ const WALLET_SCORING = 'wallet_scoring'
const COMPLIANCE = 'compliance'
const ALL_ACCOUNTS = [
{ code: 'bitfinex', display: 'Bitfinex', class: TICKER, cryptos: bitfinex.CRYPTO },
{ code: 'bitfinex', display: 'Bitfinex', class: EXCHANGE, cryptos: bitfinex.CRYPTO },
{ code: 'binance', display: 'Binance', class: TICKER, cryptos: binance.CRYPTO },
{ code: 'binanceus', display: 'Binance.us', class: TICKER, cryptos: binanceus.CRYPTO },
{
code: 'bitfinex',
display: 'Bitfinex',
class: TICKER,
cryptos: bitfinex.CRYPTO,
},
{
code: 'bitfinex',
display: 'Bitfinex',
class: EXCHANGE,
cryptos: bitfinex.CRYPTO,
},
{
code: 'binance',
display: 'Binance',
class: TICKER,
cryptos: binance.CRYPTO,
},
{
code: 'binanceus',
display: 'Binance.us',
class: TICKER,
cryptos: binanceus.CRYPTO,
},
{ code: 'cex', display: 'CEX.IO', class: TICKER, cryptos: cex.CRYPTO },
{ code: 'bitpay', display: 'Bitpay', class: TICKER, cryptos: bitpay.CRYPTO },
{ code: 'kraken', display: 'Kraken', class: TICKER, cryptos: kraken.CRYPTO },
{ code: 'bitstamp', display: 'Bitstamp', class: TICKER, cryptos: bitstamp.CRYPTO },
{
code: 'bitstamp',
display: 'Bitstamp',
class: TICKER,
cryptos: bitstamp.CRYPTO,
},
{ code: 'itbit', display: 'itBit', class: TICKER, cryptos: itbit.CRYPTO },
{ code: 'mock-ticker', display: 'Mock (Caution!)', class: TICKER, cryptos: ALL_CRYPTOS, dev: true },
{
code: 'mock-ticker',
display: 'Mock (Caution!)',
class: TICKER,
cryptos: ALL_CRYPTOS,
dev: true,
},
{ code: 'bitcoind', display: 'bitcoind', class: WALLET, cryptos: [BTC] },
{ code: 'no-layer2', display: 'No Layer 2', class: LAYER_2, cryptos: ALL_CRYPTOS },
{ code: 'infura', display: 'Infura/Alchemy', class: WALLET, cryptos: [ETH, USDT, USDC] },
{ code: 'trongrid', display: 'Trongrid', class: WALLET, cryptos: [TRX, USDT_TRON] },
{ code: 'geth', display: 'geth (deprecated)', class: WALLET, cryptos: [ETH, USDT, USDC] },
{
code: 'no-layer2',
display: 'No Layer 2',
class: LAYER_2,
cryptos: ALL_CRYPTOS,
},
{
code: 'infura',
display: 'Infura/Alchemy',
class: WALLET,
cryptos: [ETH, USDT, USDC],
},
{
code: 'trongrid',
display: 'Trongrid',
class: WALLET,
cryptos: [TRX, USDT_TRON],
},
{
code: 'geth',
display: 'geth (deprecated)',
class: WALLET,
cryptos: [ETH, USDT, USDC],
},
{ code: 'zcashd', display: 'zcashd', class: WALLET, cryptos: [ZEC] },
{ code: 'litecoind', display: 'litecoind', class: WALLET, cryptos: [LTC] },
{ code: 'dashd', display: 'dashd', class: WALLET, cryptos: [DASH] },
{ code: 'monerod', display: 'monerod', class: WALLET, cryptos: [XMR] },
{ code: 'bitcoincashd', display: 'bitcoincashd', class: WALLET, cryptos: [BCH] },
{ code: 'bitgo', display: 'BitGo', class: WALLET, cryptos: [BTC, ZEC, LTC, BCH, DASH] },
{
code: 'bitcoincashd',
display: 'bitcoincashd',
class: WALLET,
cryptos: [BCH],
},
{
code: 'bitgo',
display: 'BitGo',
class: WALLET,
cryptos: [BTC, ZEC, LTC, BCH, DASH],
},
{ code: 'galoy', display: 'Galoy', class: WALLET, cryptos: [LN] },
{ code: 'bitstamp', display: 'Bitstamp', class: EXCHANGE, cryptos: bitstamp.CRYPTO },
{
code: 'bitstamp',
display: 'Bitstamp',
class: EXCHANGE,
cryptos: bitstamp.CRYPTO,
},
{ code: 'itbit', display: 'itBit', class: EXCHANGE, cryptos: itbit.CRYPTO },
{ code: 'kraken', display: 'Kraken', class: EXCHANGE, cryptos: kraken.CRYPTO },
{ code: 'binance', display: 'Binance', class: EXCHANGE, cryptos: binance.CRYPTO },
{ code: 'binanceus', display: 'Binance.us', class: EXCHANGE, cryptos: binanceus.CRYPTO },
{
code: 'kraken',
display: 'Kraken',
class: EXCHANGE,
cryptos: kraken.CRYPTO,
},
{
code: 'binance',
display: 'Binance',
class: EXCHANGE,
cryptos: binance.CRYPTO,
},
{
code: 'binanceus',
display: 'Binance.us',
class: EXCHANGE,
cryptos: binanceus.CRYPTO,
},
{ code: 'cex', display: 'CEX.IO', class: EXCHANGE, cryptos: cex.CRYPTO },
{ code: 'mock-wallet', display: 'Mock (Caution!)', class: WALLET, cryptos: ALL_CRYPTOS, dev: true },
{ code: 'no-exchange', display: 'No exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS },
{ code: 'mock-exchange', display: 'Mock exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS, dev: true },
{
code: 'mock-wallet',
display: 'Mock (Caution!)',
class: WALLET,
cryptos: ALL_CRYPTOS,
dev: true,
},
{
code: 'no-exchange',
display: 'No exchange',
class: EXCHANGE,
cryptos: ALL_CRYPTOS,
},
{
code: 'mock-exchange',
display: 'Mock exchange',
class: EXCHANGE,
cryptos: ALL_CRYPTOS,
dev: true,
},
{ code: 'mock-sms', display: 'Mock SMS', class: SMS, dev: true },
{ code: 'mock-id-verify', display: 'Mock ID verifier', class: ID_VERIFIER, dev: true },
{
code: 'mock-id-verify',
display: 'Mock ID verifier',
class: ID_VERIFIER,
dev: true,
},
{ code: 'twilio', display: 'Twilio', class: SMS },
{ code: 'telnyx', display: 'Telnyx', class: SMS },
{ code: 'vonage', display: 'Vonage', class: SMS },
@ -58,17 +163,51 @@ const ALL_ACCOUNTS = [
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
{ code: 'mock-email', display: 'Mock Email', class: EMAIL, dev: true },
{ code: 'none', display: 'None', class: ZERO_CONF, cryptos: ALL_CRYPTOS },
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true },
{ code: 'scorechain', display: 'Scorechain', class: WALLET_SCORING, cryptos: [BTC, ETH, LTC, BCH, DASH, USDT, USDC, USDT_TRON, TRX] },
{ code: 'elliptic', display: 'Elliptic', class: WALLET_SCORING, cryptos: [BTC, ETH, LTC, BCH, USDT, USDC, USDT_TRON, TRX, ZEC] },
{ code: 'mock-scoring', display: 'Mock scoring', class: WALLET_SCORING, cryptos: ALL_CRYPTOS, dev: true },
{
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: 'scorechain',
display: 'Scorechain',
class: WALLET_SCORING,
cryptos: [BTC, ETH, LTC, BCH, DASH, USDT, USDC, USDT_TRON, TRX],
},
{
code: 'elliptic',
display: 'Elliptic',
class: WALLET_SCORING,
cryptos: [BTC, ETH, LTC, BCH, USDT, USDC, USDT_TRON, TRX, ZEC],
},
{
code: 'mock-scoring',
display: 'Mock scoring',
class: WALLET_SCORING,
cryptos: ALL_CRYPTOS,
dev: true,
},
{ code: 'sumsub', display: 'Sumsub', class: COMPLIANCE },
{ code: 'mock-compliance', display: 'Mock Compliance', class: COMPLIANCE, dev: true },
{
code: 'mock-compliance',
display: 'Mock Compliance',
class: COMPLIANCE,
dev: true,
},
]
const flags = require('minimist')(process.argv.slice(2))
const devMode = flags.dev || flags.lamassuDev
const ACCOUNT_LIST = devMode ? ALL_ACCOUNTS : _.filter(it => !it.dev)(ALL_ACCOUNTS)
const ACCOUNT_LIST = devMode
? ALL_ACCOUNTS
: _.filter(it => !it.dev)(ALL_ACCOUNTS)
module.exports = { ACCOUNT_LIST }

File diff suppressed because one or more lines are too long

View file

@ -1,255 +1,266 @@
{
"attribute": {"name":0, "nativeName":1},
"rtl": {"ar":1,"dv":1,"fa":1,"ha":1,"he":1,"ks":1,"ku":1,"ps":1,"ur":1,"yi":1},
"lang": {
"aa":["Afar","Afar"],
"ab":["Abkhazian","Аҧсуа"],
"af":["Afrikaans","Afrikaans"],
"ak":["Akan","Akana"],
"am":["Amharic","አማርኛ"],
"an":["Aragonese","Aragonés"],
"ar":["Arabic","العربية"],
"as":["Assamese","অসমীয়া"],
"av":["Avar","Авар"],
"ay":["Aymara","Aymar"],
"az":["Azerbaijani","Azərbaycanca / آذربايجان"],
"ba":["Bashkir","Башҡорт"],
"be":["Belarusian","Беларуская"],
"bg":["Bulgarian","Български"],
"bh":["Bihari","भोजपुरी"],
"bi":["Bislama","Bislama"],
"bm":["Bambara","Bamanankan"],
"bn":["Bengali","বাংলা"],
"bo":["Tibetan","བོད་ཡིག / Bod skad"],
"br":["Breton","Brezhoneg"],
"bs":["Bosnian","Bosanski"],
"ca":["Catalan","Català"],
"ce":["Chechen","Нохчийн"],
"ch":["Chamorro","Chamoru"],
"co":["Corsican","Corsu"],
"cr":["Cree","Nehiyaw"],
"cs":["Czech","Česky"],
"cu":["Old Church Slavonic / Old Bulgarian","словѣньскъ / slověnĭskŭ"],
"cv":["Chuvash","Чăваш"],
"cy":["Welsh","Cymraeg"],
"da":["Danish","Dansk"],
"de":["German","Deutsch"],
"dv":["Divehi","ދިވެހިބަސް"],
"dz":["Dzongkha","ཇོང་ཁ"],
"ee":["Ewe","Ɛʋɛ"],
"el":["Greek","Ελληνικά"],
"en":["English","English"],
"eo":["Esperanto","Esperanto"],
"es":["Spanish","Español"],
"et":["Estonian","Eesti"],
"eu":["Basque","Euskara"],
"fa":["Persian","فارسی"],
"ff":["Peul","Fulfulde"],
"fi":["Finnish","Suomi"],
"fj":["Fijian","Na Vosa Vakaviti"],
"fo":["Faroese","Føroyskt"],
"fr":["French","Français"],
"fy":["West Frisian","Frysk"],
"ga":["Irish","Gaeilge"],
"gd":["Scottish Gaelic","Gàidhlig"],
"gl":["Galician","Galego"],
"gn":["Guarani","Avañe'ẽ"],
"gu":["Gujarati","ગુજરાતી"],
"gv":["Manx","Gaelg"],
"ha":["Hausa","هَوُسَ"],
"he":["Hebrew","עברית"],
"hi":["Hindi","हिन्दी"],
"ho":["Hiri Motu","Hiri Motu"],
"hr":["Croatian","Hrvatski"],
"ht":["Haitian","Krèyol ayisyen"],
"hu":["Hungarian","Magyar"],
"hy":["Armenian","Հայերեն"],
"hz":["Herero","Otsiherero"],
"ia":["Interlingua","Interlingua"],
"id":["Indonesian","Bahasa Indonesia"],
"ie":["Interlingue","Interlingue"],
"ig":["Igbo","Igbo"],
"ii":["Sichuan Yi","ꆇꉙ / 四川彝语"],
"ik":["Inupiak","Iñupiak"],
"io":["Ido","Ido"],
"is":["Icelandic","Íslenska"],
"it":["Italian","Italiano"],
"iu":["Inuktitut","ᐃᓄᒃᑎᑐᑦ"],
"ja":["Japanese","日本語"],
"jv":["Javanese","Basa Jawa"],
"ka":["Georgian","ქართული"],
"kg":["Kongo","KiKongo"],
"ki":["Kikuyu","Gĩkũyũ"],
"kj":["Kuanyama","Kuanyama"],
"kk":["Kazakh","Қазақша"],
"kl":["Greenlandic","Kalaallisut"],
"km":["Cambodian","ភាសាខ្មែរ"],
"kn":["Kannada","ಕನ್ನಡ"],
"ko":["Korean","한국어"],
"kr":["Kanuri","Kanuri"],
"ks":["Kashmiri","कश्मीरी / كشميري"],
"ku":["Kurdish","Kurdî / كوردی"],
"kv":["Komi","Коми"],
"kw":["Cornish","Kernewek"],
"ky":["Kirghiz","Kırgızca / Кыргызча"],
"la":["Latin","Latina"],
"lb":["Luxembourgish","Lëtzebuergesch"],
"lg":["Ganda","Luganda"],
"li":["Limburgian","Limburgs"],
"ln":["Lingala","Lingála"],
"lo":["Laotian","ລາວ / Pha xa lao"],
"lt":["Lithuanian","Lietuvių"],
"lv":["Latvian","Latviešu"],
"mg":["Malagasy","Malagasy"],
"mh":["Marshallese","Kajin Majel / Ebon"],
"mi":["Maori","Māori"],
"mk":["Macedonian","Македонски"],
"ml":["Malayalam","മലയാളം"],
"mn":["Mongolian","Монгол"],
"mo":["Moldovan","Moldovenească"],
"mr":["Marathi","मराठी"],
"ms":["Malay","Bahasa Melayu"],
"mt":["Maltese","bil-Malti"],
"my":["Burmese","Myanmasa"],
"na":["Nauruan","Dorerin Naoero"],
"nd":["North Ndebele","Sindebele"],
"ne":["Nepali","नेपाली"],
"ng":["Ndonga","Oshiwambo"],
"nl":["Dutch","Nederlands"],
"nn":["Norwegian Nynorsk","Norsk (nynorsk)"],
"no":["Norwegian","Norsk (bokmål / riksmål)"],
"nr":["South Ndebele","isiNdebele"],
"nv":["Navajo","Diné bizaad"],
"ny":["Chichewa","Chi-Chewa"],
"oc":["Occitan","Occitan"],
"oj":["Ojibwa","ᐊᓂᔑᓈᐯᒧᐎᓐ / Anishinaabemowin"],
"om":["Oromo","Oromoo"],
"or":["Oriya","ଓଡ଼ିଆ"],
"os":["Ossetian / Ossetic","Иронау"],
"pa":["Panjabi / Punjabi","ਪੰਜਾਬੀ / पंजाबी / پنجابي"],
"pi":["Pali","Pāli / पाऴि"],
"pl":["Polish","Polski"],
"ps":["Pashto","پښتو"],
"pt":["Portuguese","Português"],
"qu":["Quechua","Runa Simi"],
"rm":["Raeto Romance","Rumantsch"],
"rn":["Kirundi","Kirundi"],
"ro":["Romanian","Română"],
"ru":["Russian","Русский"],
"rw":["Rwandi","Kinyarwandi"],
"sa":["Sanskrit","संस्कृतम्"],
"sc":["Sardinian","Sardu"],
"sd":["Sindhi","सिनधि"],
"se":["Northern Sami","Sámegiella"],
"sg":["Sango","Sängö"],
"sh":["Serbo-Croatian","Srpskohrvatski / Српскохрватски"],
"si":["Sinhalese","සිංහල"],
"sk":["Slovak","Slovenčina"],
"sl":["Slovenian","Slovenščina"],
"sm":["Samoan","Gagana Samoa"],
"sn":["Shona","chiShona"],
"so":["Somalia","Soomaaliga"],
"sq":["Albanian","Shqip"],
"sr":["Serbian","Српски"],
"ss":["Swati","SiSwati"],
"st":["Southern Sotho","Sesotho"],
"su":["Sundanese","Basa Sunda"],
"sv":["Swedish","Svenska"],
"sw":["Swahili","Kiswahili"],
"ta":["Tamil","தமிழ்"],
"te":["Telugu","తెలుగు"],
"tg":["Tajik","Тоҷикӣ"],
"th":["Thai","ไทย / Phasa Thai"],
"ti":["Tigrinya","ትግርኛ"],
"tk":["Turkmen","Туркмен / تركمن"],
"tl":["Tagalog / Filipino","Tagalog"],
"tn":["Tswana","Setswana"],
"to":["Tonga","Lea Faka-Tonga"],
"tr":["Turkish","Türkçe"],
"ts":["Tsonga","Xitsonga"],
"tt":["Tatar","Tatarça"],
"tw":["Twi","Twi"],
"ty":["Tahitian","Reo Mā`ohi"],
"ug":["Uyghur","Uyƣurqə / ئۇيغۇرچە"],
"uk":["Ukrainian","Українська"],
"ur":["Urdu","اردو"],
"uz":["Uzbek","Ўзбек"],
"ve":["Venda","Tshivenḓa"],
"vi":["Vietnamese","Tiếng Việt"],
"vo":["Volapük","Volapük"],
"wa":["Walloon","Walon"],
"wo":["Wolof","Wollof"],
"xh":["Xhosa","isiXhosa"],
"yi":["Yiddish","ייִדיש"],
"yo":["Yoruba","Yorùbá"],
"za":["Zhuang","Cuengh / Tôô / 壮语"],
"zh":["Chinese","中文"],
"zu":["Zulu","isiZulu"]
},
"supported": [
"en-US",
"en-CA",
"fr-QC",
"ach-UG",
"af-ZA",
"ar-SA",
"bg-BG",
"ca-ES",
"cs-CZ",
"cy-GB",
"de-DE",
"de-AT",
"de-CH",
"da-DK",
"el-GR",
"en-GB",
"en-AU",
"en-HK",
"en-IE",
"en-NZ",
"en-PR",
"es-ES",
"es-MX",
"et-EE",
"fi-FI",
"fr-FR",
"fr-CH",
"fur-IT",
"ga-IE",
"gd-GB",
"he-IL",
"hr-HR",
"hu-HU",
"hy-AM",
"id-ID",
"it-CH",
"it-IT",
"ja-JP",
"ka-GE",
"ko-KR",
"ky-KG",
"lt-LT",
"nb-NO",
"nl-BE",
"nl-NL",
"pt-PT",
"pt-BR",
"pl-PL",
"ro-RO",
"ru-RU",
"sco-GB",
"sh-HR",
"sk-SK",
"sl-SI",
"sr-SP",
"sv-SE",
"th-TH",
"tr-TR",
"uk-UA",
"vi-VN",
"zh-CN",
"zh-HK",
"zh-SG",
"zh-TW"
]
"attribute": { "name": 0, "nativeName": 1 },
"rtl": {
"ar": 1,
"dv": 1,
"fa": 1,
"ha": 1,
"he": 1,
"ks": 1,
"ku": 1,
"ps": 1,
"ur": 1,
"yi": 1
},
"lang": {
"aa": ["Afar", "Afar"],
"ab": ["Abkhazian", "Аҧсуа"],
"af": ["Afrikaans", "Afrikaans"],
"ak": ["Akan", "Akana"],
"am": ["Amharic", "አማርኛ"],
"an": ["Aragonese", "Aragonés"],
"ar": ["Arabic", "العربية"],
"as": ["Assamese", "অসমীয়া"],
"av": ["Avar", "Авар"],
"ay": ["Aymara", "Aymar"],
"az": ["Azerbaijani", "Azərbaycanca / آذربايجان"],
"ba": ["Bashkir", "Башҡорт"],
"be": ["Belarusian", "Беларуская"],
"bg": ["Bulgarian", "Български"],
"bh": ["Bihari", "भोजपुरी"],
"bi": ["Bislama", "Bislama"],
"bm": ["Bambara", "Bamanankan"],
"bn": ["Bengali", "বাংলা"],
"bo": ["Tibetan", "བོད་ཡིག / Bod skad"],
"br": ["Breton", "Brezhoneg"],
"bs": ["Bosnian", "Bosanski"],
"ca": ["Catalan", "Català"],
"ce": ["Chechen", "Нохчийн"],
"ch": ["Chamorro", "Chamoru"],
"co": ["Corsican", "Corsu"],
"cr": ["Cree", "Nehiyaw"],
"cs": ["Czech", "Česky"],
"cu": ["Old Church Slavonic / Old Bulgarian", "словѣньскъ / slověnĭskŭ"],
"cv": ["Chuvash", "Чăваш"],
"cy": ["Welsh", "Cymraeg"],
"da": ["Danish", "Dansk"],
"de": ["German", "Deutsch"],
"dv": ["Divehi", "ދިވެހިބަސް"],
"dz": ["Dzongkha", "ཇོང་ཁ"],
"ee": ["Ewe", "Ɛʋɛ"],
"el": ["Greek", "Ελληνικά"],
"en": ["English", "English"],
"eo": ["Esperanto", "Esperanto"],
"es": ["Spanish", "Español"],
"et": ["Estonian", "Eesti"],
"eu": ["Basque", "Euskara"],
"fa": ["Persian", "فارسی"],
"ff": ["Peul", "Fulfulde"],
"fi": ["Finnish", "Suomi"],
"fj": ["Fijian", "Na Vosa Vakaviti"],
"fo": ["Faroese", "Føroyskt"],
"fr": ["French", "Français"],
"fy": ["West Frisian", "Frysk"],
"ga": ["Irish", "Gaeilge"],
"gd": ["Scottish Gaelic", "Gàidhlig"],
"gl": ["Galician", "Galego"],
"gn": ["Guarani", "Avañe'ẽ"],
"gu": ["Gujarati", "ગુજરાતી"],
"gv": ["Manx", "Gaelg"],
"ha": ["Hausa", "هَوُسَ"],
"he": ["Hebrew", "עברית"],
"hi": ["Hindi", "हिन्दी"],
"ho": ["Hiri Motu", "Hiri Motu"],
"hr": ["Croatian", "Hrvatski"],
"ht": ["Haitian", "Krèyol ayisyen"],
"hu": ["Hungarian", "Magyar"],
"hy": ["Armenian", "Հայերեն"],
"hz": ["Herero", "Otsiherero"],
"ia": ["Interlingua", "Interlingua"],
"id": ["Indonesian", "Bahasa Indonesia"],
"ie": ["Interlingue", "Interlingue"],
"ig": ["Igbo", "Igbo"],
"ii": ["Sichuan Yi", "ꆇꉙ / 四川彝语"],
"ik": ["Inupiak", "Iñupiak"],
"io": ["Ido", "Ido"],
"is": ["Icelandic", "Íslenska"],
"it": ["Italian", "Italiano"],
"iu": ["Inuktitut", "ᐃᓄᒃᑎᑐᑦ"],
"ja": ["Japanese", "日本語"],
"jv": ["Javanese", "Basa Jawa"],
"ka": ["Georgian", "ქართული"],
"kg": ["Kongo", "KiKongo"],
"ki": ["Kikuyu", "Gĩkũyũ"],
"kj": ["Kuanyama", "Kuanyama"],
"kk": ["Kazakh", "Қазақша"],
"kl": ["Greenlandic", "Kalaallisut"],
"km": ["Cambodian", "ភាសាខ្មែរ"],
"kn": ["Kannada", "ಕನ್ನಡ"],
"ko": ["Korean", "한국어"],
"kr": ["Kanuri", "Kanuri"],
"ks": ["Kashmiri", "कश्मीरी / كشميري"],
"ku": ["Kurdish", "Kurdî / كوردی"],
"kv": ["Komi", "Коми"],
"kw": ["Cornish", "Kernewek"],
"ky": ["Kirghiz", "Kırgızca / Кыргызча"],
"la": ["Latin", "Latina"],
"lb": ["Luxembourgish", "Lëtzebuergesch"],
"lg": ["Ganda", "Luganda"],
"li": ["Limburgian", "Limburgs"],
"ln": ["Lingala", "Lingála"],
"lo": ["Laotian", "ລາວ / Pha xa lao"],
"lt": ["Lithuanian", "Lietuvių"],
"lv": ["Latvian", "Latviešu"],
"mg": ["Malagasy", "Malagasy"],
"mh": ["Marshallese", "Kajin Majel / Ebon"],
"mi": ["Maori", "Māori"],
"mk": ["Macedonian", "Македонски"],
"ml": ["Malayalam", "മലയാളം"],
"mn": ["Mongolian", "Монгол"],
"mo": ["Moldovan", "Moldovenească"],
"mr": ["Marathi", "मराठी"],
"ms": ["Malay", "Bahasa Melayu"],
"mt": ["Maltese", "bil-Malti"],
"my": ["Burmese", "Myanmasa"],
"na": ["Nauruan", "Dorerin Naoero"],
"nd": ["North Ndebele", "Sindebele"],
"ne": ["Nepali", "नेपाली"],
"ng": ["Ndonga", "Oshiwambo"],
"nl": ["Dutch", "Nederlands"],
"nn": ["Norwegian Nynorsk", "Norsk (nynorsk)"],
"no": ["Norwegian", "Norsk (bokmål / riksmål)"],
"nr": ["South Ndebele", "isiNdebele"],
"nv": ["Navajo", "Diné bizaad"],
"ny": ["Chichewa", "Chi-Chewa"],
"oc": ["Occitan", "Occitan"],
"oj": ["Ojibwa", "ᐊᓂᔑᓈᐯᒧᐎᓐ / Anishinaabemowin"],
"om": ["Oromo", "Oromoo"],
"or": ["Oriya", "ଓଡ଼ିଆ"],
"os": ["Ossetian / Ossetic", "Иронау"],
"pa": ["Panjabi / Punjabi", "ਪੰਜਾਬੀ / पंजाबी / پنجابي"],
"pi": ["Pali", "Pāli / पाऴि"],
"pl": ["Polish", "Polski"],
"ps": ["Pashto", "پښتو"],
"pt": ["Portuguese", "Português"],
"qu": ["Quechua", "Runa Simi"],
"rm": ["Raeto Romance", "Rumantsch"],
"rn": ["Kirundi", "Kirundi"],
"ro": ["Romanian", "Română"],
"ru": ["Russian", "Русский"],
"rw": ["Rwandi", "Kinyarwandi"],
"sa": ["Sanskrit", "संस्कृतम्"],
"sc": ["Sardinian", "Sardu"],
"sd": ["Sindhi", "सिनधि"],
"se": ["Northern Sami", "Sámegiella"],
"sg": ["Sango", "Sängö"],
"sh": ["Serbo-Croatian", "Srpskohrvatski / Српскохрватски"],
"si": ["Sinhalese", "සිංහල"],
"sk": ["Slovak", "Slovenčina"],
"sl": ["Slovenian", "Slovenščina"],
"sm": ["Samoan", "Gagana Samoa"],
"sn": ["Shona", "chiShona"],
"so": ["Somalia", "Soomaaliga"],
"sq": ["Albanian", "Shqip"],
"sr": ["Serbian", "Српски"],
"ss": ["Swati", "SiSwati"],
"st": ["Southern Sotho", "Sesotho"],
"su": ["Sundanese", "Basa Sunda"],
"sv": ["Swedish", "Svenska"],
"sw": ["Swahili", "Kiswahili"],
"ta": ["Tamil", "தமிழ்"],
"te": ["Telugu", "తెలుగు"],
"tg": ["Tajik", "Тоҷикӣ"],
"th": ["Thai", "ไทย / Phasa Thai"],
"ti": ["Tigrinya", "ትግርኛ"],
"tk": ["Turkmen", "Туркмен / تركمن"],
"tl": ["Tagalog / Filipino", "Tagalog"],
"tn": ["Tswana", "Setswana"],
"to": ["Tonga", "Lea Faka-Tonga"],
"tr": ["Turkish", "Türkçe"],
"ts": ["Tsonga", "Xitsonga"],
"tt": ["Tatar", "Tatarça"],
"tw": ["Twi", "Twi"],
"ty": ["Tahitian", "Reo Mā`ohi"],
"ug": ["Uyghur", "Uyƣurqə / ئۇيغۇرچە"],
"uk": ["Ukrainian", "Українська"],
"ur": ["Urdu", "اردو"],
"uz": ["Uzbek", "Ўзбек"],
"ve": ["Venda", "Tshivenḓa"],
"vi": ["Vietnamese", "Tiếng Việt"],
"vo": ["Volapük", "Volapük"],
"wa": ["Walloon", "Walon"],
"wo": ["Wolof", "Wollof"],
"xh": ["Xhosa", "isiXhosa"],
"yi": ["Yiddish", "ייִדיש"],
"yo": ["Yoruba", "Yorùbá"],
"za": ["Zhuang", "Cuengh / Tôô / 壮语"],
"zh": ["Chinese", "中文"],
"zu": ["Zulu", "isiZulu"]
},
"supported": [
"en-US",
"en-CA",
"fr-QC",
"ach-UG",
"af-ZA",
"ar-SA",
"bg-BG",
"ca-ES",
"cs-CZ",
"cy-GB",
"de-DE",
"de-AT",
"de-CH",
"da-DK",
"el-GR",
"en-GB",
"en-AU",
"en-HK",
"en-IE",
"en-NZ",
"en-PR",
"es-ES",
"es-MX",
"et-EE",
"fi-FI",
"fr-FR",
"fr-CH",
"fur-IT",
"ga-IE",
"gd-GB",
"he-IL",
"hr-HR",
"hu-HU",
"hy-AM",
"id-ID",
"it-CH",
"it-IT",
"ja-JP",
"ka-GE",
"ko-KR",
"ky-KG",
"lt-LT",
"nb-NO",
"nl-BE",
"nl-NL",
"pt-PT",
"pt-BR",
"pl-PL",
"ro-RO",
"ru-RU",
"sco-GB",
"sh-HR",
"sk-SK",
"sl-SI",
"sr-SP",
"sv-SE",
"th-TH",
"tr-TR",
"uk-UA",
"vi-VN",
"zh-CN",
"zh-HK",
"zh-SG",
"zh-TW"
]
}

View file

@ -7,10 +7,10 @@ const countries = require('./data/countries.json')
const currenciesRec = require('./data/currencies.json')
const languageRec = require('./data/languages.json')
function massageCurrencies (currencies) {
function massageCurrencies(currencies) {
const convert = r => ({
code: r['Alphabetic Code'],
display: r['Currency']
display: r['Currency'],
})
const top5Codes = ['USD', 'EUR', 'GBP', 'CAD', 'AUD']
const mapped = _.map(convert, currencies)
@ -37,7 +37,7 @@ const massageCryptos = cryptos => {
code: crypto['cryptoCode'],
display: crypto['display'],
codeDisplay: crypto['cryptoCodeDisplay'] ?? crypto['cryptoCode'],
isBeta: betaList.includes(crypto.cryptoCode)
isBeta: betaList.includes(crypto.cryptoCode),
})
return _.map(convert, cryptos)

View file

@ -2,7 +2,7 @@ const db = require('../db')
const cashInTx = require('../cash-in/cash-in-tx')
const { CASH_OUT_TRANSACTION_STATES } = require('../cash-out/cash-out-helper')
function transaction () {
function transaction() {
const sql = `SELECT DISTINCT * FROM (
SELECT 'type' AS type, NULL AS label, 'Cash In' AS value UNION
SELECT 'type' AS type, NULL AS label, 'Cash Out' AS value UNION
@ -27,7 +27,7 @@ function transaction () {
return db.any(sql)
}
function customer () {
function customer() {
const sql = `SELECT DISTINCT * FROM (
SELECT 'phone' AS type, phone AS value FROM customers WHERE phone IS NOT NULL UNION
SELECT 'email' AS type, email AS value FROM customers WHERE email IS NOT NULL UNION

View file

@ -7,7 +7,7 @@ const { AuthenticationError } = require('../errors')
function authDirectiveTransformer(schema, directiveName = 'auth') {
return mapSchema(schema, {
// For object types
[MapperKind.OBJECT_TYPE]: (objectType) => {
[MapperKind.OBJECT_TYPE]: objectType => {
const directive = getDirective(schema, objectType, directiveName)?.[0]
if (directive) {
const requiredAuthRole = directive.requires
@ -15,7 +15,7 @@ function authDirectiveTransformer(schema, directiveName = 'auth') {
}
return objectType
},
// For field definitions
[MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
const directive = getDirective(schema, fieldConfig, directiveName)?.[0]
@ -23,26 +23,30 @@ function authDirectiveTransformer(schema, directiveName = 'auth') {
const requiredAuthRole = directive.requires
fieldConfig._requiredAuthRole = requiredAuthRole
}
// Get the parent object type
const objectType = schema.getType(typeName)
// Apply auth check to the field's resolver
const { resolve = defaultFieldResolver } = fieldConfig
fieldConfig.resolve = function (root, args, context, info) {
const requiredRoles = fieldConfig._requiredAuthRole || objectType._requiredAuthRole
if (!requiredRoles) return resolve.apply(this, [root, args, context, info])
const requiredRoles =
fieldConfig._requiredAuthRole || objectType._requiredAuthRole
if (!requiredRoles)
return resolve.apply(this, [root, args, context, info])
const user = context.req.session.user
if (!user || !_.includes(_.upperCase(user.role), requiredRoles)) {
throw new AuthenticationError('You do not have permission to access this resource!')
throw new AuthenticationError(
'You do not have permission to access this resource!',
)
}
return resolve.apply(this, [root, args, context, info])
}
return fieldConfig
}
},
})
}

View file

@ -5,8 +5,8 @@ class AuthenticationError extends GraphQLError {
constructor() {
super('Authentication failed', {
extensions: {
code: 'UNAUTHENTICATED'
}
code: 'UNAUTHENTICATED',
},
})
}
}
@ -15,8 +15,8 @@ class InvalidCredentialsError extends GraphQLError {
constructor() {
super('Invalid credentials', {
extensions: {
code: 'INVALID_CREDENTIALS'
}
code: 'INVALID_CREDENTIALS',
},
})
}
}
@ -25,8 +25,8 @@ class UserAlreadyExistsError extends GraphQLError {
constructor() {
super('User already exists', {
extensions: {
code: 'USER_ALREADY_EXISTS'
}
code: 'USER_ALREADY_EXISTS',
},
})
}
}
@ -35,8 +35,8 @@ class InvalidTwoFactorError extends GraphQLError {
constructor() {
super('Invalid two-factor code', {
extensions: {
code: 'INVALID_TWO_FACTOR_CODE'
}
code: 'INVALID_TWO_FACTOR_CODE',
},
})
}
}
@ -45,8 +45,8 @@ class InvalidUrlError extends GraphQLError {
constructor() {
super('Invalid URL token', {
extensions: {
code: 'INVALID_URL_TOKEN'
}
code: 'INVALID_URL_TOKEN',
},
})
}
}
@ -55,8 +55,8 @@ class UserInputError extends GraphQLError {
constructor() {
super('User input error', {
extensions: {
code: ApolloServerErrorCode.BAD_USER_INPUT
}
code: ApolloServerErrorCode.BAD_USER_INPUT,
},
})
}
}
@ -67,5 +67,5 @@ module.exports = {
UserAlreadyExistsError,
InvalidTwoFactorError,
InvalidUrlError,
UserInputError
}
UserInputError,
}

View file

@ -12,60 +12,70 @@ const devMode = require('minimist')(process.argv.slice(2)).dev
const REMEMBER_ME_AGE = 90 * T.day
const generateAttestationOptions = (session, options) => {
return users.getUserById(options.userId).then(user => {
return Promise.all([credentials.getHardwareCredentialsByUserId(user.id), user])
}).then(([userDevices, user]) => {
const opts = simpleWebauthn.generateAttestationOptions({
rpName: 'Lamassu',
rpID: options.domain,
userName: user.username,
userID: user.id,
timeout: 60000,
attestationType: 'indirect',
excludeCredentials: userDevices.map(dev => ({
id: dev.data.credentialID,
type: 'public-key',
transports: ['usb', 'ble', 'nfc', 'internal']
})),
authenticatorSelection: {
userVerification: 'discouraged',
requireResidentKey: false
}
return users
.getUserById(options.userId)
.then(user => {
return Promise.all([
credentials.getHardwareCredentialsByUserId(user.id),
user,
])
})
session.webauthn = {
attestation: {
challenge: opts.challenge
}
}
return opts
})
}
const generateAssertionOptions = (session, options) => {
return userManagement.authenticateUser(options.username, options.password).then(user => {
return credentials.getHardwareCredentialsByUserId(user.id).then(devices => {
const opts = simpleWebauthn.generateAssertionOptions({
.then(([userDevices, user]) => {
const opts = simpleWebauthn.generateAttestationOptions({
rpName: 'Lamassu',
rpID: options.domain,
userName: user.username,
userID: user.id,
timeout: 60000,
allowCredentials: devices.map(dev => ({
attestationType: 'indirect',
excludeCredentials: userDevices.map(dev => ({
id: dev.data.credentialID,
type: 'public-key',
transports: ['usb', 'ble', 'nfc', 'internal']
transports: ['usb', 'ble', 'nfc', 'internal'],
})),
userVerification: 'discouraged',
rpID: options.domain
authenticatorSelection: {
userVerification: 'discouraged',
requireResidentKey: false,
},
})
session.webauthn = {
assertion: {
challenge: opts.challenge
}
attestation: {
challenge: opts.challenge,
},
}
return opts
})
})
}
const generateAssertionOptions = (session, options) => {
return userManagement
.authenticateUser(options.username, options.password)
.then(user => {
return credentials
.getHardwareCredentialsByUserId(user.id)
.then(devices => {
const opts = simpleWebauthn.generateAssertionOptions({
timeout: 60000,
allowCredentials: devices.map(dev => ({
id: dev.data.credentialID,
type: 'public-key',
transports: ['usb', 'ble', 'nfc', 'internal'],
})),
userVerification: 'discouraged',
rpID: options.domain,
})
session.webauthn = {
assertion: {
challenge: opts.challenge,
},
}
return opts
})
})
}
const validateAttestation = (session, options) => {
@ -78,98 +88,112 @@ const validateAttestation = (session, options) => {
credential: options.attestationResponse,
expectedChallenge: `${expectedChallenge}`,
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
expectedRPID: options.domain
})
])
.then(([user, verification]) => {
const { verified, attestationInfo } = verification
expectedRPID: options.domain,
}),
]).then(([user, verification]) => {
const { verified, attestationInfo } = verification
if (!(verified || attestationInfo)) {
session.webauthn = null
return false
}
if (!(verified || attestationInfo)) {
session.webauthn = null
return false
}
const {
counter,
credentialPublicKey,
credentialID
} = attestationInfo
const { counter, credentialPublicKey, credentialID } = attestationInfo
return credentials.getHardwareCredentialsByUserId(user.id)
.then(userDevices => {
const existingDevice = userDevices.find(device => device.data.credentialID === credentialID)
return credentials
.getHardwareCredentialsByUserId(user.id)
.then(userDevices => {
const existingDevice = userDevices.find(
device => device.data.credentialID === credentialID,
)
if (!existingDevice) {
const newDevice = {
counter,
credentialPublicKey,
credentialID
}
credentials.createHardwareCredential(user.id, newDevice)
if (!existingDevice) {
const newDevice = {
counter,
credentialPublicKey,
credentialID,
}
credentials.createHardwareCredential(user.id, newDevice)
}
session.webauthn = null
return verified
})
})
session.webauthn = null
return verified
})
})
}
const validateAssertion = (session, options) => {
return userManagement.authenticateUser(options.username, options.password).then(user => {
const expectedChallenge = session.webauthn.assertion.challenge
return userManagement
.authenticateUser(options.username, options.password)
.then(user => {
const expectedChallenge = session.webauthn.assertion.challenge
return credentials.getHardwareCredentialsByUserId(user.id).then(devices => {
const dbAuthenticator = _.find(dev => {
return Buffer.from(dev.data.credentialID).compare(base64url.toBuffer(options.assertionResponse.rawId)) === 0
}, devices)
return credentials
.getHardwareCredentialsByUserId(user.id)
.then(devices => {
const dbAuthenticator = _.find(dev => {
return (
Buffer.from(dev.data.credentialID).compare(
base64url.toBuffer(options.assertionResponse.rawId),
) === 0
)
}, devices)
if (!dbAuthenticator.data) {
throw new Error(`Could not find authenticator matching ${options.assertionResponse.id}`)
}
if (!dbAuthenticator.data) {
throw new Error(
`Could not find authenticator matching ${options.assertionResponse.id}`,
)
}
const convertedAuthenticator = _.merge(
dbAuthenticator.data,
{ credentialPublicKey: Buffer.from(dbAuthenticator.data.credentialPublicKey) }
)
const convertedAuthenticator = _.merge(dbAuthenticator.data, {
credentialPublicKey: Buffer.from(
dbAuthenticator.data.credentialPublicKey,
),
})
let verification
try {
verification = simpleWebauthn.verifyAssertionResponse({
credential: options.assertionResponse,
expectedChallenge: `${expectedChallenge}`,
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
expectedRPID: options.domain,
authenticator: convertedAuthenticator
})
} catch (err) {
console.error(err)
return false
}
let verification
try {
verification = simpleWebauthn.verifyAssertionResponse({
credential: options.assertionResponse,
expectedChallenge: `${expectedChallenge}`,
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
expectedRPID: options.domain,
authenticator: convertedAuthenticator,
})
} catch (err) {
console.error(err)
return false
}
const { verified, assertionInfo } = verification
const { verified, assertionInfo } = verification
if (!verified) {
session.webauthn = null
return false
}
if (!verified) {
session.webauthn = null
return false
}
dbAuthenticator.data.counter = assertionInfo.newCounter
return credentials.updateHardwareCredential(dbAuthenticator)
.then(() => {
const finalUser = { id: user.id, username: user.username, role: user.role }
session.user = finalUser
if (options.rememberMe) session.cookie.maxAge = REMEMBER_ME_AGE
dbAuthenticator.data.counter = assertionInfo.newCounter
return credentials
.updateHardwareCredential(dbAuthenticator)
.then(() => {
const finalUser = {
id: user.id,
username: user.username,
role: user.role,
}
session.user = finalUser
if (options.rememberMe) session.cookie.maxAge = REMEMBER_ME_AGE
session.webauthn = null
return verified
session.webauthn = null
return verified
})
})
})
})
}
module.exports = {
generateAttestationOptions,
generateAssertionOptions,
validateAttestation,
validateAssertion
validateAssertion,
}

View file

@ -11,35 +11,41 @@ const devMode = require('minimist')(process.argv.slice(2)).dev
const REMEMBER_ME_AGE = 90 * T.day
const generateAttestationOptions = (session, options) => {
return users.getUserById(options.userId).then(user => {
return Promise.all([credentials.getHardwareCredentialsByUserId(user.id), user])
}).then(([userDevices, user]) => {
const opts = simpleWebauthn.generateAttestationOptions({
rpName: 'Lamassu',
rpID: options.domain,
userName: user.username,
userID: user.id,
timeout: 60000,
attestationType: 'indirect',
excludeCredentials: userDevices.map(dev => ({
id: dev.data.credentialID,
type: 'public-key',
transports: ['usb', 'ble', 'nfc', 'internal']
})),
authenticatorSelection: {
userVerification: 'discouraged',
requireResidentKey: false
}
return users
.getUserById(options.userId)
.then(user => {
return Promise.all([
credentials.getHardwareCredentialsByUserId(user.id),
user,
])
})
.then(([userDevices, user]) => {
const opts = simpleWebauthn.generateAttestationOptions({
rpName: 'Lamassu',
rpID: options.domain,
userName: user.username,
userID: user.id,
timeout: 60000,
attestationType: 'indirect',
excludeCredentials: userDevices.map(dev => ({
id: dev.data.credentialID,
type: 'public-key',
transports: ['usb', 'ble', 'nfc', 'internal'],
})),
authenticatorSelection: {
userVerification: 'discouraged',
requireResidentKey: false,
},
})
session.webauthn = {
attestation: {
challenge: opts.challenge
session.webauthn = {
attestation: {
challenge: opts.challenge,
},
}
}
return opts
})
return opts
})
}
const generateAssertionOptions = (session, options) => {
@ -50,16 +56,16 @@ const generateAssertionOptions = (session, options) => {
allowCredentials: devices.map(dev => ({
id: dev.data.credentialID,
type: 'public-key',
transports: ['usb', 'ble', 'nfc', 'internal']
transports: ['usb', 'ble', 'nfc', 'internal'],
})),
userVerification: 'discouraged',
rpID: options.domain
rpID: options.domain,
})
session.webauthn = {
assertion: {
challenge: opts.challenge
}
challenge: opts.challenge,
},
}
return opts
@ -77,40 +83,38 @@ const validateAttestation = (session, options) => {
credential: options.attestationResponse,
expectedChallenge: `${expectedChallenge}`,
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
expectedRPID: options.domain
})
])
.then(([user, verification]) => {
const { verified, attestationInfo } = verification
expectedRPID: options.domain,
}),
]).then(([user, verification]) => {
const { verified, attestationInfo } = verification
if (!(verified || attestationInfo)) {
session.webauthn = null
return false
}
if (!(verified || attestationInfo)) {
session.webauthn = null
return false
}
const {
counter,
credentialPublicKey,
credentialID
} = attestationInfo
const { counter, credentialPublicKey, credentialID } = attestationInfo
return credentials.getHardwareCredentialsByUserId(user.id)
.then(userDevices => {
const existingDevice = userDevices.find(device => device.data.credentialID === credentialID)
return credentials
.getHardwareCredentialsByUserId(user.id)
.then(userDevices => {
const existingDevice = userDevices.find(
device => device.data.credentialID === credentialID,
)
if (!existingDevice) {
const newDevice = {
counter,
credentialPublicKey,
credentialID
}
credentials.createHardwareCredential(user.id, newDevice)
if (!existingDevice) {
const newDevice = {
counter,
credentialPublicKey,
credentialID,
}
credentials.createHardwareCredential(user.id, newDevice)
}
session.webauthn = null
return verified
})
})
session.webauthn = null
return verified
})
})
}
const validateAssertion = (session, options) => {
@ -119,17 +123,24 @@ const validateAssertion = (session, options) => {
return credentials.getHardwareCredentialsByUserId(user.id).then(devices => {
const dbAuthenticator = _.find(dev => {
return Buffer.from(dev.data.credentialID).compare(base64url.toBuffer(options.assertionResponse.rawId)) === 0
return (
Buffer.from(dev.data.credentialID).compare(
base64url.toBuffer(options.assertionResponse.rawId),
) === 0
)
}, devices)
if (!dbAuthenticator.data) {
throw new Error(`Could not find authenticator matching ${options.assertionResponse.id}`)
throw new Error(
`Could not find authenticator matching ${options.assertionResponse.id}`,
)
}
const convertedAuthenticator = _.merge(
dbAuthenticator.data,
{ credentialPublicKey: Buffer.from(dbAuthenticator.data.credentialPublicKey) }
)
const convertedAuthenticator = _.merge(dbAuthenticator.data, {
credentialPublicKey: Buffer.from(
dbAuthenticator.data.credentialPublicKey,
),
})
let verification
try {
@ -138,7 +149,7 @@ const validateAssertion = (session, options) => {
expectedChallenge: `${expectedChallenge}`,
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
expectedRPID: options.domain,
authenticator: convertedAuthenticator
authenticator: convertedAuthenticator,
})
} catch (err) {
console.error(err)
@ -148,20 +159,22 @@ const validateAssertion = (session, options) => {
const { verified, assertionInfo } = verification
if (!verified) {
context.req.session.webauthn = null
return false
}
dbAuthenticator.data.counter = assertionInfo.newCounter
return credentials.updateHardwareCredential(dbAuthenticator)
.then(() => {
const finalUser = { id: user.id, username: user.username, role: user.role }
session.user = finalUser
if (options.rememberMe) session.cookie.maxAge = REMEMBER_ME_AGE
return credentials.updateHardwareCredential(dbAuthenticator).then(() => {
const finalUser = {
id: user.id,
username: user.username,
role: user.role,
}
session.user = finalUser
if (options.rememberMe) session.cookie.maxAge = REMEMBER_ME_AGE
session.webauthn = null
return verified
})
session.webauthn = null
return verified
})
})
})
}
@ -170,5 +183,5 @@ module.exports = {
generateAttestationOptions,
generateAssertionOptions,
validateAttestation,
validateAssertion
validateAssertion,
}

View file

@ -22,19 +22,19 @@ const generateAttestationOptions = (session, options) => {
excludeCredentials: devices.map(dev => ({
id: dev.data.credentialID,
type: 'public-key',
transports: ['usb', 'ble', 'nfc', 'internal']
transports: ['usb', 'ble', 'nfc', 'internal'],
})),
authenticatorSelection: {
authenticatorAttachment: 'cross-platform',
userVerification: 'discouraged',
requireResidentKey: false
}
requireResidentKey: false,
},
})
session.webauthn = {
attestation: {
challenge: opts.challenge
}
challenge: opts.challenge,
},
}
return opts
@ -48,16 +48,16 @@ const generateAssertionOptions = (session, options) => {
allowCredentials: devices.map(dev => ({
id: dev.data.credentialID,
type: 'public-key',
transports: ['usb', 'ble', 'nfc', 'internal']
transports: ['usb', 'ble', 'nfc', 'internal'],
})),
userVerification: 'discouraged',
rpID: options.domain
rpID: options.domain,
})
session.webauthn = {
assertion: {
challenge: opts.challenge
}
challenge: opts.challenge,
},
}
return opts
})
@ -73,50 +73,52 @@ const validateAttestation = (session, options) => {
credential: options.attestationResponse,
expectedChallenge: `${expectedChallenge}`,
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
expectedRPID: options.domain
})
])
.then(([user, verification]) => {
const { verified, attestationInfo } = verification
expectedRPID: options.domain,
}),
]).then(([user, verification]) => {
const { verified, attestationInfo } = verification
if (!(verified || attestationInfo)) {
session.webauthn = null
return verified
}
const {
fmt,
counter,
aaguid,
credentialPublicKey,
credentialID,
credentialType,
userVerified,
attestationObject,
} = attestationInfo
return credentials
.getHardwareCredentialsByUserId(user.id)
.then(userDevices => {
const existingDevice = userDevices.find(
device => device.data.credentialID === credentialID,
)
if (!existingDevice) {
const newDevice = {
fmt,
counter,
aaguid,
credentialPublicKey,
credentialID,
credentialType,
userVerified,
attestationObject,
}
credentials.createHardwareCredential(user.id, newDevice)
}
if (!(verified || attestationInfo)) {
session.webauthn = null
return verified
}
const {
fmt,
counter,
aaguid,
credentialPublicKey,
credentialID,
credentialType,
userVerified,
attestationObject
} = attestationInfo
return credentials.getHardwareCredentialsByUserId(user.id)
.then(userDevices => {
const existingDevice = userDevices.find(device => device.data.credentialID === credentialID)
if (!existingDevice) {
const newDevice = {
fmt,
counter,
aaguid,
credentialPublicKey,
credentialID,
credentialType,
userVerified,
attestationObject
}
credentials.createHardwareCredential(user.id, newDevice)
}
session.webauthn = null
return verified
})
})
})
})
}
const validateAssertion = (session, options) => {
@ -124,17 +126,24 @@ const validateAssertion = (session, options) => {
return credentials.getHardwareCredentials().then(devices => {
const dbAuthenticator = _.find(dev => {
return Buffer.from(dev.data.credentialID).compare(base64url.toBuffer(options.assertionResponse.rawId)) === 0
return (
Buffer.from(dev.data.credentialID).compare(
base64url.toBuffer(options.assertionResponse.rawId),
) === 0
)
}, devices)
if (!dbAuthenticator.data) {
throw new Error(`Could not find authenticator matching ${options.assertionResponse.id}`)
throw new Error(
`Could not find authenticator matching ${options.assertionResponse.id}`,
)
}
const convertedAuthenticator = _.merge(
dbAuthenticator.data,
{ credentialPublicKey: Buffer.from(dbAuthenticator.data.credentialPublicKey) }
)
const convertedAuthenticator = _.merge(dbAuthenticator.data, {
credentialPublicKey: Buffer.from(
dbAuthenticator.data.credentialPublicKey,
),
})
let verification
try {
@ -143,7 +152,7 @@ const validateAssertion = (session, options) => {
expectedChallenge: `${expectedChallenge}`,
expectedOrigin: `https://${options.domain}${devMode ? `:3001` : ``}`,
expectedRPID: options.domain,
authenticator: convertedAuthenticator
authenticator: convertedAuthenticator,
})
} catch (err) {
console.error(err)
@ -160,16 +169,19 @@ const validateAssertion = (session, options) => {
dbAuthenticator.data.counter = assertionInfo.newCounter
return Promise.all([
credentials.updateHardwareCredential(dbAuthenticator),
users.getUserById(dbAuthenticator.user_id)
])
.then(([_, user]) => {
const finalUser = { id: user.id, username: user.username, role: user.role }
session.user = finalUser
session.cookie.maxAge = REMEMBER_ME_AGE
session.webauthn = null
return verified
})
users.getUserById(dbAuthenticator.user_id),
]).then(([, user]) => {
const finalUser = {
id: user.id,
username: user.username,
role: user.role,
}
session.user = finalUser
session.cookie.maxAge = REMEMBER_ME_AGE
session.webauthn = null
return verified
})
})
}
@ -177,5 +189,5 @@ module.exports = {
generateAttestationOptions,
generateAssertionOptions,
validateAttestation,
validateAssertion
validateAssertion,
}

View file

@ -5,7 +5,7 @@ const FIDOUsernameless = require('./FIDOUsernamelessStrategy')
const STRATEGIES = {
FIDO2FA,
FIDOPasswordless,
FIDOUsernameless
FIDOUsernameless,
}
// FIDO2FA, FIDOPasswordless or FIDOUsernameless
@ -13,5 +13,5 @@ const CHOSEN_STRATEGY = 'FIDO2FA'
module.exports = {
CHOSEN_STRATEGY,
strategy: STRATEGIES[CHOSEN_STRATEGY]
strategy: STRATEGIES[CHOSEN_STRATEGY],
}

View file

@ -14,11 +14,16 @@ const credentials = require('../../../hardware-credentials')
const REMEMBER_ME_AGE = 90 * T.day
const authenticateUser = (username, password) => {
return users.getUserByUsername(username)
return users
.getUserByUsername(username)
.then(user => {
const hashedPassword = user.password
if (!hashedPassword || !user.enabled) throw new authErrors.InvalidCredentialsError()
return Promise.all([argon2.verify(hashedPassword, password), hashedPassword])
if (!hashedPassword || !user.enabled)
throw new authErrors.InvalidCredentialsError()
return Promise.all([
argon2.verify(hashedPassword, password),
hashedPassword,
])
})
.then(([isMatch, hashedPassword]) => {
if (!isMatch) throw new authErrors.InvalidCredentialsError()
@ -32,7 +37,9 @@ const authenticateUser = (username, password) => {
const destroySessionIfSameUser = (context, user) => {
const sessionUser = getUserFromCookie(context)
if (sessionUser && user.id === sessionUser.id) { context.req.session.destroy() }
if (sessionUser && user.id === sessionUser.id) {
context.req.session.destroy()
}
}
const destroySessionIfBeingUsed = (sessID, context) => {
@ -56,15 +63,13 @@ const initializeSession = (context, user, rememberMe) => {
}
const executeProtectedAction = (code, id, context, action) => {
return users.getUserById(id)
.then(user => {
if (user.role !== 'superuser') {
return action()
}
return users.getUserById(id).then(user => {
if (user.role !== 'superuser') {
return action()
}
return confirm2FA(code, context)
.then(() => action())
})
return confirm2FA(code, context).then(() => action())
})
}
const getUserData = context => {
@ -79,10 +84,18 @@ const get2FASecret = (username, password) => {
return authenticateUser(username, password)
.then(user => {
const secret = otplib.authenticator.generateSecret()
const otpauth = otplib.authenticator.keyuri(user.username, constants.AUTHENTICATOR_ISSUER_ENTITY, secret)
return Promise.all([users.saveTemp2FASecret(user.id, secret), secret, otpauth])
const otpauth = otplib.authenticator.keyuri(
user.username,
constants.AUTHENTICATOR_ISSUER_ENTITY,
secret,
)
return Promise.all([
users.saveTemp2FASecret(user.id, secret),
secret,
otpauth,
])
})
.then(([_, secret, otpauth]) => {
.then(([, secret, otpauth]) => {
return { secret, otpauth }
})
}
@ -103,35 +116,43 @@ const confirm2FA = (token, context) => {
const validateRegisterLink = token => {
if (!token) throw new authErrors.InvalidUrlError()
return users.validateUserRegistrationToken(token)
.then(r => {
if (!r.success) throw new authErrors.InvalidUrlError()
return { username: r.username, role: r.role }
})
return users.validateUserRegistrationToken(token).then(r => {
if (!r.success) throw new authErrors.InvalidUrlError()
return { username: r.username, role: r.role }
})
}
const validateResetPasswordLink = token => {
if (!token) throw new authErrors.InvalidUrlError()
return users.validateAuthToken(token, 'reset_password')
.then(r => {
if (!r.success) throw new authErrors.InvalidUrlError()
return { id: r.userID }
})
return users.validateAuthToken(token, 'reset_password').then(r => {
if (!r.success) throw new authErrors.InvalidUrlError()
return { id: r.userID }
})
}
const validateReset2FALink = token => {
if (!token) throw new authErrors.InvalidUrlError()
return users.validateAuthToken(token, 'reset_twofa')
return users
.validateAuthToken(token, 'reset_twofa')
.then(r => {
if (!r.success) throw new authErrors.InvalidUrlError()
return users.getUserById(r.userID)
})
.then(user => {
const secret = otplib.authenticator.generateSecret()
const otpauth = otplib.authenticator.keyuri(user.username, constants.AUTHENTICATOR_ISSUER_ENTITY, secret)
return Promise.all([users.saveTemp2FASecret(user.id, secret), user, secret, otpauth])
const otpauth = otplib.authenticator.keyuri(
user.username,
constants.AUTHENTICATOR_ISSUER_ENTITY,
secret,
)
return Promise.all([
users.saveTemp2FASecret(user.id, secret),
user,
secret,
otpauth,
])
})
.then(([_, user, secret, otpauth]) => {
.then(([, user, secret, otpauth]) => {
return { user_id: user.id, secret, otpauth }
})
}
@ -144,7 +165,10 @@ const deleteSession = (sessionID, context) => {
const login = (username, password) => {
return authenticateUser(username, password)
.then(user => {
return Promise.all([credentials.getHardwareCredentialsByUserId(user.id), user.twofa_code])
return Promise.all([
credentials.getHardwareCredentialsByUserId(user.id),
user.twofa_code,
])
})
.then(([devices, twoFASecret]) => {
if (!_.isEmpty(devices)) return 'FIDO'
@ -153,21 +177,32 @@ const login = (username, password) => {
}
const input2FA = (username, password, rememberMe, code, context) => {
return authenticateUser(username, password)
.then(user => {
const secret = user.twofa_code
const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
initializeSession(context, user, rememberMe)
return true
return authenticateUser(username, password).then(user => {
const secret = user.twofa_code
const isCodeValid = otplib.authenticator.verify({
token: code,
secret: secret,
})
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
initializeSession(context, user, rememberMe)
return true
})
}
const setup2FA = (username, password, rememberMe, codeConfirmation, context) => {
const setup2FA = (
username,
password,
rememberMe,
codeConfirmation,
context,
) => {
return authenticateUser(username, password)
.then(user => {
const isCodeValid = otplib.authenticator.verify({ token: codeConfirmation, secret: user.temp_twofa_code })
const isCodeValid = otplib.authenticator.verify({
token: codeConfirmation,
secret: user.temp_twofa_code,
})
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
initializeSession(context, user, rememberMe)
@ -202,24 +237,23 @@ const createReset2FAToken = (code, userID, context) => {
}
const createRegisterToken = (username, role) => {
return users.getUserByUsername(username)
.then(user => {
if (user) throw new authErrors.UserAlreadyExistsError()
return users.getUserByUsername(username).then(user => {
if (user) throw new authErrors.UserAlreadyExistsError()
return users.createUserRegistrationToken(username, role)
})
return users.createUserRegistrationToken(username, role)
})
}
const register = (token, username, password, role) => {
return users.getUserByUsername(username)
.then(user => {
if (user) throw new authErrors.UserAlreadyExistsError()
return users.register(token, username, password, role).then(() => true)
})
return users.getUserByUsername(username).then(user => {
if (user) throw new authErrors.UserAlreadyExistsError()
return users.register(token, username, password, role).then(() => true)
})
}
const resetPassword = (token, userID, newPassword, context) => {
return users.getUserById(userID)
return users
.getUserById(userID)
.then(user => {
destroySessionIfSameUser(context, user)
return users.updatePassword(token, user.id, newPassword)
@ -228,9 +262,13 @@ const resetPassword = (token, userID, newPassword, context) => {
}
const reset2FA = (token, userID, code, context) => {
return users.getUserById(userID)
return users
.getUserById(userID)
.then(user => {
const isCodeValid = otplib.authenticator.verify({ token: code, secret: user.temp_twofa_code })
const isCodeValid = otplib.authenticator.verify({
token: code,
secret: user.temp_twofa_code,
})
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
destroySessionIfSameUser(context, user)
@ -240,7 +278,10 @@ const reset2FA = (token, userID, code, context) => {
}
const getToken = context => {
if (_.isNil(context.req.cookies['lamassu_sid']) || _.isNil(context.req.session.user.id))
if (
_.isNil(context.req.cookies['lamassu_sid']) ||
_.isNil(context.req.session.user.id)
)
throw new authErrors.AuthenticationError('Authentication failed')
return context.req.session.user.id
@ -267,5 +308,5 @@ module.exports = {
register,
resetPassword,
reset2FA,
getToken
getToken,
}

View file

@ -2,8 +2,8 @@ const bills = require('../../services/bills')
const resolvers = {
Query: {
bills: (...[, { filters }]) => bills.getBills(filters)
}
bills: (...[, { filters }]) => bills.getBills(filters),
},
}
module.exports = resolvers

View file

@ -3,7 +3,7 @@ const blacklist = require('../../../blacklist')
const resolvers = {
Query: {
blacklist: () => blacklist.getBlacklist(),
blacklistMessages: () => blacklist.getMessages()
blacklistMessages: () => blacklist.getMessages(),
},
Mutation: {
deleteBlacklistRow: (...[, { address }]) =>
@ -11,8 +11,8 @@ const resolvers = {
insertBlacklistRow: (...[, { address }]) =>
blacklist.insertIntoBlacklist(address),
editBlacklistMessage: (...[, { id, content }]) =>
blacklist.editBlacklistMessage(id, content)
}
blacklist.editBlacklistMessage(id, content),
},
}
module.exports = resolvers

View file

@ -5,13 +5,21 @@ const logDateFormat = require('../../../logs').logDateFormat
const resolvers = {
Query: {
cashboxBatches: () => cashbox.getBatches(),
cashboxBatchesCsv: (...[, { from, until, timezone }]) => cashbox.getBatches(from, until)
.then(data => parseAsync(logDateFormat(timezone, cashbox.logFormatter(data), ['created'])))
cashboxBatchesCsv: (...[, { from, until, timezone }]) =>
cashbox
.getBatches(from, until)
.then(data =>
parseAsync(
logDateFormat(timezone, cashbox.logFormatter(data), ['created']),
),
),
},
Mutation: {
createBatch: (...[, { deviceId, cashboxCount }]) => cashbox.createCashboxBatch(deviceId, cashboxCount),
editBatch: (...[, { id, performedBy }]) => cashbox.editBatchById(id, performedBy)
}
createBatch: (...[, { deviceId, cashboxCount }]) =>
cashbox.createCashboxBatch(deviceId, cashboxCount),
editBatch: (...[, { id, performedBy }]) =>
cashbox.editBatchById(id, performedBy),
},
}
module.exports = resolvers

View file

@ -1,11 +1,15 @@
const { accounts: accountsConfig, countries, languages } = require('../../config')
const {
accounts: accountsConfig,
countries,
languages,
} = require('../../config')
const resolver = {
Query: {
countries: () => countries,
languages: () => languages,
accountsConfig: () => accountsConfig
}
accountsConfig: () => accountsConfig,
},
}
module.exports = resolver

View file

@ -3,8 +3,8 @@ const { coins, currencies } = require('../../config')
const resolver = {
Query: {
currencies: () => currencies,
cryptoCurrencies: () => coins
}
cryptoCurrencies: () => coins,
},
}
module.exports = resolver

View file

@ -2,32 +2,55 @@ const authentication = require('../modules/userManagement')
const queries = require('../../services/customInfoRequests')
const DataLoader = require('dataloader')
const customerCustomInfoRequestsLoader = new DataLoader(ids => queries.batchGetAllCustomInfoRequestsForCustomer(ids), { cache: false })
const customerCustomInfoRequestsLoader = new DataLoader(
ids => queries.batchGetAllCustomInfoRequestsForCustomer(ids),
{ cache: false },
)
const customInfoRequestLoader = new DataLoader(ids => queries.batchGetCustomInfoRequest(ids), { cache: false })
const customInfoRequestLoader = new DataLoader(
ids => queries.batchGetCustomInfoRequest(ids),
{ cache: false },
)
const resolvers = {
Customer: {
customInfoRequests: parent => customerCustomInfoRequestsLoader.load(parent.id)
customInfoRequests: parent =>
customerCustomInfoRequestsLoader.load(parent.id),
},
CustomRequestData: {
customInfoRequest: parent => customInfoRequestLoader.load(parent.infoRequestId)
customInfoRequest: parent =>
customInfoRequestLoader.load(parent.infoRequestId),
},
Query: {
customInfoRequests: (...[, { onlyEnabled }]) => queries.getCustomInfoRequests(onlyEnabled),
customerCustomInfoRequests: (...[, { customerId }]) => queries.getAllCustomInfoRequestsForCustomer(customerId),
customerCustomInfoRequest: (...[, { customerId, infoRequestId }]) => queries.getCustomInfoRequestForCustomer(customerId, infoRequestId)
customInfoRequests: (...[, { onlyEnabled }]) =>
queries.getCustomInfoRequests(onlyEnabled),
customerCustomInfoRequests: (...[, { customerId }]) =>
queries.getAllCustomInfoRequestsForCustomer(customerId),
customerCustomInfoRequest: (...[, { customerId, infoRequestId }]) =>
queries.getCustomInfoRequestForCustomer(customerId, infoRequestId),
},
Mutation: {
insertCustomInfoRequest: (...[, { customRequest }]) => queries.addCustomInfoRequest(customRequest),
removeCustomInfoRequest: (...[, { id }]) => queries.removeCustomInfoRequest(id),
editCustomInfoRequest: (...[, { id, customRequest }]) => queries.editCustomInfoRequest(id, customRequest),
setAuthorizedCustomRequest: (...[, { customerId, infoRequestId, override }, context]) => {
insertCustomInfoRequest: (...[, { customRequest }]) =>
queries.addCustomInfoRequest(customRequest),
removeCustomInfoRequest: (...[, { id }]) =>
queries.removeCustomInfoRequest(id),
editCustomInfoRequest: (...[, { id, customRequest }]) =>
queries.editCustomInfoRequest(id, customRequest),
setAuthorizedCustomRequest: (
...[, { customerId, infoRequestId, override }, context]
) => {
const token = authentication.getToken(context)
return queries.setAuthorizedCustomRequest(customerId, infoRequestId, override, token)
return queries.setAuthorizedCustomRequest(
customerId,
infoRequestId,
override,
token,
)
},
setCustomerCustomInfoRequest: (...[, { customerId, infoRequestId, data }]) => queries.setCustomerData(customerId, infoRequestId, data)
}
setCustomerCustomInfoRequest: (
...[, { customerId, infoRequestId, data }]
) => queries.setCustomerData(customerId, infoRequestId, data),
},
}
module.exports = resolvers

View file

@ -6,41 +6,56 @@ const customerNotes = require('../../../customer-notes')
const machineLoader = require('../../../machine-loader')
const addLastUsedMachineName = customer =>
(customer.lastUsedMachine ? machineLoader.getMachineName(customer.lastUsedMachine) : Promise.resolve(null))
.then(lastUsedMachineName => Object.assign(customer, { lastUsedMachineName }))
(customer.lastUsedMachine
? machineLoader.getMachineName(customer.lastUsedMachine)
: Promise.resolve(null)
).then(lastUsedMachineName =>
Object.assign(customer, { lastUsedMachineName }),
)
const resolvers = {
Customer: {
isAnonymous: parent => (parent.customerId === anonymous.uuid)
isAnonymous: parent => parent.customerId === anonymous.uuid,
},
Query: {
customers: (...[, { phone, email, name, address, id }]) => customers.getCustomersList(phone, name, address, id, email),
customer: (...[, { customerId }]) => customers.getCustomerById(customerId).then(addLastUsedMachineName),
customerFilters: () => filters.customer()
customers: (...[, { phone, email, name, address, id }]) =>
customers.getCustomersList(phone, name, address, id, email),
customer: (...[, { customerId }]) =>
customers.getCustomerById(customerId).then(addLastUsedMachineName),
customerFilters: () => filters.customer(),
},
Mutation: {
setCustomer: (root, { customerId, customerInput }, context, info) => {
setCustomer: (root, { customerId, customerInput }, context) => {
const token = authentication.getToken(context)
if (customerId === anonymous.uuid) return customers.getCustomerById(customerId)
if (customerId === anonymous.uuid)
return customers.getCustomerById(customerId)
return customers.updateCustomer(customerId, customerInput, token)
},
addCustomField: (...[, { customerId, label, value }]) => customers.addCustomField(customerId, label, value),
saveCustomField: (...[, { customerId, fieldId, value }]) => customers.saveCustomField(customerId, fieldId, value),
removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId),
addCustomField: (...[, { customerId, label, value }]) =>
customers.addCustomField(customerId, label, value),
saveCustomField: (...[, { customerId, fieldId, value }]) =>
customers.saveCustomField(customerId, fieldId, value),
removeCustomField: (...[, [{ customerId, fieldId }]]) =>
customers.removeCustomField(customerId, fieldId),
editCustomer: async (root, { customerId, customerEdit }, context) => {
const token = authentication.getToken(context)
const editedData = await customerEdit
return customers.edit(customerId, editedData, token)
},
replacePhoto: async (root, { customerId, photoType, newPhoto }, context) => {
replacePhoto: async (
root,
{ customerId, photoType, newPhoto },
context,
) => {
const token = authentication.getToken(context)
const { file } = newPhoto
const photo = await file
if (!photo) return customers.getCustomerById(customerId)
return customers.updateEditedPhoto(customerId, photo, photoType)
return customers
.updateEditedPhoto(customerId, photo, photoType)
.then(newPatch => customers.edit(customerId, newPatch, token))
},
deleteEditedData: (root, { customerId, customerEdit }) => {
deleteEditedData: (root, { customerId }) => {
// TODO: NOT IMPLEMENTING THIS FEATURE FOR THE CURRENT VERSION
return customers.getCustomerById(customerId)
},
@ -55,12 +70,13 @@ const resolvers = {
deleteCustomerNote: (...[, { noteId }]) => {
return customerNotes.deleteCustomerNote(noteId)
},
createCustomer: (...[, { phoneNumber }]) => customers.add({ phone: phoneNumber }),
createCustomer: (...[, { phoneNumber }]) =>
customers.add({ phone: phoneNumber }),
enableTestCustomer: (...[, { customerId }]) =>
customers.enableTestCustomer(customerId),
disableTestCustomer: (...[, { customerId }]) =>
customers.disableTestCustomer(customerId)
}
customers.disableTestCustomer(customerId),
},
}
module.exports = resolvers

View file

@ -2,8 +2,8 @@ const funding = require('../../services/funding')
const resolvers = {
Query: {
funding: () => funding.getFunding()
}
funding: () => funding.getFunding(),
},
}
module.exports = resolvers

View file

@ -47,7 +47,7 @@ const resolvers = [
status,
transaction,
user,
version
version,
]
module.exports = mergeResolvers(resolvers)

View file

@ -1,5 +1,4 @@
const { parseAsync } = require('json2csv')
const _ = require('lodash/fp')
const logs = require('../../../logs')
const serverLogs = require('../../services/server-logs')
@ -8,15 +7,23 @@ const resolvers = {
Query: {
machineLogs: (...[, { deviceId, from, until, limit, offset }]) =>
logs.simpleGetMachineLogs(deviceId, from, until, limit, offset),
machineLogsCsv: (...[, { deviceId, from, until, limit, offset, timezone }]) =>
logs.simpleGetMachineLogs(deviceId, from, until, limit, offset)
.then(res => parseAsync(logs.logDateFormat(timezone, res, ['timestamp']))),
machineLogsCsv: (
...[, { deviceId, from, until, limit, offset, timezone }]
) =>
logs
.simpleGetMachineLogs(deviceId, from, until, limit, offset)
.then(res =>
parseAsync(logs.logDateFormat(timezone, res, ['timestamp'])),
),
serverLogs: (...[, { from, until, limit, offset }]) =>
serverLogs.getServerLogs(from, until, limit, offset),
serverLogsCsv: (...[, { from, until, limit, offset, timezone }]) =>
serverLogs.getServerLogs(from, until, limit, offset)
.then(res => parseAsync(logs.logDateFormat(timezone, res, ['timestamp'])))
}
serverLogs
.getServerLogs(from, until, limit, offset)
.then(res =>
parseAsync(logs.logDateFormat(timezone, res, ['timestamp'])),
),
},
}
module.exports = resolvers

View file

@ -3,24 +3,30 @@ const DataLoader = require('dataloader')
const loyalty = require('../../../loyalty')
const { getSlimCustomerByIdBatch } = require('../../../customers')
const customerLoader = new DataLoader(ids => {
return getSlimCustomerByIdBatch(ids)
}, { cache: false })
const customerLoader = new DataLoader(
ids => {
return getSlimCustomerByIdBatch(ids)
},
{ cache: false },
)
const resolvers = {
IndividualDiscount: {
customer: parent => customerLoader.load(parent.customerId)
customer: parent => customerLoader.load(parent.customerId),
},
Query: {
promoCodes: () => loyalty.getAvailablePromoCodes(),
individualDiscounts: () => loyalty.getAvailableIndividualDiscounts()
individualDiscounts: () => loyalty.getAvailableIndividualDiscounts(),
},
Mutation: {
createPromoCode: (...[, { code, discount }]) => loyalty.createPromoCode(code, discount),
createPromoCode: (...[, { code, discount }]) =>
loyalty.createPromoCode(code, discount),
deletePromoCode: (...[, { codeId }]) => loyalty.deletePromoCode(codeId),
createIndividualDiscount: (...[, { customerId, discount }]) => loyalty.createIndividualDiscount(customerId, discount),
deleteIndividualDiscount: (...[, { discountId }]) => loyalty.deleteIndividualDiscount(discountId)
}
createIndividualDiscount: (...[, { customerId, discount }]) =>
loyalty.createIndividualDiscount(customerId, discount),
deleteIndividualDiscount: (...[, { discountId }]) =>
loyalty.deleteIndividualDiscount(discountId),
},
}
module.exports = resolvers

View file

@ -3,25 +3,29 @@ const DataLoader = require('dataloader')
const { machineAction } = require('../../services/machines')
const machineLoader = require('../../../machine-loader')
const machineEventsByIdBatch = require('../../../postgresql_interface').machineEventsByIdBatch
const machineEventsByIdBatch =
require('../../../postgresql_interface').machineEventsByIdBatch
const machineEventsLoader = new DataLoader(ids => {
return machineEventsByIdBatch(ids)
}, { cache: false })
const machineEventsLoader = new DataLoader(
ids => {
return machineEventsByIdBatch(ids)
},
{ cache: false },
)
const resolvers = {
Machine: {
latestEvent: parent => machineEventsLoader.load(parent.deviceId)
latestEvent: parent => machineEventsLoader.load(parent.deviceId),
},
Query: {
machines: () => machineLoader.getMachineNames(),
machine: (...[, { deviceId }]) => machineLoader.getMachine(deviceId),
unpairedMachines: () => machineLoader.getUnpairedMachines()
unpairedMachines: () => machineLoader.getUnpairedMachines(),
},
Mutation: {
machineAction: (...[, { deviceId, action, cashUnits, newName }, context]) =>
machineAction({ deviceId, action, cashUnits, newName }, context)
}
machineAction({ deviceId, action, cashUnits, newName }, context),
},
}
module.exports = resolvers

View file

@ -2,8 +2,8 @@ const exchange = require('../../../exchange')
const resolvers = {
Query: {
getMarkets: () => exchange.getMarkets()
}
getMarkets: () => exchange.getMarkets(),
},
}
module.exports = resolvers

View file

@ -4,12 +4,13 @@ const resolvers = {
Query: {
notifications: () => notifierQueries.getNotifications(),
hasUnreadNotifications: () => notifierQueries.hasUnreadNotifications(),
alerts: () => notifierQueries.getAlerts()
alerts: () => notifierQueries.getAlerts(),
},
Mutation: {
toggleClearNotification: (...[, { id, read }]) => notifierQueries.setRead(id, read),
clearAllNotifications: () => notifierQueries.markAllAsRead()
}
toggleClearNotification: (...[, { id, read }]) =>
notifierQueries.setRead(id, read),
clearAllNotifications: () => notifierQueries.markAllAsRead(),
},
}
module.exports = resolvers

View file

@ -2,8 +2,8 @@ const pairing = require('../../services/pairing')
const resolvers = {
Mutation: {
createPairingTotem: (...[, { name }]) => pairing.totem(name)
}
createPairingTotem: (...[, { name }]) => pairing.totem(name),
},
}
module.exports = resolvers

View file

@ -10,12 +10,12 @@ const resolvers = {
return pi.getRawRates().then(r => {
return {
withCommissions: pi.buildRates(r),
withoutCommissions: pi.buildRatesNoCommission(r)
withoutCommissions: pi.buildRatesNoCommission(r),
}
})
}),
fiatRates: () => forex.getFiatRates()
}
fiatRates: () => forex.getFiatRates(),
},
}
module.exports = resolvers

View file

@ -6,8 +6,8 @@ const resolvers = {
checkAgainstSanctions: (...[, { customerId }, context]) => {
const token = authentication.getToken(context)
return sanctions.checkByUser(customerId, token)
}
}
},
},
}
module.exports = resolvers

View file

@ -1,9 +1,13 @@
const { DateTimeISOResolver, JSONResolver, JSONObjectResolver } = require('graphql-scalars')
const {
DateTimeISOResolver,
JSONResolver,
JSONObjectResolver,
} = require('graphql-scalars')
const resolvers = {
JSON: JSONResolver,
JSONObject: JSONObjectResolver,
DateTimeISO: DateTimeISOResolver
DateTimeISO: DateTimeISOResolver,
}
module.exports = resolvers

View file

@ -3,12 +3,13 @@ const settingsLoader = require('../../../new-settings-loader')
const resolvers = {
Query: {
accounts: () => settingsLoader.showAccounts(),
config: () => settingsLoader.loadLatestConfigOrNone()
config: () => settingsLoader.loadLatestConfigOrNone(),
},
Mutation: {
saveAccounts: (...[, { accounts }]) => settingsLoader.saveAccounts(accounts),
saveAccounts: (...[, { accounts }]) =>
settingsLoader.saveAccounts(accounts),
saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config),
}
},
}
module.exports = resolvers

View file

@ -2,13 +2,14 @@ const smsNotices = require('../../../sms-notices')
const resolvers = {
Query: {
SMSNotices: () => smsNotices.getSMSNotices()
SMSNotices: () => smsNotices.getSMSNotices(),
},
Mutation: {
editSMSNotice: (...[, { id, event, message }]) => smsNotices.editSMSNotice(id, event, message),
editSMSNotice: (...[, { id, event, message }]) =>
smsNotices.editSMSNotice(id, event, message),
enableSMSNotice: (...[, { id }]) => smsNotices.enableSMSNotice(id),
disableSMSNotice: (...[, { id }]) => smsNotices.disableSMSNotice(id)
}
disableSMSNotice: (...[, { id }]) => smsNotices.disableSMSNotice(id),
},
}
module.exports = resolvers

View file

@ -2,8 +2,8 @@ const supervisor = require('../../services/supervisor')
const resolvers = {
Query: {
uptime: () => supervisor.getAllProcessInfo()
}
uptime: () => supervisor.getAllProcessInfo(),
},
}
module.exports = resolvers

View file

@ -1,6 +1,5 @@
const DataLoader = require('dataloader')
const { parseAsync } = require('json2csv')
const _ = require('lodash/fp')
const filters = require('../../filters')
const cashOutTx = require('../../../cash-out/cash-out-tx')
@ -9,35 +8,124 @@ const transactions = require('../../services/transactions')
const anonymous = require('../../../constants').anonymousCustomer
const logDateFormat = require('../../../logs').logDateFormat
const transactionsLoader = new DataLoader(ids => transactions.getCustomerTransactionsBatch(ids), { cache: false })
const transactionsLoader = new DataLoader(
ids => transactions.getCustomerTransactionsBatch(ids),
{ cache: false },
)
const resolvers = {
Customer: {
transactions: parent => transactionsLoader.load(parent.id)
transactions: parent => transactionsLoader.load(parent.id),
},
Transaction: {
isAnonymous: parent => (parent.customerId === anonymous.uuid)
isAnonymous: parent => parent.customerId === anonymous.uuid,
},
Query: {
transactions: (...[, { from, until, limit, offset, txClass, deviceId, customerName, fiatCode, cryptoCode, toAddress, status, swept, excludeTestingCustomers }]) =>
transactions.batch(from, until, limit, offset, txClass, deviceId, customerName, fiatCode, cryptoCode, toAddress, status, swept, excludeTestingCustomers),
transactionsCsv: (...[, { from, until, limit, offset, txClass, deviceId, customerName, fiatCode, cryptoCode, toAddress, status, swept, timezone, excludeTestingCustomers, simplified }]) =>
transactions.batch(from, until, limit, offset, txClass, deviceId, customerName, fiatCode, cryptoCode, toAddress, status, swept, excludeTestingCustomers, simplified)
.then(data => parseAsync(logDateFormat(timezone, data, ['created', 'sendTime', 'publishedAt']))),
transactions: (
...[
,
{
from,
until,
limit,
offset,
txClass,
deviceId,
customerName,
fiatCode,
cryptoCode,
toAddress,
status,
swept,
excludeTestingCustomers,
},
]
) =>
transactions.batch(
from,
until,
limit,
offset,
txClass,
deviceId,
customerName,
fiatCode,
cryptoCode,
toAddress,
status,
swept,
excludeTestingCustomers,
),
transactionsCsv: (
...[
,
{
from,
until,
limit,
offset,
txClass,
deviceId,
customerName,
fiatCode,
cryptoCode,
toAddress,
status,
swept,
timezone,
excludeTestingCustomers,
simplified,
},
]
) =>
transactions
.batch(
from,
until,
limit,
offset,
txClass,
deviceId,
customerName,
fiatCode,
cryptoCode,
toAddress,
status,
swept,
excludeTestingCustomers,
simplified,
)
.then(data =>
parseAsync(
logDateFormat(timezone, data, [
'created',
'sendTime',
'publishedAt',
]),
),
),
transactionCsv: (...[, { id, txClass, timezone }]) =>
transactions.getTx(id, txClass).then(data =>
parseAsync(logDateFormat(timezone, [data], ['created', 'sendTime', 'publishedAt']))
),
transactions
.getTx(id, txClass)
.then(data =>
parseAsync(
logDateFormat(
timezone,
[data],
['created', 'sendTime', 'publishedAt'],
),
),
),
txAssociatedDataCsv: (...[, { id, txClass, timezone }]) =>
transactions.getTxAssociatedData(id, txClass).then(data =>
parseAsync(logDateFormat(timezone, data, ['created']))
),
transactionFilters: () => filters.transaction()
transactions
.getTxAssociatedData(id, txClass)
.then(data => parseAsync(logDateFormat(timezone, data, ['created']))),
transactionFilters: () => filters.transaction(),
},
Mutation: {
cancelCashOutTransaction: (...[, { id }]) => cashOutTx.cancel(id),
cancelCashInTransaction: (...[, { id }]) => cashInTx.cancel(id)
}
cancelCashInTransaction: (...[, { id }]) => cashInTx.cancel(id),
},
}
module.exports = resolvers

View file

@ -19,7 +19,11 @@ const getAttestationQueryOptions = variables => {
const getAssertionQueryOptions = variables => {
switch (authentication.CHOSEN_STRATEGY) {
case 'FIDO2FA':
return { username: variables.username, password: variables.password, domain: variables.domain }
return {
username: variables.username,
password: variables.password,
domain: variables.domain,
}
case 'FIDOPasswordless':
return { username: variables.username, domain: variables.domain }
case 'FIDOUsernameless':
@ -32,11 +36,23 @@ const getAssertionQueryOptions = variables => {
const getAttestationMutationOptions = variables => {
switch (authentication.CHOSEN_STRATEGY) {
case 'FIDO2FA':
return { userId: variables.userID, attestationResponse: variables.attestationResponse, domain: variables.domain }
return {
userId: variables.userID,
attestationResponse: variables.attestationResponse,
domain: variables.domain,
}
case 'FIDOPasswordless':
return { userId: variables.userID, attestationResponse: variables.attestationResponse, domain: variables.domain }
return {
userId: variables.userID,
attestationResponse: variables.attestationResponse,
domain: variables.domain,
}
case 'FIDOUsernameless':
return { userId: variables.userID, attestationResponse: variables.attestationResponse, domain: variables.domain }
return {
userId: variables.userID,
attestationResponse: variables.attestationResponse,
domain: variables.domain,
}
default:
return {}
}
@ -45,11 +61,25 @@ const getAttestationMutationOptions = variables => {
const getAssertionMutationOptions = variables => {
switch (authentication.CHOSEN_STRATEGY) {
case 'FIDO2FA':
return { username: variables.username, password: variables.password, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse, domain: variables.domain }
return {
username: variables.username,
password: variables.password,
rememberMe: variables.rememberMe,
assertionResponse: variables.assertionResponse,
domain: variables.domain,
}
case 'FIDOPasswordless':
return { username: variables.username, rememberMe: variables.rememberMe, assertionResponse: variables.assertionResponse, domain: variables.domain }
return {
username: variables.username,
rememberMe: variables.rememberMe,
assertionResponse: variables.assertionResponse,
domain: variables.domain,
}
case 'FIDOUsernameless':
return { assertionResponse: variables.assertionResponse, domain: variables.domain }
return {
assertionResponse: variables.assertionResponse,
domain: variables.domain,
}
default:
return {}
}
@ -59,34 +89,82 @@ const resolver = {
Query: {
users: () => users.getUsers(),
sessions: () => sessionManager.getSessions(),
userSessions: (...[, { username }]) => sessionManager.getSessionsByUsername(username),
userData: (...[, {}, context]) => userManagement.getUserData(context),
get2FASecret: (...[, { username, password }]) => userManagement.get2FASecret(username, password),
confirm2FA: (...[, { code }, context]) => userManagement.confirm2FA(code, context),
validateRegisterLink: (...[, { token }]) => userManagement.validateRegisterLink(token),
validateResetPasswordLink: (...[, { token }]) => userManagement.validateResetPasswordLink(token),
validateReset2FALink: (...[, { token }]) => userManagement.validateReset2FALink(token),
generateAttestationOptions: (...[, variables, context]) => authentication.strategy.generateAttestationOptions(context.req.session, getAttestationQueryOptions(variables)),
generateAssertionOptions: (...[, variables, context]) => authentication.strategy.generateAssertionOptions(context.req.session, getAssertionQueryOptions(variables))
userSessions: (...[, { username }]) =>
sessionManager.getSessionsByUsername(username),
userData: (...[, , context]) => userManagement.getUserData(context),
get2FASecret: (...[, { username, password }]) =>
userManagement.get2FASecret(username, password),
confirm2FA: (...[, { code }, context]) =>
userManagement.confirm2FA(code, context),
validateRegisterLink: (...[, { token }]) =>
userManagement.validateRegisterLink(token),
validateResetPasswordLink: (...[, { token }]) =>
userManagement.validateResetPasswordLink(token),
validateReset2FALink: (...[, { token }]) =>
userManagement.validateReset2FALink(token),
generateAttestationOptions: (...[, variables, context]) =>
authentication.strategy.generateAttestationOptions(
context.req.session,
getAttestationQueryOptions(variables),
),
generateAssertionOptions: (...[, variables, context]) =>
authentication.strategy.generateAssertionOptions(
context.req.session,
getAssertionQueryOptions(variables),
),
},
Mutation: {
enableUser: (...[, { confirmationCode, id }, context]) => userManagement.enableUser(confirmationCode, id, context),
disableUser: (...[, { confirmationCode, id }, context]) => userManagement.disableUser(confirmationCode, id, context),
deleteSession: (...[, { sid }, context]) => userManagement.deleteSession(sid, context),
deleteUserSessions: (...[, { username }]) => sessionManager.deleteSessionsByUsername(username),
changeUserRole: (...[, { confirmationCode, id, newRole }, context]) => userManagement.changeUserRole(confirmationCode, id, newRole, context),
login: (...[, { username, password }]) => userManagement.login(username, password),
input2FA: (...[, { username, password, rememberMe, code }, context]) => userManagement.input2FA(username, password, rememberMe, code, context),
setup2FA: (...[, { username, password, rememberMe, codeConfirmation }, context]) => userManagement.setup2FA(username, password, rememberMe, codeConfirmation, context),
createResetPasswordToken: (...[, { confirmationCode, userID }, context]) => userManagement.createResetPasswordToken(confirmationCode, userID, context),
createReset2FAToken: (...[, { confirmationCode, userID }, context]) => userManagement.createReset2FAToken(confirmationCode, userID, context),
createRegisterToken: (...[, { username, role }]) => userManagement.createRegisterToken(username, role),
register: (...[, { token, username, password, role }]) => userManagement.register(token, username, password, role),
resetPassword: (...[, { token, userID, newPassword }, context]) => userManagement.resetPassword(token, userID, newPassword, context),
reset2FA: (...[, { token, userID, code }, context]) => userManagement.reset2FA(token, userID, code, context),
validateAttestation: (...[, variables, context]) => authentication.strategy.validateAttestation(context.req.session, getAttestationMutationOptions(variables)),
validateAssertion: (...[, variables, context]) => authentication.strategy.validateAssertion(context.req.session, getAssertionMutationOptions(variables))
}
enableUser: (...[, { confirmationCode, id }, context]) =>
userManagement.enableUser(confirmationCode, id, context),
disableUser: (...[, { confirmationCode, id }, context]) =>
userManagement.disableUser(confirmationCode, id, context),
deleteSession: (...[, { sid }, context]) =>
userManagement.deleteSession(sid, context),
deleteUserSessions: (...[, { username }]) =>
sessionManager.deleteSessionsByUsername(username),
changeUserRole: (...[, { confirmationCode, id, newRole }, context]) =>
userManagement.changeUserRole(confirmationCode, id, newRole, context),
login: (...[, { username, password }]) =>
userManagement.login(username, password),
input2FA: (...[, { username, password, rememberMe, code }, context]) =>
userManagement.input2FA(username, password, rememberMe, code, context),
setup2FA: (
...[, { username, password, rememberMe, codeConfirmation }, context]
) =>
userManagement.setup2FA(
username,
password,
rememberMe,
codeConfirmation,
context,
),
createResetPasswordToken: (...[, { confirmationCode, userID }, context]) =>
userManagement.createResetPasswordToken(
confirmationCode,
userID,
context,
),
createReset2FAToken: (...[, { confirmationCode, userID }, context]) =>
userManagement.createReset2FAToken(confirmationCode, userID, context),
createRegisterToken: (...[, { username, role }]) =>
userManagement.createRegisterToken(username, role),
register: (...[, { token, username, password, role }]) =>
userManagement.register(token, username, password, role),
resetPassword: (...[, { token, userID, newPassword }, context]) =>
userManagement.resetPassword(token, userID, newPassword, context),
reset2FA: (...[, { token, userID, code }, context]) =>
userManagement.reset2FA(token, userID, code, context),
validateAttestation: (...[, variables, context]) =>
authentication.strategy.validateAttestation(
context.req.session,
getAttestationMutationOptions(variables),
),
validateAssertion: (...[, variables, context]) =>
authentication.strategy.validateAssertion(
context.req.session,
getAssertionMutationOptions(variables),
),
},
}
module.exports = resolver

View file

@ -2,8 +2,8 @@ const serverVersion = require('../../../../package.json').version
const resolvers = {
Query: {
serverVersion: () => serverVersion
}
serverVersion: () => serverVersion,
},
}
module.exports = resolvers

View file

@ -3,5 +3,5 @@ const resolvers = require('./resolvers')
module.exports = {
resolvers: resolvers,
typeDefs: types
typeDefs: types,
}

View file

@ -14,7 +14,11 @@ const typeDef = gql`
type Query {
cashboxBatches: [CashboxBatch] @auth
cashboxBatchesCsv(from: DateTimeISO, until: DateTimeISO, timezone: String): String @auth
cashboxBatchesCsv(
from: DateTimeISO
until: DateTimeISO
timezone: String
): String @auth
}
type Mutation {

View file

@ -1,10 +1,9 @@
const gql = require('graphql-tag')
const typeDef = gql`
type CustomInfoRequest {
id: ID!,
enabled: Boolean,
id: ID!
enabled: Boolean
customRequest: JSON
}
@ -42,15 +41,31 @@ const typeDef = gql`
type Query {
customInfoRequests(onlyEnabled: Boolean): [CustomInfoRequest] @auth
customerCustomInfoRequests(customerId: ID!): [CustomRequestData] @auth
customerCustomInfoRequest(customerId: ID!, infoRequestId: ID!): CustomRequestData @auth
customerCustomInfoRequest(
customerId: ID!
infoRequestId: ID!
): CustomRequestData @auth
}
type Mutation {
insertCustomInfoRequest(customRequest: CustomRequestInput!): CustomInfoRequest @auth
insertCustomInfoRequest(
customRequest: CustomRequestInput!
): CustomInfoRequest @auth
removeCustomInfoRequest(id: ID!): CustomInfoRequest @auth
editCustomInfoRequest(id: ID!, customRequest: CustomRequestInput!): CustomInfoRequest @auth
setAuthorizedCustomRequest(customerId: ID!, infoRequestId: ID!, override: String!): Boolean @auth
setCustomerCustomInfoRequest(customerId: ID!, infoRequestId: ID!, data: JSON!): Boolean @auth
editCustomInfoRequest(
id: ID!
customRequest: CustomRequestInput!
): CustomInfoRequest @auth
setAuthorizedCustomRequest(
customerId: ID!
infoRequestId: ID!
override: String!
): Boolean @auth
setCustomerCustomInfoRequest(
customerId: ID!
infoRequestId: ID!
data: JSON!
): Boolean @auth
}
`

View file

@ -95,20 +95,37 @@ const typeDef = gql`
}
type Query {
customers(phone: String, name: String, email: String, address: String, id: String): [Customer] @auth
customers(
phone: String
name: String
email: String
address: String
id: String
): [Customer] @auth
customer(customerId: ID!): Customer @auth
customerFilters: [Filter] @auth
}
type Mutation {
setCustomer(customerId: ID!, customerInput: CustomerInput): Customer @auth
addCustomField(customerId: ID!, label: String!, value: String!): Boolean @auth
saveCustomField(customerId: ID!, fieldId: ID!, value: String!): Boolean @auth
addCustomField(customerId: ID!, label: String!, value: String!): Boolean
@auth
saveCustomField(customerId: ID!, fieldId: ID!, value: String!): Boolean
@auth
removeCustomField(customerId: ID!, fieldId: ID!): Boolean @auth
editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth
deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth
replacePhoto(customerId: ID!, photoType: String, newPhoto: Upload): Customer @auth
createCustomerNote(customerId: ID!, title: String!, content: String!): Boolean @auth
deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer
@auth
replacePhoto(
customerId: ID!
photoType: String
newPhoto: Upload
): Customer @auth
createCustomerNote(
customerId: ID!
title: String!
content: String!
): Boolean @auth
editCustomerNote(noteId: ID!, newContent: String!): Boolean @auth
deleteCustomerNote(noteId: ID!): Boolean @auth
createCustomer(phoneNumber: String): Customer @auth

View file

@ -47,7 +47,7 @@ const types = [
status,
transaction,
user,
version
version,
]
module.exports = mergeTypeDefs(types)

View file

@ -16,10 +16,34 @@ const typeDef = gql`
}
type Query {
machineLogs(deviceId: ID!, from: DateTimeISO, until: DateTimeISO, limit: Int, offset: Int): [MachineLog] @auth
machineLogsCsv(deviceId: ID!, from: DateTimeISO, until: DateTimeISO, limit: Int, offset: Int, timezone: String): String @auth
serverLogs(from: DateTimeISO, until: DateTimeISO, limit: Int, offset: Int): [ServerLog] @auth
serverLogsCsv(from: DateTimeISO, until: DateTimeISO, limit: Int, offset: Int, timezone: String): String @auth
machineLogs(
deviceId: ID!
from: DateTimeISO
until: DateTimeISO
limit: Int
offset: Int
): [MachineLog] @auth
machineLogsCsv(
deviceId: ID!
from: DateTimeISO
until: DateTimeISO
limit: Int
offset: Int
timezone: String
): String @auth
serverLogs(
from: DateTimeISO
until: DateTimeISO
limit: Int
offset: Int
): [ServerLog] @auth
serverLogsCsv(
from: DateTimeISO
until: DateTimeISO
limit: Int
offset: Int
timezone: String
): String @auth
}
`

View file

@ -6,7 +6,7 @@ const typeDef = gql`
customer: DiscountCustomer!
discount: Int
}
type DiscountCustomer {
id: ID!
phone: String
@ -27,7 +27,10 @@ const typeDef = gql`
type Mutation {
createPromoCode(code: String!, discount: Int!): PromoCode @auth
deletePromoCode(codeId: ID!): PromoCode @auth
createIndividualDiscount(customerId: ID!, discount: Int!): IndividualDiscount @auth
createIndividualDiscount(
customerId: ID!
discount: Int!
): IndividualDiscount @auth
deleteIndividualDiscount(discountId: ID!): IndividualDiscount @auth
}
`

View file

@ -30,7 +30,7 @@ const typeDef = gql`
frontTimestamp: DateTimeISO
scanTimestamp: DateTimeISO
}
type CashUnits {
cashbox: Int
cassette1: Int
@ -98,7 +98,12 @@ const typeDef = gql`
}
type Mutation {
machineAction(deviceId:ID!, action: MachineAction!, cashUnits: CashUnitsInput, newName: String): Machine @auth
machineAction(
deviceId: ID!
action: MachineAction!
cashUnits: CashUnitsInput
newName: String
): Machine @auth
}
`

View file

@ -21,7 +21,8 @@ const typeDef = gql`
}
type Mutation {
editSMSNotice(id: ID!, event: SMSNoticeEvent!, message: String!): SMSNotice @auth
editSMSNotice(id: ID!, event: SMSNoticeEvent!, message: String!): SMSNotice
@auth
enableSMSNotice(id: ID!): SMSNotice @auth
disableSMSNotice(id: ID!): SMSNotice @auth
}

View file

@ -60,8 +60,38 @@ const typeDef = gql`
}
type Query {
transactions(from: DateTimeISO, until: DateTimeISO, limit: Int, offset: Int, txClass: String, deviceId: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String, swept: Boolean, excludeTestingCustomers: Boolean): [Transaction] @auth
transactionsCsv(from: DateTimeISO, until: DateTimeISO, limit: Int, offset: Int, txClass: String, deviceId: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String, swept: Boolean, timezone: String, excludeTestingCustomers: Boolean, simplified: Boolean): String @auth
transactions(
from: DateTimeISO
until: DateTimeISO
limit: Int
offset: Int
txClass: String
deviceId: String
customerName: String
fiatCode: String
cryptoCode: String
toAddress: String
status: String
swept: Boolean
excludeTestingCustomers: Boolean
): [Transaction] @auth
transactionsCsv(
from: DateTimeISO
until: DateTimeISO
limit: Int
offset: Int
txClass: String
deviceId: String
customerName: String
fiatCode: String
cryptoCode: String
toAddress: String
status: String
swept: Boolean
timezone: String
excludeTestingCustomers: Boolean
simplified: Boolean
): String @auth
transactionCsv(id: ID, txClass: String, timezone: String): String @auth
txAssociatedDataCsv(id: ID, txClass: String, timezone: String): String @auth
transactionFilters: [Filter] @auth

View file

@ -4,13 +4,17 @@ const logger = require('../../logger')
let schemaCache = Date.now()
const cleanUserSessions = (cleanInterval) => (req, res, next) => {
const cleanUserSessions = cleanInterval => (req, res, next) => {
const now = Date.now()
if (schemaCache + cleanInterval > now) return next()
logger.debug(`Clearing expired sessions for schema 'public'`)
return db.none('DELETE FROM $1^ WHERE expire < to_timestamp($2 / 1000.0)', [USER_SESSIONS_TABLE_NAME, now])
return db
.none('DELETE FROM $1^ WHERE expire < to_timestamp($2 / 1000.0)', [
USER_SESSIONS_TABLE_NAME,
now,
])
.then(() => {
schemaCache = now
return next()

View file

@ -8,9 +8,10 @@ const buildApolloContext = async ({ req, res }) => {
const user = await users.verifyAndUpdateUser(
req.session.user.id,
req.headers['user-agent'] || 'Unknown',
req.ip
req.ip,
)
if (!user || !user.enabled) throw new AuthenticationError('Authentication failed')
if (!user || !user.enabled)
throw new AuthenticationError('Authentication failed')
req.session.ua = req.headers['user-agent'] || 'Unknown'
req.session.ipAddress = req.ip

View file

@ -5,5 +5,5 @@ const session = require('./session')
module.exports = {
cleanUserSessions,
buildApolloContext,
session
session,
}

View file

@ -6,21 +6,24 @@ const db = require('../../db')
const { USER_SESSIONS_TABLE_NAME } = require('../../constants')
const { getOperatorId } = require('../../operator')
router.use('*', async (req, res, next) => getOperatorId('authentication').then(operatorId => session({
store: new PgSession({
pgPromise: db,
tableName: USER_SESSIONS_TABLE_NAME
}),
name: 'lamassu_sid',
secret: operatorId,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: true
}
})(req, res, next))
router.use('*', async (req, res, next) =>
getOperatorId('authentication').then(operatorId =>
session({
store: new PgSession({
pgPromise: db,
tableName: USER_SESSIONS_TABLE_NAME,
}),
name: 'lamassu_sid',
secret: operatorId,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: true,
},
})(req, res, next),
),
)
module.exports = router

View file

@ -4,7 +4,9 @@ const pgp = require('pg-promise')()
const db = require('../../db')
const getBills = filters => {
const deviceStatement = !_.isNil(filters.deviceId) ? `WHERE device_id = ${pgp.as.text(filters.deviceId)}` : ``
const deviceStatement = !_.isNil(filters.deviceId)
? `WHERE device_id = ${pgp.as.text(filters.deviceId)}`
: ``
const batchStatement = filter => {
switch (filter) {
case 'none':
@ -12,7 +14,9 @@ const getBills = filters => {
case 'any':
return `WHERE b.cashbox_batch_id IS NOT NULL`
default:
return _.isNil(filter) ? `` : `WHERE b.cashbox_batch_id = ${pgp.as.text(filter)}`
return _.isNil(filter)
? ``
: `WHERE b.cashbox_batch_id = ${pgp.as.text(filter)}`
}
}
@ -22,10 +26,12 @@ const getBills = filters => {
const sql2 = `SELECT b.id, b.fiat, b.fiat_code, b.created, b.cashbox_batch_id, b.device_id FROM empty_unit_bills b ${deviceStatement} ${!_.isNil(filters.deviceId) && !_.isNil(filters.batch) ? `AND ${_.replace('WHERE', '', batchStatement(filters.batch))}` : `${batchStatement(filters.batch)}`}`
return Promise.all([db.any(sql), db.any(sql2)])
.then(([bills, operationalBills]) => _.map(_.mapKeys(_.camelCase), _.concat(bills, operationalBills)))
return Promise.all([db.any(sql), db.any(sql2)]).then(
([bills, operationalBills]) =>
_.map(_.mapKeys(_.camelCase), _.concat(bills, operationalBills)),
)
}
module.exports = {
getBills
getBills,
}

View file

@ -2,7 +2,10 @@ const db = require('../../db')
const uuid = require('uuid')
const _ = require('lodash/fp')
const pgp = require('pg-promise')()
const { loadLatestConfigOrNone, saveConfig } = require('../../../lib/new-settings-loader')
const {
loadLatestConfigOrNone,
saveConfig,
} = require('../../../lib/new-settings-loader')
const getCustomInfoRequests = (onlyEnabled = false) => {
const sql = onlyEnabled
@ -12,38 +15,57 @@ const getCustomInfoRequests = (onlyEnabled = false) => {
return res.map(item => ({
id: item.id,
enabled: item.enabled,
customRequest: item.custom_request
customRequest: item.custom_request,
}))
})
}
const addCustomInfoRequest = (customRequest) => {
const sql = 'INSERT INTO custom_info_requests (id, custom_request) VALUES ($1, $2)'
const addCustomInfoRequest = customRequest => {
const sql =
'INSERT INTO custom_info_requests (id, custom_request) VALUES ($1, $2)'
const id = uuid.v4()
return db.none(sql, [id, customRequest]).then(() => ({ id }))
}
const removeCustomInfoRequest = (id) => {
const removeCustomInfoRequest = 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 }));
.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) => {
return db.none('UPDATE custom_info_requests SET custom_request = $1 WHERE id=$2', [customRequest, id]).then(() => ({ id, customRequest }))
return db
.none('UPDATE custom_info_requests SET custom_request = $1 WHERE id=$2', [
customRequest,
id,
])
.then(() => ({ id, customRequest }))
}
const getAllCustomInfoRequestsForCustomer = (customerId) => {
const getAllCustomInfoRequestsForCustomer = customerId => {
const sql = `SELECT * FROM customers_custom_info_requests WHERE customer_id = $1`
return db.any(sql, [customerId]).then(res => res.map(item => ({
customerId: item.customer_id,
infoRequestId: item.info_request_id,
customerData: item.customer_data,
override: item.override,
overrideAt: item.override_at,
overrideBy: item.override_by
})))
return db.any(sql, [customerId]).then(res =>
res.map(item => ({
customerId: item.customer_id,
infoRequestId: item.info_request_id,
customerData: item.customer_data,
override: item.override,
overrideAt: item.override_at,
overrideBy: item.override_by,
})),
)
}
const getCustomInfoRequestForCustomer = (customerId, infoRequestId) => {
@ -55,12 +77,12 @@ const getCustomInfoRequestForCustomer = (customerId, infoRequestId) => {
customerData: item.customer_data,
override: item.override,
overrideAt: item.override_at,
overrideBy: item.override_by
overrideBy: item.override_by,
}
})
}
const batchGetAllCustomInfoRequestsForCustomer = (customerIds) => {
const batchGetAllCustomInfoRequestsForCustomer = customerIds => {
const sql = `SELECT * FROM customers_custom_info_requests WHERE customer_id IN ($1^)`
return db.any(sql, [_.map(pgp.as.text, customerIds).join(',')]).then(res => {
const map = _.groupBy('customer_id', res)
@ -72,40 +94,49 @@ const batchGetAllCustomInfoRequestsForCustomer = (customerIds) => {
customerData: item.customer_data,
override: item.override,
overrideAt: item.override_at,
overrideBy: item.override_by
overrideBy: item.override_by,
}))
})
})
}
const getCustomInfoRequest = (infoRequestId) => {
const getCustomInfoRequest = infoRequestId => {
const sql = `SELECT * FROM custom_info_requests WHERE id = $1`
return db.one(sql, [infoRequestId]).then(item => ({
id: item.id,
enabled: item.enabled,
customRequest: item.custom_request
customRequest: item.custom_request,
}))
}
const batchGetCustomInfoRequest = (infoRequestIds) => {
const batchGetCustomInfoRequest = infoRequestIds => {
if (infoRequestIds.length === 0) return Promise.resolve([])
const sql = `SELECT * FROM custom_info_requests WHERE id IN ($1^)`
return db.any(sql, [_.map(pgp.as.text, infoRequestIds).join(',')]).then(res => {
const map = _.groupBy('id', res)
return infoRequestIds.map(id => {
const item = map[id][0] // since id is primary key the array always has 1 element
return {
id: item.id,
enabled: item.enabled,
customRequest: item.custom_request
}
return db
.any(sql, [_.map(pgp.as.text, infoRequestIds).join(',')])
.then(res => {
const map = _.groupBy('id', res)
return infoRequestIds.map(id => {
const item = map[id][0] // since id is primary key the array always has 1 element
return {
id: item.id,
enabled: item.enabled,
customRequest: item.custom_request,
}
})
})
})
}
const setAuthorizedCustomRequest = (customerId, infoRequestId, override, token) => {
const setAuthorizedCustomRequest = (
customerId,
infoRequestId,
override,
token,
) => {
const sql = `UPDATE customers_custom_info_requests SET override = $1, override_by = $2, override_at = now() WHERE customer_id = $3 AND info_request_id = $4`
return db.none(sql, [override, token, customerId, infoRequestId]).then(() => true)
return db
.none(sql, [override, token, customerId, infoRequestId])
.then(() => true)
}
const setCustomerData = (customerId, infoRequestId, data) => {
@ -138,5 +169,5 @@ module.exports = {
batchGetCustomInfoRequest,
setAuthorizedCustomRequest,
setCustomerData,
setCustomerDataViaMachine
setCustomerDataViaMachine,
}

View file

@ -7,55 +7,65 @@ const ticker = require('../../ticker')
const txBatching = require('../../tx-batching')
const { utils: coinUtils } = require('@lamassu/coins')
function computeCrypto (cryptoCode, _balance) {
function computeCrypto(cryptoCode, _balance) {
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
const unitScale = cryptoRec.unitScale
return new BN(_balance).shiftedBy(-unitScale).decimalPlaces(5)
}
function computeFiat (rate, cryptoCode, _balance) {
function computeFiat(rate, cryptoCode, _balance) {
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
const unitScale = cryptoRec.unitScale
return new BN(_balance).shiftedBy(-unitScale).times(rate).decimalPlaces(5)
}
function getSingleCoinFunding (settings, fiatCode, cryptoCode) {
function getSingleCoinFunding(settings, fiatCode, cryptoCode) {
const promises = [
wallet.newFunding(settings, cryptoCode),
ticker.getRates(settings, fiatCode, cryptoCode),
txBatching.getOpenBatchCryptoValue(cryptoCode)
txBatching.getOpenBatchCryptoValue(cryptoCode),
]
return Promise.all(promises)
.then(([fundingRec, ratesRec, batchRec]) => {
const rates = ratesRec.rates
const rate = (rates.ask.plus(rates.bid)).div(2)
const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance
const fiatConfirmedBalance = computeFiat(rate, cryptoCode, fundingConfirmedBalance)
const pending = fundingRec.fundingPendingBalance.minus(batchRec)
const fiatPending = computeFiat(rate, cryptoCode, pending)
const fundingAddress = fundingRec.fundingAddress
const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress)
return Promise.all(promises).then(([fundingRec, ratesRec, batchRec]) => {
const rates = ratesRec.rates
const rate = rates.ask.plus(rates.bid).div(2)
const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance
const fiatConfirmedBalance = computeFiat(
rate,
cryptoCode,
fundingConfirmedBalance,
)
const pending = fundingRec.fundingPendingBalance.minus(batchRec)
const fiatPending = computeFiat(rate, cryptoCode, pending)
const fundingAddress = fundingRec.fundingAddress
const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress)
return {
return {
cryptoCode,
fundingAddress,
fundingAddressUrl,
confirmedBalance: computeCrypto(
cryptoCode,
fundingAddress,
fundingAddressUrl,
confirmedBalance: computeCrypto(cryptoCode, fundingConfirmedBalance).toFormat(5),
pending: computeCrypto(cryptoCode, pending).toFormat(5),
fiatConfirmedBalance: fiatConfirmedBalance,
fiatPending: fiatPending,
fiatCode
}
})
fundingConfirmedBalance,
).toFormat(5),
pending: computeCrypto(cryptoCode, pending).toFormat(5),
fiatConfirmedBalance: fiatConfirmedBalance,
fiatPending: fiatPending,
fiatCode,
}
})
}
// Promise.allSettled not running on current version of node
const reflect = p => p.then(value => ({ value, status: 'fulfilled' }), error => ({ error: error.toString(), status: 'rejected' }))
const reflect = p =>
p.then(
value => ({ value, status: 'fulfilled' }),
error => ({ error: error.toString(), status: 'rejected' }),
)
function getFunding () {
function getFunding() {
return settingsLoader.loadLatest().then(settings => {
const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config)
const fiatCode = configManager.getGlobalLocale(settings.config).fiatCurrency
@ -63,12 +73,15 @@ function getFunding () {
const cryptoCurrencies = coinUtils.cryptoCurrencies()
const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies)
const promises = cryptoDisplays.map(it => getSingleCoinFunding(settings, fiatCode, it.cryptoCode))
return Promise.all(promises.map(reflect))
.then((response) => {
const mapped = response.map(it => _.merge({ errorMsg: it.error }, it.value))
return _.toArray(_.merge(mapped, cryptoDisplays))
})
const promises = cryptoDisplays.map(it =>
getSingleCoinFunding(settings, fiatCode, it.cryptoCode),
)
return Promise.all(promises.map(reflect)).then(response => {
const mapped = response.map(it =>
_.merge({ errorMsg: it.error }, it.value),
)
return _.toArray(_.merge(mapped, cryptoDisplays))
})
})
}

View file

@ -1,16 +1,23 @@
const db = require('../../db')
function validateUser (username, password) {
function validateUser(username, password) {
return db.tx(t => {
const q1 = t.one('SELECT * FROM users WHERE username=$1 AND password=$2', [username, password])
const q2 = t.none('UPDATE users SET last_accessed = now() WHERE username=$1', [username])
const q1 = t.one('SELECT * FROM users WHERE username=$1 AND password=$2', [
username,
password,
])
const q2 = t.none(
'UPDATE users SET last_accessed = now() WHERE username=$1',
[username],
)
return t.batch([q1, q2])
return t
.batch([q1, q2])
.then(([user]) => user)
.catch(() => false)
})
}
module.exports = {
validateUser
validateUser,
}

View file

@ -1,19 +1,26 @@
const machineLoader = require('../../machine-loader')
const { UserInputError } = require('../graphql/errors')
function getMachine (machineId) {
return machineLoader.getMachines()
function getMachine(machineId) {
return machineLoader
.getMachines()
.then(machines => machines.find(({ deviceId }) => deviceId === machineId))
}
function machineAction ({ deviceId, action, cashUnits, newName }, context) {
function machineAction({ deviceId, action, cashUnits, newName }, context) {
const operatorId = context.res.locals.operatorId
return getMachine(deviceId)
.then(machine => {
if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
if (!machine)
throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
return machine
})
.then(machineLoader.setMachine({ deviceId, action, cashUnits, newName }, operatorId))
.then(
machineLoader.setMachine(
{ deviceId, action, cashUnits, newName },
operatorId,
),
)
.then(getMachine(deviceId))
}

View file

@ -3,7 +3,6 @@ const pify = require('pify')
const readFile = pify(fs.readFile)
const crypto = require('crypto')
const baseX = require('base-x')
const { parse, NIL } = require('uuid')
const db = require('../../db')
const pairing = require('../../pairing')
@ -16,19 +15,23 @@ const HOSTNAME = process.env.HOSTNAME
const unpair = pairing.unpair
function totem (name) {
return readFile(CA_PATH)
.then(data => {
const caHash = crypto.createHash('sha256').update(data).digest()
const token = crypto.randomBytes(32)
const hexToken = token.toString('hex')
const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex')
const buf = Buffer.concat([caHash, token, Buffer.from(HOSTNAME)])
const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)'
function totem(name) {
return readFile(CA_PATH).then(data => {
const caHash = crypto.createHash('sha256').update(data).digest()
const token = crypto.randomBytes(32)
const hexToken = token.toString('hex')
const caHexToken = crypto
.createHash('sha256')
.update(hexToken)
.digest('hex')
const buf = Buffer.concat([caHash, token, Buffer.from(HOSTNAME)])
const sql =
'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)'
return db.none(sql, [hexToken, caHexToken, name])
.then(() => bsAlpha.encode(buf))
})
return db
.none(sql, [hexToken, caHexToken, name])
.then(() => bsAlpha.encode(buf))
})
}
module.exports = { totem, unpair }

View file

@ -1,16 +1,21 @@
const _ = require('lodash/fp')
const uuid = require('uuid')
const db = require('../../db')
function getServerLogs (from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0) {
function getServerLogs(
from = new Date(0).toISOString(),
until = new Date().toISOString(),
limit = null,
offset = 0,
) {
const sql = `select id, log_level, timestamp, message from server_logs
where timestamp >= $1 and timestamp <= $2
order by timestamp desc
limit $3
offset $4`
return db.any(sql, [ from, until, limit, offset ])
return db
.any(sql, [from, until, limit, offset])
.then(_.map(_.mapKeys(_.camelCase)))
}

View file

@ -6,8 +6,8 @@ const { promisify } = require('util')
// [inet_http_server]
// port = 127.0.0.1:9001
function getAllProcessInfo () {
const convertStates = (state) => {
function getAllProcessInfo() {
const convertStates = state => {
// From http://supervisord.org/subprocess.html#process-states
switch (state) {
case 'STOPPED':
@ -33,28 +33,30 @@ function getAllProcessInfo () {
const client = xmlrpc.createClient({
host: 'localhost',
port: '9001',
path: '/RPC2'
path: '/RPC2',
})
client.methodCall[promisify.custom] = (method, params) => {
return new Promise((resolve, reject) => client.methodCall(method, params, (err, value) => {
if (err) reject(err)
else resolve(value)
}))
return new Promise((resolve, reject) =>
client.methodCall(method, params, (err, value) => {
if (err) reject(err)
else resolve(value)
}),
)
}
return promisify(client.methodCall)('supervisor.getAllProcessInfo', [])
.then((value) => {
return value.map(process => (
{
name: process.name,
state: convertStates(process.statename),
uptime: (process.statename === 'RUNNING') ? process.now - process.start : 0
}
))
.then(value => {
return value.map(process => ({
name: process.name,
state: convertStates(process.statename),
uptime:
process.statename === 'RUNNING' ? process.now - process.start : 0,
}))
})
.catch((error) => {
if (error.code === 'ECONNREFUSED') logger.error('Failed to connect to supervisord HTTP server.')
.catch(error => {
if (error.code === 'ECONNREFUSED')
logger.error('Failed to connect to supervisord HTTP server.')
else logger.error(error)
})
}

View file

@ -4,14 +4,16 @@ const pgp = require('pg-promise')()
const db = require('../../db')
const BN = require('../../bn')
const { utils: coinUtils } = require('@lamassu/coins')
const machineLoader = require('../../machine-loader')
const tx = require('../../tx')
const cashInTx = require('../../cash-in/cash-in-tx')
const { REDEEMABLE_AGE, CASH_OUT_TRANSACTION_STATES } = require('../../cash-out/cash-out-helper')
const {
REDEEMABLE_AGE,
CASH_OUT_TRANSACTION_STATES,
} = require('../../cash-out/cash-out-helper')
const NUM_RESULTS = 1000
function addProfits (txs) {
function addProfits(txs) {
return _.map(it => {
const profit = getProfit(it).toString()
return _.set('profit', profit, it)
@ -28,7 +30,7 @@ const DEVICE_NAME_QUERY = `
END AS machine_name
`
const DEVICE_NAME_JOINS = `
const DEVICE_NAME_JOINS = `
LEFT JOIN devices d ON txs.device_id = d.device_id
LEFT JOIN (
SELECT device_id, name, unpaired, paired
@ -38,7 +40,7 @@ const DEVICE_NAME_QUERY = `
AND (txs.created >= ud.paired)
`
function batch (
function batch(
from = new Date(0).toISOString(),
until = new Date().toISOString(),
limit = null,
@ -52,20 +54,19 @@ function batch (
status = null,
swept = null,
excludeTestingCustomers = false,
simplified
simplified,
) {
const isCsvExport = _.isBoolean(simplified)
const packager = _.flow(
_.flatten,
_.orderBy(_.property('created'), ['desc']),
_.map(_.flow(
camelize,
_.mapKeys(k =>
k == 'cashInFee' ? 'fixedFee' :
k
)
)),
addProfits
_.map(
_.flow(
camelize,
_.mapKeys(k => (k == 'cashInFee' ? 'fixedFee' : k)),
),
),
addProfits,
)
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
@ -138,46 +139,164 @@ function batch (
let promises
if (hasCashInOnlyFilters && hasCashOutOnlyFilters) {
throw new Error('Trying to filter transactions with mutually exclusive filters')
throw new Error(
'Trying to filter transactions with mutually exclusive filters',
)
}
if (hasCashInOnlyFilters) {
promises = [db.any(cashInSql, [cashInTx.PENDING_INTERVAL, from, until, limit, offset, txClass, deviceId, customerName, fiatCode, cryptoCode, toAddress, status])]
promises = [
db.any(cashInSql, [
cashInTx.PENDING_INTERVAL,
from,
until,
limit,
offset,
txClass,
deviceId,
customerName,
fiatCode,
cryptoCode,
toAddress,
status,
]),
]
} else if (hasCashOutOnlyFilters) {
promises = [db.any(cashOutSql, [REDEEMABLE_AGE, from, until, limit, offset, txClass, deviceId, customerName, fiatCode, cryptoCode, toAddress, status, swept])]
promises = [
db.any(cashOutSql, [
REDEEMABLE_AGE,
from,
until,
limit,
offset,
txClass,
deviceId,
customerName,
fiatCode,
cryptoCode,
toAddress,
status,
swept,
]),
]
} else {
promises = [
db.any(cashInSql, [cashInTx.PENDING_INTERVAL, from, until, limit, offset, txClass, deviceId, customerName, fiatCode, cryptoCode, toAddress, status]),
db.any(cashOutSql, [REDEEMABLE_AGE, from, until, limit, offset, txClass, deviceId, customerName, fiatCode, cryptoCode, toAddress, status, swept])
db.any(cashInSql, [
cashInTx.PENDING_INTERVAL,
from,
until,
limit,
offset,
txClass,
deviceId,
customerName,
fiatCode,
cryptoCode,
toAddress,
status,
]),
db.any(cashOutSql, [
REDEEMABLE_AGE,
from,
until,
limit,
offset,
txClass,
deviceId,
customerName,
fiatCode,
cryptoCode,
toAddress,
status,
swept,
]),
]
}
return Promise.all(promises)
.then(packager)
.then(res =>
!isCsvExport ? res :
// GQL transactions and transactionsCsv both use this function and
// if we don't check for the correct simplified value, the Transactions page polling
// will continuously build a csv in the background
simplified ? simplifiedBatch(res) :
advancedBatch(res)
!isCsvExport
? res
: // GQL transactions and transactionsCsv both use this function and
// if we don't check for the correct simplified value, the Transactions page polling
// will continuously build a csv in the background
simplified
? simplifiedBatch(res)
: advancedBatch(res),
)
}
function advancedBatch (data) {
const fields = ['txClass', 'id', 'deviceId', 'toAddress', 'cryptoAtoms',
'cryptoCode', 'fiat', 'fiatCode', 'fee', 'status', 'fiatProfit', 'cryptoAmount',
'dispense', 'notified', 'redeem', 'phone', 'error', 'fixedFee',
'created', 'confirmedAt', 'hdIndex', 'swept', 'timedout',
'dispenseConfirmed', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4',
'provisionedRecycler1', 'provisionedRecycler2', 'provisionedRecycler3', 'provisionedRecycler4', 'provisionedRecycler5', 'provisionedRecycler6',
'denomination1', 'denomination2', 'denomination3', 'denomination4',
'denominationRecycler1', 'denominationRecycler2', 'denominationRecycler3', 'denominationRecycler4', 'denominationRecycler5', 'denominationRecycler6',
'errorCode', 'customerId', 'txVersion', 'publishedAt', 'termsAccepted', 'layer2Address',
'commissionPercentage', 'rawTickerPrice', 'receivedCryptoAtoms',
'discount', 'txHash', 'customerPhone', 'customerEmail', 'customerIdCardDataNumber',
'customerIdCardDataExpiration', 'customerIdCardData', 'customerName', 'sendTime',
'customerFrontCameraPath', 'customerIdCardPhotoPath', 'expired', 'machineName', 'walletScore']
function advancedBatch(data) {
const fields = [
'txClass',
'id',
'deviceId',
'toAddress',
'cryptoAtoms',
'cryptoCode',
'fiat',
'fiatCode',
'fee',
'status',
'fiatProfit',
'cryptoAmount',
'dispense',
'notified',
'redeem',
'phone',
'error',
'fixedFee',
'created',
'confirmedAt',
'hdIndex',
'swept',
'timedout',
'dispenseConfirmed',
'provisioned1',
'provisioned2',
'provisioned3',
'provisioned4',
'provisionedRecycler1',
'provisionedRecycler2',
'provisionedRecycler3',
'provisionedRecycler4',
'provisionedRecycler5',
'provisionedRecycler6',
'denomination1',
'denomination2',
'denomination3',
'denomination4',
'denominationRecycler1',
'denominationRecycler2',
'denominationRecycler3',
'denominationRecycler4',
'denominationRecycler5',
'denominationRecycler6',
'errorCode',
'customerId',
'txVersion',
'publishedAt',
'termsAccepted',
'layer2Address',
'commissionPercentage',
'rawTickerPrice',
'receivedCryptoAtoms',
'discount',
'txHash',
'customerPhone',
'customerEmail',
'customerIdCardDataNumber',
'customerIdCardDataExpiration',
'customerIdCardData',
'customerName',
'sendTime',
'customerFrontCameraPath',
'customerIdCardPhotoPath',
'expired',
'machineName',
'walletScore',
]
const addAdvancedFields = _.map(it => ({
...it,
@ -191,28 +310,48 @@ function advancedBatch (data) {
return _.compose(_.map(_.pick(fields)), addAdvancedFields)(data)
}
function simplifiedBatch (data) {
const fields = ['txClass', 'id', 'created', 'machineName', 'fee',
'cryptoCode', 'cryptoAtoms', 'fiat', 'fiatCode', 'phone', 'email', 'toAddress',
'txHash', 'dispense', 'error', 'status', 'fiatProfit', 'cryptoAmount']
function simplifiedBatch(data) {
const fields = [
'txClass',
'id',
'created',
'machineName',
'fee',
'cryptoCode',
'cryptoAtoms',
'fiat',
'fiatCode',
'phone',
'email',
'toAddress',
'txHash',
'dispense',
'error',
'status',
'fiatProfit',
'cryptoAmount',
]
const addSimplifiedFields = _.map(it => ({
...it,
status: getStatus(it),
fiatProfit: getProfit(it).toString(),
cryptoAmount: getCryptoAmount(it).toString()
cryptoAmount: getCryptoAmount(it).toString(),
}))
return _.compose(_.map(_.pick(fields)), addSimplifiedFields)(data)
}
const getCryptoAmount = it => coinUtils.toUnit(BN(it.cryptoAtoms), it.cryptoCode)
const getCryptoAmount = it =>
coinUtils.toUnit(BN(it.cryptoAtoms), it.cryptoCode)
const getProfit = it => {
/* fiat - crypto*tickerPrice */
const calcCashInProfit = (fiat, crypto, tickerPrice) => fiat.minus(crypto.times(tickerPrice))
const calcCashInProfit = (fiat, crypto, tickerPrice) =>
fiat.minus(crypto.times(tickerPrice))
/* crypto*tickerPrice - fiat */
const calcCashOutProfit = (fiat, crypto, tickerPrice) => crypto.times(tickerPrice).minus(fiat)
const calcCashOutProfit = (fiat, crypto, tickerPrice) =>
crypto.times(tickerPrice).minus(fiat)
const fiat = BN(it.fiat)
const crypto = getCryptoAmount(it)
@ -247,10 +386,15 @@ const getStatus = it => {
return getCashInStatus(it)
}
function getCustomerTransactionsBatch (ids) {
const packager = _.flow(it => {
return it
}, _.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize))
function getCustomerTransactionsBatch(ids) {
const packager = _.flow(
it => {
return it
},
_.flatten,
_.orderBy(_.property('created'), ['desc']),
_.map(camelize),
)
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
c.phone AS customer_phone,
@ -292,16 +436,25 @@ function getCustomerTransactionsBatch (ids) {
WHERE c.id IN ($1^)
ORDER BY created DESC limit $2`
return Promise.all([
db.any(cashInSql, [_.map(pgp.as.text, ids).join(','), cashInTx.PENDING_INTERVAL, NUM_RESULTS]),
db.any(cashOutSql, [_.map(pgp.as.text, ids).join(','), NUM_RESULTS, REDEEMABLE_AGE])
db.any(cashInSql, [
_.map(pgp.as.text, ids).join(','),
cashInTx.PENDING_INTERVAL,
NUM_RESULTS,
]),
db.any(cashOutSql, [
_.map(pgp.as.text, ids).join(','),
NUM_RESULTS,
REDEEMABLE_AGE,
]),
])
.then(packager).then(transactions => {
.then(packager)
.then(transactions => {
const transactionMap = _.groupBy('customerId', transactions)
return ids.map(id => transactionMap[id])
})
}
function single (txId) {
function single(txId) {
const packager = _.flow(_.compact, _.map(camelize))
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
@ -344,18 +497,17 @@ function single (txId) {
return Promise.all([
db.oneOrNone(cashInSql, [cashInTx.PENDING_INTERVAL, txId]),
db.oneOrNone(cashOutSql, [txId, REDEEMABLE_AGE])
db.oneOrNone(cashOutSql, [txId, REDEEMABLE_AGE]),
])
.then(packager)
.then(_.head)
}
function cancel (txId) {
return tx.cancel(txId)
.then(() => single(txId))
function cancel(txId) {
return tx.cancel(txId).then(() => single(txId))
}
function getTx (txId, txClass) {
function getTx(txId, txClass) {
const cashInSql = `select 'cashIn' as tx_class, txs.*,
((not txs.send_confirmed) and (txs.created <= now() - interval $1)) as expired
from cash_in_txs as txs
@ -372,7 +524,7 @@ function getTx (txId, txClass) {
: db.oneOrNone(cashOutSql, [txId, REDEEMABLE_AGE])
}
function getTxAssociatedData (txId, txClass) {
function getTxAssociatedData(txId, txClass) {
const billsSql = `select 'bills' as bills, b.* from bills b where cash_in_txs_id = $1`
const actionsSql = `select 'cash_out_actions' as cash_out_actions, actions.* from cash_out_actions actions where tx_id = $1`
@ -381,15 +533,27 @@ function getTxAssociatedData (txId, txClass) {
: db.manyOrNone(actionsSql, [txId])
}
function updateTxCustomerPhoto (customerId, txId, direction, data) {
function updateTxCustomerPhoto(customerId, txId, direction, data) {
const formattedData = _.mapKeys(_.snakeCase, data)
const cashInSql = 'UPDATE cash_in_txs SET tx_customer_photo_at = $1, tx_customer_photo_path = $2 WHERE customer_id=$3 AND id=$4'
const cashInSql =
'UPDATE cash_in_txs SET tx_customer_photo_at = $1, tx_customer_photo_path = $2 WHERE customer_id=$3 AND id=$4'
const cashOutSql = 'UPDATE cash_out_txs SET tx_customer_photo_at = $1, tx_customer_photo_path = $2 WHERE customer_id=$3 AND id=$4'
const cashOutSql =
'UPDATE cash_out_txs SET tx_customer_photo_at = $1, tx_customer_photo_path = $2 WHERE customer_id=$3 AND id=$4'
return direction === 'cashIn'
? db.oneOrNone(cashInSql, [formattedData.tx_customer_photo_at, formattedData.tx_customer_photo_path, customerId, txId])
: db.oneOrNone(cashOutSql, [formattedData.tx_customer_photo_at, formattedData.tx_customer_photo_path, customerId, txId])
? db.oneOrNone(cashInSql, [
formattedData.tx_customer_photo_at,
formattedData.tx_customer_photo_path,
customerId,
txId,
])
: db.oneOrNone(cashOutSql, [
formattedData.tx_customer_photo_at,
formattedData.tx_customer_photo_path,
customerId,
txId,
])
}
module.exports = {
@ -399,5 +563,5 @@ module.exports = {
getCustomerTransactionsBatch,
getTx,
getTxAssociatedData,
updateTxCustomerPhoto
updateTxCustomerPhoto,
}