Checks before validation
This commit is contained in:
parent
e0e6fc3173
commit
63a1b29d2d
1 changed files with 206 additions and 220 deletions
426
src/routes.js
426
src/routes.js
|
|
@ -511,236 +511,222 @@ module.exports = async (
|
||||||
try {
|
try {
|
||||||
const health = await checkHealth()
|
const health = await checkHealth()
|
||||||
const walletInitialized = await walletExists()
|
const walletInitialized = await walletExists()
|
||||||
// If we're connected to lnd, unlock the wallet using the password supplied
|
const { alias, password, invite, accessSecret } = req.body
|
||||||
// and generate an auth token if that operation was successful.
|
const lndUp = health.LNDStatus.success
|
||||||
if (health.LNDStatus.success && walletInitialized) {
|
|
||||||
const { alias, password, invite, accessSecret } = req.body
|
|
||||||
|
|
||||||
await recreateLnServices()
|
if (!lndUp) {
|
||||||
|
throw new Error(health.LNDStatus.message)
|
||||||
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 (!walletInitialized) {
|
if (!walletInitialized) {
|
||||||
res.status(500).json({
|
throw new Error('Please create a wallet before authenticating')
|
||||||
field: 'wallet',
|
}
|
||||||
errorMessage: 'Please create a wallet before authenticating',
|
|
||||||
|
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
|
success: false
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(500)
|
const trustedKeysEnabled =
|
||||||
res.json({
|
process.env.TRUSTED_KEYS === 'true' || !process.env.TRUSTED_KEYS
|
||||||
field: 'health',
|
const trustedKeys = await Storage.get('trustedPKs')
|
||||||
errorMessage: sanitizeLNDError(health.LNDStatus.message),
|
// Falls back to true if trusted keys is disabled in .env
|
||||||
success: false
|
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 false
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Unlock Error:', err)
|
logger.error('Unlock Error:', err)
|
||||||
res.status(400)
|
res.status(400)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue