Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Captcha in forms #204

Merged
merged 6 commits into from
Mar 18, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
verify captcha token serverside
pettinarip committed Mar 14, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 0a3ecf1b6d8337502e275fdf686ba11170621741
5 changes: 3 additions & 2 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -37,5 +37,6 @@ GOOGLE_ACADEMIC_SHEET_NAME=
GOOGLE_DEVCON_SPREADSHEET_ID=
GOOGLE_DEVCON_SHEET_NAME=

# captcha sitekey
NEXT_PUBLIC_CAPTCHA_SITEKEY=
# captcha
NEXT_PUBLIC_HCAPTCHA_SITEKEY=
HCAPTCHA_SECRET=
9 changes: 9 additions & 0 deletions additional.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Fields, Files } from 'formidable';
nhsz marked this conversation as resolved.
Show resolved Hide resolved
import { IncomingMessage } from 'http';

declare module 'next' {
export interface NextApiRequest extends IncomingMessage {
fields?: Fields;
files?: Files;
}
}
8 changes: 3 additions & 5 deletions src/components/forms/AcademicGrantsForm.tsx
Original file line number Diff line number Diff line change
@@ -36,9 +36,7 @@ import {
} from './constants';
import { ACADEMIC_GRANTS_THANK_YOU_PAGE_URL, TOAST_OPTIONS } from '../../constants';

import { AcademicGrantsFormData, ApplyingAs, BasicForm, GrantsReferralSource } from '../../types';

interface AcademicGrantsFormForm extends AcademicGrantsFormData, BasicForm {}
import { AcademicGrantsFormData, ApplyingAs, GrantsReferralSource } from '../../types';

export const AcademicGrantsForm: FC = () => {
const router = useRouter();
@@ -53,7 +51,7 @@ export const AcademicGrantsForm: FC = () => {
value: '',
label: ''
});
const methods = useForm<AcademicGrantsFormForm>({
const methods = useForm<AcademicGrantsFormData>({
mode: 'onBlur'
});
const {
@@ -65,7 +63,7 @@ export const AcademicGrantsForm: FC = () => {
reset
} = methods;

const onSubmit = async ({ captchaToken, ...data }: AcademicGrantsFormForm) => {
const onSubmit = async (data: AcademicGrantsFormData) => {
return api.academicGrants
.submit(data)
.then(res => {
8 changes: 3 additions & 5 deletions src/components/forms/DevconGrantsForm.tsx
Original file line number Diff line number Diff line change
@@ -30,9 +30,7 @@ import {
} from './constants';
import { DEVCON_GRANTS_THANK_YOU_PAGE_URL, TOAST_OPTIONS } from '../../constants';

import { BasicForm, DevconGrantsFormData, EventFormat } from '../../types';

interface DevconGrantsFormForm extends DevconGrantsFormData, BasicForm {}
import { DevconGrantsFormData, EventFormat } from '../../types';

export const DevconGrantsForm: FC = () => {
const router = useRouter();
@@ -48,7 +46,7 @@ export const DevconGrantsForm: FC = () => {
EVENT_FORMAT_OPTIONS[2].value // hibrid
].includes((eventFormat as EventFormat).value);

const methods = useForm<DevconGrantsFormForm>({
const methods = useForm<DevconGrantsFormData>({
mode: 'onBlur'
});
const {
@@ -59,7 +57,7 @@ export const DevconGrantsForm: FC = () => {
reset
} = methods;

const onSubmit = async ({ captchaToken, ...data }: DevconGrantsFormForm) => {
const onSubmit = async (data: DevconGrantsFormData) => {
return api.devconGrants
.submit(data)
.then(res => {
8 changes: 3 additions & 5 deletions src/components/forms/GranteeFinanceForm.tsx
Original file line number Diff line number Diff line change
@@ -23,16 +23,14 @@ import { api } from './api';

import { GRANTEE_FINANCE_THANK_YOU_PAGE_URL, TOAST_OPTIONS } from '../../constants';

import { GranteeFinanceFormData, TokenPreference, PaymentPreference, BasicForm } from '../../types';

interface GranteeFinanceFormForm extends GranteeFinanceFormData, BasicForm {}
import { GranteeFinanceFormData, TokenPreference, PaymentPreference } from '../../types';

export const GranteeFinanceForm: FC = () => {
const [paymentPreference, setPaymentPreference] = useState<PaymentPreference>('');
const [tokenPreference, setTokenPreference] = useState<TokenPreference>('ETH');
const router = useRouter();
const toast = useToast();
const methods = useForm<GranteeFinanceFormForm>({
const methods = useForm<GranteeFinanceFormData>({
mode: 'onBlur'
});
const {
@@ -49,7 +47,7 @@ export const GranteeFinanceForm: FC = () => {
const preferETH = receivesCrypto && tokenPreference === 'ETH';
const preferDAI = receivesCrypto && tokenPreference === 'DAI';

const onSubmit = async ({ captchaToken, ...data }: GranteeFinanceFormForm) => {
const onSubmit = async (data: GranteeFinanceFormData) => {
return api.granteeFinance
.submit(data)
.then(res => {
8 changes: 3 additions & 5 deletions src/components/forms/OfficeHoursForm.tsx
Original file line number Diff line number Diff line change
@@ -35,17 +35,15 @@ import {
} from './constants';
import { OFFICE_HOURS_THANK_YOU_PAGE_URL, TOAST_OPTIONS } from '../../constants';

import { BasicForm, IndividualOrTeam, OfficeHoursFormData, ReasonForMeeting } from '../../types';

interface OfficeHoursForm extends OfficeHoursFormData, BasicForm {}
import { IndividualOrTeam, OfficeHoursFormData, ReasonForMeeting } from '../../types';

export const OfficeHoursForm: FC = () => {
const [individualOrTeam, setIndividualOrTeam] = useState<IndividualOrTeam>('Individual');
const [reasonForMeeting, setReasonForMeeting] = useState<ReasonForMeeting>(['']);
const router = useRouter();
const toast = useToast();

const methods = useForm<OfficeHoursForm>({
const methods = useForm<OfficeHoursFormData>({
mode: 'onBlur'
});
const {
@@ -57,7 +55,7 @@ export const OfficeHoursForm: FC = () => {
reset
} = methods;

const onSubmit = async ({ captchaToken, ...data }: OfficeHoursForm) => {
const onSubmit = async (data: OfficeHoursFormData) => {
return api.officeHours
.submit(data)
.then(res => {
8 changes: 3 additions & 5 deletions src/components/forms/ProjectGrantsForm.tsx
Original file line number Diff line number Diff line change
@@ -43,11 +43,9 @@ import {
TOAST_OPTIONS
} from '../../constants';

import { BasicForm, ProjectGrantsFormData, ReferralSource } from '../../types';
import { ProjectGrantsFormData, ReferralSource } from '../../types';
import { RemoveIcon } from '../UI/icons';

interface ProjectGrantsForm extends ProjectGrantsFormData, BasicForm {}

export const ProjectGrantsForm: FC = () => {
const router = useRouter();
const toast = useToast();
@@ -58,7 +56,7 @@ export const ProjectGrantsForm: FC = () => {
label: ''
});

const methods = useForm<ProjectGrantsForm>({
const methods = useForm<ProjectGrantsFormData>({
mode: 'onBlur'
});

@@ -90,7 +88,7 @@ export const ProjectGrantsForm: FC = () => {
);
const { getRootProps, getInputProps } = useDropzone({ onDrop });

const onSubmit = async ({ captchaToken, ...data }: ProjectGrantsForm) => {
const onSubmit = async (data: ProjectGrantsFormData) => {
return api.projectGrants
.submit(data)
.then(res => {
7 changes: 2 additions & 5 deletions src/components/forms/SmallGrantsForm.tsx
Original file line number Diff line number Diff line change
@@ -35,15 +35,12 @@ import {
import { SMALL_GRANTS_THANK_YOU_PAGE_URL, TOAST_OPTIONS } from '../../constants';

import {
BasicForm,
IndividualOrTeam,
ProjectCategory,
RepeatApplicant,
SmallGrantsFormData
} from '../../types';

interface SmallGrantsFormForm extends SmallGrantsFormData, BasicForm {}

export const SmallGrantsForm: FC = () => {
const router = useRouter();
const toast = useToast();
@@ -55,7 +52,7 @@ export const SmallGrantsForm: FC = () => {
label: ''
});

const methods = useForm<SmallGrantsFormForm>({
const methods = useForm<SmallGrantsFormData>({
mode: 'onBlur'
});
const {
@@ -72,7 +69,7 @@ export const SmallGrantsForm: FC = () => {

const isAnEvent = (projectCategory as ProjectCategory).value === COMMUNITY_EVENT;

const onSubmit = async ({ captchaToken, ...data }: SmallGrantsFormForm) => {
const onSubmit = async (data: SmallGrantsFormData) => {
return api.smallGrants
.submit(data, isAProject)
.then(res => {
24 changes: 19 additions & 5 deletions src/components/forms/fields/Captcha.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
import React, { FC, useEffect } from 'react';
import React, { FC, useCallback, useEffect, useRef } from 'react';
import HCaptcha from '@hcaptcha/react-hcaptcha';
import { useFormContext } from 'react-hook-form';

export const Captcha: FC = () => {
const { register, reset, setValue, getValues } = useFormContext();
const captchaRef = useRef<HCaptcha>(null);
const { register, reset: resetForm, setValue, getValues, formState } = useFormContext();

const reset = useCallback(() => {
const { captchaToken, ...values } = getValues();
resetForm({ ...values });
}, [getValues, resetForm]);

useEffect(() => {
register('captchaToken', { required: true });
});

useEffect(() => {
// Whenever the form is submitted reset the captcha input
if (formState.isSubmitted && captchaRef.current) {
captchaRef.current.resetCaptcha();
reset();
}
}, [formState, reset]);

const onVerify = (token: string) => {
setValue('captchaToken', token, { shouldValidate: true });
};

const onExpire = () => {
// when token expires, reset the captcha field
const { captchaToken, ...values } = getValues();
reset({ ...values });
reset();
};

return (
<HCaptcha
sitekey={process.env.NEXT_PUBLIC_CAPTCHA_SITEKEY!}
ref={captchaRef}
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITEKEY!}
onVerify={onVerify}
onExpire={onExpire}
/>
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -157,6 +157,7 @@ export const NAV_LINKS: NavLink[] = [
// external links
export const ETHRESEARCH_URL = 'https://ethresear.ch/';
export const DEVCON_URL = 'https://devcon.org/';
export const HCAPTCHA_VERIFY_URL = 'https://hcaptcha.com/siteverify';

// api
export const DOWNLOAD_APPLICATION_URL = '/projectGrantsApplication.docx';
2 changes: 2 additions & 0 deletions src/middlewares/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './verifyCaptcha';
export * from './multipartyParse';
25 changes: 25 additions & 0 deletions src/middlewares/multipartyParse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import formidable from 'formidable';
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';

/**
* Parses multipart/form-data
*/
export const multipartyParse =
(handler: NextApiHandler, options: formidable.Options) =>
(req: NextApiRequest, res: NextApiResponse) => {
const form = formidable(options);

form.parse(req, async (err, fields, files) => {
if (err) {
console.error(err);
res.status(400).json({ status: 'fail' });
return;
}

// Extend `req` object with parsed fields and files
req.fields = fields;
req.files = files;

handler(req, res);
});
};
43 changes: 43 additions & 0 deletions src/middlewares/verifyCaptcha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';

import { HCAPTCHA_VERIFY_URL } from '../constants';

const { HCAPTCHA_SECRET } = process.env;

/**
* Verifies client captcha token against hCaptcha endpoint
*/
export const verifyCaptcha =
(handler: NextApiHandler) => async (req: NextApiRequest, res: NextApiResponse) => {
let captchaToken;

if (req.fields?.captchaToken) {
captchaToken = req.fields.captchaToken;
}

if (req.body?.captchaToken) {
captchaToken = req.body.captchaToken;
}

if (!captchaToken) {
return res.status(400).json({ status: 'captcha failed' });
}

const response = await fetch(HCAPTCHA_VERIFY_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `response=${captchaToken}&secret=${HCAPTCHA_SECRET}`
});

if (!response.ok) {
return res.status(400).json({ status: 'captcha failed' });
}

const { success } = await response.json();

if (!success) {
return res.status(400).json({ status: 'captcha failed' });
}

return handler(req, res);
};
5 changes: 4 additions & 1 deletion src/pages/api/academic-grants.ts
Original file line number Diff line number Diff line change
@@ -2,11 +2,12 @@ import jsforce from 'jsforce';
import { NextApiRequest, NextApiResponse } from 'next';

import addRowToSpreadsheet from '../../utils/addRowToSpreadsheet';
import { verifyCaptcha } from '../../middlewares';

const googleSpreadsheetId = process.env.GOOGLE_ACADEMIC_SPREADSHEET_ID;
const googleSheetName = process.env.GOOGLE_ACADEMIC_SHEET_NAME;

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { body } = req;
const {
firstName: FirstName,
@@ -122,3 +123,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
});
}

export default verifyCaptcha(handler);
5 changes: 4 additions & 1 deletion src/pages/api/devcon-grants.ts
Original file line number Diff line number Diff line change
@@ -2,11 +2,12 @@ import jsforce from 'jsforce';
import { NextApiRequest, NextApiResponse } from 'next';

import addRowToSpreadsheet from '../../utils/addRowToSpreadsheet';
import { verifyCaptcha } from '../../middlewares';

const googleSpreadsheetId = process.env.GOOGLE_DEVCON_SPREADSHEET_ID;
const googleSheetName = process.env.GOOGLE_DEVCON_SHEET_NAME;

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { body } = req;
const {
firstName: FirstName,
@@ -102,3 +103,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
});
}

export default verifyCaptcha(handler);
6 changes: 5 additions & 1 deletion src/pages/api/grantee-finance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import jsforce from 'jsforce';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
import { verifyCaptcha } from '../../middlewares';

async function handler(req: NextApiRequest, res: NextApiResponse) {
const { body } = req;
const {
beneficiaryName: Beneficiary_Name__c,
@@ -73,3 +75,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
});
}

export default verifyCaptcha(handler);
6 changes: 5 additions & 1 deletion src/pages/api/office-hours.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import jsforce from 'jsforce';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
import { verifyCaptcha } from '../../middlewares';

async function handler(req: NextApiRequest, res: NextApiResponse) {
const { body } = req;
const {
firstName: FirstName,
@@ -62,3 +64,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
);
});
}

export default verifyCaptcha(handler);
223 changes: 109 additions & 114 deletions src/pages/api/project-grants.ts
Original file line number Diff line number Diff line change
@@ -1,137 +1,128 @@
import fs from 'fs';
import jsforce from 'jsforce';
import { NextApiRequest, NextApiResponse } from 'next';
import formidable, { File } from 'formidable';
import type { File } from 'formidable';

import { multipartyParse, verifyCaptcha } from '../../middlewares';
import { MAX_PROPOSAL_FILE_SIZE } from '../../constants';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const form = formidable({
maxFileSize: MAX_PROPOSAL_FILE_SIZE
});
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { fields = {}, files = {} } = req;

form.parse(req, (err, fields, files) => {
if (err) {
console.error(err);
res.status(400).json({ status: 'fail' });
return;
const fieldsSanitized = Object.keys(fields).reduce<typeof fields>((prev, key) => {
let value = fields[key];
if (typeof value === 'string') {
value = value.trim();
}

const fieldsSanitized = Object.keys(fields).reduce<typeof fields>((prev, key) => {
let value = fields[key];
if (typeof value === 'string') {
value = value.trim();
}

return {
...prev,
[key]: value
};
}, {});

const { SF_PROD_LOGIN_URL, SF_PROD_USERNAME, SF_PROD_PASSWORD, SF_PROD_SECURITY_TOKEN } =
process.env;
return {
...prev,
[key]: value
};
}, {});

const conn = new jsforce.Connection({
// you can change loginUrl to connect to sandbox or prerelease env.
loginUrl: SF_PROD_LOGIN_URL
});
const { SF_PROD_LOGIN_URL, SF_PROD_USERNAME, SF_PROD_PASSWORD, SF_PROD_SECURITY_TOKEN } =
process.env;

const application = {
FirstName: fieldsSanitized.firstName,
LastName: fieldsSanitized.lastName,
Email: fieldsSanitized.email,
Company: fieldsSanitized.company,
Project_Name__c: fieldsSanitized.projectName,
Website: fieldsSanitized.website,
Github_Link__c: fieldsSanitized.github,
Twitter__c: fieldsSanitized.twitter,
Team_Profile__c: fieldsSanitized.teamProfile,
Project_Description__c: fieldsSanitized.projectDescription,
Category__c: fieldsSanitized.projectCategory,
Requested_Amount__c: fieldsSanitized.requestedAmount,
npsp__CompanyCity__c: fieldsSanitized.city,
npsp__CompanyCountry__c: fieldsSanitized.country,
Time_Zone__c: fieldsSanitized.timezone,
Referral_Source__c: fieldsSanitized.howDidYouHearAboutESP,
Referral_Source_if_Other__c: fieldsSanitized.referralSourceIfOther,
Referrals__c: fieldsSanitized.referrals,
RecordTypeId: process.env.SF_RECORD_TYPE_PROJECT_GRANTS
};
const conn = new jsforce.Connection({
// you can change loginUrl to connect to sandbox or prerelease env.
loginUrl: SF_PROD_LOGIN_URL
});

conn.login(SF_PROD_USERNAME!, `${SF_PROD_PASSWORD}${SF_PROD_SECURITY_TOKEN}`, err => {
if (err) {
return console.error(err);
}
const application = {
FirstName: fieldsSanitized.firstName,
LastName: fieldsSanitized.lastName,
Email: fieldsSanitized.email,
Company: fieldsSanitized.company,
Project_Name__c: fieldsSanitized.projectName,
Website: fieldsSanitized.website,
Github_Link__c: fieldsSanitized.github,
Twitter__c: fieldsSanitized.twitter,
Team_Profile__c: fieldsSanitized.teamProfile,
Project_Description__c: fieldsSanitized.projectDescription,
Category__c: fieldsSanitized.projectCategory,
Requested_Amount__c: fieldsSanitized.requestedAmount,
npsp__CompanyCity__c: fieldsSanitized.city,
npsp__CompanyCountry__c: fieldsSanitized.country,
Time_Zone__c: fieldsSanitized.timezone,
Referral_Source__c: fieldsSanitized.howDidYouHearAboutESP,
Referral_Source_if_Other__c: fieldsSanitized.referralSourceIfOther,
Referrals__c: fieldsSanitized.referrals,
RecordTypeId: process.env.SF_RECORD_TYPE_PROJECT_GRANTS
};

conn.login(SF_PROD_USERNAME!, `${SF_PROD_PASSWORD}${SF_PROD_SECURITY_TOKEN}`, err => {
if (err) {
return console.error(err);
}

let createdLeadID: string;
let createdLeadID: string;

// Single record creation
conn.sobject('Lead').create(application, (err, ret) => {
if (err || !ret.success) {
console.error(err);
res.status(400).json({ status: 'fail' });
} else {
console.log(`Project Grants Lead with ID: ${ret.id} has been created!`);
// Single record creation
conn.sobject('Lead').create(application, (err, ret) => {
if (err || !ret.success) {
console.error(err);
res.status(400).json({ status: 'fail' });
} else {
console.log(`Project Grants Lead with ID: ${ret.id} has been created!`);

createdLeadID = ret.id;
console.log({ createdLeadID });
createdLeadID = ret.id;
console.log({ createdLeadID });

const uploadProposal = files.uploadProposal as File;
console.log({ uploadProposal });
const uploadProposal = files.uploadProposal as File;
console.log({ uploadProposal });

if (!uploadProposal) {
res.status(200).json({ status: 'ok' });
return;
}
if (!uploadProposal) {
res.status(200).json({ status: 'ok' });
return;
}

let uploadProposalContent;
try {
// turn file into base64 encoding
uploadProposalContent = fs.readFileSync(uploadProposal.filepath, {
encoding: 'base64'
});
} catch (error) {
console.error(error);
res.status(500).json({ status: 'fail' });
return;
}
let uploadProposalContent;
try {
// turn file into base64 encoding
uploadProposalContent = fs.readFileSync(uploadProposal.filepath, {
encoding: 'base64'
});
} catch (error) {
console.error(error);
res.status(500).json({ status: 'fail' });
return;
}

// Document upload
conn.sobject('ContentVersion').create(
{
Title: `[PROPOSAL] ${application.Project_Name__c} - ${createdLeadID}`,
PathOnClient: uploadProposal.originalFilename,
VersionData: uploadProposalContent // base64 encoded file content
},
async (err, uploadedFile) => {
if (err || !uploadedFile.success) {
console.error(err);

res.status(400).json({ status: 'fail' });
} else {
console.log({ uploadedFile });
console.log(`Document has been uploaded successfully!`);

const contentDocument = await conn
.sobject<{
Id: string;
ContentDocumentId: string;
}>('ContentVersion')
.retrieve(uploadedFile.id);

await conn.sobject('ContentDocumentLink').create({
ContentDocumentId: contentDocument.ContentDocumentId,
LinkedEntityId: createdLeadID,
ShareType: 'V'
});

res.status(200).json({ status: 'ok' });
}
// Document upload
conn.sobject('ContentVersion').create(
{
Title: `[PROPOSAL] ${application.Project_Name__c} - ${createdLeadID}`,
PathOnClient: uploadProposal.originalFilename,
VersionData: uploadProposalContent // base64 encoded file content
},
async (err, uploadedFile) => {
if (err || !uploadedFile.success) {
console.error(err);

res.status(400).json({ status: 'fail' });
} else {
console.log({ uploadedFile });
console.log(`Document has been uploaded successfully!`);

const contentDocument = await conn
.sobject<{
Id: string;
ContentDocumentId: string;
}>('ContentVersion')
.retrieve(uploadedFile.id);

await conn.sobject('ContentDocumentLink').create({
ContentDocumentId: contentDocument.ContentDocumentId,
LinkedEntityId: createdLeadID,
ShareType: 'V'
});

res.status(200).json({ status: 'ok' });
}
);
}
});
}
);
}
});
});
}
@@ -141,3 +132,7 @@ export const config = {
bodyParser: false
}
};

export default multipartyParse(verifyCaptcha(handler), {
maxFileSize: MAX_PROPOSAL_FILE_SIZE
});
6 changes: 5 additions & 1 deletion src/pages/api/small-grants/event.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import jsforce from 'jsforce';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
import { verifyCaptcha } from '../../../middlewares';

async function handler(req: NextApiRequest, res: NextApiResponse) {
const { body } = req;
const {
firstName: FirstName,
@@ -86,3 +88,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
);
});
}

export default verifyCaptcha(handler);
6 changes: 5 additions & 1 deletion src/pages/api/small-grants/project.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import jsforce from 'jsforce';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
import { verifyCaptcha } from '../../../middlewares';

async function handler(req: NextApiRequest, res: NextApiResponse) {
const { body } = req;
const {
firstName: FirstName,
@@ -88,3 +90,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
);
});
}

export default verifyCaptcha(handler);
20 changes: 10 additions & 10 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -17,15 +17,15 @@ export interface TabsMap {
[name: string]: number;
}

export interface BasicForm {
export interface CaptchaForm {
captchaToken: string;
}

export type NewsletterFormData = {
email: string;
};

export type ProjectGrantsFormData = {
export interface ProjectGrantsFormData extends CaptchaForm {
firstName: string; // SF API: FirstName
lastName: string; // SF API: LastName
email: string; // SF API: Email
@@ -45,9 +45,9 @@ export type ProjectGrantsFormData = {
referralSourceIfOther: string; // SF API: Referral_Source_if_Other__c
referrals: string; // SF API: Referrals__c
uploadProposal: File;
};
}

export type SmallGrantsFormData = {
export interface SmallGrantsFormData extends CaptchaForm {
firstName: string; // SF API: FirstName
lastName: string; // SF API: LastName
email: string; // SF API: Email
@@ -87,9 +87,9 @@ export type SmallGrantsFormData = {
eventBudgetBreakdown: string; // SF API: Proposed_Timeline__c
eventRequestedAmount: string; // SF API: Sponsorship_Monetary_Request__c
additionalInfo: string; // SF API: Additional_Information__c
};
}

export type GranteeFinanceFormData = {
export interface GranteeFinanceFormData extends CaptchaForm {
// these fields map to SF's Contract object fields
paymentPreference: PaymentPreference;
beneficiaryName: string; // SF API: Beneficiary_Name__c
@@ -114,9 +114,9 @@ export type GranteeFinanceFormData = {
bankAddress: string; // SF API: Bank_Address__c
IBAN: string; // SF API: IBAN_Account_Number__c
SWIFTCode: string; // SF API: SWIFT_Code_BIC__c
};
}

export interface OfficeHoursFormData {
export interface OfficeHoursFormData extends CaptchaForm {
firstName: string; // SF API: FirstName
lastName: string; // SF API: LastName
email: string; // SF API: Email
@@ -132,7 +132,7 @@ export interface OfficeHoursFormData {
timezone: Timezone; // SF API: Time_Zone__c
}

export interface AcademicGrantsFormData {
export interface AcademicGrantsFormData extends CaptchaForm {
firstName: string; // SF API: FirstName
lastName: string; // SF API: LastName
email: string; // SF API: Email
@@ -168,7 +168,7 @@ export interface AcademicGrantsFormData {
additionalInfo: string; // SF API: Additional_Information__c
}

export interface DevconGrantsFormData {
export interface DevconGrantsFormData extends CaptchaForm {
firstName: string; // SF API: FirstName
lastName: string; // SF API: LastName
email: string; // SF API: Email
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -15,6 +15,6 @@
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "additional.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}