FreeCAPTCHA/app.py
2023-06-29 15:01:21 -06:00

131 lines
5.3 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from flask import Flask, render_template, request, make_response, redirect
from freecaptcha import captcha
import uuid
import os
from cryptography.fernet import Fernet
import jwt
import base64
# Generate a new AES key
key = Fernet.generate_key()
JWT_SECRET_KEY = 'obviously this needs to be an env variable secret thingie.'
# Create an instance of the Fernet cipher with the key
cipher = Fernet(key)
# Encrypt a message
message = b'This is a secret message.'
ciphertext = cipher.encrypt(message)
# Decrypt the ciphertext
app = Flask(__name__, static_url_path='', static_folder='static',)
@app.route("/captcha", methods=['GET', 'POST'])
def captcha_handler():
if request.method == "POST":
captcha_attempt = len(list(request.form))
token = request.cookies.get('Authorization').split('Bearer ')[-1]
try:
# TODO: set JWT to expire very soon.
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=['HS256'])
b64_and_encrypted_correct_answer = payload['encrypted_correct_answer']
encrypted_correct_answer_bytes = base64.b64decode(b64_and_encrypted_correct_answer)
correct_answer = cipher.decrypt(encrypted_correct_answer_bytes).decode('utf-8').split('|||')[0]
return f'''
The correct answer was {correct_answer}
You flipped it {captcha_attempt}
'''
except jwt.ExpiredSignatureError:
return 'Token expired. Please log in again.'
except jwt.InvalidTokenError:
return 'Invalid token. Authentication required.'
## Get their guess from POST body
## Get JWT cookie
## Decrypt payload
## Check if guess (from post body) == correct answer (from decrypted JWT payload)
## If so: give them a new JWT for winning the CAPTCHA, and forward them to the URL they originally wanted to access precaptcha (just like oauth does)
## If not: Redirect them to the GET version of this same URL, with warning enabled to tell them they failed
if request.method == "GET":
image_path = captcha.random_image()
answer, options = captcha.captchafy(image_path)
print('the correct answer is: ', answer)
# remember to store the salt since we'll need it when we compare the hashes
salt = uuid.uuid4()
plaintext_bytes = bytes(str(answer) + '|||' + str(salt), 'utf-8')
encrypted_bytes = cipher.encrypt(plaintext_bytes)
ciphertext = base64.b64encode(encrypted_bytes).decode('utf-8')
token = jwt.encode({
'encrypted_correct_answer': ciphertext,
'salt': str(salt)
}, JWT_SECRET_KEY, algorithm='HS256')
# Set the Authorization header cookie with the JWT
resp = make_response(captcha.generate_captcha_html(list(options)))
resp.set_cookie('Authorization', f'Bearer {token}')
return resp
# Set JWT with encrypted answer as HTTP cookie
# issue: they could compare our encrypted answer with how we encrypt all numbers 1-6, since they will have seem them before.
# We could solve this with a salt, but then we have to store salts. We could include the salt in plaintext, but then they could build a rainbow table of our guess + salts (since there are only 6 possible guesses)
# the solution is to use salts big enough that we don't have to fear them repeating / being turned into a rainbow table.
# We will use UUID's as the salts.
#
# Anyway, we pass the data to our Jinja template and render it.
# Flask should take care of unsupported methods for us.
## Handle cookie
## Get random image
## Generate
return captcha.generate_captcha_html(os.listdir('static/images/'))
@app.route("/captcha_old", methods=['GET', 'POST'])
def login():
# This means they just submitted a CAPTCHA
# We need to see if they got it right
incorrect_captcha = False
if request.method == 'POST':
captcha_guess = len(list(request.form))
print(request.form.get('captcha'))
# What if they POST with the cookie below absent? Uh oh...
captcha_cookie = request.cookies.get('freecaptcha_cookie')
real_answer = captcha_solutions.get(captcha_cookie, None)
if real_answer is not None:
if captcha_guess == int(real_answer):
captcha_solved.append(captcha_cookie)
return redirect("/", code=302)
else:
incorrect_captcha = True
# Select an image
image_path = captcha.random_image()
# Generate list of rotated versions of image
# and save which one is correct
# change answer to be the number of turns needed?
answer, options = captcha.captchafy(image_path)
print(answer)
# Provide the CAPTCHA options to the web page using the CAPTCHA
resp = make_response(render_template("index.html", captcha_options=options, incorrect_captcha=incorrect_captcha))
resp = make_response(captcha.generate_captcha_html(list(options)))
# Track this user with a cookie and store the correct answer
# by linking the cookie with the answer, we can check their answer later
freecaptcha_cookie = str(uuid.uuid4())
resp.set_cookie('freecaptcha_cookie', freecaptcha_cookie)
captcha_solutions[freecaptcha_cookie] = answer
return resp