Skip to content

Instantly share code, notes, and snippets.

@bcnzer
Last active September 28, 2022 22:26
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bcnzer/5911b9375df24489ad327fecf6e4878c to your computer and use it in GitHub Desktop.
Save bcnzer/5911b9375df24489ad327fecf6e4878c to your computer and use it in GitHub Desktop.
Example of a Cloudflare Worker handling Google reCAPTCHA in an "edged out" POST request
// NOTE: all auth code has been removed for the sake of brevity. Please see part 2 of blog series
// for more info: https://liftcodeplay.com/2018/10/16/pushing-my-api-to-the-edge-part-2-authentication-and-authorization/
addEventListener('fetch', event => {
event.respondWith(handleRequest(event))
})
const genderFemale = 'Female'
const genderMale = 'Male'
/**
* Double-check all the fields that got sent
*/
function validateEntryForm(body) {
if (!body) return false
if (!body.firstName || !body.lastName || !body.dateOfBirth || !body.nominatedDivision || !body.nominatedWeightClass) return false
const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
if (!body.emailAddress || !emailRegex.test(body.emailAddress)) return false
const mobileNumberRegex = /^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/
if (!body.mobileNumber || !mobileNumberRegex.test(body.mobileNumber)) return false
console.log(body.gender)
if (!body.gender || !(body.gender === genderMale || body.gender === genderFemale)) return false
const pbRegex = /^\d+\.?\d*$/
if (!!body.personalBestSquat && !pbRegex.test(body.personalBestSquat)) return false
if (!!body.personalBestBench && !pbRegex.test(body.personalBestBench)) return false
if (!!body.personalBestDeadlift && !pbRegex.test(body.personalBestDeadlift)) return false
return true
}
/**
* Entry point of the worker
*/
async function handleRequest(event) {
// Generate the CORS headers I'll have to return with requests
const corsHeaders = setCorsHeaders(new Headers())
try {
const requestMethod = event.request.method
const requestUrl = new URL(event.request.url)
console.log(requestUrl)
// Always return the same CORS info
if(requestMethod === 'OPTIONS') {
return new Response('', { headers:corsHeaders })
}
// POST the ENTRY DATA to the queue but first check the reCAPTCHA
if (requestMethod === 'POST' && requestUrl.hostname === 'api.powerlifting.com' && requestUrl.pathname === '/v1/entry') {
console.log(event.request)
const requestBody = await event.request.json()
if (!validateEntryForm(requestBody)) {
return new Response('Invalid body', { status: 400, headers:corsHeaders })
}
const recaptchaToken = event.request.headers.get('g-recaptcha')
if (!recaptchaToken) {
return new Response('Invalid reCAPTCHA', { status: 400, headers:corsHeaders })
}
// My reCAPTCHA secret is stored in KV. KVs are encrypted at rest and in transit
const recaptchaSecret = await SIGNUP_DATA.get('recaptchasecret')
const recaptchaResponse = await fetch(
`https://www.google.com/recaptcha/api/siteverify?secret=${recaptchaSecret}&response=${recaptchaToken}`, {
method: 'POST'
})
const recaptchaBody = await recaptchaResponse.json()
if (recaptchaBody.success == true) {
/* TODO - Send it onto a queue - make a REST API call. Could be something like this:
await fetch(
`https://myqueueurl.com/${someValue}`, {
method: 'POST',
body: requestBody
})
*/
return new Response('', { status: 202, headers:corsHeaders })
} else {
return new Response('reCAPTCHA failed', { status: 400, headers:corsHeaders })
}
}
// GET the competition info from KV
const entryRegex = /^\/v1\/entry\/\d+$/ // i.e. /v1/entry/17043
if (requestMethod === 'GET' && requestUrl.hostname === 'api.powerlifting.com' && entryRegex.test(requestUrl.pathname)) {
const entryCompId = requestUrl.pathname.split('/')[3]
console.log(entryCompId)
const entryData = await SIGNUP_DATA.get(entryCompId)
return new Response(entryData, { status: 200, headers:corsHeaders })
}
// Get ALL the WEIGHT CLASSES and DIVISIONS from KV and return them
if (requestMethod === 'GET' && requestUrl.hostname === 'api.powerlifting.com' && requestUrl.pathname === '/v1/entry/weightclassanddivisions') {
return getWeightClassAndDivisions(corsHeaders)
}
// Get the DIVISIONS from KV and return them
if (requestMethod === 'GET' && requestUrl.hostname === 'api.powerlifting.com' && requestUrl.pathname === '/v1/entry/divisions') {
return getDivisions(requestUrl, corsHeaders)
}
// Get the WEIGHT CLASSES from KV and return them
if (requestMethod === 'GET' && requestUrl.hostname === 'api.powerlifting.com' && requestUrl.pathname === '/v1/entry/weightclasses') {
return getWeightClasses(requestUrl, corsHeaders)
}
const response = await fetch(event.request)
return response
}
catch (err) {
console.error(err)
return new Response(err.stack, { status: 500, headers:corsHeaders })
}
}
function setCorsHeaders(headers) {
headers.set('Access-Control-Allow-Origin', '*')
headers.set('Access-Control-Allow-Methods', 'POST, GET')
headers.set('Access-Control-Allow-Headers', 'access-control-allow-headers, g-recaptcha')
headers.set('Access-Control-Max-Age', 1728000)
return headers
}
/**
* Get the static DIVISIONS from KV and return them
*/
async function getDivisions(requestUrl, corsHeaders) {
const age = requestUrl.searchParams.get('age')
const gender = requestUrl.searchParams.get('gender')
if (!!gender && gender !== genderFemale && gender !== genderMale) {
return new Response('', { status: 400, headers:corsHeaders })
}
const storedDivisions = await SIGNUP_DATA.get('ipf2018')
let divisions = JSON.parse(storedDivisions).divisions
if (!!age || !!gender) {
divisions = divisions.filter(function(div) {
if (!!age && !!gender) {
// If you specify an age you must specify a gender
return age >= div.startAge && age <= div.endAge && gender === div.gender
} else if (!age && !!gender) {
// It's fine if you just specify a gender
return gender === div.gender
} else if (!!age && !gender) {
// Not ok if you specify an age but no gender
return false
}
// Return everything
return true
})
}
divisions = divisions.map(function(div) {
return {
"gender": div.gender,
'name': div.name,
'abbrev': div.abbrev
}
})
console.log(divisions)
return new Response(JSON.stringify(divisions), { status: 200, headers:corsHeaders })
}
/**
* Get the static WEIGHT CLASSES from KV and return them
*/
async function getWeightClasses(requestUrl, corsHeaders) {
const weight = requestUrl.searchParams.get('weight')
const gender = requestUrl.searchParams.get('gender')
if (!!gender && gender !== genderFemale && gender !== genderMale) {
return new Response('', { status: 400, headers:corsHeaders })
}
const storedWeightClasses = await SIGNUP_DATA.get('ipf2018')
let weightClasses = JSON.parse(storedWeightClasses).weightClasses
if (!!weight) {
weightClasses = weightClasses.filter(function(wc) {
return weight >= wc.startWeight && weight <= wc.endWeight
})
}
if (!!weight || !!gender) {
weightClasses = weightClasses.filter(function(wc) {
if (!!weight && !!gender) {
// If you specify an weight you must specify a gender
return weight >= wc.startWeight && weight <= wc.endWeight && gender === wc.gender
} else if (!weight && !!gender) {
// It's fine if you just specify a gender
return gender === wc.gender
} else if (!!weight && !gender) {
// Not ok if you specify a weight but no gender
return false
}
// Return everything
return true
})
}
weightClasses = weightClasses.map(function(wc) {
return {
"gender": wc.gender,
'name': wc.name
}
})
console.log(weightClasses)
return new Response(JSON.stringify(weightClasses), { status: 200, headers:corsHeaders })
}
/**
* Get all the weight classes and divisions, for both headers, and return it as one data set
*/
async function getWeightClassAndDivisions(corsHeaders) {
let storeData = await SIGNUP_DATA.get('ipf2018')
return new Response(storeData, { status: 200, headers:corsHeaders })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment