Dependency inject me into post validation schemas to enforce no forwarding posts to self (#485)
				
					
				
			Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									16a6f93708
								
							
						
					
					
						commit
						1a6dc879a2
					
				@ -621,7 +621,7 @@ export default {
 | 
				
			|||||||
      return await models.item.update({ where: { id: Number(id) }, data })
 | 
					      return await models.item.update({ where: { id: Number(id) }, data })
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    upsertLink: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
 | 
					    upsertLink: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
 | 
				
			||||||
      await ssValidate(linkSchema, item, models)
 | 
					      await ssValidate(linkSchema, item, models, me)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (id) {
 | 
					      if (id) {
 | 
				
			||||||
        return await updateItem(parent, { id, ...item }, { me, models })
 | 
					        return await updateItem(parent, { id, ...item }, { me, models })
 | 
				
			||||||
@ -630,7 +630,7 @@ export default {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    upsertDiscussion: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
 | 
					    upsertDiscussion: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
 | 
				
			||||||
      await ssValidate(discussionSchema, item, models)
 | 
					      await ssValidate(discussionSchema, item, models, me)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (id) {
 | 
					      if (id) {
 | 
				
			||||||
        return await updateItem(parent, { id, ...item }, { me, models })
 | 
					        return await updateItem(parent, { id, ...item }, { me, models })
 | 
				
			||||||
@ -639,7 +639,7 @@ export default {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    upsertBounty: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
 | 
					    upsertBounty: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
 | 
				
			||||||
      await ssValidate(bountySchema, item, models)
 | 
					      await ssValidate(bountySchema, item, models, me)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (id) {
 | 
					      if (id) {
 | 
				
			||||||
        return await updateItem(parent, { id, ...item }, { me, models })
 | 
					        return await updateItem(parent, { id, ...item }, { me, models })
 | 
				
			||||||
@ -656,7 +656,7 @@ export default {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
        : 0
 | 
					        : 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await ssValidate(pollSchema, item, models, optionCount)
 | 
					      await ssValidate(pollSchema, item, models, me, optionCount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (id) {
 | 
					      if (id) {
 | 
				
			||||||
        return await updateItem(parent, { id, ...item }, { me, models })
 | 
					        return await updateItem(parent, { id, ...item }, { me, models })
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ import CancelButton from './cancel-button'
 | 
				
			|||||||
import { useCallback } from 'react'
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
import { normalizeForwards } from '../lib/form'
 | 
					import { normalizeForwards } from '../lib/form'
 | 
				
			||||||
import { MAX_TITLE_LENGTH } from '../lib/constants'
 | 
					import { MAX_TITLE_LENGTH } from '../lib/constants'
 | 
				
			||||||
 | 
					import { useMe } from './me'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function BountyForm ({
 | 
					export function BountyForm ({
 | 
				
			||||||
  item,
 | 
					  item,
 | 
				
			||||||
@ -25,7 +26,8 @@ export function BountyForm ({
 | 
				
			|||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const router = useRouter()
 | 
					  const router = useRouter()
 | 
				
			||||||
  const client = useApolloClient()
 | 
					  const client = useApolloClient()
 | 
				
			||||||
  const schema = bountySchema(client)
 | 
					  const me = useMe()
 | 
				
			||||||
 | 
					  const schema = bountySchema(client, me)
 | 
				
			||||||
  const [upsertBounty] = useMutation(
 | 
					  const [upsertBounty] = useMutation(
 | 
				
			||||||
    gql`
 | 
					    gql`
 | 
				
			||||||
      mutation upsertBounty(
 | 
					      mutation upsertBounty(
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ import CancelButton from './cancel-button'
 | 
				
			|||||||
import { useCallback } from 'react'
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
import { normalizeForwards } from '../lib/form'
 | 
					import { normalizeForwards } from '../lib/form'
 | 
				
			||||||
import { MAX_TITLE_LENGTH } from '../lib/constants'
 | 
					import { MAX_TITLE_LENGTH } from '../lib/constants'
 | 
				
			||||||
 | 
					import { useMe } from './me'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DiscussionForm ({
 | 
					export function DiscussionForm ({
 | 
				
			||||||
  item, sub, editThreshold, titleLabel = 'title',
 | 
					  item, sub, editThreshold, titleLabel = 'title',
 | 
				
			||||||
@ -23,11 +24,11 @@ export function DiscussionForm ({
 | 
				
			|||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const router = useRouter()
 | 
					  const router = useRouter()
 | 
				
			||||||
  const client = useApolloClient()
 | 
					  const client = useApolloClient()
 | 
				
			||||||
  const schema = discussionSchema(client)
 | 
					  const me = useMe()
 | 
				
			||||||
 | 
					  const schema = discussionSchema(client, me)
 | 
				
			||||||
  // if Web Share Target API was used
 | 
					  // if Web Share Target API was used
 | 
				
			||||||
  const shareTitle = router.query.title
 | 
					  const shareTitle = router.query.title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // const me = useMe()
 | 
					 | 
				
			||||||
  const [upsertDiscussion] = useMutation(
 | 
					  const [upsertDiscussion] = useMutation(
 | 
				
			||||||
    gql`
 | 
					    gql`
 | 
				
			||||||
      mutation upsertDiscussion($sub: String, $id: ID, $title: String!, $text: String, $boost: Int, $forward: [ItemForwardInput], $hash: String, $hmac: String) {
 | 
					      mutation upsertDiscussion($sub: String, $id: ID, $title: String!, $text: String, $boost: Int, $forward: [ItemForwardInput], $hash: String, $hmac: String) {
 | 
				
			||||||
 | 
				
			|||||||
@ -16,11 +16,13 @@ import { SubSelectInitial } from './sub-select-form'
 | 
				
			|||||||
import CancelButton from './cancel-button'
 | 
					import CancelButton from './cancel-button'
 | 
				
			||||||
import { normalizeForwards } from '../lib/form'
 | 
					import { normalizeForwards } from '../lib/form'
 | 
				
			||||||
import { MAX_TITLE_LENGTH } from '../lib/constants'
 | 
					import { MAX_TITLE_LENGTH } from '../lib/constants'
 | 
				
			||||||
 | 
					import { useMe } from './me'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function LinkForm ({ item, sub, editThreshold, children }) {
 | 
					export function LinkForm ({ item, sub, editThreshold, children }) {
 | 
				
			||||||
  const router = useRouter()
 | 
					  const router = useRouter()
 | 
				
			||||||
  const client = useApolloClient()
 | 
					  const client = useApolloClient()
 | 
				
			||||||
  const schema = linkSchema(client)
 | 
					  const me = useMe()
 | 
				
			||||||
 | 
					  const schema = linkSchema(client, me)
 | 
				
			||||||
  // if Web Share Target API was used
 | 
					  // if Web Share Target API was used
 | 
				
			||||||
  const shareUrl = router.query.url
 | 
					  const shareUrl = router.query.url
 | 
				
			||||||
  const shareTitle = router.query.title
 | 
					  const shareTitle = router.query.title
 | 
				
			||||||
 | 
				
			|||||||
@ -12,11 +12,13 @@ import { SubSelectInitial } from './sub-select-form'
 | 
				
			|||||||
import CancelButton from './cancel-button'
 | 
					import CancelButton from './cancel-button'
 | 
				
			||||||
import { useCallback } from 'react'
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
import { normalizeForwards } from '../lib/form'
 | 
					import { normalizeForwards } from '../lib/form'
 | 
				
			||||||
 | 
					import { useMe } from './me'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function PollForm ({ item, sub, editThreshold, children }) {
 | 
					export function PollForm ({ item, sub, editThreshold, children }) {
 | 
				
			||||||
  const router = useRouter()
 | 
					  const router = useRouter()
 | 
				
			||||||
  const client = useApolloClient()
 | 
					  const client = useApolloClient()
 | 
				
			||||||
  const schema = pollSchema(client)
 | 
					  const me = useMe()
 | 
				
			||||||
 | 
					  const schema = pollSchema(client, me)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [upsertPoll] = useMutation(
 | 
					  const [upsertPoll] = useMutation(
 | 
				
			||||||
    gql`
 | 
					    gql`
 | 
				
			||||||
 | 
				
			|||||||
@ -58,7 +58,7 @@ async function usernameExists (client, name) {
 | 
				
			|||||||
  return !!user
 | 
					  return !!user
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function advPostSchemaMembers (client) {
 | 
					export function advPostSchemaMembers (client, me) {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    boost: intValidator
 | 
					    boost: intValidator
 | 
				
			||||||
      .min(BOOST_MIN, `must be blank or at least ${BOOST_MIN}`).test({
 | 
					      .min(BOOST_MIN, `must be blank or at least ${BOOST_MIN}`).test({
 | 
				
			||||||
@ -66,18 +66,25 @@ export function advPostSchemaMembers (client) {
 | 
				
			|||||||
        test: async boost => !boost || boost % BOOST_MIN === 0,
 | 
					        test: async boost => !boost || boost % BOOST_MIN === 0,
 | 
				
			||||||
        message: `must be divisble be ${BOOST_MIN}`
 | 
					        message: `must be divisble be ${BOOST_MIN}`
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
    // XXX this lets you forward to youself (it's financially equivalent but it should be disallowed)
 | 
					 | 
				
			||||||
    forward: array()
 | 
					    forward: array()
 | 
				
			||||||
      .max(MAX_FORWARDS, `you can only configure ${MAX_FORWARDS} forward recipients`)
 | 
					      .max(MAX_FORWARDS, `you can only configure ${MAX_FORWARDS} forward recipients`)
 | 
				
			||||||
      .of(object().shape({
 | 
					      .of(object().shape({
 | 
				
			||||||
        nym: string().required('must specify a stacker').test({
 | 
					        nym: string().required('must specify a stacker')
 | 
				
			||||||
          name: 'nym',
 | 
					          .test({
 | 
				
			||||||
          test: async name => {
 | 
					            name: 'nym',
 | 
				
			||||||
            if (!name || !name.length) return true
 | 
					            test: async name => {
 | 
				
			||||||
            return await usernameExists(client, name)
 | 
					              if (!name || !name.length) return false
 | 
				
			||||||
          },
 | 
					              return await usernameExists(client, name)
 | 
				
			||||||
          message: 'stacker does not exist'
 | 
					            },
 | 
				
			||||||
        }),
 | 
					            message: 'stacker does not exist'
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          .test({
 | 
				
			||||||
 | 
					            name: 'self',
 | 
				
			||||||
 | 
					            test: async name => {
 | 
				
			||||||
 | 
					              return me?.name !== name
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            message: 'cannot forward to yourself'
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
        pct: intValidator.required('must specify a percentage').min(1, 'percentage must be at least 1').max(100, 'percentage must not exceed 100')
 | 
					        pct: intValidator.required('must specify a percentage').min(1, 'percentage must be at least 1').max(100, 'percentage must not exceed 100')
 | 
				
			||||||
      }))
 | 
					      }))
 | 
				
			||||||
      .compact((v) => !v.nym && !v.pct)
 | 
					      .compact((v) => !v.nym && !v.pct)
 | 
				
			||||||
@ -100,35 +107,35 @@ export function subSelectSchemaMembers (client) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function bountySchema (client) {
 | 
					export function bountySchema (client, me) {
 | 
				
			||||||
  return object({
 | 
					  return object({
 | 
				
			||||||
    title: titleValidator,
 | 
					    title: titleValidator,
 | 
				
			||||||
    bounty: intValidator
 | 
					    bounty: intValidator
 | 
				
			||||||
      .min(1000, 'must be at least 1000')
 | 
					      .min(1000, 'must be at least 1000')
 | 
				
			||||||
      .max(1000000, 'must be at most 1m'),
 | 
					      .max(1000000, 'must be at most 1m'),
 | 
				
			||||||
    ...advPostSchemaMembers(client),
 | 
					    ...advPostSchemaMembers(client, me),
 | 
				
			||||||
    ...subSelectSchemaMembers()
 | 
					    ...subSelectSchemaMembers()
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function discussionSchema (client) {
 | 
					export function discussionSchema (client, me) {
 | 
				
			||||||
  return object({
 | 
					  return object({
 | 
				
			||||||
    title: titleValidator,
 | 
					    title: titleValidator,
 | 
				
			||||||
    ...advPostSchemaMembers(client),
 | 
					    ...advPostSchemaMembers(client, me),
 | 
				
			||||||
    ...subSelectSchemaMembers()
 | 
					    ...subSelectSchemaMembers()
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function linkSchema (client) {
 | 
					export function linkSchema (client, me) {
 | 
				
			||||||
  return object({
 | 
					  return object({
 | 
				
			||||||
    title: titleValidator,
 | 
					    title: titleValidator,
 | 
				
			||||||
    url: string().matches(URL_REGEXP, 'invalid url').required('required'),
 | 
					    url: string().matches(URL_REGEXP, 'invalid url').required('required'),
 | 
				
			||||||
    ...advPostSchemaMembers(client),
 | 
					    ...advPostSchemaMembers(client, me),
 | 
				
			||||||
    ...subSelectSchemaMembers()
 | 
					    ...subSelectSchemaMembers()
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function pollSchema (client, numExistingChoices = 0) {
 | 
					export function pollSchema (client, me, numExistingChoices = 0) {
 | 
				
			||||||
  return object({
 | 
					  return object({
 | 
				
			||||||
    title: titleValidator,
 | 
					    title: titleValidator,
 | 
				
			||||||
    options: array().of(
 | 
					    options: array().of(
 | 
				
			||||||
@ -144,7 +151,7 @@ export function pollSchema (client, numExistingChoices = 0) {
 | 
				
			|||||||
      message: `at least ${MIN_POLL_NUM_CHOICES} choices required`,
 | 
					      message: `at least ${MIN_POLL_NUM_CHOICES} choices required`,
 | 
				
			||||||
      test: arr => arr.length >= MIN_POLL_NUM_CHOICES - numExistingChoices
 | 
					      test: arr => arr.length >= MIN_POLL_NUM_CHOICES - numExistingChoices
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    ...advPostSchemaMembers(client),
 | 
					    ...advPostSchemaMembers(client, me),
 | 
				
			||||||
    ...subSelectSchemaMembers()
 | 
					    ...subSelectSchemaMembers()
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user