From 3cfbaf4638ad699d3ebaf19420a6e95e57c1a46a Mon Sep 17 00:00:00 2001 From: k00b Date: Wed, 30 Oct 2024 22:26:45 -0500 Subject: [PATCH] validate generated fields --- api/resolvers/wallet.js | 8 +++++--- components/banners.js | 3 ++- pages/settings/passphrase/index.js | 3 ++- pages/settings/wallets/[wallet].js | 10 +++++++--- wallets/common.js | 8 ++++---- wallets/config.js | 5 ++++- wallets/lnc/index.js | 15 +++++++++------ wallets/validate.js | 12 ++++++++---- 8 files changed, 41 insertions(+), 23 deletions(-) diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index 4b21ac40..268b4ad9 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -24,6 +24,7 @@ import { lnAddrOptions } from '@/lib/lnurl' import { GqlAuthenticationError, GqlAuthorizationError, GqlInputError } from '@/lib/error' import { getNodeSockets, getOurPubkey } from '../lnd' import validateWallet from '@/wallets/validate' +import { canReceive } from '@/wallets/common' function injectResolvers (resolvers) { console.group('injected GraphQL resolvers:') @@ -43,9 +44,10 @@ function injectResolvers (resolvers) { field: walletDef.walletField, type: walletDef.walletType }, - testCreateInvoice: walletDef.testCreateInvoice && validateLightning - ? (data) => walletDef.testCreateInvoice(data, { me, models }) - : null + testCreateInvoice: + walletDef.testCreateInvoice && validateLightning && canReceive({ def: walletDef, config: data }) + ? (data) => walletDef.testCreateInvoice(data, { me, models }) + : null }, { settings, data, diff --git a/components/banners.js b/components/banners.js index a36f5b54..72387e7c 100644 --- a/components/banners.js +++ b/components/banners.js @@ -7,6 +7,7 @@ import { WELCOME_BANNER_MUTATION } from '@/fragments/users' import { useToast } from '@/components/toast' import { BALANCE_LIMIT_MSATS } from '@/lib/constants' import { msatsToSats, numWithUnits } from '@/lib/format' +import Link from 'next/link' export function WelcomeBanner ({ Banner }) { const { me } = useMe() @@ -132,7 +133,7 @@ export function WalletSecurityBanner ({ isActive }) { Listen up, pardner! Put a limit on yer spendin' wallet or hook up a wallet that's only for Stacker News. It'll keep them varmints from cleanin' out yer whole goldmine if they rustle up yer wallet.

- Your spending wallet's credentials are never sent to our servers in plain text. To sync across devices, enable device sync in your settings. + Your spending wallet's credentials are never sent to our servers in plain text. To sync across devices, enable device sync in your settings.

) diff --git a/pages/settings/passphrase/index.js b/pages/settings/passphrase/index.js index 612d53ca..d52e3a72 100644 --- a/pages/settings/passphrase/index.js +++ b/pages/settings/passphrase/index.js @@ -17,7 +17,8 @@ export const getServerSideProps = getGetServerSideProps({ authRequired: true }) export default function DeviceSync ({ ssrData }) { const { me } = useMe() const { onVaultKeySet, beforeDisconnectVault } = useWallets() - const { key, setVaultKey, clearVault, disconnectVault } = useVaultConfigurator({ onVaultKeySet, beforeDisconnectVault }) + const { key, setVaultKey, clearVault, disconnectVault } = + useVaultConfigurator({ onVaultKeySet, beforeDisconnectVault }) const [passphrase, setPassphrase] = useState() const setSeedPassphrase = useCallback(async (passphrase) => { diff --git a/pages/settings/wallets/[wallet].js b/pages/settings/wallets/[wallet].js index 3b934c7f..f1c1a026 100644 --- a/pages/settings/wallets/[wallet].js +++ b/pages/settings/wallets/[wallet].js @@ -52,7 +52,8 @@ export default function WalletSettings () { const validate = useCallback(async (data) => { try { - await validateWallet(wallet.def, data, { yupOptions: { abortEarly: false }, topLevel: false }) + await validateWallet(wallet.def, data, + { yupOptions: { abortEarly: false }, topLevel: false, skipGenerated: true }) } catch (error) { if (error instanceof ValidationError) { return error.inner.reduce((acc, error) => { @@ -81,7 +82,7 @@ export default function WalletSettings () { values.enabled = true } - await save(values, true) + await save(values, values.enabled) toaster.success('saved settings') router.push('/settings/wallets') @@ -137,7 +138,10 @@ function ReceiveSettings ({ walletDef }) { function WalletFields ({ wallet }) { return wallet.def.fields - .map(({ name, label = '', type, help, optional, editable, requiredWithout, validate, clientOnly, serverOnly, ...props }, i) => { + .map(({ + name, label = '', type, help, optional, editable, requiredWithout, + validate, clientOnly, serverOnly, generated, ...props + }, i) => { const rawProps = { ...props, name, diff --git a/wallets/common.js b/wallets/common.js index 7bcef430..43c89ac3 100644 --- a/wallets/common.js +++ b/wallets/common.js @@ -62,14 +62,14 @@ export function isClientField (f) { function checkFields ({ fields, config }) { // a wallet is configured if all of its required fields are set let val = fields.every(f => { - if (f.optional && !f.requiredWithout) return true + if ((f.optional || f.generated) && !f.requiredWithout) return true return !!config?.[f.name] }) // however, a wallet is not configured if all fields are optional and none are set // since that usually means that one of them is required if (val && fields.length > 0) { - val = !(fields.every(f => f.optional) && fields.every(f => !config?.[f.name])) + val = !(fields.every(f => f.optional || f.generated) && fields.every(f => !config?.[f.name])) } return val @@ -81,12 +81,12 @@ export function isConfigured ({ def, config }) { function isSendConfigured ({ def, config }) { const fields = def.fields.filter(isClientField) - return checkFields({ fields, config }) + return (fields.length > 0 || def.isAvailable?.()) && checkFields({ fields, config }) } function isReceiveConfigured ({ def, config }) { const fields = def.fields.filter(isServerField) - return checkFields({ fields, config }) + return fields.length > 0 && checkFields({ fields, config }) } export function canSend ({ def, config }) { diff --git a/wallets/config.js b/wallets/config.js index 16014040..25185a8c 100644 --- a/wallets/config.js +++ b/wallets/config.js @@ -37,7 +37,7 @@ export function useWalletConfigurator (wallet) { let serverConfig = serverWithShared if (canSend({ def: wallet.def, config: clientConfig })) { - let transformedConfig = await validateWallet(wallet.def, clientWithShared) + let transformedConfig = await validateWallet(wallet.def, clientWithShared, { skipGenerated: true }) if (transformedConfig) { clientConfig = Object.assign(clientConfig, transformedConfig) } @@ -46,6 +46,8 @@ export function useWalletConfigurator (wallet) { if (transformedConfig) { clientConfig = Object.assign(clientConfig, transformedConfig) } + // validate again to ensure generated fields are valid + await validateWallet(wallet.def, clientConfig) } } else if (canReceive({ def: wallet.def, config: serverConfig })) { const transformedConfig = await validateWallet(wallet.def, serverConfig) @@ -74,6 +76,7 @@ export function useWalletConfigurator (wallet) { // if vault is active, encrypt and send to server regardless of wallet type if (isActive) { await _saveToServer(serverConfig, clientConfig, validateLightning) + await _detachFromLocal() } else { if (canSend({ def: wallet.def, config: clientConfig })) { await _saveToLocal(clientConfig) diff --git a/wallets/lnc/index.js b/wallets/lnc/index.js index 51d41ad9..03a795bd 100644 --- a/wallets/lnc/index.js +++ b/wallets/lnc/index.js @@ -36,23 +36,26 @@ export const fields = [ { name: 'localKey', type: 'text', - optional: true, hidden: true, - clientOnly: true + clientOnly: true, + generated: true, + validate: string() }, { name: 'remoteKey', type: 'text', - optional: true, hidden: true, - clientOnly: true + clientOnly: true, + generated: true, + validate: string() }, { name: 'serverHost', type: 'text', - optional: true, hidden: true, - clientOnly: true + clientOnly: true, + generated: true, + validate: string() } ] diff --git a/wallets/validate.js b/wallets/validate.js index 8131189c..7d7734d7 100644 --- a/wallets/validate.js +++ b/wallets/validate.js @@ -12,8 +12,8 @@ import * as Yup from '@/lib/yup' import { canReceive } from './common' export default async function validateWallet (walletDef, data, - { yupOptions = { abortEarly: true }, topLevel = true, serverSide = false } = {}) { - let schema = composeWalletSchema(walletDef, serverSide) + { yupOptions = { abortEarly: true }, topLevel = true, serverSide = false, skipGenerated = false } = {}) { + let schema = composeWalletSchema(walletDef, serverSide, skipGenerated) if (canReceive({ def: walletDef, config: data })) { schema = schema.concat(autowithdrawSchemaMembers) @@ -58,12 +58,16 @@ function createFieldSchema (name, validate) { } } -function composeWalletSchema (walletDef, serverSide) { +function composeWalletSchema (walletDef, serverSide, skipGenerated) { const { fields } = walletDef const vaultEntrySchemas = { required: [], optional: [] } const schemaShape = fields.reduce((acc, field) => { - const { name, validate, optional, clientOnly, requiredWithout } = field + const { name, validate, optional, generated, clientOnly, requiredWithout } = field + + if (generated && skipGenerated) { + return acc + } if (clientOnly && serverSide) { // For server-side validation, accumulate clientOnly fields as vaultEntries