Skip to content

Instantly share code, notes, and snippets.

@justsml
Created February 23, 2020 23:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justsml/04083707a30f34859d126b2e98e74967 to your computer and use it in GitHub Desktop.
Save justsml/04083707a30f34859d126b2e98e74967 to your computer and use it in GitHub Desktop.
Uses Cloudflare KV data store & Google ReCaptcha!
// Note to self: Currently running here: https://dash.cloudflare.com/eb29900091c43bef22edfb71df934698/workers/edit/fanclub
/*
## Utility Functions
*/
const isEmailShaped = email => email && email.length > 5 && email.indexOf('@') > -1
const sendJSON = (data, status = 200) => new Response(
typeof data !== 'string' ? JSON.stringify(data) : data, {
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
status
})
/*
## Data Methods
*/
const usersDB = {
async find(key) {
const result = await FANCLUB.get(key)
return result && result.length >= 1 ? JSON.parse(result) : result
},
async create({name, email, ip, originalReferer, referer}) {
return FANCLUB.put(email, JSON.stringify({name, email, ip, originalReferer, referer, verified: false}))
},
async list({ limit = 100 } = {}) {
const results = await FANCLUB.list({ limit })
return (results && results.keys).map(o => o.name)
}
}
/*
## Request Handlers
*/
const handlers = {
async listUsers(request) {
const keyList = await usersDB.list({ limit: 100 })
return sendJSON(keyList)
},
async signupUser(request) {
try {
const secret = await CONFIG.get('RECAPTCHA_SECRET')
const ip = request.headers.get('CF-Connecting-IP')
const body = JSON.parse(await request.text())
const {name, email, originalReferer, referer} = body
const {recaptcha} = body
if (!isEmailShaped(email)) throw new Error('Invalid email agument')
if (! (await verifyReCaptcha({response: recaptcha, remoteip: ip, secret}))) throw new Error('Verify Captcha & try again')
if (await usersDB.find(email)) throw new Error('Email already registered!')
await usersDB.create({name, email, ip, originalReferer, referer, verified: false})
return sendJSON({message: `Successfully registered ${name}`})
} catch (err) {
return sendJSON({message: err.message, stack: err.stack}, 500)
}
}
}
/*
## ReCaptcha Dependency
More info: https://developers.google.com/recaptcha/docs/verify#api_request
*/
async function verifyReCaptcha({response, remoteip, secret}) {
if (!secret) throw new Error('Admin Error: Please configure required server variable `RECAPTCHA_SECRET`.')
if (!response) throw new Error('API Error: Missing parameter `recaptcha` or `response`.')
return fetch(`https://www.google.com/recaptcha/api/siteverify`, {
method: 'POST',
body: new URLSearchParams({secret, response, remoteip}).toString()
})
.then(response => response.json())
.then(data => {
console.info('recaptcha response:', data)
if (!data || !data.success) return false
return {message: `successfully verified captcha`, ...data, success: data.success}
})
}
/*
## Cloudflare Tie-in
*/
async function handleRequest(request) {
if (typeof CONFIG === 'undefined' || typeof FANCLUB === 'undefined') return sendJSON({message: 'System Error: Key-value stores not configured.'})
if (request.method === 'GET') {
console.error('WARNING: DATA LEAK HERE!!!! REMOVE IN PROD!')
return handlers.listUsers(request)
} else if (request.method === 'POST') {
return handlers.signupUser(request)
} else {
return sendJSON({message: 'The specified http path & method not supported.'}, 400)
}
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment