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:
SatsAllDay 2023-09-12 12:56:59 -04:00 committed by GitHub
parent 16a6f93708
commit 1a6dc879a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 41 additions and 27 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,17 +66,24 @@ 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')
.test({
name: 'nym', name: 'nym',
test: async name => { test: async name => {
if (!name || !name.length) return true if (!name || !name.length) return false
return await usernameExists(client, name) 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')
})) }))
@ -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()
}) })
} }