Always check res.ok and content-type header (#1621)

* Always check res.ok and content-type header

* Fix blink body consumed before we use it

* Always consume response body to avoid memory leaks
This commit is contained in:
ekzyis 2024-11-20 19:46:39 +01:00 committed by GitHub
parent c88afc5aae
commit f45144cb1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 46 additions and 25 deletions

View File

@ -1,7 +1,7 @@
import fetch from 'cross-fetch' import fetch from 'cross-fetch'
import crypto from 'crypto' import crypto from 'crypto'
import { getAgent } from '@/lib/proxy' import { getAgent } from '@/lib/proxy'
import { assertContentTypeJson } from './url' import { assertContentTypeJson, assertResponseOk } from './url'
export const createInvoice = async ({ socket, rune, cert, label, description, msats, expiry }) => { export const createInvoice = async ({ socket, rune, cert, label, description, msats, expiry }) => {
const agent = getAgent({ hostname: socket, cert }) const agent = getAgent({ hostname: socket, cert })
@ -27,9 +27,8 @@ export const createInvoice = async ({ socket, rune, cert, label, description, ms
}) })
}) })
if (!res.ok) { assertResponseOk(res)
assertContentTypeJson(res) assertContentTypeJson(res)
}
const inv = await res.json() const inv = await res.json()
if (inv.error) { if (inv.error) {

View File

@ -213,9 +213,21 @@ export function parseNwcUrl (walletConnectUrl) {
return params return params
} }
export function assertResponseOk (res) {
if (!res.ok) {
// consume response body to avoid memory leaks
// see https://github.com/nodejs/node/issues/51162
res.text().catch(() => {})
throw new Error(`POST ${res.url}: ${res.status} ${res.statusText}`)
}
}
export function assertContentTypeJson (res) { export function assertContentTypeJson (res) {
const contentType = res.headers.get('content-type') const contentType = res.headers.get('content-type')
if (!contentType || !contentType.includes('application/json')) { if (!contentType || !contentType.includes('application/json')) {
// consume response body to avoid memory leaks
// see https://github.com/nodejs/node/issues/51162
res.text().catch(() => {})
throw new Error(`POST ${res.url}: ${res.status} ${res.statusText}`) throw new Error(`POST ${res.url}: ${res.status} ${res.statusText}`)
} }
} }

View File

@ -1,3 +1,5 @@
import { assertContentTypeJson, assertResponseOk } from '@/lib/url'
export const galoyBlinkUrl = 'https://api.blink.sv/graphql' export const galoyBlinkUrl = 'https://api.blink.sv/graphql'
export const galoyBlinkDashboardUrl = 'https://dashboard.blink.sv/' export const galoyBlinkDashboardUrl = 'https://dashboard.blink.sv/'
@ -37,15 +39,10 @@ export async function request (authToken, query, variables = {}) {
body: JSON.stringify({ query, variables }) body: JSON.stringify({ query, variables })
} }
const res = await fetch(galoyBlinkUrl, options) const res = await fetch(galoyBlinkUrl, options)
if (res.status >= 400 && res.status <= 599) {
// consume res assertResponseOk(res)
res.text().catch(() => {}) assertContentTypeJson(res)
if (res.status === 401) {
throw new Error('unauthorized')
} else {
throw new Error('API responded with HTTP ' + res.status)
}
}
return res.json() return res.json()
} }

View File

@ -1,5 +1,6 @@
import { msatsSatsFloor } from '@/lib/format' import { msatsSatsFloor } from '@/lib/format'
import { lnAddrOptions } from '@/lib/lnurl' import { lnAddrOptions } from '@/lib/lnurl'
import { assertContentTypeJson, assertResponseOk } from '@/lib/url'
export * from '@/wallets/lightning-address' export * from '@/wallets/lightning-address'
@ -24,8 +25,13 @@ export const createInvoice = async (
} }
// call callback with amount and conditionally comment // call callback with amount and conditionally comment
const res = await (await fetch(callbackUrl.toString())).json() const res = await fetch(callbackUrl.toString())
if (res.status === 'ERROR') {
assertResponseOk(res)
assertContentTypeJson(res)
const body = await res.json()
if (body.status === 'ERROR') {
throw new Error(res.reason) throw new Error(res.reason)
} }

View File

@ -33,8 +33,9 @@ async function getWallet ({ url, adminKey, invoiceKey }) {
headers.append('X-Api-Key', adminKey || invoiceKey) headers.append('X-Api-Key', adminKey || invoiceKey)
const res = await fetch(url + path, { method: 'GET', headers }) const res = await fetch(url + path, { method: 'GET', headers })
assertContentTypeJson(res)
if (!res.ok) { if (!res.ok) {
assertContentTypeJson(res)
const errBody = await res.json() const errBody = await res.json()
throw new Error(errBody.detail) throw new Error(errBody.detail)
} }
@ -54,8 +55,9 @@ async function postPayment (bolt11, { url, adminKey }) {
const body = JSON.stringify({ bolt11, out: true }) const body = JSON.stringify({ bolt11, out: true })
const res = await fetch(url + path, { method: 'POST', headers, body }) const res = await fetch(url + path, { method: 'POST', headers, body })
assertContentTypeJson(res)
if (!res.ok) { if (!res.ok) {
assertContentTypeJson(res)
const errBody = await res.json() const errBody = await res.json()
throw new Error(errBody.detail) throw new Error(errBody.detail)
} }
@ -73,8 +75,9 @@ async function getPayment (paymentHash, { url, adminKey }) {
headers.append('X-Api-Key', adminKey) headers.append('X-Api-Key', adminKey)
const res = await fetch(url + path, { method: 'GET', headers }) const res = await fetch(url + path, { method: 'GET', headers })
assertContentTypeJson(res)
if (!res.ok) { if (!res.ok) {
assertContentTypeJson(res)
const errBody = await res.json() const errBody = await res.json()
throw new Error(errBody.detail) throw new Error(errBody.detail)
} }

View File

@ -44,8 +44,9 @@ export async function createInvoice (
agent, agent,
body body
}) })
assertContentTypeJson(res)
if (!res.ok) { if (!res.ok) {
assertContentTypeJson(res)
const errBody = await res.json() const errBody = await res.json()
throw new Error(errBody.detail) throw new Error(errBody.detail)
} }

View File

@ -1,3 +1,5 @@
import { assertContentTypeJson, assertResponseOk } from '@/lib/url'
export * from '@/wallets/phoenixd' export * from '@/wallets/phoenixd'
export async function testSendPayment (config, { logger }) { export async function testSendPayment (config, { logger }) {
@ -24,9 +26,9 @@ export async function sendPayment (bolt11, { url, primaryPassword }) {
headers, headers,
body body
}) })
if (!res.ok) {
throw new Error(`POST ${res.url}: ${res.status} ${res.statusText}`) assertResponseOk(res)
} assertContentTypeJson(res)
const payment = await res.json() const payment = await res.json()
const preimage = payment.paymentPreimage const preimage = payment.paymentPreimage

View File

@ -1,4 +1,5 @@
import { msatsToSats } from '@/lib/format' import { msatsToSats } from '@/lib/format'
import { assertContentTypeJson, assertResponseOk } from '@/lib/url'
export * from '@/wallets/phoenixd' export * from '@/wallets/phoenixd'
@ -28,9 +29,9 @@ export async function createInvoice (
headers, headers,
body body
}) })
if (!res.ok) {
throw new Error(`POST ${res.url}: ${res.status} ${res.statusText}`) assertResponseOk(res)
} assertContentTypeJson(res)
const payment = await res.json() const payment = await res.json()
return payment.serialized return payment.serialized