handle other possible base64-like string encodings

This commit is contained in:
keyan 2024-02-18 15:08:10 -06:00
parent a9ade0354d
commit fe0d960208
4 changed files with 61 additions and 28 deletions

View File

@ -6,12 +6,11 @@ import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
import lnpr from 'bolt11'
import { SELECT } from './item'
import { lnAddrOptions } from '../../lib/lnurl'
import { msatsToSats, msatsToSatsDecimal } from '../../lib/format'
import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '../../lib/format'
import { LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '../../lib/validate'
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '../../lib/constants'
import { datePivot } from '../../lib/time'
import assertGofacYourself from './ofac'
import { HEX_REGEX } from '../../lib/macaroon'
export async function getInvoice (parent, { id }, { me, models, lnd }) {
const inv = await models.invoice.findUnique({
@ -408,13 +407,9 @@ export default {
return { id }
},
upsertWalletLND: async (parent, { settings, ...data }, { me, models }) => {
// store hex inputs as base64
if (HEX_REGEX.test(data.macaroon)) {
data.macaroon = Buffer.from(data.macaroon, 'hex').toString('base64')
}
if (HEX_REGEX.test(data.cert)) {
data.cert = Buffer.from(data.cert, 'hex').toString('base64')
}
// make sure inputs are base64
data.macaroon = ensureB64(data.macaroon)
data.cert = ensureB64(data.cert)
return await upsertWallet(
{

View File

@ -53,3 +53,42 @@ export const msatsToSatsDecimal = msats => {
}
return fixedDecimal(Number(msats) / 1000.0, 3)
}
export const hexToB64 = hexstring => {
return btoa(hexstring.match(/\w{2}/g).map(function (a) {
return String.fromCharCode(parseInt(a, 16))
}).join(''))
}
// some base64 encoders get fancy and remove padding
export const ensureB64Padding = str => {
return str + Array((4 - str.length % 4) % 4 + 1).join('=')
}
export const B64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
export const B64_URL_REGEX = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}[.=]{2}|[A-Za-z0-9_-]{3}[.=])?$/
export const HEX_REGEX = /^[0-9a-fA-F]+$/
export const ensureB64 = hexOrB64Url => {
if (HEX_REGEX.test(hexOrB64Url)) {
return hexToB64(hexOrB64Url)
}
hexOrB64Url = ensureB64Padding(hexOrB64Url)
// some folks use url-safe base64
if (B64_URL_REGEX.test(hexOrB64Url)) {
// Convert from URL-safe base64 to regular base64
hexOrB64Url = hexOrB64Url.replace(/-/g, '+').replace(/_/g, '/').replace(/\./g, '=')
switch (hexOrB64Url.length % 4) {
case 2: hexOrB64Url += '=='; break
case 3: hexOrB64Url += '='; break
}
}
if (B64_REGEX.test(hexOrB64Url)) {
return hexOrB64Url
}
throw new Error('not a valid hex or base64 url or base64 encoded string')
}

View File

@ -2,20 +2,11 @@ import { importMacaroon, base64ToBytes } from 'macaroon'
import { MacaroonId } from './macaroon-id'
import isEqual from 'lodash/isEqual'
import isEqualWith from 'lodash/isEqualWith'
export const B64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
export const HEX_REGEX = /^[0-9a-fA-F]+$/
import { ensureB64 } from './format'
function decodeMacaroon (macaroon) {
if (HEX_REGEX.test(macaroon)) {
return importMacaroon(Buffer.from(macaroon, 'hex'))
}
if (B64_REGEX.test(macaroon)) {
return importMacaroon(Buffer.from(macaroon, 'base64'))
}
throw new Error('invalid macaroon encoding')
macaroon = ensureB64(macaroon)
return importMacaroon(Buffer.from(macaroon, 'base64'))
}
function macaroonOPs (macaroon) {

View File

@ -6,10 +6,10 @@ import {
} from './constants'
import { SUPPORTED_CURRENCIES } from './currency'
import { NOSTR_MAX_RELAY_NUM, NOSTR_PUBKEY_BECH32, NOSTR_PUBKEY_HEX } from './nostr'
import { msatsToSats, numWithUnits, abbrNum } from './format'
import { msatsToSats, numWithUnits, abbrNum, ensureB64 } from './format'
import * as usersFragments from '../fragments/users'
import * as subsFragments from '../fragments/subs'
import { B64_REGEX, HEX_REGEX, isInvoicableMacaroon, isInvoiceMacaroon } from './macaroon'
import { isInvoicableMacaroon, isInvoiceMacaroon } from './macaroon'
import { parseNwcUrl } from './url'
const { SUB } = subsFragments
@ -154,10 +154,18 @@ const lightningAddressValidator = process.env.NODE_ENV === 'development'
'address is no good')
: string().email('address is no good')
const hexOrBase64Validator = string().or([
string().matches(HEX_REGEX),
string().matches(B64_REGEX)
], 'invalid encoding')
const hexOrBase64Validator = string().test({
name: 'hex-or-base64',
message: 'invalid encoding',
test: (val) => {
try {
ensureB64(val)
return true
} catch {
return false
}
}
})
async function usernameExists (name, { client, models }) {
if (!client && !models) {
@ -313,7 +321,7 @@ export function LNDAutowithdrawSchema ({ me } = {}) {
macaroon: hexOrBase64Validator.required('required').test({
name: 'macaroon',
test: v => isInvoiceMacaroon(v) || isInvoicableMacaroon(v),
message: 'not an invoice macaroon'
message: 'not an invoice macaroon or an invoicable macaroon'
}),
cert: hexOrBase64Validator,
...autowithdrawSchemaMembers({ me })