validate generated fields

This commit is contained in:
k00b 2024-10-30 22:26:45 -05:00
parent b1fc341017
commit 3cfbaf4638
8 changed files with 41 additions and 23 deletions

View File

@ -24,6 +24,7 @@ import { lnAddrOptions } from '@/lib/lnurl'
import { GqlAuthenticationError, GqlAuthorizationError, GqlInputError } from '@/lib/error' import { GqlAuthenticationError, GqlAuthorizationError, GqlInputError } from '@/lib/error'
import { getNodeSockets, getOurPubkey } from '../lnd' import { getNodeSockets, getOurPubkey } from '../lnd'
import validateWallet from '@/wallets/validate' import validateWallet from '@/wallets/validate'
import { canReceive } from '@/wallets/common'
function injectResolvers (resolvers) { function injectResolvers (resolvers) {
console.group('injected GraphQL resolvers:') console.group('injected GraphQL resolvers:')
@ -43,9 +44,10 @@ function injectResolvers (resolvers) {
field: walletDef.walletField, field: walletDef.walletField,
type: walletDef.walletType type: walletDef.walletType
}, },
testCreateInvoice: walletDef.testCreateInvoice && validateLightning testCreateInvoice:
? (data) => walletDef.testCreateInvoice(data, { me, models }) walletDef.testCreateInvoice && validateLightning && canReceive({ def: walletDef, config: data })
: null ? (data) => walletDef.testCreateInvoice(data, { me, models })
: null
}, { }, {
settings, settings,
data, data,

View File

@ -7,6 +7,7 @@ import { WELCOME_BANNER_MUTATION } from '@/fragments/users'
import { useToast } from '@/components/toast' import { useToast } from '@/components/toast'
import { BALANCE_LIMIT_MSATS } from '@/lib/constants' import { BALANCE_LIMIT_MSATS } from '@/lib/constants'
import { msatsToSats, numWithUnits } from '@/lib/format' import { msatsToSats, numWithUnits } from '@/lib/format'
import Link from 'next/link'
export function WelcomeBanner ({ Banner }) { export function WelcomeBanner ({ Banner }) {
const { me } = useMe() 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. 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.
</p> </p>
<p className='line-height-md'> <p className='line-height-md'>
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, <Alert.Link as={Link} href='/settings/passphrase'>enable device sync in your settings</Alert.Link>.
</p> </p>
</Alert> </Alert>
) )

View File

@ -17,7 +17,8 @@ export const getServerSideProps = getGetServerSideProps({ authRequired: true })
export default function DeviceSync ({ ssrData }) { export default function DeviceSync ({ ssrData }) {
const { me } = useMe() const { me } = useMe()
const { onVaultKeySet, beforeDisconnectVault } = useWallets() 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 [passphrase, setPassphrase] = useState()
const setSeedPassphrase = useCallback(async (passphrase) => { const setSeedPassphrase = useCallback(async (passphrase) => {

View File

@ -52,7 +52,8 @@ export default function WalletSettings () {
const validate = useCallback(async (data) => { const validate = useCallback(async (data) => {
try { 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) { } catch (error) {
if (error instanceof ValidationError) { if (error instanceof ValidationError) {
return error.inner.reduce((acc, error) => { return error.inner.reduce((acc, error) => {
@ -81,7 +82,7 @@ export default function WalletSettings () {
values.enabled = true values.enabled = true
} }
await save(values, true) await save(values, values.enabled)
toaster.success('saved settings') toaster.success('saved settings')
router.push('/settings/wallets') router.push('/settings/wallets')
@ -137,7 +138,10 @@ function ReceiveSettings ({ walletDef }) {
function WalletFields ({ wallet }) { function WalletFields ({ wallet }) {
return wallet.def.fields 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 = { const rawProps = {
...props, ...props,
name, name,

View File

@ -62,14 +62,14 @@ export function isClientField (f) {
function checkFields ({ fields, config }) { function checkFields ({ fields, config }) {
// a wallet is configured if all of its required fields are set // a wallet is configured if all of its required fields are set
let val = fields.every(f => { 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] return !!config?.[f.name]
}) })
// however, a wallet is not configured if all fields are optional and none are set // 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 // since that usually means that one of them is required
if (val && fields.length > 0) { 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 return val
@ -81,12 +81,12 @@ export function isConfigured ({ def, config }) {
function isSendConfigured ({ def, config }) { function isSendConfigured ({ def, config }) {
const fields = def.fields.filter(isClientField) const fields = def.fields.filter(isClientField)
return checkFields({ fields, config }) return (fields.length > 0 || def.isAvailable?.()) && checkFields({ fields, config })
} }
function isReceiveConfigured ({ def, config }) { function isReceiveConfigured ({ def, config }) {
const fields = def.fields.filter(isServerField) const fields = def.fields.filter(isServerField)
return checkFields({ fields, config }) return fields.length > 0 && checkFields({ fields, config })
} }
export function canSend ({ def, config }) { export function canSend ({ def, config }) {

View File

@ -37,7 +37,7 @@ export function useWalletConfigurator (wallet) {
let serverConfig = serverWithShared let serverConfig = serverWithShared
if (canSend({ def: wallet.def, config: clientConfig })) { 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) { if (transformedConfig) {
clientConfig = Object.assign(clientConfig, transformedConfig) clientConfig = Object.assign(clientConfig, transformedConfig)
} }
@ -46,6 +46,8 @@ export function useWalletConfigurator (wallet) {
if (transformedConfig) { if (transformedConfig) {
clientConfig = Object.assign(clientConfig, 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 })) { } else if (canReceive({ def: wallet.def, config: serverConfig })) {
const transformedConfig = await validateWallet(wallet.def, 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 vault is active, encrypt and send to server regardless of wallet type
if (isActive) { if (isActive) {
await _saveToServer(serverConfig, clientConfig, validateLightning) await _saveToServer(serverConfig, clientConfig, validateLightning)
await _detachFromLocal()
} else { } else {
if (canSend({ def: wallet.def, config: clientConfig })) { if (canSend({ def: wallet.def, config: clientConfig })) {
await _saveToLocal(clientConfig) await _saveToLocal(clientConfig)

View File

@ -36,23 +36,26 @@ export const fields = [
{ {
name: 'localKey', name: 'localKey',
type: 'text', type: 'text',
optional: true,
hidden: true, hidden: true,
clientOnly: true clientOnly: true,
generated: true,
validate: string()
}, },
{ {
name: 'remoteKey', name: 'remoteKey',
type: 'text', type: 'text',
optional: true,
hidden: true, hidden: true,
clientOnly: true clientOnly: true,
generated: true,
validate: string()
}, },
{ {
name: 'serverHost', name: 'serverHost',
type: 'text', type: 'text',
optional: true,
hidden: true, hidden: true,
clientOnly: true clientOnly: true,
generated: true,
validate: string()
} }
] ]

View File

@ -12,8 +12,8 @@ import * as Yup from '@/lib/yup'
import { canReceive } from './common' import { canReceive } from './common'
export default async function validateWallet (walletDef, data, export default async function validateWallet (walletDef, data,
{ yupOptions = { abortEarly: true }, topLevel = true, serverSide = false } = {}) { { yupOptions = { abortEarly: true }, topLevel = true, serverSide = false, skipGenerated = false } = {}) {
let schema = composeWalletSchema(walletDef, serverSide) let schema = composeWalletSchema(walletDef, serverSide, skipGenerated)
if (canReceive({ def: walletDef, config: data })) { if (canReceive({ def: walletDef, config: data })) {
schema = schema.concat(autowithdrawSchemaMembers) 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 { fields } = walletDef
const vaultEntrySchemas = { required: [], optional: [] } const vaultEntrySchemas = { required: [], optional: [] }
const schemaShape = fields.reduce((acc, field) => { 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) { if (clientOnly && serverSide) {
// For server-side validation, accumulate clientOnly fields as vaultEntries // For server-side validation, accumulate clientOnly fields as vaultEntries