Skip to content

Commit

Permalink
feat: validate new event form with captcha
Browse files Browse the repository at this point in the history
  • Loading branch information
mplewis committed Nov 20, 2024
1 parent 17857d3 commit 2547322
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 7 deletions.
2 changes: 1 addition & 1 deletion api/src/graphql/events.sdl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const schema = gql`
ownerEmail: String!
title: String!
responseConfig: ResponseConfig!
captcha: String!
captchaResponse: String!
}
input UpdateEventInput {
Expand Down
25 changes: 25 additions & 0 deletions api/src/lib/captcha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import querystring from 'querystring'

import { RECAPTCHA_SERVER_KEY } from 'src/app.config'

import { logger } from './logger'

const RECAPTCHA_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify'

/** Return true if the given captcha token was valid, false otherwise. */
export async function validateCaptcha(token: string): Promise<boolean> {
try {
const data = { secret: RECAPTCHA_SERVER_KEY, response: token }
const res = await fetch(RECAPTCHA_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: querystring.stringify(data),
})
const json = await res.json()
logger.debug({ json }, 'reCAPTCHA response')
return json.success
} catch (err) {
logger.error({ err }, 'Failed to validate reCAPTCHA')
return false
}
}
12 changes: 10 additions & 2 deletions api/src/services/events/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import type {
PublicResponse,
} from 'types/graphql'

import { validate } from '@redwoodjs/api'
import { RedwoodError, validate } from '@redwoodjs/api'

import { validateCaptcha } from 'src/lib/captcha'
import { db } from 'src/lib/db'
import { sendEventDetails } from 'src/lib/email/template/event'
import { notifyEventCreated, notifyEventUpdated } from 'src/lib/notify/event'
Expand Down Expand Up @@ -98,7 +99,14 @@ export const eventByPreviewToken: QueryResolvers['eventByPreviewToken'] =
export const createEvent: MutationResolvers['createEvent'] = async ({
input: _input,
}) => {
const { captcha, ...input } = _input
const { captchaResponse, ...input } = _input

const valid = await validateCaptcha(captchaResponse)
if (!valid)
throw new RedwoodError(
'Could not validate reCAPTCHA. Please refresh the page and try again.'
)

validate(input.ownerEmail, 'email', { email: true })
validate(input.title, 'title', {
custom: {
Expand Down
1 change: 1 addition & 0 deletions app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ export const S3_REGION = process.env.S3_REGION
export const S3_BUCKET = process.env.S3_BUCKET
export const S3_NAMESPACE = process.env.S3_NAMESPACE

export const RECAPTCHA_SERVER_KEY = process.env.RECAPTCHA_SERVER_KEY
export const RECAPTCHA_CLIENT_KEY = process.env.REDWOOD_ENV_RECAPTCHA_CLIENT_KEY
15 changes: 11 additions & 4 deletions web/src/pages/NewEventPage/NewEventPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const NewEventPage = () => {
const { formState } = formMethods

const [redirecting, setRedirecting] = useState(false)
const [captcha, setCaptcha] = useState<string | null>(null)
const [captchaResponse, setCaptchaResponse] = useState<string | null>(null)

const [create, { loading, error }] = useMutation<
CreateEventMutation,
Expand All @@ -93,7 +93,9 @@ const NewEventPage = () => {
className="mt-3"
formMethods={formMethods}
onSubmit={(input: FormValues) =>
create({ variables: { input: { ...input, captcha } } })
create({
variables: { input: { ...input, captchaResponse } },
})
}
>
<FormField name="ownerEmail" text="Email Address*">
Expand Down Expand Up @@ -140,11 +142,16 @@ const NewEventPage = () => {
</SelectField>
</FormField>

<ReCAPTCHA sitekey={RECAPTCHA_CLIENT_KEY} onChange={setCaptcha} />
<ReCAPTCHA
sitekey={RECAPTCHA_CLIENT_KEY}
onChange={setCaptchaResponse}
/>

<Submit
className="button is-success mt-3"
disabled={loading || redirecting || !formState.isValid || !captcha}
disabled={
loading || redirecting || !formState.isValid || !captchaResponse
}
>
{loading || redirecting ? 'Creating...' : 'Create New Event'}
</Submit>
Expand Down

0 comments on commit 2547322

Please sign in to comment.