Skip to content

Commit

Permalink
Merge pull request #34 from ngshiheng/cf-turnstile-integration
Browse files Browse the repository at this point in the history
feat: add Cloudflare Turnstile to site
  • Loading branch information
ngshiheng authored Oct 6, 2022
2 parents 397444c + d5ef6cf commit b81bd8e
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 23 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
.vscode
dist
node_modules
worker
test.js
worker
2 changes: 1 addition & 1 deletion scripts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const options = {

let shortenLink

const BASE_URL = 'https://atomic-url-staging.jerrynsh.workers.dev/'
const BASE_URL = __ENV.TEST_URL
const DUMMY_ORIGINAL_URL = 'https://jerrynsh.com/i-built-my-own-tiny-url/'

export default function () {
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/createShortUrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ export const createShortUrl = async (request, event) => {
return response
} catch (error) {
console.error(error, error.stack)
return new Response('Unexpected Error', { status: 500 })
return new Response('Internal Server Error', { status: 500 })
}
}
2 changes: 1 addition & 1 deletion src/handlers/redirectShortUrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ export const redirectShortUrl = async ({ params }) => {
if (originalUrl) {
return Response.redirect(originalUrl, 301)
}
return new Response(NOT_FOUND_PAGE, { headers: { 'content-type': 'text/html;charset=UTF-8' } })
return new Response(NOT_FOUND_PAGE, { headers: { 'Content-Type': 'text/html;charset=UTF-8' }, status: 404 })
}
51 changes: 36 additions & 15 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,25 @@
<script src="https://kit.fontawesome.com/15181efa86.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/bulma.min.css" />
<script async defer data-website-id="9860fa16-de2e-4eaa-b0b4-f11c471ad4d6" src="https://umami.jerrynsh.com/umami.js"></script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" async defer></script>
<script>
const submitURL = () => {
let statusElement = document.getElementById('status')
let originalUrlElement = document.getElementById('url')
let sitekey = '0x4AAAAAAAAujdqolLL946vD'
let turnstileToken

window.onloadTurnstileCallback = function () {
const turnstileOptions = {
sitekey,
theme: 'light',
callback: function (token) {
turnstileToken = token
},
}
turnstile.render('#cf-turnstile-widget', turnstileOptions)
}

const submitURL = async () => {
const statusElement = document.getElementById('status')
const originalUrlElement = document.getElementById('url')

if (!originalUrlElement.reportValidity()) {
throw new Error('Invalid URL.')
Expand All @@ -26,19 +41,20 @@
statusElement.classList.add('is-loading')

const originalUrl = originalUrlElement.value
const body = JSON.stringify({ originalUrl })
const body = JSON.stringify({ originalUrl, turnstileToken })

const res = await fetch('/api/url', { method: 'POST', body })
statusElement.classList.remove('is-loading')

if (res.status === 200) {
const { shortUrl } = await res.json()
statusElement.innerHTML = shortUrl
} else {
statusElement.innerHTML = `⛔ ${res.statusText}`
}

fetch('/api/url', { method: 'POST', body })
.then((data) => data.json())
.then((data) => {
statusElement.classList.remove('is-loading')
statusElement.innerHTML = data.shortUrl
})
.catch((error) => {
statusElement.classList.remove('is-loading')
statusElement.innerHTML = '⛔ To Err is Human'
})
originalUrlElement.value = ''
turnstile.reset('#cf-turnstile-widget')
}

const copyToClipboard = (elementId) => {
Expand Down Expand Up @@ -93,12 +109,17 @@ <h1 class="title is-4">Shorten A URL</h1>
<input
class="input is-link is-primary is-medium is-rounded"
type="url"
placeholder="https://jerrynsh.com/"
placeholder="https://jerrynsh.com"
id="url"
required
/>
</div>
</div>
<div class="checkbox mb-3">
<!-- The Turnstile widget will be injected in the following div. -->
<div id="cf-turnstile-widget"></div>
<!-- end. -->
</div>
<button id="submit" class="button is-block is-primary is-rounded is-fullwidth is-medium" onclick="submitURL()">Shorten</button>
<br />
<button class="button is-info is-rounded is-small" onclick="copyToClipboard('status')">
Expand Down
5 changes: 3 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ import { createShortUrl } from './handlers/createShortUrl'
import { redirectShortUrl } from './handlers/redirectShortUrl'
import { shortUrlCacheMiddleware } from './middleware/shortUrlCache'
import { siteFilterMiddleware } from './middleware/siteFilter'
import { turnstileMiddleware } from './middleware/turnstile'

const router = Router()

// GET landing page html
router.get('/', () => new Response(LANDING_PAGE_HTML, { headers: { 'content-type': 'text/html;charset=UTF-8' } }))
router.get('/', () => new Response(LANDING_PAGE_HTML, { headers: { 'Content-Type': 'text/html;charset=UTF-8' } }))

// GET redirects short URL to its original URL.
router.get('/:text', redirectShortUrl)

// POST creates a short URL that is associated with its an original URL.
router.post('/api/url', shortUrlCacheMiddleware, siteFilterMiddleware, createShortUrl)
router.post('/api/url', turnstileMiddleware, shortUrlCacheMiddleware, siteFilterMiddleware, createShortUrl)

// All incoming requests are passed to the router where your routes are called and the response is sent.
addEventListener('fetch', (e) => {
Expand Down
4 changes: 2 additions & 2 deletions src/middleware/shortUrlCache.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/*
A cache middleware that checks if `originalUrl` is present in the `URL_CACHE` cache.
Do note that Cache API is not enabled on `workers.dev`
Do note that Cache API is not enabled on `workers.dev`.
You will need to deploy this over a custom domain to see it work.
*/
export const shortUrlCacheMiddleware = async (request) => {
const { originalUrl } = await request.clone().json()

if (!originalUrl) {
return new Response('Invalid Request Body', {
return new Response('Bad Request', {
status: 400,
})
}
Expand Down
29 changes: 29 additions & 0 deletions src/middleware/turnstile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const TURNSTILE_SITEVERIFY_ENDPOINT = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'

/*
Turnstile is Cloudflare's smart CAPTCHA alternative.
A middleware that calls Cloudflare's siteverify endpoint to validate the Turnstile widget response.
Do note to set `TURNSTILE_SECRET` and `TEST_URL` accordingly.
*/
export const turnstileMiddleware = async (request) => {
/* eslint-disable no-undef */
if (request.url == TEST_URL) {
console.log('Skipping Turnstile verification for tests.')
return
}

const { turnstileToken } = await request.clone().json()

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

const verification = await response.json()
if (!verification.success) {
return new Response('Too Many Requests', { status: 429 })
}
}

0 comments on commit b81bd8e

Please sign in to comment.