validate generated fields
This commit is contained in:
parent
b1fc341017
commit
3cfbaf4638
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 }) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue