From b96757b366f2c43f8c76d7f84b15fa243fac6843 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Thu, 4 Jul 2024 22:42:14 +0200 Subject: [PATCH] Move all validation schema into lib/validate --- components/wallet/lnbits.js | 28 ++------------- components/wallet/lnc.js | 18 ++-------- components/wallet/nwc.js | 29 ++-------------- lib/validate.js | 68 +++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 69 deletions(-) diff --git a/components/wallet/lnbits.js b/components/wallet/lnbits.js index e28c2ee5..2002f809 100644 --- a/components/wallet/lnbits.js +++ b/components/wallet/lnbits.js @@ -1,5 +1,4 @@ -import { TOR_REGEXP } from '@/lib/url' -import { object, string } from 'yup' +import { LNbitsSchema } from '@/lib/validate' export const name = 'lnbits' @@ -28,30 +27,7 @@ export async function validate ({ logger, url, adminKey }) { logger.ok('wallet found') } -export const schema = object({ - url: process.env.NODE_ENV === 'development' - ? string() - .or([string().matches(/^(http:\/\/)?localhost:\d+$/), string().url()], 'invalid url') - .required('required').trim() - : string().url().required('required').trim() - .test(async (url, context) => { - if (TOR_REGEXP.test(url)) { - // allow HTTP and HTTPS over Tor - if (!/^https?:\/\//.test(url)) { - return context.createError({ message: 'http or https required' }) - } - return true - } - try { - // force HTTPS over clearnet - await string().https().validate(url) - } catch (err) { - return context.createError({ message: err.message }) - } - return true - }), - adminKey: string().length(32) -}) +export const schema = LNbitsSchema export async function sendPayment ({ bolt11, url, adminKey }) { const response = await postPayment(url, adminKey, bolt11) diff --git a/components/wallet/lnc.js b/components/wallet/lnc.js index 6cd8545f..492e59dc 100644 --- a/components/wallet/lnc.js +++ b/components/wallet/lnc.js @@ -1,12 +1,11 @@ -import bip39Words from '@/lib/bip39-words' import LNC from '@lightninglabs/lnc-web' import { Mutex } from 'async-mutex' -import { string, array, object } from 'yup' import { Form, PasswordInput, SubmitButton } from '@/components/form' import CancelButton from '@/components/cancel-button' import { InvoiceCanceledError, InvoiceExpiredError } from '@/components/payment' import { bolt11Tags } from '@/lib/bolt11' import { Status } from '@/components/wallet' +import { LNCSchema } from '@/lib/validate' export const name = 'lnc' @@ -58,20 +57,7 @@ export async function validate ({ me, logger, pairingPhrase, password }) { } } -export const schema = object({ - pairingPhrase: array() - .transform(function (value, originalValue) { - if (this.isType(value) && value !== null) { - return value - } - return originalValue ? originalValue.split(/[\s]+/) : [] - }) - .of(string().trim().oneOf(bip39Words, ({ value }) => `'${value}' is not a valid pairing phrase word`)) - .min(2, 'needs at least two words') - .max(10, 'max 10 words') - .required('required'), - password: string() -}) +export const schema = LNCSchema const mutex = new Mutex() diff --git a/components/wallet/nwc.js b/components/wallet/nwc.js index bed8ccda..be335580 100644 --- a/components/wallet/nwc.js +++ b/components/wallet/nwc.js @@ -1,7 +1,6 @@ -import { NOSTR_PUBKEY_HEX } from '@/lib/nostr' import { parseNwcUrl } from '@/lib/url' +import { NWCSchema } from '@/lib/validate' import { Relay, finalizeEvent, nip04 } from 'nostr-tools' -import { object, string } from 'yup' export const name = 'nwc' @@ -19,31 +18,7 @@ export const card = { badges: ['send only', 'non-custodialish'] } -export const schema = object({ - nwcUrl: string() - .required('required') - .test(async (nwcUrl, context) => { - // run validation in sequence to control order of errors - // inspired by https://github.com/jquense/yup/issues/851#issuecomment-1049705180 - try { - await string().required('required').validate(nwcUrl) - await string().matches(/^nostr\+?walletconnect:\/\//, { message: 'must start with nostr+walletconnect://' }).validate(nwcUrl) - let relayUrl, walletPubkey, secret - try { - ({ relayUrl, walletPubkey, secret } = parseNwcUrl(nwcUrl)) - } catch { - // invalid URL error. handle as if pubkey validation failed to not confuse user. - throw new Error('pubkey must be 64 hex chars') - } - await string().required('pubkey required').trim().matches(NOSTR_PUBKEY_HEX, 'pubkey must be 64 hex chars').validate(walletPubkey) - await string().required('relay url required').trim().wss('relay must use wss://').validate(relayUrl) - await string().required('secret required').trim().matches(/^[0-9a-fA-F]{64}$/, 'secret must be 64 hex chars').validate(secret) - } catch (err) { - return context.createError({ message: err.message }) - } - return true - }) -}) +export const schema = NWCSchema export async function validate ({ logger, nwcUrl }) { const { relayUrl, walletPubkey } = parseNwcUrl(nwcUrl) diff --git a/lib/validate.js b/lib/validate.js index 73547a51..d80f38c2 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -12,6 +12,8 @@ import * as subsFragments from '@/fragments/subs' import { isInvoicableMacaroon, isInvoiceMacaroon } from './macaroon' import { datePivot } from './time' import { decodeRune } from '@/lib/cln' +import { TOR_REGEXP, parseNwcUrl } from '@/lib/url' +import bip39Words from '@/lib/bip39-words' const { SUB } = subsFragments const { NAME_QUERY } = usersFragments @@ -303,6 +305,72 @@ export function advSchema (args) { }) } +export const LNbitsSchema = object({ + url: process.env.NODE_ENV === 'development' + ? string() + .or([string().matches(/^(http:\/\/)?localhost:\d+$/), string().url()], 'invalid url') + .required('required').trim() + : string().url().required('required').trim() + .test(async (url, context) => { + if (TOR_REGEXP.test(url)) { + // allow HTTP and HTTPS over Tor + if (!/^https?:\/\//.test(url)) { + return context.createError({ message: 'http or https required' }) + } + return true + } + try { + // force HTTPS over clearnet + await string().https().validate(url) + } catch (err) { + return context.createError({ message: err.message }) + } + return true + }), + adminKey: string().length(32) +}) + +export const NWCSchema = object({ + nwcUrl: string() + .required('required') + .test(async (nwcUrl, context) => { + // run validation in sequence to control order of errors + // inspired by https://github.com/jquense/yup/issues/851#issuecomment-1049705180 + try { + await string().required('required').validate(nwcUrl) + await string().matches(/^nostr\+?walletconnect:\/\//, { message: 'must start with nostr+walletconnect://' }).validate(nwcUrl) + let relayUrl, walletPubkey, secret + try { + ({ relayUrl, walletPubkey, secret } = parseNwcUrl(nwcUrl)) + } catch { + // invalid URL error. handle as if pubkey validation failed to not confuse user. + throw new Error('pubkey must be 64 hex chars') + } + await string().required('pubkey required').trim().matches(NOSTR_PUBKEY_HEX, 'pubkey must be 64 hex chars').validate(walletPubkey) + await string().required('relay url required').trim().wss('relay must use wss://').validate(relayUrl) + await string().required('secret required').trim().matches(/^[0-9a-fA-F]{64}$/, 'secret must be 64 hex chars').validate(secret) + } catch (err) { + return context.createError({ message: err.message }) + } + return true + }) +}) + +export const LNCSchema = object({ + pairingPhrase: array() + .transform(function (value, originalValue) { + if (this.isType(value) && value !== null) { + return value + } + return originalValue ? originalValue.split(/[\s]+/) : [] + }) + .of(string().trim().oneOf(bip39Words, ({ value }) => `'${value}' is not a valid pairing phrase word`)) + .min(2, 'needs at least two words') + .max(10, 'max 10 words') + .required('required'), + password: string() +}) + export function lnAddrAutowithdrawSchema ({ me } = {}) { return object({ address: lightningAddressValidator.required('required').test({