Skip to content

Commit 58a75e2

Browse files
authored
Setup cloudflare turnstile support (#268)
* Setup cloudflare turnstile support * Fix staging vs prod build * Fix coderabbit
1 parent 8e8a182 commit 58a75e2

File tree

12 files changed

+95
-25
lines changed

12 files changed

+95
-25
lines changed

.env.development

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ NEXT_PUBLIC_RUN_ENV=dev
22
NEXT_PUBLIC_TICKETING_BASE_URL='https://xclhkh0duc.execute-api.us-east-1.amazonaws.com/default'
33
NEXT_PUBLIC_MEMBERSHIP_BASE_URL='https://infra-membership-api.aws.qa.acmuiuc.org'
44
NEXT_PUBLIC_MERCH_API_BASE_URL='https://merchapi.acm.illinois.edu'
5-
NEXT_PUBLIC_EVENTS_API_BASE_URL='https://core.aws.qa.acmuiuc.org'
5+
NEXT_PUBLIC_CORE_API_BASE_URL='https://core.aws.qa.acmuiuc.org'
6+
NEXT_PUBLIC_TURNSTILE_SITE_KEY='1x00000000000000000000AA'

.env.production renamed to .env.prod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ NEXT_PUBLIC_RUN_ENV=prod
22
NEXT_PUBLIC_TICKETING_BASE_URL='https://ticketing.aws.acmuiuc.org'
33
NEXT_PUBLIC_MEMBERSHIP_BASE_URL='https://infra-membership-api.aws.acmuiuc.org'
44
NEXT_PUBLIC_MERCH_API_BASE_URL='https://merchapi.acm.illinois.edu'
5-
NEXT_PUBLIC_EVENTS_API_BASE_URL='https://core.acm.illinois.edu'
5+
NEXT_PUBLIC_CORE_API_BASE_URL='https://core.acm.illinois.edu'
6+
NEXT_PUBLIC_TURNSTILE_SITE_KEY='0x4AAAAAACLtNvWF7VjCKZfe'

.env.staging

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ NEXT_PUBLIC_RUN_ENV=dev
22
NEXT_PUBLIC_TICKETING_BASE_URL='https://xclhkh0duc.execute-api.us-east-1.amazonaws.com/default'
33
NEXT_PUBLIC_MEMBERSHIP_BASE_URL='https://infra-membership-api.aws.qa.acmuiuc.org'
44
NEXT_PUBLIC_MERCH_API_BASE_URL='https://merchapi.acm.illinois.edu'
5-
NEXT_PUBLIC_EVENTS_API_BASE_URL='https://core.acm.illinois.edu'
5+
NEXT_PUBLIC_CORE_API_BASE_URL='https://core.acm.illinois.edu'
6+
NEXT_PUBLIC_TURNSTILE_SITE_KEY='1x00000000000000000000AA'

.github/workflows/deploy.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ jobs:
3737
run: yarn -D
3838

3939
- name: Build website
40-
run: yarn build
40+
run: |
41+
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
42+
yarn build:prod
43+
else
44+
yarn build:staging
45+
fi
4146
4247
- name: Deploy to Cloudflare
4348
id: deploy

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
"scripts": {
77
"dev": "next dev",
88
"start": "yarn dev",
9-
"dev:events": "cross-env NEXT_PUBLIC_EVENTS_API_BASE_URL=https://infra-events-api.aws.qa.acmuiuc.org && next dev",
10-
"build": "next build && cp src/_redirects build/_redirects",
9+
"build:dev": "env-cmd -f .env.development next build && yarn postbuild",
10+
"build:prod": "env-cmd -f .env.prod next build && yarn postbuild",
11+
"build:staging": "env-cmd -f .env.staging next build && yarn postbuild",
12+
"postbuild": "cp src/_redirects build/_redirects",
1113
"serve": "serve build",
1214
"lint": "next lint",
1315
"storybook": "storybook dev -p 6006",
@@ -19,6 +21,7 @@
1921
"@azure/msal-browser": "^4.15.0",
2022
"@azure/msal-react": "^3.0.15",
2123
"@heroui/react": "2.7.6",
24+
"@marsidev/react-turnstile": "^1.4.1",
2225
"axios": "^0.28.0",
2326
"framer-motion": "^10.17.9",
2427
"lottie-react": "^2.3.1",
@@ -47,6 +50,7 @@
4750
"@types/react-dom": "^18",
4851
"autoprefixer": "^10.4.16",
4952
"cross-env": "^7.0.3",
53+
"env-cmd": "^11.0.0",
5054
"eslint": "^8",
5155
"eslint-config-next": "^14.2.5",
5256
"eslint-config-prettier": "^10.1.2",
@@ -63,4 +67,4 @@
6367
"jackspeak": "2.1.1"
6468
},
6569
"packageManager": "yarn@1.22.19"
66-
}
70+
}

src/app/(main)/calendar/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const Calendar = () => {
3333
const [validOrganizations, setValidOrganizations] =
3434
useState<string[]>(OrganizationList);
3535
useEffect(() => {
36-
const baseurl = process.env.NEXT_PUBLIC_EVENTS_API_BASE_URL;
36+
const baseurl = process.env.NEXT_PUBLIC_CORE_API_BASE_URL;
3737
if (!baseurl) {
3838
return setAllEvents([]);
3939
}

src/app/(membership)/admin/sync/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ interface ErrorCode {
3030
message: string;
3131
}
3232

33-
const baseUrl = process.env.NEXT_PUBLIC_EVENTS_API_BASE_URL;
3433
const WrappedSync = () => {
3534
return (
3635
<Suspense>

src/app/(membership)/check-membership/page.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,8 @@ interface ErrorCode {
3131
message: string;
3232
}
3333

34-
enum InputStatus {
35-
EMPTY,
36-
INVALID,
37-
VALID,
38-
}
39-
40-
const baseUrl = process.env.NEXT_PUBLIC_EVENTS_API_BASE_URL;
41-
const walletApiBaseUrl = process.env.NEXT_PUBLIC_EVENTS_API_BASE_URL;
34+
const baseUrl = process.env.NEXT_PUBLIC_CORE_API_BASE_URL;
35+
const walletApiBaseUrl = process.env.NEXT_PUBLIC_CORE_API_BASE_URL;
4236

4337
const Payment = () => {
4438
const [netId, setNetId] = useState('');

src/app/(membership)/membership/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ enum InputStatus {
3838
VALID,
3939
}
4040

41-
const baseUrl = process.env.NEXT_PUBLIC_EVENTS_API_BASE_URL;
41+
const baseUrl = process.env.NEXT_PUBLIC_CORE_API_BASE_URL;
4242
const WrappedPayment = () => {
4343
return (
4444
<Suspense>

src/app/(membership)/merch/page.tsx

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import Layout from '../MembershipLayout';
2525
import { IPublicClientApplication, AccountInfo } from '@azure/msal-browser';
2626
import { getUserAccessToken, initMsalClient } from '@/utils/msal';
2727
import { syncIdentity } from '@/utils/api';
28+
import { Turnstile } from '@marsidev/react-turnstile';
2829

2930
const decimalHelper = (num: number) => {
3031
if (Number.isInteger(num)) {
@@ -47,7 +48,11 @@ enum InputStatus {
4748
}
4849

4950
const baseUrl = process.env.NEXT_PUBLIC_MERCH_API_BASE_URL;
50-
const coreBaseUrl = process.env.NEXT_PUBLIC_EVENTS_API_BASE_URL;
51+
const coreBaseUrl = process.env.NEXT_PUBLIC_CORE_API_BASE_URL;
52+
const turnstileSiteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
53+
if (!turnstileSiteKey) {
54+
throw new Error("Turnstile site key missing.")
55+
}
5156

5257
const WrappedMerchItem = () => {
5358
return (
@@ -61,6 +66,7 @@ const MerchItem = () => {
6166
const itemid = useSearchParams().get('id') || '';
6267
const [merchList, setMerchList] = useState<Record<string, any>>({});
6368
const [pca, setPca] = useState<IPublicClientApplication | null>(null);
69+
const [token, setToken] = React.useState<string>()
6470

6571
// Form State
6672
const [email, setEmail] = useState('');
@@ -79,6 +85,14 @@ const MerchItem = () => {
7985

8086
const modalErrorMessage = useDisclosure();
8187
const [errorMessage, setErrorMessage] = useState<ErrorCode | null>(null);
88+
const clearTurnstileToken = () => setToken(undefined);
89+
const turnstileWidget = (id: string) => <Turnstile
90+
id={id}
91+
siteKey={turnstileSiteKey}
92+
onSuccess={setToken}
93+
onExpire={clearTurnstileToken}
94+
onError={clearTurnstileToken}
95+
/>;
8296

8397
useEffect(() => {
8498
(async () => {
@@ -243,27 +257,48 @@ const MerchItem = () => {
243257
if (!pca) {
244258
setErrorMessage({ code: 403, message: 'Authentication service is not initialized.' });
245259
modalErrorMessage.onOpen();
260+
setIsLoading(false);
261+
return;
262+
}
263+
264+
if (!token) {
265+
setErrorMessage({ code: 400, message: 'Please complete the security verification.' });
266+
modalErrorMessage.onOpen();
267+
setIsLoading(false);
246268
return;
247269
}
248270

249271
const accessToken = await getUserAccessToken(pca);
250272
if (!accessToken) {
251273
setErrorMessage({ code: 403, message: 'Failed to retrieve authentication token.' });
252274
modalErrorMessage.onOpen();
275+
setIsLoading(false);
253276
return;
254277
}
255278

256279
await syncIdentity(accessToken);
257280

258281
const url = `${baseUrl}/api/v1/checkout/session?itemid=${itemid}&size=${size}&quantity=${quantity}`;
259-
axios.get(url, { headers: { 'x-uiuc-token': accessToken } })
282+
axios.get(url, {
283+
headers: {
284+
'x-uiuc-token': accessToken,
285+
'x-turnstile-token': token,
286+
}
287+
})
260288
.then(response => window.location.replace(response.data))
261289
.catch(handleApiError);
262290
};
263291

264292
const purchaseHandler = async () => {
265293
setIsLoading(true);
266294

295+
if (!token) {
296+
setErrorMessage({ code: 400, message: 'Please complete the security verification.' });
297+
modalErrorMessage.onOpen();
298+
setIsLoading(false);
299+
return;
300+
}
301+
267302
if (selectedTab === 'illinois') {
268303
if (!pca || !user) {
269304
setErrorMessage({ code: 403, message: 'You must be logged in to purchase with Illinois Checkout.' });
@@ -275,7 +310,11 @@ const MerchItem = () => {
275310
} else { // Guest flow
276311
// Non-Illinois email, use guest checkout
277312
const url = `${baseUrl}/api/v1/checkout/session?itemid=${itemid}&size=${size}&quantity=${quantity}&email=${email}`;
278-
axios.get(url) // No auth token needed
313+
axios.get(url, {
314+
headers: {
315+
'x-turnstile-token': token,
316+
}
317+
})
279318
.then(response => window.location.replace(response.data))
280319
.catch(handleApiError);
281320
}
@@ -431,6 +470,7 @@ const MerchItem = () => {
431470
color={inputQuantityStatus === InputStatus.INVALID ? 'danger' : 'default'}
432471
errorMessage={inputQuantityStatus === InputStatus.INVALID && 'Invalid Quantity'}
433472
/>
473+
{turnstileWidget('wid1')}
434474
<Button
435475
color="primary" size="lg"
436476
isDisabled={!isFormValidated || isLoading || totalCapacity() === 0}
@@ -479,6 +519,7 @@ const MerchItem = () => {
479519
color={inputQuantityStatus === InputStatus.INVALID ? 'danger' : 'default'}
480520
errorMessage={inputQuantityStatus === InputStatus.INVALID && 'Invalid Quantity'}
481521
/>
522+
{turnstileWidget('wid2')}
482523
<Button
483524
color="primary" size="lg"
484525
isDisabled={!isFormValidated || isLoading || totalCapacity() === 0}

0 commit comments

Comments
 (0)