diff --git a/.env.example b/.env.example index 095ebff9..0e149bba 100644 --- a/.env.example +++ b/.env.example @@ -8,8 +8,6 @@ MS_TO_TOKEN_EXPIRATION=4500000 SHOCK_ENCRYPTION_ECC=true CACHE_HEADERS_MANDATORY=true SHOCK_CACHE=true -# Use only if disabling LND encrypt phrase (security risk) -TRUSTED_KEYS=true # SSH Tunnel Provider LOCAL_TUNNEL_SERVER=https://tunnel.rip # Default content to your own seed server diff --git a/.vscode/settings.json b/.vscode/settings.json index aa72f34d..c6028c15 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,25 +6,78 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "cSpell.words": [ "acked", + "addinvoice", "Authing", + "channelbalance", "ciphertext", + "closechannel", + "closedchannels", + "Cltv", + "connectpeer", + "disconnectpeer", "eccrypto", "endregion", "ephem", "epriv", "Epub", + "estimatefee", + "estimateroutefee", + "exportallchanbackups", + "exportchanbackup", "falsey", + "forwardinghistory", + "getchaninfo", + "getinfo", + "getnetworkinfo", + "getnodeinfo", "GUNRPC", + "Healthz", + "initwall", "ISEA", + "keysend", + "kubernetes", + "listchannels", + "listinvoices", + "listpayments", + "listpeers", + "listunspent", + "lndchanbackups", "LNDRPC", "lndstreaming", + "lnrpc", + "lres", + "msgpack", + "newaddress", + "openchannel", + "otheruser", + "payreq", + "pendingchannels", + "preimage", "PUBKEY", + "qrcode", + "queryroute", "radata", "Reqs", + "resave", + "satoshis", + "sendcoins", + "sendmany", + "sendpayment", + "sendtoroute", + "serverhost", + "serverport", "shockping", "SHOCKWALLET", + "signmessage", "thenables", + "trackpayment", + "txid", + "unfollow", + "Unlocker", "unsubscription", - "uuidv" + "utxos", + "uuidv", + "verifymessage", + "walletbalance" ] } diff --git a/composers/windows-2network-alice/docker-compose.yml b/composers/windows-2network-alice/docker-compose.yml index 95c107df..d03ab95a 100644 --- a/composers/windows-2network-alice/docker-compose.yml +++ b/composers/windows-2network-alice/docker-compose.yml @@ -13,5 +13,4 @@ services: - 9835:9835 volumes: - C:\Users\boufn\.polar\networks\2\volumes\lnd\alice:/root/.lnd - environment: - TRUSTED_KEYS: 'false' \ No newline at end of file + \ No newline at end of file diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 2e8d1ca0..b1b8243a 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -22,7 +22,7 @@ const Getters = require('./getters') const Key = require('./key') const Utils = require('./utils') const SchemaManager = require('../../schema') -const LNDHealthMananger = require('../../../utils/lightningServices/errors') +const LNDHealthManager = require('../../../utils/lightningServices/errors') const { enrollContentTokens, selfContentToken } = require('../../seed') /// @@ -305,7 +305,7 @@ const setCurrentStreamInfo = (encryptedCurrentStreamInfo, user) => }) /** - * @typedef {object} SpontPaymentOptions + * @typedef {object} SpontaneousPaymentOptions * @prop {Common.Schema.OrderTargetType} type * @prop {string=} ackInfo */ @@ -320,7 +320,7 @@ const setCurrentStreamInfo = (encryptedCurrentStreamInfo, user) => * @param {number} amount * @param {string} memo * @param {number} feeLimit - * @param {SpontPaymentOptions} opts + * @param {SpontaneousPaymentOptions} opts * @throws {Error} If no response in less than 20 seconds from the recipient, or * lightning cannot find a route for the payment. * @returns {Promise} The payment's preimage. @@ -498,7 +498,7 @@ const sendSpontaneousPayment = async ( feeLimit, payment_request: orderResponse.response }) - const myLndPub = LNDHealthMananger.lndPub + const myLndPub = LNDHealthManager.lndPub if ( (opts.type !== 'contentReveal' && opts.type !== 'torrentSeed' && diff --git a/src/routes.js b/src/routes.js index e53c3356..b179f5f8 100644 --- a/src/routes.js +++ b/src/routes.js @@ -1,9 +1,10 @@ /** * @prettier */ +// @ts-check 'use strict' -const Axios = require('axios') +const Axios = require('axios').default const Crypto = require('crypto') const Storage = require('node-persist') const logger = require('../config/log') @@ -12,11 +13,11 @@ const responseTime = require('response-time') const uuid = require('uuid/v4') const Common = require('shock-common') const isARealUsableNumber = require('lodash/isFinite') -const Big = require('big.js') -const size = require('lodash/size') -const { range, flatten, evolve } = require('ramda') +const Big = require('big.js').default +const { evolve } = require('ramda') const path = require('path') const cors = require('cors') +const ECCrypto = require('eccrypto') const getListPage = require('../utils/paginate') const auth = require('../services/auth/auth') @@ -30,8 +31,6 @@ const { nonEncryptedRoutes } = require('../utils/protectedRoutes') const GunActions = require('../services/gunDB/contact-api/actions') -const GunGetters = require('../services/gunDB/contact-api/getters') -const GunKey = require('../services/gunDB/contact-api/key') const LV2 = require('../utils/lightningServices/v2') const GunWriteRPC = require('../services/gunDB/rpc') const Key = require('../services/gunDB/contact-api/key') @@ -45,11 +44,17 @@ const SESSION_ID = uuid() // module.exports = (app) => { module.exports = async ( - app, + _app, config, mySocketsEvents, - { serverPort, CA, CA_KEY, useTLS } + { serverPort, useTLS, CA, CA_KEY, runPrivateKey, runPublicKey, accessSecret } ) => { + /** + * @typedef {import('express').Application} Application + */ + + const app = /** @type {Application} */ (_app) + try { const Http = Axios.create({ httpsAgent: new httpsAgent.Agent( @@ -73,15 +78,78 @@ module.exports = async ( return message } + const unlockWallet = password => + new Promise((resolve, reject) => { + try { + const args = { + wallet_password: Buffer.from(password, 'utf-8') + } + const { walletUnlocker } = LightningServices.services + walletUnlocker.unlockWallet(args, (unlockErr, unlockResponse) => { + if (unlockErr) { + reject(unlockErr) + return + } + + resolve(unlockResponse) + }) + } catch (err) { + if (err.message === 'unknown service lnrpc.WalletUnlocker') { + resolve({ + field: 'walletUnlocker', + message: 'Wallet already unlocked' + }) + return + } + + logger.error('Unlock Error:', err) + + reject({ + field: 'wallet', + code: err.code, + message: sanitizeLNDError(err.message) + }) + } + }) + const getAvailableService = () => { return lndErrorManager.getAvailableService() } + // Hack to check whether or not a wallet exists + const walletExists = async () => { + try { + const availableService = await getAvailableService() + if (availableService.service === 'lightning') { + return true + } + + if (availableService.service === 'walletUnlocker') { + const randomPassword = Crypto.randomBytes(4).toString('hex') + try { + await unlockWallet(randomPassword) + return true + } catch (err) { + if (err.message.indexOf('invalid passphrase') > -1) { + return true + } + return false + } + } + } catch (err) { + logger.error('LND error:', err) + return false + } + } + const checkHealth = async () => { let LNDStatus = {} try { const serviceStatus = await getAvailableService() - LNDStatus = serviceStatus + LNDStatus = { + ...serviceStatus, + walletExists: await walletExists() + } } catch (e) { LNDStatus = { message: e.message, @@ -96,7 +164,8 @@ module.exports = async ( const APIStatus = { message: APIHealth.data, responseTime: APIHealth.headers['x-response-time'], - success: true + success: true, + encryptionPublicKey: runPublicKey.toString('base64') } return { LNDStatus, @@ -108,7 +177,8 @@ module.exports = async ( const APIStatus = { message: err?.response?.data, responseTime: err?.response?.headers['x-response-time'], - success: false + success: false, + encryptionPublicKey: runPublicKey.toString('base64') } logger.warn('Failed to retrieve API status', APIStatus) return { @@ -155,66 +225,6 @@ module.exports = async ( next() } - const unlockWallet = password => - new Promise((resolve, reject) => { - try { - const args = { - wallet_password: Buffer.from(password, 'utf-8') - } - const { walletUnlocker } = LightningServices.services - walletUnlocker.unlockWallet(args, (unlockErr, unlockResponse) => { - if (unlockErr) { - reject(unlockErr) - return - } - - resolve(unlockResponse) - }) - } catch (err) { - if (err.message === 'unknown service lnrpc.WalletUnlocker') { - resolve({ - field: 'walletUnlocker', - message: 'Wallet already unlocked' - }) - return - } - - logger.error('Unlock Error:', err) - - reject({ - field: 'wallet', - code: err.code, - message: sanitizeLNDError(err.message) - }) - } - }) - - // Hack to check whether or not a wallet exists - const walletExists = async () => { - try { - const availableService = await getAvailableService() - if (availableService.service === 'lightning') { - return true - } - - if (availableService.service === 'walletUnlocker') { - const randomPassword = Crypto.randomBytes(4).toString('hex') - try { - await unlockWallet(randomPassword) - return true - } catch (err) { - if (err.message.indexOf('invalid passphrase') > -1) { - return true - } - return false - } - } - } catch (err) { - logger.error('LND error:', err) - return false - } - } - app.use( cors({ credentials: true, @@ -237,7 +247,7 @@ module.exports = async ( return next() } - if (!deviceId) { + if (typeof deviceId !== 'string' || !deviceId) { const error = { field: 'deviceId', message: 'Please specify a device ID' @@ -266,13 +276,15 @@ module.exports = async ( logger.info('Decrypting ECC message...') - const decryptedMessage = await ECC.decryptMessage({ - deviceId, - encryptedMessage: req.body - }) + const asBuffers = await ECC.convertToEncryptedMessage(req.body) + + const decryptedMessage = await ECCrypto.decrypt( + runPrivateKey, + asBuffers + ) // eslint-disable-next-line - req.body = JSON.parse(decryptedMessage) + req.body = JSON.parse(decryptedMessage.toString('utf8')) return next() } catch (err) { @@ -301,7 +313,7 @@ module.exports = async ( } else { try { const response = await auth.validateToken( - req.headers.authorization.replace('Bearer ', '') + (req.headers.authorization || '').replace('Bearer ', '') ) if (response.valid) { next() @@ -442,12 +454,28 @@ module.exports = async ( app.post('/api/encryption/exchange', async (req, res) => { try { - const { publicKey, deviceId } = req.body + let { publicKey, deviceId } = req.body - if (!publicKey) { - return res.status(400).json({ + if (Buffer.isBuffer(accessSecret)) { + logger.info('Will decrypt public key and device ID for key exchange.') + + publicKey = await ECCrypto.decrypt( + accessSecret, + ECC.convertToEncryptedMessage(publicKey) + ) + deviceId = await ECCrypto.decrypt( + accessSecret, + ECC.convertToEncryptedMessage(deviceId) + ) + + publicKey = publicKey.toString('utf8') + deviceId = deviceId.toString('utf8') + } + + if (typeof publicKey !== 'string' || !publicKey) { + return res.status(500).json({ field: 'publicKey', - message: 'Please provide a valid public key' + errorMessage: 'Please provide a valid public key' }) } @@ -457,22 +485,23 @@ module.exports = async ( deviceId ) ) { - return res.status(400).json({ + return res.status(500).json({ field: 'deviceId', - message: 'Please provide a valid device ID' + errorMessage: 'Please provide a valid device ID' }) } - const authorizedDevice = await ECC.authorizeDevice({ + await ECC.authorizeDevice({ deviceId, publicKey }) - return res.json(authorizedDevice) + res.sendStatus(200) } catch (err) { logger.error(err) return res.status(401).json({ field: 'unknown', - message: err + message: err, + errorMessage: err.message || err }) } }) @@ -507,240 +536,194 @@ module.exports = async ( } } + /** + * Get the latest channel backups before subscribing. + */ + const saveChannelsBackup = async () => { + const { getUser } = require('../services/gunDB/Mediator') + const { lightning } = LightningServices.services + const SEA = require('../services/gunDB/Mediator').mySEA + await Common.Utils.makePromise((res, rej) => { + lightning.exportAllChannelBackups({}, (err, channelBackups) => { + if (err) { + return rej(new Error(err.details)) + } + + res( + GunActions.saveChannelsBackup( + JSON.stringify(channelBackups), + getUser(), + SEA + ) + ) + }) + }) + } + + /** + * Register to listen for channel backups. + */ + const onNewChannelBackup = () => { + const { getUser } = require('../services/gunDB/Mediator') + const { lightning } = LightningServices.services + const SEA = require('../services/gunDB/Mediator').mySEA + + logger.warn('Subscribing to channel backup ...') + + const stream = lightning.SubscribeChannelBackups({}) + + stream.on('data', data => { + logger.info(' New channel backup data') + GunActions.saveChannelsBackup(JSON.stringify(data), getUser(), SEA) + }) + stream.on('end', () => { + logger.info('Channel backup stream ended, starting a new one...') + // Prevents call stack overflow exceptions + //process.nextTick(onNewChannelBackup) + }) + stream.on('error', err => { + logger.error('Channel backup stream error:', err) + }) + stream.on('status', status => { + logger.error('Channel backup stream status:', status) + switch (status.code) { + case 0: { + logger.info('Channel backup stream ok') + break + } + case 2: { + //Happens to fire when the grpc client lose access to macaroon file + logger.warn('Channel backup got UNKNOWN error status') + break + } + case 12: { + logger.warn( + 'Channel backup LND locked, new registration in 60 seconds' + ) + process.nextTick(() => + setTimeout(() => onNewChannelBackup(), 60000) + ) + break + } + case 13: { + //https://grpc.github.io/grpc/core/md_doc_statuscodes.html + logger.error('Channel backup INTERNAL LND error') + break + } + case 14: { + logger.error( + 'Channel backup LND disconnected, sockets reconnecting in 30 seconds...' + ) + process.nextTick(() => + setTimeout(() => onNewChannelBackup(), 30000) + ) + break + } + default: { + logger.error('[event:transaction:new] UNKNOWN LND error') + } + } + }) + } + app.post('/api/lnd/auth', async (req, res) => { try { const health = await checkHealth() const walletInitialized = await walletExists() - // If we're connected to lnd, unlock the wallet using the password supplied - // and generate an auth token if that operation was successful. - if (health.LNDStatus.success && walletInitialized) { - const { alias, password, invite, accessSecret } = req.body + const { alias, pass } = req.body + const lndUp = health.LNDStatus.success + const walletUnlocked = health.LNDStatus.walletStatus === 'unlocked' + const { authorization = '' } = req.headers + const allowUnlockedLND = process.env.ALLOW_UNLOCKED_LND === 'true' + const { lightning } = LightningServices.services - await recreateLnServices() - - if (GunDB.isAuthenticated()) { - GunDB.logoff() - } - - const publicKey = await GunDB.authenticate(alias, password) - - if (!publicKey) { - res.status(401).json({ - field: 'alias', - errorMessage: 'Invalid alias/password combination', - success: false - }) - return false - } - - const trustedKeysEnabled = - process.env.TRUSTED_KEYS === 'true' || !process.env.TRUSTED_KEYS - const trustedKeys = await Storage.get('trustedPKs') - // Falls back to true if trusted keys is disabled in .env - const [isKeyTrusted = !trustedKeysEnabled] = ( - trustedKeys || [] - ).filter(trustedKey => trustedKey === publicKey) - const walletUnlocked = health.LNDStatus.walletStatus === 'unlocked' - const { authorization = '' } = req.headers - - if (!isKeyTrusted) { - logger.warn('Untrusted public key!') - } - - if (!walletUnlocked) { - await unlockWallet(password) - } - let secretUsed = null - if (accessSecret) { - secretUsed = await Storage.get( - `UnlockedAccessSecrets/${accessSecret}` - ) - } - if ( - walletUnlocked && - !authorization && - !isKeyTrusted && - (process.env.ALLOW_UNLOCKED_LND !== 'true' || secretUsed !== false) - ) { - res.status(401).json({ - field: 'alias', - errorMessage: - 'Invalid alias/password combination (Untrusted Device)', - success: false - }) - return - } - - if ( - walletUnlocked && - !isKeyTrusted && - (process.env.ALLOW_UNLOCKED_LND !== 'true' || secretUsed !== false) - ) { - const validatedToken = await validateToken( - authorization.replace('Bearer ', '') - ) - - if (!validatedToken) { - res.status(401).json({ - field: 'alias', - errorMessage: - 'Invalid alias/password combination (Untrusted Auth Token)', - success: false - }) - return - } - } - - if (secretUsed === false) { - await Storage.setItem(`UnlockedAccessSecrets/${accessSecret}`, true) - } - - if (!isKeyTrusted) { - await Storage.set('trustedPKs', [...(trustedKeys || []), publicKey]) - } - - const { lightning } = LightningServices.services - - // Generate auth token and send it as a JSON response - const token = await auth.generateToken() - - // wait for wallet to warm up - await Common.Utils.makePromise((res, rej) => { - let tries = 0 - let intervalID = null - - intervalID = setInterval(() => { - if (tries === 7) { - rej(new Error(`Wallet did not warm up in under 7 seconds.`)) - - clearInterval(intervalID) - return - } - - tries++ - - lightning.listInvoices({}, err => { - if (!err) { - clearInterval(intervalID) - res() - } - }) - }, 1000) - }) - - //get the latest channel backups before subscribing - const user = require('../services/gunDB/Mediator').getUser() - const SEA = require('../services/gunDB/Mediator').mySEA - - await Common.Utils.makePromise((res, rej) => { - lightning.exportAllChannelBackups({}, (err, channelBackups) => { - if (err) { - return rej(new Error(err.details)) - } - - res( - GunActions.saveChannelsBackup( - JSON.stringify(channelBackups), - user, - SEA - ) - ) - }) - }) - - // Send an event to update lightning's status - mySocketsEvents.emit('updateLightning') - - //register to listen for channel backups - const onNewChannelBackup = () => { - logger.warn('Subscribing to channel backup ...') - const stream = lightning.SubscribeChannelBackups({}) - stream.on('data', data => { - logger.info(' New channel backup data') - GunActions.saveChannelsBackup(JSON.stringify(data), user, SEA) - }) - stream.on('end', () => { - logger.info('Channel backup stream ended, starting a new one...') - // Prevents call stack overflow exceptions - //process.nextTick(onNewChannelBackup) - }) - stream.on('error', err => { - logger.error('Channel backup stream error:', err) - }) - stream.on('status', status => { - logger.error('Channel backup stream status:', status) - switch (status.code) { - case 0: { - logger.info('Channel backup stream ok') - break - } - case 2: { - //Happens to fire when the grpc client lose access to macaroon file - logger.warn('Channel backup got UNKNOWN error status') - break - } - case 12: { - logger.warn( - 'Channel backup LND locked, new registration in 60 seconds' - ) - process.nextTick(() => - setTimeout(() => onNewChannelBackup(), 60000) - ) - break - } - case 13: { - //https://grpc.github.io/grpc/core/md_doc_statuscodes.html - logger.error('Channel backup INTERNAL LND error') - break - } - case 14: { - logger.error( - 'Channel backup LND disconnected, sockets reconnecting in 30 seconds...' - ) - process.nextTick(() => - setTimeout(() => onNewChannelBackup(), 30000) - ) - break - } - default: { - logger.error('[event:transaction:new] UNKNOWN LND error') - } - } - }) - } - - onNewChannelBackup() - - setTimeout(() => { - channelRequest(invite) - }, 30 * 1000) - res.json({ - authorization: token, - user: { - alias, - publicKey - } - }) - - return true + if (!lndUp) { + throw new Error(health.LNDStatus.message) } if (!walletInitialized) { - res.status(500).json({ - field: 'wallet', - errorMessage: 'Please create a wallet before authenticating', - success: false - }) - return false + throw new Error('Please create a wallet before authenticating') } - res.status(500) - res.json({ - field: 'health', - errorMessage: sanitizeLNDError(health.LNDStatus.message), - success: false + await recreateLnServices() + + if (GunDB.isAuthenticated()) { + GunDB.logoff() + } + + const publicKey = await GunDB.authenticate(alias, pass) + + if (!publicKey) { + throw new Error('Invalid alias/password combination') + } + + if (!walletUnlocked) { + await unlockWallet(pass) + } + + if (walletUnlocked && !authorization && !allowUnlockedLND) { + throw new Error( + 'Invalid alias/password combination (Untrusted Device)' + ) + } + + if (walletUnlocked && !allowUnlockedLND) { + const validatedToken = await validateToken( + authorization.replace('Bearer ', '') + ) + + if (!validatedToken) { + throw new Error( + 'Invalid alias/password combination (Untrusted Auth Token)' + ) + } + } + + // Generate auth token and send it as a JSON response + const token = await auth.generateToken() + + // wait for wallet to warm up + await Common.Utils.makePromise((res, rej) => { + let tries = 0 + let intervalID = null + + intervalID = setInterval(() => { + if (tries === 7) { + rej(new Error(`Wallet did not warm up in under 7 seconds.`)) + + clearInterval(intervalID) + return + } + + tries++ + + lightning.listInvoices({}, err => { + if (!err) { + clearInterval(intervalID) + res() + } + }) + }, 1000) + }) + + saveChannelsBackup() + + // Send an event to update lightning's status + mySocketsEvents.emit('updateLightning') + + onNewChannelBackup() + + setTimeout(() => { + channelRequest() + }, 30 * 1000) + + res.json({ + authorization: token, + user: { + alias, + publicKey + } }) - return false } catch (err) { logger.error('Unlock Error:', err) res.status(400) @@ -756,8 +739,10 @@ module.exports = async ( app.post('/api/lnd/wallet', async (req, res) => { try { const { walletUnlocker } = LightningServices.services - const { password, alias, invite } = req.body + const { password, alias } = req.body const healthResponse = await checkHealth() + const isUnlocked = healthResponse.LNDStatus.service !== 'walletUnlocker' + if (!alias) { return res.status(400).json({ field: 'alias', @@ -780,139 +765,108 @@ module.exports = async ( }) } - if (healthResponse.LNDStatus.service !== 'walletUnlocker') { - return res.status(400).json({ - field: 'wallet', - errorMessage: 'Wallet is already unlocked' - }) + if (isUnlocked) { + throw new Error('Wallet is already unlocked') } - walletUnlocker.genSeed({}, async (genSeedErr, genSeedResponse) => { - try { - if (genSeedErr) { - logger.debug('GenSeed Error:', genSeedErr) + const [genSeedErr, genSeedResponse] = await new Promise(res => { + walletUnlocker.genSeed({}, (_genSeedErr, _genSeedResponse) => { + res([_genSeedErr, _genSeedResponse]) + }) + }) - const healthResponse = await checkHealth() - if (healthResponse.LNDStatus.success) { - const message = genSeedErr.details - return res.status(400).json({ - field: 'GenSeed', - errorMessage: message, - success: false - }) + if (genSeedErr) { + logger.debug('GenSeed Error:', genSeedErr) + + const healthResponse = await checkHealth() + if (healthResponse.LNDStatus.success) { + const message = genSeedErr.details + throw new Error(message) + } + + throw new Error('LND is down') + } + + logger.debug('GenSeed:', genSeedResponse) + + const mnemonicPhrase = genSeedResponse.cipher_seed_mnemonic + const walletArgs = { + wallet_password: Buffer.from(password, 'utf8'), + cipher_seed_mnemonic: mnemonicPhrase + } + + // Register user before creating wallet + const publicKey = await GunDB.register(alias, password) + + await GunActions.saveSeedBackup( + mnemonicPhrase, + GunDB.getUser(), + GunDB.mySEA + ) + + const [initWalletErr, initWalletResponse] = await new Promise(res => { + walletUnlocker.initWallet( + walletArgs, + (_initWalletErr, _initWalletResponse) => { + res([_initWalletErr, _initWalletResponse]) + } + ) + }) + + if (initWalletErr) { + logger.error('initWallet Error:', initWalletErr.message) + const healthResponse = await checkHealth() + if (healthResponse.LNDStatus.success) { + const errorMessage = initWalletErr.details + + throw new Error(errorMessage) + } + throw new Error('LND is down') + } + + logger.info('initWallet:', initWalletResponse) + + const waitUntilFileExists = seconds => { + logger.info( + `Waiting for admin.macaroon to be created. Seconds passed: ${seconds} Path: ${LightningServices.servicesConfig.macaroonPath}` + ) + setTimeout(async () => { + try { + const macaroonExists = await FS.access( + LightningServices.servicesConfig.macaroonPath + ) + + if (!macaroonExists) { + return waitUntilFileExists(seconds + 1) } - return res.status(500).json({ - field: 'health', - errorMessage: 'LND is down', - success: false + logger.info('admin.macaroon file created') + + await LightningServices.init() + + const token = await auth.generateToken() + setTimeout(() => { + channelRequest() + }, 30 * 1000) + return res.json({ + mnemonicPhrase, + authorization: token, + user: { + alias, + publicKey + } + }) + } catch (err) { + logger.error(err) + res.status(500).json({ + field: 'unknown', + errorMessage: sanitizeLNDError(err.message) }) } + }, 1000) + } - logger.debug('GenSeed:', genSeedResponse) - const mnemonicPhrase = genSeedResponse.cipher_seed_mnemonic - const walletArgs = { - wallet_password: Buffer.from(password, 'utf8'), - cipher_seed_mnemonic: mnemonicPhrase - } - - // Register user before creating wallet - const publicKey = await GunDB.register(alias, password) - - await GunActions.saveSeedBackup( - mnemonicPhrase, - GunDB.getUser(), - GunDB.mySEA - ) - - const trustedKeys = await Storage.get('trustedPKs') - await Storage.setItem('trustedPKs', [ - ...(trustedKeys || []), - publicKey - ]) - - walletUnlocker.initWallet( - walletArgs, - async (initWalletErr, initWalletResponse) => { - try { - if (initWalletErr) { - logger.error('initWallet Error:', initWalletErr.message) - const healthResponse = await checkHealth() - if (healthResponse.LNDStatus.success) { - const errorMessage = initWalletErr.details - - return res.status(400).json({ - field: 'initWallet', - errorMessage, - success: false - }) - } - return res.status(500).json({ - field: 'health', - errorMessage: 'LND is down', - success: false - }) - } - logger.info('initWallet:', initWalletResponse) - - const waitUntilFileExists = seconds => { - logger.info( - `Waiting for admin.macaroon to be created. Seconds passed: ${seconds} Path: ${LightningServices.servicesConfig.macaroonPath}` - ) - setTimeout(async () => { - try { - const macaroonExists = await FS.access( - LightningServices.servicesConfig.macaroonPath - ) - - if (!macaroonExists) { - return waitUntilFileExists(seconds + 1) - } - - logger.info('admin.macaroon file created') - - await LightningServices.init() - - const token = await auth.generateToken() - setTimeout(() => { - channelRequest(invite) - }, 30 * 1000) - return res.json({ - mnemonicPhrase, - authorization: token, - user: { - alias, - publicKey - } - }) - } catch (err) { - logger.error(err) - res.status(400).json({ - field: 'unknown', - errorMessage: sanitizeLNDError(err.message) - }) - } - }, 1000) - } - - waitUntilFileExists(1) - } catch (err) { - logger.error(err) - return res.status(500).json({ - field: 'unknown', - errorMessage: err - }) - } - } - ) - } catch (err) { - logger.error(err) - return res.status(500).json({ - field: 'unknown', - errorMessage: err.message || err - }) - } - }) + waitUntilFileExists(1) } catch (err) { logger.error(err) return res.status(500).json({ @@ -924,14 +878,14 @@ module.exports = async ( app.post('/api/lnd/wallet/existing', async (req, res) => { try { - const { password, alias, invite, accessSecret } = req.body + const { password, alias } = req.body const healthResponse = await checkHealth() const exists = await walletExists() + const allowUnlockedLND = process.env.ALLOW_UNLOCKED_LND === 'true' + const isLocked = healthResponse.LNDStatus.service === 'walletUnlocker' + if (!exists) { - return res.status(500).json({ - field: 'wallet', - errorMessage: 'LND wallet does not exist, please create a new one' - }) + throw new Error('LND wallet does not exist, please create a new one') } if (!alias) { @@ -955,47 +909,28 @@ module.exports = async ( "Please specify a password that's longer than 8 characters" }) } - let secretUsed = null - if (accessSecret) { - secretUsed = await Storage.get( - `UnlockedAccessSecrets/${accessSecret}` + + if (!isLocked && !allowUnlockedLND) { + throw new Error( + 'Wallet is already unlocked. Please restart your LND instance and try again.' ) } - if ( - healthResponse.LNDStatus.service !== 'walletUnlocker' && - (process.env.ALLOW_UNLOCKED_LND !== 'true' || secretUsed !== false) - ) { - return res.status(400).json({ - field: 'wallet', - errorMessage: - 'Wallet is already unlocked. Please restart your LND instance and try again.' - }) - } - if (secretUsed === false) { - await Storage.setItem(`UnlockedAccessSecrets/${accessSecret}`, true) - } try { - if (healthResponse.LNDStatus.service === 'walletUnlocker') { + if (isLocked) { await unlockWallet(password) } - } catch (err) { - return res.status(401).json({ - field: 'wallet', - errorMessage: 'Invalid LND wallet password' - }) + } catch (_) { + throw new Error('Invalid LND wallet password') } // Register user after verifying wallet password const publicKey = await GunDB.register(alias, password) - const trustedKeys = await Storage.get('trustedPKs') - await Storage.setItem('trustedPKs', [...(trustedKeys || []), publicKey]) - // Generate Access Token const token = await auth.generateToken() setTimeout(() => { - channelRequest(invite) + channelRequest() }, 30 * 1000) res.json({ authorization: token, @@ -1193,6 +1128,12 @@ module.exports = async ( app.get('/api/lnd/unifiedTrx', (req, res) => { const { lightning } = LightningServices.services const { itemsPerPage, page, reversed = true } = req.query + if (typeof itemsPerPage !== 'number') { + throw new TypeError('itemsPerPage not a number') + } + if (typeof page !== 'number') { + throw new TypeError('page not a number') + } const offset = (page - 1) * itemsPerPage lightning.listPayments({}, (err, { payments = [] } = {}) => { if (err) { @@ -1307,8 +1248,15 @@ module.exports = async ( app.get('/api/lnd/listpayments', (req, res) => { const { lightning } = LightningServices.services const { itemsPerPage, page, paginate = true } = req.query + if (typeof itemsPerPage !== 'number') { + throw new TypeError('itemsPerPage not a number') + } + if (typeof page !== 'number') { + throw new TypeError('page not a number') + } lightning.listPayments( { + // TODO include_incomplete: !!req.include_incomplete }, (err, { payments = [] } = {}) => { @@ -1340,7 +1288,8 @@ module.exports = async ( max_payments: x => Number(x), reversed: x => x === 'true' }, - req.query + // TODO Validate + /** @type {any} */ (req.query) )) if (typeof include_incomplete !== 'boolean') { @@ -1385,11 +1334,23 @@ module.exports = async ( app.get('/api/lnd/listinvoices', (req, res) => { const { lightning } = LightningServices.services const { page, itemsPerPage, reversed = true } = req.query + if (typeof itemsPerPage !== 'number') { + throw new TypeError('itemsPerPage not a number') + } + if (typeof page !== 'number') { + throw new TypeError('page not a number') + } const offset = (page - 1) * itemsPerPage // const limit = page * itemsPerPage; lightning.listInvoices( { reversed, index_offset: offset, num_max_invoices: itemsPerPage }, - async (err, { invoices, last_index_offset } = {}) => { + async ( + err, + { invoices, last_index_offset } = { + invoices: [], + last_index_offset: 1 + } + ) => { if (err) { logger.debug('ListInvoices Error:', err) const health = await checkHealth() @@ -1963,6 +1924,12 @@ module.exports = async ( app.get('/api/lnd/transactions', (req, res) => { const { lightning } = LightningServices.services const { page, paginate = true, itemsPerPage } = req.query + if (typeof page !== 'number') { + throw new TypeError('page is not a number') + } + if (typeof itemsPerPage !== 'number') { + throw new TypeError('itemsPerPage is not a number') + } lightning.getTransactions({}, (err, { transactions = [] } = {}) => { if (err) { return handleError(res, err) @@ -1994,6 +1961,10 @@ module.exports = async ( app.get('/api/lnd/closedchannels', (req, res) => { const { lightning } = LightningServices.services const { closeTypeFilters = [] } = req.query + if (!Array.isArray(closeTypeFilters)) { + throw new TypeError('closeTypeFilters not an Array') + } + // @ts-expect-error I dunno what's going on here, all arrays have reduce() const lndFilters = closeTypeFilters.reduce( (filters, filter) => ({ ...filters, [filter]: true }), {} @@ -2033,8 +2004,6 @@ module.exports = async ( }) }) - const GunEvent = Common.Constants.Event - app.get('/api/gun/lndchanbackups', async (req, res) => { try { const user = require('../services/gunDB/Mediator').getUser() @@ -2042,6 +2011,11 @@ module.exports = async ( const SEA = require('../services/gunDB/Mediator').mySEA const mySecret = require('../services/gunDB/Mediator').getMySecret() const encBackup = await user.get(Key.CHANNELS_BACKUP).then() + if (typeof encBackup !== 'string') { + throw new TypeError( + 'Encrypted backup fetched from gun not an string.' + ) + } const backup = await SEA.decrypt(encBackup, mySecret) logger.info(backup) res.json({ data: backup }) @@ -2062,23 +2036,23 @@ module.exports = async ( feeLimit, sessionUuid } = req.body - logger.info('handling spont pay') + logger.info('handling spontaneous pay') if (!feeLimit) { logger.error( - 'please provide a "feeLimit" to the send spont payment request' + 'please provide a "feeLimit" to the send spontaneous payment request' ) return res.status(500).json({ errorMessage: - 'please provide a "feeLimit" to the send spont payment request' + 'please provide a "feeLimit" to the send spontaneous payment request' }) } if (!recipientPub || !amount) { logger.info( - 'please provide a "recipientPub" and "amount" to the send spont payment request' + 'please provide a "recipientPub" and "amount" to the send spontaneous payment request' ) return res.status(500).json({ errorMessage: - 'please provide a "recipientPub" and "amount" to the send spont payment request' + 'please provide a "recipientPub" and "amount" to the send spontaneous payment request' }) } try { @@ -2087,12 +2061,13 @@ module.exports = async ( amount, memo, feeLimit, + // TODO maxParts, timeoutSeconds ) res.json({ preimage, sessionUuid }) } catch (err) { - logger.info('spont pay err:', err) + logger.info('spontaneous pay err:', err) return res.status(500).json({ errorMessage: err.message }) @@ -2102,12 +2077,10 @@ module.exports = async ( app.post(`/api/gun/wall/`, async (req, res) => { try { const { tags, title, contentItems, enableTipsOverlay } = req.body - const SEA = require('../services/gunDB/Mediator').mySEA const postRes = await GunActions.createPostNew( tags, title, - contentItems, - SEA + contentItems ) if (enableTipsOverlay) { const [postID] = postRes @@ -2150,8 +2123,6 @@ module.exports = async ( * @typedef {import('express-serve-static-core').RequestHandler

} RequestHandler */ - const ap = /** @type {Application} */ (app) - /** * @typedef {object} FollowsRouteParams * @prop {(string|undefined)=} publicKey @@ -2167,14 +2138,14 @@ module.exports = async ( throw new Error(`Missing publicKey route param.`) } - await GunActions.follow(req.params.publicKey, false) + await GunActions.follow(publicKey, false) // 201 would be extraneous here. Implement it inside app.put - return res.status(200).json({ + res.status(200).json({ ok: true }) } catch (err) { - return res.status(500).json({ + res.status(500).json({ errorMessage: err.message || 'Unknown error inside /api/gun/follows/' }) } @@ -2190,19 +2161,19 @@ module.exports = async ( throw new Error(`Missing publicKey route param.`) } - await GunActions.unfollow(req.params.publicKey) + await GunActions.unfollow(publicKey) - return res.status(200).json({ + res.status(200).json({ ok: true }) } catch (err) { - return res.status(500).json({ + res.status(500).json({ errorMessage: err.message || 'Unknown error inside /api/gun/follows/' }) } } - ap.get('/api/gun/initwall', async (req, res) => { + app.get('/api/gun/initwall', async (req, res) => { try { await GunActions.initWall() res.json({ ok: true }) @@ -2213,8 +2184,8 @@ module.exports = async ( }) } }) - ap.put(`/api/gun/follows/:publicKey`, apiGunFollowsPut) - ap.delete(`/api/gun/follows/:publicKey`, apiGunFollowsDelete) + app.put(`/api/gun/follows/:publicKey`, apiGunFollowsPut) + app.delete(`/api/gun/follows/:publicKey`, apiGunFollowsDelete) /** * @type {RequestHandler<{}>} @@ -2258,20 +2229,20 @@ module.exports = async ( await GunActions.generateHandshakeAddress() } - return res.status(200).json({ + res.status(200).json({ ok: true }) } catch (err) { logger.error(err) - return res.status(500).json({ + res.status(500).json({ errorMessage: err.message }) } } - ap.put(`/api/gun/me`, apiGunMePut) + app.put(`/api/gun/me`, apiGunMePut) - ap.get(`/api/gun/auth`, (_, res) => { + app.get(`/api/gun/auth`, (_, res) => { const { isAuthenticated } = require('../services/gunDB/Mediator') return res.status(200).json({ @@ -2329,7 +2300,6 @@ module.exports = async ( } if (type === 'once') node.once(listener) - if (type === 'load') node.load(listener) if (type === 'specialOnce') node.specialOnce(listener) }) } @@ -2343,7 +2313,7 @@ module.exports = async ( */ const EPUB_FOR_DECRYPT_HEADER = 'epub-for-decryption' - ap.get('/api/gun/once/:path', async (req, res) => { + app.get('/api/gun/once/:path', async (req, res) => { try { const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER) @@ -2367,7 +2337,7 @@ module.exports = async ( } }) - ap.get('/api/gun/specialOnce/:path', async (req, res) => { + app.get('/api/gun/specialOnce/:path', async (req, res) => { try { const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER) @@ -2391,7 +2361,7 @@ module.exports = async ( } }) - ap.get('/api/gun/load/:path', async (req, res) => { + app.get('/api/gun/load/:path', async (req, res) => { try { const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER) @@ -2415,7 +2385,7 @@ module.exports = async ( } }) - ap.get('/api/gun/user/once/:path', async (req, res) => { + app.get('/api/gun/user/once/:path', async (req, res) => { try { const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER) @@ -2439,7 +2409,7 @@ module.exports = async ( } }) - ap.get('/api/gun/user/specialOnce/:path', async (req, res) => { + app.get('/api/gun/user/specialOnce/:path', async (req, res) => { try { const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER) @@ -2463,7 +2433,7 @@ module.exports = async ( } }) - ap.get('/api/gun/user/load/:path', async (req, res) => { + app.get('/api/gun/user/load/:path', async (req, res) => { try { const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER) @@ -2487,9 +2457,9 @@ module.exports = async ( } }) - ap.get('/api/gun/otheruser/:publicKey/:type/:path', async (req, res) => { + app.get('/api/gun/otheruser/:publicKey/:type/:path', async (req, res) => { try { - const allowedTypes = ['once', 'load', 'open', 'specialOnce'] + const allowedTypes = ['once', 'open', 'specialOnce'] const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const epubForDecryption = req.header(EPUB_FOR_DECRYPT_HEADER) const { path /*:rawPath*/, publicKey, type } = req.params @@ -2511,6 +2481,7 @@ module.exports = async ( const data = await handleGunFetch({ path, startFromUserGraph: false, + // @ts-expect-error Validated above type, publicKey, publicKeyForDecryption, @@ -2537,7 +2508,7 @@ module.exports = async ( } }) - ap.post('/api/lnd/cb/:methodName', (req, res) => { + app.post('/api/lnd/cb/:methodName', (req, res) => { try { const { lightning } = LightningServices.services const { methodName } = req.params @@ -2568,7 +2539,7 @@ module.exports = async ( } }) - ap.post('/api/gun/put', async (req, res) => { + app.post('/api/gun/put', async (req, res) => { try { const { path, value } = req.body logger.info(`gun PUT: ${path}`) @@ -2589,7 +2560,7 @@ module.exports = async ( } }) - ap.post('/api/gun/set', async (req, res) => { + app.post('/api/gun/set', async (req, res) => { try { const { path, value } = req.body logger.info(`gun SET: ${path}`) @@ -2611,14 +2582,16 @@ module.exports = async ( } }) - ap.get('/api/log', async (_, res) => { + app.get('/api/log', async (_, res) => { try { // https://github.com/winstonjs/winston#querying-logs /** * @type {import('winston').QueryOptions} */ const options = { - from: new Date() - 1 * 60 * 60 * 1000, + // @ts-expect-error Winston's typings don't account for supporting + // numbers here. + from: (new Date()).valueOf() - 1 * 60 * 60 * 1000, until: new Date() } @@ -2643,7 +2616,7 @@ module.exports = async ( } }) //this is for OBS notifications, not wired with UI. - ap.get('/api/subscribeStream', (req, res) => { + app.get('/api/subscribeStream', (req, res) => { try { res.sendFile(path.join(__dirname, '../public/obsOverlay.html')) } catch (e) { @@ -2653,7 +2626,7 @@ module.exports = async ( }) } }) - ap.post('/api/enableNotificationsOverlay', (req, res) => { + app.post('/api/enableNotificationsOverlay', (req, res) => { try { const { postID } = req.body if (!postID) { @@ -2673,7 +2646,7 @@ module.exports = async ( } }) //this is for wasLive/isLive status - ap.post('/api/listenStream', (req, res) => { + app.post('/api/listenStream', (req, res) => { try { startedStream(req.body) return res.status(200).json({ @@ -2687,7 +2660,7 @@ module.exports = async ( }) } }) - ap.post('/api/stopStream', (req, res) => { + app.post('/api/stopStream', (req, res) => { try { endStream(req.body) return res.status(200).json({ @@ -2702,7 +2675,7 @@ module.exports = async ( } }) - ap.get('/', (req, res) => { + app.get('/', (req, res) => { try { res.sendFile(path.join(__dirname, '../public/localHomepage.html')) } catch (e) { @@ -2713,8 +2686,8 @@ module.exports = async ( } }) - ap.get('/qrCodeGenerator', (req, res) => { - console.log('qrrerrr') + app.get('/qrCodeGenerator', (req, res) => { + console.log('/qrCodeGenerator') try { res.sendFile(path.join(__dirname, '../public/qrcode.min.js')) } catch (e) { @@ -2725,7 +2698,7 @@ module.exports = async ( } }) - ap.get('/api/accessInfo', async (req, res) => { + app.get('/api/accessInfo', async (req, res) => { if (req.ip !== '127.0.0.1') { res.json({ field: 'origin', @@ -2778,7 +2751,7 @@ module.exports = async ( } }) - ap.post('/api/initUserInformation', async (req, res) => { + app.post('/api/initUserInformation', async (req, res) => { try { const user = require('../services/gunDB/Mediator').getUser() await UserInitializer.InitUserData(user) diff --git a/src/server.js b/src/server.js index 69a7a427..cc667f77 100644 --- a/src/server.js +++ b/src/server.js @@ -1,6 +1,21 @@ /** * @prettier */ +// @ts-check + +const ECCrypto = require('eccrypto') + +const ECC = require('../utils/ECC') + +/** + * This API run's private key. + */ +const runPrivateKey = ECCrypto.generatePrivate() +/** + * This API run's public key. + */ +const runPublicKey = ECCrypto.getPublic(runPrivateKey) + process.on('uncaughtException', e => { console.log('something bad happened!') console.log(e) @@ -20,7 +35,6 @@ const server = program => { const { Logger: CommonLogger } = require('shock-common') const binaryParser = require('socket.io-msgpack-parser') - const ECC = require('../utils/ECC') const LightningServices = require('../utils/lightningServices') const app = Express() @@ -31,11 +45,17 @@ const server = program => { const qrcode = require('qrcode-terminal') const relayClient = require('hybrid-relay-client/build') const { - unprotectedRoutes, sensitiveRoutes, nonEncryptedRoutes } = require('../utils/protectedRoutes') + /** + * An offline-only private key used for authenticating a client's key + * exchange. Neither the tunnel nor the WWW should see this private key, it + * should only be served through STDOUT (via QR or else). + */ + const accessSecret = program.tunnel ? ECCrypto.generatePrivate() : null + // load app default configuration data const defaults = require('../config/defaults')(program.mainnet) const rootFolder = program.rootPath || process.resourcesPath || __dirname @@ -82,41 +102,6 @@ const server = program => { .digest('hex') } - const cacheCheck = ({ req, res, args, send }) => { - if ( - (process.env.SHOCK_CACHE === 'true' || !process.env.SHOCK_CACHE) && - req.method === 'GET' - ) { - const dataHash = hashData(args[0]).slice(-8) - res.set('shock-cache-hash', dataHash) - - logger.debug('shock-cache-hash:', req.headers['shock-cache-hash']) - logger.debug('Data Hash:', dataHash) - if ( - !req.headers['shock-cache-hash'] && - (process.env.CACHE_HEADERS_MANDATORY === 'true' || - !process.env.CACHE_HEADERS_MANDATORY) - ) { - logger.warn( - "Request is missing 'shock-cache-hash' header, please make sure to include that in each GET request in order to benefit from reduced data usage" - ) - return { cached: false, hash: dataHash } - } - - if (req.headers['shock-cache-hash'] === dataHash) { - logger.debug('Same Hash Detected!') - args[0] = null - res.status(304) - send.apply(res, args) - return { cached: true, hash: dataHash } - } - - return { cached: false, hash: dataHash } - } - - return { cached: false, hash: null } - } - /** * @param {Express.Request} req * @param {Express.Response} res @@ -140,6 +125,7 @@ const server = program => { return } + // @ts-expect-error res.send = (...args) => { if (args[0] && args[0].ciphertext && args[0].iv) { logger.warn('Response loop detected!') @@ -147,20 +133,23 @@ const server = program => { return } - const authorized = ECC.isAuthorizedDevice({ - deviceId - }) + if (typeof deviceId !== 'string' || !deviceId) { + // TODO + } + + const authorized = ECC.devicePublicKeys.has(deviceId) // Using classic promises syntax to avoid // modifying res.send's return type if (authorized && process.env.SHOCK_ENCRYPTION_ECC !== 'false') { - ECC.encryptMessage({ - deviceId, - message: args[0] - }).then(encryptedMessage => { - args[0] = JSON.stringify(encryptedMessage) - oldSend.apply(res, args) - }) + const devicePub = Buffer.from(ECC.devicePublicKeys.get(deviceId)) + + ECCrypto.encrypt(devicePub, Buffer.from(args[0], 'utf-8')).then( + encryptedMessage => { + args[0] = JSON.stringify(encryptedMessage) + oldSend.apply(res, args) + } + ) } if (!authorized || process.env.SHOCK_ENCRYPTION_ECC === 'false') { @@ -195,7 +184,7 @@ const server = program => { await LightningServices.init() } - await new Promise((resolve, reject) => { + await /** @type {Promise} */ (new Promise((resolve, reject) => { LightningServices.services.lightning.getInfo({}, (err, res) => { if ( err && @@ -207,9 +196,7 @@ const server = program => { resolve() } }) - }) - - const auth = require('../services/auth/auth') + })) app.use(compression()) @@ -320,7 +307,7 @@ const server = program => { return httpsServer } - const httpServer = Http.Server(app) + const httpServer = new Http.Server(app) return httpServer } catch (err) { logger.error(err.message) @@ -328,7 +315,7 @@ const server = program => { 'An error has occurred while finding an LND cert to use to open an HTTPS server' ) logger.warn('Falling back to opening an HTTP server...') - const httpServer = Http.Server(app) + const httpServer = new Http.Server(app) return httpServer } } @@ -366,11 +353,13 @@ const server = program => { }, Sockets, { - serverHost, serverPort, useTLS: program.useTLS, CA, - CA_KEY + CA_KEY, + runPrivateKey, + runPublicKey, + accessSecret } ) @@ -408,12 +397,12 @@ const server = program => { Storage.setItem('relay/url', noProtocolAddress) ]) const dataToQr = JSON.stringify({ - internalIP: `${params.relayId}@${noProtocolAddress}`, - walletPort: 443, - externalIP: `${params.relayId}@${noProtocolAddress}` + URI: `https://${params.relayId}@${noProtocolAddress}`, + // Null-check is just to please typescript + accessSecret: accessSecret && accessSecret.toString('base64') }) - qrcode.generate(dataToQr, { small: true }) - logger.info(`connect to ${params.relayId}@${noProtocolAddress}`) + qrcode.generate(dataToQr, { small: false }) + logger.info(`connect to ${params.relayId}@${noProtocolAddress}:443`) } else { logger.error('!! Relay did not connect to server !!') } @@ -436,6 +425,7 @@ const server = program => { } serverInstance.listen(serverPort, serverHost) logger.info('App listening on ' + serverHost + ' port ' + serverPort) + // @ts-expect-error module.server = serverInstance } catch (err) { logger.error({ exception: err, message: err.message, code: err.code }) diff --git a/utils/ECC/index.js b/utils/ECC/index.js index 3d30c2b9..b31f01d1 100644 --- a/utils/ECC/index.js +++ b/utils/ECC/index.js @@ -1 +1,8 @@ -module.exports = require('./ECC') \ No newline at end of file +/** + * @format + */ +//@ts-check + +module.exports = require('./ECC') + +module.exports.convertToEncryptedMessage = require('./crypto').convertToEncryptedMessage diff --git a/utils/lightningServices/types.ts b/utils/lightningServices/types.ts index ca0d034f..fb180e22 100644 --- a/utils/lightningServices/types.ts +++ b/utils/lightningServices/types.ts @@ -134,15 +134,15 @@ export interface Services { } export interface ListChannelsReq { - active_only: boolean - inactive_only: boolean - public_only: boolean - private_only: boolean + active_only?: boolean + inactive_only?: boolean + public_only?: boolean + private_only?: boolean /** * Filters the response for channels with a target peer's pubkey. If peer is * empty, all channels will be returned. */ - peer: Common.Bytes + peer?: Common.Bytes } /** @@ -193,4 +193,8 @@ export interface AddInvoiceRes { * all payments for this invoice as we require it for end to end security. */ payment_addr: Common.Bytes + /** + * Custom property, by us. + */ + liquidityCheck?: boolean }