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 { 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,

View File

@ -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.
</p>
<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>
</Alert>
)

View File

@ -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) => {

View File

@ -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,

View File

@ -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 }) {

View File

@ -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)

View File

@ -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()
}
]

View File

@ -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