Skip to content

Commit 964d32a

Browse files
committed
move away from google recaptcha to cloudflare turnstile
1 parent e257133 commit 964d32a

File tree

5 files changed

+38
-31
lines changed

5 files changed

+38
-31
lines changed

.env.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ AWS_ACCESS_KEY_ID=''
33
AWS_SECRET_ACCESS_KEY=''
44
AWS_REGION='us-east-1'
55
SES_FROM_EMAIL=''
6-
RECAPTCHASITEKEY=''
7-
RECAPTCHASECRETKEY=''
6+
TURNSTILE_SITE_KEY=''
7+
TURNSTILE_SECRET_KEY=''

server.py

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -111,27 +111,25 @@ def create_email(to_email, identifier, text, all_attachments, reference=''):
111111

112112
return msg
113113

114-
def validate_recaptcha(recaptcha_response):
114+
def validate_turnstile(turnstile_response):
115115
"""
116-
Validates the ReCaptcha response using Google's API.
116+
Validates the Turnstile response using Cloudflare's API.
117117
"""
118-
secret_key = os.getenv('RECAPTCHASECRETKEY')
118+
secret_key = os.getenv('TURNSTILE_SECRET_KEY')
119119
payload = {
120120
'secret': secret_key,
121-
'response': recaptcha_response
121+
'response': turnstile_response
122122
}
123-
response = requests.post('https://www.google.com/recaptcha/api/siteverify', data=payload)
123+
response = requests.post('https://challenges.cloudflare.com/turnstile/v0/siteverify', data=payload)
124124
result = response.json()
125125

126126
# Log the validation result
127-
logging.info(f"ReCaptcha validation response: {result}")
127+
logging.info(f"Turnstile validation response: {result}")
128128

129129
if not result.get('success'):
130-
raise ValueError('ReCaptcha verification failed.')
131-
132-
# Check action and score thresholds for additional security
133-
if result.get('score', 1.0) < 0.5:
134-
raise ValueError('ReCaptcha score is too low, indicating potential abuse.')
130+
error_codes = result.get('error-codes', [])
131+
logging.error(f"Turnstile verification failed with error codes: {error_codes}")
132+
raise ValueError('Turnstile verification failed.')
135133

136134
def send_email(message):
137135
"""
@@ -187,11 +185,11 @@ def get_forwarded_address():
187185
return get_remote_address()
188186

189187
# Validate required environment variables
190-
required_env_vars = ['RECAPTCHASITEKEY', 'RECAPTCHASECRETKEY', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION', 'SES_FROM_EMAIL']
188+
required_env_vars = ['TURNSTILE_SITE_KEY', 'TURNSTILE_SECRET_KEY', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION', 'SES_FROM_EMAIL']
191189
validate_env_vars(required_env_vars)
192190

193-
RECAPTCHASITEKEY = os.environ['RECAPTCHASITEKEY']
194-
RECAPTCHASECRETKEY = os.environ['RECAPTCHASECRETKEY']
191+
TURNSTILE_SITE_KEY = os.environ['TURNSTILE_SITE_KEY']
192+
TURNSTILE_SECRET_KEY = os.environ['TURNSTILE_SECRET_KEY']
195193
AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID']
196194
AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY']
197195
AWS_REGION = os.environ['AWS_REGION']
@@ -222,7 +220,7 @@ def get_forwarded_address():
222220

223221
@app.route('/', methods=['GET'])
224222
def index():
225-
return render_template('index.html', notice='', hascaptcha=True, attachments_number=Config.NUMBER_OF_ATTACHMENTS, recaptcha_sitekey=RECAPTCHASITEKEY)
223+
return render_template('index.html', notice='', hascaptcha=True, attachments_number=Config.NUMBER_OF_ATTACHMENTS, turnstile_sitekey=TURNSTILE_SITE_KEY)
226224

227225
@app.route('/submit-encrypted-data', methods=['POST'])
228226
@limiter.limit("3 per minute")
@@ -231,14 +229,14 @@ def submit():
231229
# Parse JSON data from request
232230
data = request.get_json()
233231

234-
# Validate ReCaptcha
235-
recaptcha_response = data.get('g-recaptcha-response', '')
236-
if not recaptcha_response:
237-
logging.warning(f"Missing ReCaptcha response. Potential bypass attempt detected from IP: {request.remote_addr}")
238-
return jsonify({'status': 'failure', 'message': 'Missing ReCaptcha token'}), 400
232+
# Validate Turnstile
233+
turnstile_response = data.get('cf-turnstile-response', '')
234+
if not turnstile_response:
235+
logging.warning(f"Missing Turnstile response. Potential bypass attempt detected from IP: {request.remote_addr}")
236+
return jsonify({'status': 'failure', 'message': 'Missing Turnstile token'}), 400
239237

240238
try:
241-
validate_recaptcha(recaptcha_response)
239+
validate_turnstile(turnstile_response)
242240
except ValueError as e:
243241
return jsonify({'status': 'failure', 'message': str(e)}), 400
244242

static/css/style.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,14 @@ textarea#text { width: 760px; height: 200px; }
8080
width: 100%;
8181
min-width: 300px;
8282
}
83+
84+
/* Cloudflare Turnstile widget styling */
85+
.cf-turnstile {
86+
margin: 10px 0;
87+
}
88+
89+
/* Ensure Turnstile iframe has proper styling */
90+
.cf-turnstile iframe {
91+
border-radius: 4px;
92+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
93+
}

static/js/app.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,11 @@ function acceptEncryptedData(data) {
157157

158158
if (dataArray.receivedChunks == dataArray.requiredChunks) {
159159
console.log('all chunks received, submitting form');
160-
const gRecaptchaBlock = document.getElementById('gRecaptcha');
160+
const cfTurnstileBlock = document.getElementById('cfTurnstile');
161161
const recipient = document.getElementById("recipientSelect");
162162
const reference = document.getElementById("reference");
163163

164-
dataArray['g-recaptcha-response'] = gRecaptchaBlock ? grecaptcha.getResponse() : null;
164+
dataArray['cf-turnstile-response'] = cfTurnstileBlock ? turnstile.getResponse() : null;
165165
dataArray['recipient'] = recipient.value;
166166
dataArray['reference'] = reference.value;
167167

@@ -316,14 +316,12 @@ async function encryptFile(filename, file) {
316316
return { name: filename, data: encrypted };
317317
}
318318

319-
// var gRecaptchaResponse = null;
320-
function captchaSolved(recaptchaResponse) {
321-
// gRecaptchaResponse = recaptchaResponse;
319+
// Turnstile callback functions
320+
function captchaSolved(turnstileResponse) {
322321
document.getElementById("button").disabled = false;
323322
}
324323

325324
function captchaExpired() {
326-
// gRecaptchaResponse = null;
327325
document.getElementById("button").disabled = true;
328326
}
329327

templates/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<script src="static/js/public-keys.js" type="text/javascript"></script>
66
<script src="static/js/dropzone.min.js"></script>
77
<link href="static/css/dropzone.min.css" rel="stylesheet" type="text/css" />
8-
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
8+
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
99
<script src="static/js/app.js" type="text/javascript"></script>
1010
{% endblock %}
1111
{% block body %}
@@ -41,7 +41,7 @@
4141

4242
{% if hascaptcha %}
4343
<br />
44-
<div class="g-recaptcha" id="gRecaptcha" data-sitekey="{{ recaptcha_sitekey }}" data-callback="captchaSolved" data-expired-callback="captchaExpired"></div>
44+
<div class="cf-turnstile" id="cfTurnstile" data-sitekey="{{ turnstile_sitekey }}" data-theme="light" data-callback="captchaSolved" data-expired-callback="captchaExpired"></div>
4545
{% endif %}
4646
<br />
4747
<br />

0 commit comments

Comments
 (0)